IOS的Assistive Touch效果很炫酷,可以任意拖拽,同时点击后会展开菜单栏。然而,这不只是IOS的特权,Android也可以实现。但是由于悬浮窗需要申请权限,所以本文仅在app内实现,可以任意拖拽,并可以响应点击事件。

一、效果图

效果还是不错的。上图看出虽然没有像IOS一样弹出菜单栏,仅仅以Toast和旋转动画的效果代替了(因为太懒了,更炫酷的效果交给你们的想象了)。但是确实支持点击事件,并且和拖拽事件不冲突。

二、实现原理

1、拖拽实现

很简单,设置TouchListener监听,实现onTouch方法,在ACTION_MOVE的过程中随着x,y坐标的移动更新浮动按钮的位置。下面具体介绍重写onTouch方法的具体实现监听ACTION_DOWN事件

case MotionEvent.ACTION_DOWN:{                mDownPointerId = MotionEventCompat.getPointerId(event, 0);                mPreviousX = event.getRawX();                mPreviousY = event.getRawY();                break;            }

记录初始的坐标以及触摸点。监听ACTION_MOVE事件

case MotionEvent.ACTION_MOVE:{                if (mDownPointerId >= 0) {                    int index = MotionEventCompat.getActionIndex(event);                    int id = MotionEventCompat.getPointerId(event, index);                    if (id == mDownPointerId) {                        boolean update = adjustMarginParams(view, event);                        if (!update) {                            break;                        }                        mFloatView.requestLayout();                        mHasMoved = true;                        result = true;                    }                }                break;            }

其中最重要的是adjustMarginParams(view, event)方法,来更新浮动按钮的相对位置。

