欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

实现可拖拽移动的悬浮按钮

程序员文章站 2024-03-15 20:03:18
...

前言

  最近想要实现一个可拖拽移动的FAB按钮,这里记录一下个人的思路与经验。


如何监听FAB按钮的移动?

  我们可以实现View.OnTouchListener接口,在onTouch( )方法中获取FAB按钮移动时的位置参数。或者可以选择复写View自身的onTouchEvent( )方法,实现方式大同小异。


移动范围超出屏幕怎么办?

  通过逻辑判断限制FAB移动的范围。


如何区分FAB按钮的”拖拽移动” 和 “点击事件?”

  如果同时监听FAB按钮onTouch和onClick事件 , 在onTouch事件中执行拖动操作,在onClick中实现事件逻辑,但是onTouch和OnClick会有冲突。
 
  onTouch事件的返回值是boolean类型的,如果返回true ,那么事件就会被拦截,onclick方法不会被调用;返回false,事件不被消费和拦截,onClick方法会同时被调用。
 
  策略一:要想把OnTouch和onClick事件完全的区分,这里的想法就是在 OnTouch中的MotionEvent.ACTION_DOWN 时,记录下点(X1,Y1),在 MotionEvent.ACTION_UP 时,记录下点(X2,Y2),然后比对 俩点之间的距离,如果小于一个较小数值(比如5),就认为是Click事件,onTouch中返回false,如果距离较大,可以当作onTouch事件去处理,返回true.
 
 策略二:


如何实现FAB按钮的移动?

  通过监听OnTouch( )方法或者覆写onTouchEvent( )方法,我们已经可以获取到拖拽FAB按钮时的实时位置了,接下来我们如何表现这种位置移动的“变化“? 
  

  • 通过view.layout()方式。任何布局上的空间都可以支持这种方式移动,上下左右参数值是相对于父viewgroup而言的。

  • 调用MarginLayoutParams.setMargins(),重新设置控件位置参数来实现控件移动效果。这种方式比较适合RelativeLayout、FrameLayout,AbsoluteLayout,对于LinearLayout,因为最后增加的控件总在最下或最右,所以达不到移动效果,TableLayout也不行。 

  • 通过view.setX() 、view.setY()方式。任意布局上的空间都可以支持这种方式移动,参数值是指相对于自身原本位置X坐标轴和Y坐标轴上的偏移量。 


各种实现方式注意事项


 方案1使用View.layout()方式实现FAB按钮的移动
 

public class FloatButton extends android.support.design.widget.FloatingActionButton implements View.OnTouchListener{

    int lastX, lastY;
    int originX, originY;
    int screenWidth ;
    int screenHeight ;
    int distance;

    public FloatButton(Context context) {
        super(context);
        getScreenWidthAndHeight(context);
        setOnTouchListener(this);
    }

    public FloatButton(Context context, AttributeSet attrs) {
        super(context, attrs);
        getScreenWidthAndHeight(context);
        setOnTouchListener(this);
    }

    public FloatButton(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        getScreenWidthAndHeight(context);
        setOnTouchListener(this);

    }


    @Override
    public boolean onTouch(View v, MotionEvent event) {
        int ea = event.getAction();
        switch (ea) {
            case MotionEvent.ACTION_DOWN:
                lastX = (int) event.getRawX();// 获取触摸事件触摸位置的原始X坐标
                lastY = (int) event.getRawY();
                originX = lastX;
                originY = lastY;
                break;
            case MotionEvent.ACTION_MOVE:
                int dx = (int) event.getRawX() - lastX;
                int dy = (int) event.getRawY() - lastY;
                int l = v.getLeft() + dx;
                int b = v.getBottom() + dy;
                int r = v.getRight() + dx;
                int t = v.getTop() + dy;
                // 下面判断移动是否超出屏幕
                if (l < 0) {
                    l = 0;
                    r = l + v.getWidth();
                }
                if (t < 0) {
                    t = 0;
                    b = t + v.getHeight();
                }
                if (r > screenWidth) {
                    r = screenWidth;
                    l = r - v.getWidth();
                }
                if (b > screenHeight) {
                    b = screenHeight;
                    t = b - v.getHeight();
                }
                v.layout(l, t, r, b);
                lastX = (int) event.getRawX();
                lastY = (int) event.getRawY();
                v.postInvalidate();
                break;
            case MotionEvent.ACTION_UP:
                distance = (int) event.getRawX() - originX + (int)event.getRawY() - originY;

                break;
        }
        return false;

    }

