使用ViewDragHelper实现可移动可点击控件

概述

上一篇文章WindowManager实现可移动可点击(可只在应用中显示)悬浮窗写了利用WindowManager实现可移动可点击的悬浮窗,这一篇主要讲的是在单个界面中利用ViewDragHelper实现可移动可点击控件。

(一)普通方法实现

在利用ViewDragHelper实现之前,我们先用普通方法实现看看,通过自定义一个父布局,在父布局中添加一个可移动的控件。
这里我创建了一个类DragLayout2继承FrameLayout:

public class DragLayout2 extends FrameLayout {
    private Context mContext;
    private View mDragView; //可移动控件
    private float downX; //手指落下时的x坐标值
    private float downY; //手指落下时的y坐标值
    private OnClickListener mOnClickListener; //给可移动控件添加点击事件的回调接口
    private boolean isMove = false; //判断可移动控件是否移动了
    public DragLayout2(Context context) {
        this(context,null);
    }

    public DragLayout2(Context context, AttributeSet attrs) {
        super(context, attrs);
        this.mContext = context;
    }
}

添加可移动控件,可以在构造函数中进行:

        //通过布局填充器添加
        mDragView = LayoutInflater.from(mContext).inflate(R.layout.kefu, null);
        ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(DensityUtil.dip2px(mContext,52), DensityUtil.dip2px(mContext,52)); //设置控件宽高
        this.addView(mDragView,params);

如果想要通过获取布局中子控件的方式得到mDragView,需要重写onFinishInflate(),在布局完成后得到:

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        mDragView = getChildAt(1); //1是当前布局下的位置,代表第2个直接子控件
    }

我这里就直接使用第一种方法了,不用再在相应的布局文件中添加。用第一种方法,我这里要让控件出来的时候就直接显示在右下角,如下图所示:
这里写图片描述
可以通过调用layout(int l, int t, int r, int b)方法设置布局:

    mDragView.layout(getWidth()-mDragView.getWidth(),
                    getHeight()-mDragView.getHeight(),
                    getWidth(),
                    getHeight());

layout四个参数分别代表控件左边距离整个布局左边的距离,控件右边距离整个布局左边的距离,控件顶部距离整个布局顶部的距离,控件底部距离整个布局顶部的距离,参考下图:
这里写图片描述
这步设置需要放在DragLayout2执行onLayout方法之后,因为在执行该方法后才能获取到整个布局的宽高——getWidth( )和getHeight( )获得的值才不会为0。

然后最重要的就是给控件设置触摸事件了:

    mDragView.setOnTouchListener(new OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                switch (event.getAction()) {
                    case MotionEvent.ACTION_DOWN:
                        downX = event.getX();
                        downY = event.getY();
                        isMove = false;
                        break;
                    case MotionEvent.ACTION_MOVE:
                        float xDistance = event.getX() - downX; //x轴移动距离
                        float yDistance = event.getY() - downY; //y轴移动距离
                        if (xDistance != 0 && yDistance != 0) {
                            int l = (int) (v.getLeft() + xDistance);
                            int r = (int) (v.getRight() + xDistance);
                            int t = (int) (v.getTop() + yDistance);
                            int b = (int) (v.getBottom() + yDistance);
                            if (l < 0){ //使移动不能超过左边界
                                l = 0;
                                r = mDragView.getWidth();
                            }
                            if (r > getWidth()){ //使移动不能超过右边界
                                r = getWidth();
                                l = r - mDragView.getWidth();
                            }
                            if (t < 0){ //使移动不能超过上边界
                                t = 0;
                                b = mDragView.getHeight();
                            }
                            if (b > getHeight()){ //使移动不能超过下边界
                                b = getHeight();
                                t = b - mDragView.getHeight();
                            }
                            v.layout(l, t, r, b); //设置位置
                            isMove = true;
                        }
                        break;
                    case MotionEvent.ACTION_UP:
                        v.layout(getWidth()-
                                mDragView.getWidth(),
                                v.getTop(),getWidth(),
                                v.getBottom()); //设置手指抬起时控件移到右边
                        if (isMove){
                            return true;
                        }
                        break;
                }
                return false;
            }
        });

触摸事件返回false时事件没被执行,点击事件可以被执行;返回true时说明消费了事件,点击事件就不会再被执行了。这里我定义了一个布尔变量isMove,当它移动了isMove就为true,onTouch方法就返回true,就不会触发点击事件。
然后设置一个setOnClickListener方法,让Activity可以通过调用该方法触发可移动控件的点击事件:

    public void setOnClickListener(OnClickListener onClickListener) {
        mOnClickListener = onClickListener;
    }
        mDragView.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                mOnClickListener.onClick(v);
            }
        });

