Android实现可拖动的悬浮按钮控件

有时候,我们需要一个可移动的菜单,Android本身并没有这样的控件,小弟不才,最近研究了下,并参考了网上的一些资料,自己动手封装了个,因为是第一个版本,可能会有一些bug,欢迎留言指导。我们先简单来说一下原理: 随着我们的手势移动,控件就随着移动到某个位置,关键点是我们怎么处理控件的onTouch方法,这里我们需要监听MotionEvent的三个状态,如下:

@Override
public boolean onTouch(View v, MotionEvent event) {
    switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            //手势按下
            break;
        case MotionEvent.ACTION_MOVE:
            //手势移动
            break;
        case MotionEvent.ACTION_UP:
            //手势抬起
            break;
    }
    return super.onTouchEvent(event);;
}

首先在手势按下的时候,我们记住起始的位置:
mStartX = mLastX = (int) event.getRawX();
mStartY = mLastY = (int) event.getRawY();
因为刚按下,所以最后一次的坐标也跟起始的坐标一样。
其次,在MotionEvent.ACTION_MOVE事件(手势移动)中对菜单控件进行移动,代码如下:
int left, top, right, bottom;
int dx = (int) event.getRawX() – mLastX;//记录距上次move事件后手势移动的x轴长度
int dy = (int) event.getRawY() – mLastY;//记录距上次move事件后手势移动的y轴长度
left = v.getLeft() + dx;//手势位置变化后也变化控件的坐标
if (left < 0) {
left = 0;//x轴的左边不能小于0 防止控件无法全部显示或不显示
}
right = left + v.getWidth();
if (right > mScreenWidth) {
right = mScreenWidth;//x轴的右边边不能大于屏宽 防止控件无法全部显示或不显示
left = right – v.getWidth();
}
top = v.getTop() + dy;
if (top < mStatusBarHeight + 2) {
top = mStatusBarHeight + 2;//控件顶部不能小于状态栏的高度
}
bottom = top + v.getHeight();
if (bottom > mScreenHeight) {
bottom = mScreenHeight;//底部不能大于屏高
top = bottom – v.getHeight();
}
v.layout(left, top, right, bottom);//重置控件的位置
mLastX = (int) event.getRawX();//记录最后一次x坐标
mLastY = (int) event.getRawY();//记录最后一次y坐标
经过以上实现,我们就可以实现控件随着手势的移动而移动。到这里会有一个问题,就是你按home键后程序回到后台,然后再进入程序,控件会出现在起始的位置并不会出现在你手势移动后的最后位置,这个问题我找了好久才发现原因,原因是因为我们调用v.layout方法设置位置后并没有改变控件的LayoutParam里面的参数,你重新进入后,页面draw时是根据LayoutParam里面的参数来绘制的,既然找到了问题,那就好办了,简单的处理方法就是在你手势移动结束的时候设置LayoutParam,一般来讲没必要在move方法里面设置,在up方法里处理就好了,所以我们还需要在MotionEvent.ACTION_UP加上如下代码:
v.setLayoutParams(createLayoutParams(v.getLeft(), v.getTop(), 0, 0));
其实createLayoutParams方法就是创建控件的LayoutParam,如下:
private FrameLayout.LayoutParams createLayoutParams(int left, int top, int right, int bottom) {
FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(mBuilder.size, mBuilder.size);
layoutParams.setMargins(left, top, right, bottom);
return layoutParams;
}
到此,基本上就差不多实现主要功能了,但是如果我们要为该控件添加点击事件呢?
其实我们可以这么处理,当我们手指移动的距离小于某个数据时,我们不要消耗该手势事件,这个时候就会触发oncllick事件,而当我们的手指移动的距离大于某个数据时,我们才消耗手势事件,这个时候onclick就不会触发,代码如下:
v.setLayoutParams(createLayoutParams(v.getLeft(), v.getTop(), 0, 0));
//这里需设置LayoutParams,不然按home后回再到页面等view会回到原来的地方
float endX = event.getRawX();
float endY = event.getRawY();
if (Math.abs(endX – mStartX) > 5 || Math.abs(endY – mStartY) > 5) {
//防止点击的时候稍微有点移动点击事件被拦截了
mTouchResult = true;
}
if (mTouchResult && mBuilder.needNearEdge) {
//是否每次都移至屏幕边沿
moveNearEdge();
}
最后的代码意思是(其中mTouchResult为onTouch方法的返回值),当前是实现移动控件的操作,并且需要控件移动屏幕的边沿时,就把控件移至屏幕边沿(这是另外实现的功能,在这里就不多阐述了)。
好了,主要的原理和代码就是这些,我把代码放在github上,链接如下:
github.com/linqssonny/…
上面附有使用的方法,如大家遇到什么问题可以加我Q:252624617交流,如有bug之类的欢迎在github上提出,我会继续完善。

    原文作者:Android源码分析
    原文地址: https://juejin.im/entry/5a649d7bf265da3e2f010d10
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