    private void getScreenWidthAndHeight(Context context) {
        WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        DisplayMetrics dm = new DisplayMetrics();
        wm.getDefaultDisplay().getMetrics(dm);
        screenWidth = dm.widthPixels;
        screenHeight = dm.heightPixels;

    }

}

  优劣:这种方式实现是最简单的的,但是当同一个viewgroup中有控件更新(界面刷新)时,移动的控件会复位,即回到一开始的位置.
  
  
  仔细观察下图,布局中上方的Banner轮播图和FAB按钮是在同一个viewgroup内的,每当轮播图刷新的时候,FAB按钮就回到了原来的位置,所以在该种情况下,不适合用于view.layout()方式实现位移。解决方法未知……
  
  
  缺陷展示效果图:
  实现可拖拽移动的悬浮按钮


   方案2:调用MarginLayoutParams.setMargins()设置View的MarginLeft以及MarginTop属性值从而确立View移动的实时位置。

  该方案可参考:Android-满屏幕拖动的控件

  注意事项:这种方式比较适合RelativeLayout、FrameLayout,AbsoluteLayout,对于LinearLayout,因为最后增加的控件总在最下或最右,所以达不到移动效果,TableLayout也不行。并且你在XML里设置的一些属性值很可能会影响到View的移动。例如:你在XML文件里为FAB按钮的初始位置设置了 android:layout_marginRight=”xxx” 或者landroid:layout_alignParentBottom=”true”属性值。


   方案3使用view.setX()、view.setY()方式实现FAB按钮的移动
    
  代码来自:安卓可拖拽悬浮按钮二

public class DragFloatActionButton extends FloatingActionButton{

    private int parentHeight;     //父容器高度
    private int parentWidth;         //父容器宽度
    private int lastX;               
    private int lastY;
    private boolean isDrag;

    public DragFloatActionButton(Context context) {
        super(context);
    }

    public DragFloatActionButton(Context context, AttributeSet attrs) {
        super(context, attrs);

    }

    public DragFloatActionButton(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

    }

   @Override
    public boolean onTouchEvent(MotionEvent event) {
        int rawX = (int) event.getRawX();
        int rawY = (int) event.getRawY();
        switch (event.getAction() & MotionEvent.ACTION_MASK) {
            case MotionEvent.ACTION_DOWN:
                setPressed(true);             //设置该Button状态为摁下 Pressed状态
                isDrag = false;               //首次摁下时设置 正在拖拽?false

                //表示子组件要自己消费这次事件,告诉父组件不要拦截(抢走)这次的事件
                 getParent().requestDisallowInterceptTouchEvent(true); 
                lastX = rawX;
                lastY = rawY;       //记录上次首次触碰到屏幕时Button的坐标  (lastX ,lastY)
                ViewGroup parent;
                if (getParent() != null) {                          //如果有父容器
                    parent = (ViewGroup) getParent();
                    parentHeight = parent.getHeight();            //获取容器的高度
                    parentWidth = parent.getWidth();             //获取容器的宽度
                }
                break;


 //如果没有父容器直接退出,不实现拖拽移动,限定button只能在有父容器的情况下实现有范围的移动
                if (parentHeight <= 0 || parentWidth == 0) {  

                    isDrag = false;                          //不能拖拽
                    break;                                  //直接退出
                } else {
                    isDrag = true;                        //正在拖拽
                }
                int dx = rawX - lastX;                  //获取手势X坐标轴上的实时位移量
                int dy = rawY - lastY;                 //获取手势Y坐标轴上的实时位移量


 //这里修复一些华为手机无法触发点击事件

                int distance = (int) Math.sqrt(dx * dx + dy * dy);
                if (distance == 0) {
                    isDrag = false;
                    break;

 //getX()当前view距离父容器左边缘的距离 +dx 手势X坐标实时位移量 = 意图当前view距离父容器左边缘的距离                             
//getY()当前view距离父容器顶部边缘的距离 +dy 手势Y坐标实时位移量 = 意图view距离父容器顶部边缘的距离

                float x = getX() + dx;  
                float y = getY() + dy;                     


//检测是否到达边缘 左上右下   并且限制view的移动范围为其父容器范围
//x < 0            表示向左移动超出父容器        
//此时设置 x = 0    继续向左拖拽悬浮按钮不移动
//x > parentWidth-getWidth()        表示向右移动超出父容器        
//此时设置 x = parentWidth-getWidth()    
//代表往X坐标轴上右方向移动的最大偏移量为  父容器宽度-自身宽度  (因为Button的右边也不能超出屏幕所以减去button宽度)

//getY() < 0       表示当前Button超出父容器上方    ->  
//令 y = 0   回到 父容器正上方
//getY()+ getHeight > parentHeight  表示  当前Button超出父容器下方  
//令 y = 父容器高度  -  Button高度   回到父容器正下方


                x = x < 0 ? 0 : x > parentWidth - getWidth() ? parentWidth - getWidth() : x;
                y = getY() < 0 ? 0 : getY() + getHeight() > parentHeight ? parentHeight - getHeight() : y;


                setX(x);           //设置当前view距离父容器左边缘的距离
                setY(y);          // 设置当前view距离父容器左边缘的距离
                lastX = rawX;
                lastY = rawY;
                break;
            case MotionEvent.ACTION_UP:
                if (isDrag) {
                    setPressed(false);
                }
                break;
        }

        //如果是拖拽则消耗事件,否则正常传递即可。
        return isDrag || super.onTouchEvent(event);
    }

  
   优点:这里实现了FAB按钮的拖拽移动功能,并且不会影响为FAB按钮设置onClick事件,并且即使同一个viewgroup中有控件更新(界面刷新)时,移动的控件也不会复位回到一开始的位置。
  
      实现可拖拽移动的悬浮按钮

  没时间写完了。。。明天继续写
  还有很多细节要完善。。。。。。


参考:


相关标签: 移动