private boolean adjustMarginParams(View v, MotionEvent event) {        float x =  event.getRawX();        float y =  event.getRawY();        float deltaX = x - mPreviousX;        float deltaY = y - mPreviousY;        if (!mHasMoved) {            if (Math.abs(deltaX) < mTouchSlop && Math.abs(deltaY) < mTouchSlop) {                return false;            }        }        //左上角位置        int newX = (int)x - mFloatView.getWidth() / 2;        int newY = (int)y - mFloatView.getHeight() / 2;        newX = Math.max(newX, mBoundsInScreen.left + mEdgePaddingLeft);        newX = Math.min(newX, mBoundsInScreen.right - mEdgePaddingRight - mFloatView.getWidth());        newY = Math.max(newY, mBoundsInScreen.top + mEdgePaddingTop);        newY = Math.min(newY, mBoundsInScreen.bottom - mEdgePaddingBottom - mFloatView.getHeight());        mFloatViewWindowParam.x = newX;        mFloatViewWindowParam.y = newY - mParentMarginTop;        return true;    }

其中mBoundsInScreen代表浮动按钮可移动的矩形范围。根据当前event内的坐标与mBoundsInScreen范围比较,选择最终拖拽到达的位置,设置给浮动按钮的布局参数mFloatViewWindowParam,然后调用requestLayout更新布局。监听ACTION_UP/ACTION_CANCEL

case MotionEvent.ACTION_UP:            case MotionEvent.ACTION_CANCEL:{                if (mDownPointerId >= 0 && mHasMoved) {                    event.setAction(MotionEvent.ACTION_CANCEL);                    adjustMarginParams(view, event);                    mFloatView.requestLayout();                    int center = (mBoundsInScreen.width() - mFloatView.getWidth()) / 2;                    int x = mFloatViewWindowParam.x;                    int destX = 0;                    int posX = Gravity.LEFT;                    //抬起时 根据位置强制把浮动按钮归于左边或右边                    if (x < center) { // 左边                        destX = mBoundsInScreen.left + mEdgePaddingLeft;                    } else {                        posX = Gravity.RIGHT;                        destX = mBoundsInScreen.right - mEdgePaddingRight - mFloatView.getWidth();                    }                    if (mFloatButtonCallback != null) {                        float posY = 0;                        if (mBoundsInScreen.height() - mFloatView.getHeight() != 0) {                            posY = 1f * (mFloatViewWindowParam.y - mBoundsInScreen.top) / (mBoundsInScreen.height() - mFloatView.getHeight());                        }                        mFloatButtonCallback.onPositionChanged(destX, mFloatViewWindowParam.y, posX, posY);                    }                    int deltaHorizon = destX - x;                    //小于100直接移动 否则开启动画                    if (Math.abs(deltaHorizon) < 100) {                        mFloatViewWindowParam.x = destX;                        mFloatView.requestLayout();                    } else {                        ValueAnimator animator = ValueAnimator.ofInt(x, destX);                        animator.setInterpolator(mInterpolator);                        if (mUpdateListener == null) {                            mUpdateListener = new FloatAnimatorUpdateListener();                            mUpdateListener.setUpdateView(FloatTouchListener.this);                        }                        animator.addUpdateListener(mUpdateListener);                        animator.setDuration(200);                        animator.start();                    }                }                resetStatus();                break;            }        }

实现当抬起的瞬间,根据当前所处坐标靠左还是靠右,把浮动按钮置于左边缘或者右边缘。同时,调用回调,把移动相对位置传给回调函数,实现拖拽监听。当从当前位置移动到左/右边缘的距离小于100时,直接移动,否则实现动画减速移动效果。如此简单便可实现任意拖拽的效果了,具体一些细节要细看源码实现。

2.点击实现

也许有人会认为点击事件很好实现啊,setOnClickListener()设置个监听就可以实现了。不信你去试试,没用。其实点击实现才是本篇文章的精髓,因为灵活应用到了事件分发机制。从事件分发机制中我们知道,就优先级而言:onTouchListener>onClickListenr。上面的拖拽事件已经消费了onTouchListener(即onTouch方法中返回true),那么就不会下发到onClickListenr,自然就不会产生点击事件。也许你想让onTouchListener不消费,然后不就下发到onClickListenr了么?确实这样可以实现点击事件,但是拖拽功能又实现不了了。因为onTouch方法中返回false,确实onClickListenr接收到了事件,然后消费掉。可是因为onTouch方法中返回false,所以接下来的一切事件不能接受,没办法响应拖拽效果了。通过上面的分析,最终的解决办法就是:onTouch方法中,在接收到ACTION_DOWN后,返回false,交给onClickListenr处理。剩下的ACTION_MOVE/ACTION_UP等事件,返回true,交给onTouchListener处理,这样自然就可以既实现拖拽效果又实现点击效果了。具体实现以下面伪代码为例

  public boolean onTouch(View view, MotionEvent event) {        int action = MotionEventCompat.getActionMasked(event);        if (mFloatButtonCallback != null) {            mFloatButtonCallback.onTouch();        }        boolean result = false;        switch (action) {            case MotionEvent.ACTION_DOWN:{               ....................................................                break;            }            case MotionEvent.ACTION_MOVE:{               ....................................................                result = true;                break;            }            case MotionEvent.ACTION_UP:            case MotionEvent.ACTION_CANCEL:{                ................................................                break;            }        }        return result;    }

原理就是这么简单,更加炫酷的效果可自定义实现源码地址:https://github.com/LRH1993/FloatingDragButton到这里就结束啦往期精彩回顾:

  • Android实现短信验证码自动填充功能

  • Android仿echo精美弹幕功能

  • Android实现头像重叠排列功能

  • Android仿QQ个性标签功能

  • Android仿QQ侧滑删除的功能

android 按键会触发ontouch吗?_Android实现炫酷的拖拽浮动按钮相关推荐

  1. android 按键会触发ontouch吗?_这次,我把Android事件分发机制翻了个遍

    一名优秀的Android开发,需要一份完备的 知识体系,在这里,让我们一起成长为自己所想的那样~. 这次说下Android中的事件分发机制 从开始点击屏幕开始,就会产生从Activity开始到deco ...

  2. android 浮动按钮拖拽,小程序拖拽浮动按钮

    小程序拖拽浮动按钮 2019-5-22    分类: 小程序 小程序 浮动  拖拽 按钮 不借助movable-area自带的组件,实现拖拽效果 wxml + js: var startPoint; ...

  3. android录音波浪动画_Android实现炫酷的波浪下载Loading动画

    1. 简介和效果分析 一直都觉得有很多loading动画挺炫酷的,然后自己看过一些之后也想实现一个,加强一下对绘制view的练习,能力有限,很多地方的实现的有欠考虑和逻辑优化,不管怎么样画了两天还是把 ...

  4. Android 可拖拽悬浮按钮

    转自http://www.jianshu.com/p/ba3e5fc5cff1 实现思路 通过重写控件的onTouchEvent方法监听触摸效果. 通过View的setX()和setY()方法实现移动 ...

  5. android可拖拽悬浮控件和Kotlin的可拖拽悬浮控件/可拖拽悬浮按钮带Demo附件

    本文讲解的是一个实现了可拖拽的悬浮按钮,并添加了吸附边框的功能. 借鉴于:https://www.jianshu.com/p/4f55bcbc1b83 在此之前,先了解下其简单的使用方式吧: 原文地址 ...

  6. android中拖拽浮动按钮,Android自定义view实现拖拽选择按钮

    本文实例为大家分享了Android实现拖拽选择按钮的具体代码,供大家参考,具体内容如下 DragChooseDemo 效果图 Attributes属性(布局文件中的自定义属性) 半径.文字大小.按钮个 ...

  7. android 登录注册动画,Android开发(14)——动画实战:炫酷登录

    本节内容 1.第三方库实现虚化 2.添加输入框和按钮 3.按钮状态 4.键盘隐藏 5.监听焦点改变的事件 6.手臂旋转动画 7.手掌和手臂动画 Demo简介 1.做一个炫酷登录的界面. image.p ...

  8. android lottie字体json,从json文件到炫酷动画-Lottie实现思路和源码分析

    从json文件到炫酷动画-Lottie实现思路和源码分析,Lottie是最近Airbnb开源的动画项目,支持Android.iOS.ReactNaitve三个平台,本文分析主要Lottie把json文 ...

  9. Android 自定义可拖拽悬浮按钮

    一.添加依赖 compile 'com.android.support:design:26.1.0' 后面的版本号要和 implementation 'com.android.support:appc ...

最新文章

  1. 高级数据结构研究-B树系列以及红黑树
  2. 中国非动物胶市场来产销需求及发展潜力研究报告2022版
  3. visual studio快捷键 Qt creator快捷键
  4. Django 基本使用及目录结构
  5. web前端模块化开发_真正的模块化Web应用程序:为什么没有开发标准?
  6. angular设置referer_Angular-cli 构建应用的一些配置
  7. linux按键检测结束,关于Linux下按键的检测
  8. python闭包和函数调用区别_对python闭包(内嵌函数)的理解
  9. NSGA_2总结梳理附代码按行详细注解
  10. 借用计算机简谱,电脑编辑打印简谱之路怎么走——“电脑简谱助手”操作系列谈之二...
  11. 计算机房档案管理,河南省数字档案馆机房管理制度
  12. Mybatis实现订单案例的五表联合操作
  13. ff7重制版青魔法_《FF7重制》敌方招式获取方式与效果
  14. ubuntu上通过命令行导出mysql数据库文件到widows系统上
  15. 抖音上几百万粉丝的能有什么用?
  16. 退休后多长时间能领到工资
  17. MySQL数据库的核心MVCC详解
  18. 3D人脸查看器和匹配器
  19. 伯克利BLAM纯激光SLAM
  20. 程序员应该如何写好自己的简历

热门文章

  1. 读人、看人、做人(图)
  2. 运算符优先级 必熟记,放到心里
  3. RTP格式图 NNEXB格式和RTP格式
  4. restful风格使用小例
  5. Spring 定时任务的几种实现
  6. Linux中打开文件管理器的命令
  7. Sublime 插件- px 转rem
  8. 容器+AOP实现动态部署(四)
  9. [codevs1039]数的划分
  10. Oracle if else if for case