最后效果如下:
这里写图片描述

(二)使用ViewDragHelper实现

使用ViewDragHelper来实现就不用自己写触摸事件了,通过在ViewDragHelper.Callback回调中重写方法就行了。
一、创建实例:

    /**
     * Factory method to create a new ViewDragHelper.
     *
     * @param forParent Parent view to monitor
     * @param sensitivity Multiplier for how sensitive the helper should be about detecting
     *                    the start of a drag. Larger values are more sensitive. 1.0f is normal.
     * @param cb Callback to provide information and receive events
     * @return a new ViewDragHelper instance
     */
    public static ViewDragHelper create(ViewGroup forParent, float sensitivity, Callback cb) {
        final ViewDragHelper helper = create(forParent, cb);
        helper.mTouchSlop = (int) (helper.mTouchSlop * (1 / sensitivity));
        return helper;
    }

通过调用create(ViewGroup forParent, float sensitivity, Callback cb)方法创建实例,该方法有三个参数,第一个参数传入要监视的布局,传入this代表该布局本身;第二个参数是敏感度,1代表正常的敏感度,值越大越敏感;第三个参数是提供信息和接收事件的回调。

private ViewDragHelper mViewDragHelper; //定义一个ViewDragHelper全局变量
mViewDragHelper = ViewDragHelper.create(this, 1.0f, new ViewDragHelper.Callback() {
            @Override
            public boolean tryCaptureView(View child, int pointerId) {
                return child == mDragView;
            }//可以判断是否要捕获当前的view,返回true时表示可以捕获

            @Override  //控制child在水平方向上的位置,不限制边界返回left即可
            public int clampViewPositionHorizontal(View child, int left, int dx) {
                if(left <0){
                    //限制左边界
                    left = 0;
                }else if (left > (getMeasuredWidth() - child.getMeasuredWidth())){
                    //限制右边界
                    left = getMeasuredWidth() - child.getMeasuredWidth();
                }
                return left;
            }

            @Override  //控制child在竖直方向上的位置,不限制边界返回top即可
            public int clampViewPositionVertical(View child, int top, int dy) {
                if(top <0){
                    //限制上边界
                    top = 0;
                }else if (top > (getMeasuredHeight() - child.getMeasuredHeight())){
                    //限制下边界
                    top = getMeasuredHeight() - child.getMeasuredHeight();
                }
                return top;
            }

            @Override  //不重写该方法默认返回0,返回0时若还设置了点击事件则水平方向不能移动
            public int getViewHorizontalDragRange(View child) {
                return getMeasuredWidth() - child.getMeasuredWidth();
            }

            @Override  //不重写该方法默认返回0,返回0时若还设置了点击事件则竖直方向不能移动
            public int getViewVerticalDragRange(View child) {
                return getMeasuredHeight() - child.getMeasuredHeight();
            }

            @Override   //处理手指释放的事件
            public void onViewReleased(View releasedChild, float xvel, float yvel) {
                super.onViewReleased(releasedChild, xvel, yvel);
                if (releasedChild == mDragView){
                    mViewDragHelper.smoothSlideViewTo(releasedChild,getMeasuredWidth() - releasedChild.getMeasuredWidth(),releasedChild.getTop()); //平滑移动到最右边
                    ViewCompat.postInvalidateOnAnimation(DragLayout.this); //刷新布局
                }
            }
        });

二、触摸、拦截事件:
callback中需要重写的方法弄好之后,需要对事件监听做相应的处理,重写onInterceptTouchEvent和onTouchEvent方法:

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        return mViewDragHelper.shouldInterceptTouchEvent(ev);  //由viewDragHelper来判断是否应该拦截此事件
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        mViewDragHelper.processTouchEvent(event);
        return true; //消费掉事件,让ViewDragHelper来处理
    }

三、处理computeScroll():

    @Override
    public void computeScroll() {
        super.computeScroll();
        if (mViewDragHelper.continueSettling(true)){ //判断是否结束
            ViewCompat.postInvalidateOnAnimation(DragLayout.this);
        }
    }

该方法由父级调用,以请求孩子在需要时更新mScrollX和mScrollY的值。不重写该方法,之前手指释放时的动画效果就不会执行。

以上就完成了,其中布局添加和点击事件跟用普通方法实现一样,最终效果图也差不多。
GitHub链接地址

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值