ViewDragHelper详解及实现QQ聊天信息侧滑

    ViewDragHelper通常是在自定义的ViewGroup中使用,通过ViewDragHelper,我们可以很方便的实现ViewGroup中子View的滑动。

    ViewDragHelper有几个常见的方法:

        ViewDragHelper dragHelper=ViewDragHelper.create(this, new ViewDragHelper.Callback() {

            /*
            * child - Child the user is attempting to capture
            * pointerId - ID of the pointer attempting the capture
            * 该方法在ViewGroup中的子View被点击时会调用一次
            * 第一个参数child是被点击的子View
            * 如果返回值为true,表示响应,后面的所有方法才能执行
            * 如果直接 return true,则表示,所有的子View都响应
            * 可以进行判断,如果是指定的View则返回true
            */
            @Override
            public boolean tryCaptureView(@NonNull View child, int pointerId) {
                return false;
            }

            /*
            * 水平滑动响应
            * 该方法只响应水平滑动,在滑动过程中持续响应。
            * 注意:该方法调用时,View位置还未改变
            * child 滑动的View
            * left  滑动后View左边距与父View左边距的距离
            * dx    滑动的距离
            * 返回值为滑动View的最终left
            * 如:子View child,child的原本left为20
            * 手指在其上向右滑动30,此时调用该方法,参数left=20+30,dx=30。
            * 如return left,则child滑到left为50处。如果return 20,则不滑动。
            *
            */
            @Override
            public int clampViewPositionHorizontal(@NonNull View child, int left, int dx) {
                return super.clampViewPositionHorizontal(child, left, dx);
            }

            /*
            * 垂直滑动响应,与水平滑动类似
            */
            @Override
            public int clampViewPositionVertical(@NonNull View child, int top, int dy) {
                return super.clampViewPositionVertical(child, top, dy);
            }
            /*
            * 与上面的垂直水平滑动类似,不同之处在于:
            * 该方法调用时,View位置已经发生改变。
            * 如果View已经滑动到无法滑动,其参数值固定不变的
            * */
            @Override
            public void onViewPositionChanged(@NonNull View changedView, int left, int top, int dx, int dy) {
                super.onViewPositionChanged(changedView, left, top, dx, dy);
            }

            /*
            * 松开View时调用
            * xvel - X velocity of the pointer as it left the screen in pixels per second.
            * yvel - Y velocity of the pointer as it left the screen in pixels per second.
            * 貌似是手指松开时的滑动速度
            * */
            @Override
            public void onViewReleased(@NonNull View releasedChild, float xvel, float yvel) {
                super.onViewReleased(releasedChild, xvel, yvel);
            }
        });

接下来开始实现类似QQ的消息滑动效果,下面是效果图



首先是XML代码

<?xml version="1.0" encoding="utf-8"?>
<com.example.m.viewdraghelpertest.VDHLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal"
            android:background="@color/colorAccent"
            android:id="@+id/front">
                <ImageView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:src="@drawable/ic_launcher_background"/>
                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="match_parent"
                    android:text="Hello World!"
                    android:textSize="16sp"
                    android:gravity="center" />
        </LinearLayout>
    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:orientation="horizontal"
        android:id="@+id/back"
        android:background="@color/colorPrimary">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:textSize="16sp"
            android:layout_gravity="center"
            android:text="置顶"
            android:gravity="center"/>
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:textSize="16sp"
            android:layout_gravity="center"
            android:text="删除"
            android:gravity="center"/>
    </LinearLayout>

</com.example.m.viewdraghelpertest.VDHLayout>


    最外层的自定义类VDCLayout继承了FrameLayout,通过ViewDragHelper的帮助,实现了上述效果。

    首先,因为VDCLayout继承了FrameLayout,所以front和back这两个LinearLayout是重叠在一起的。所以一开始我们需要让back的位置在front的左边,也就是屏幕外。

    VDCLayout中,重写onSizeChanged()方法,获取back和front的宽度。然后在重写的onLayout方法中,通过back的layout方法,将back放在front右边。

    back和front对象的获取在onFinishInflate()中完成

  @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        mFront=findViewById(R.id.front);
        mBack=findViewById(R.id.back);
    }
@Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);
        //参数为各边距离父控件的距离
        mBack.layout(mFrontWidth,0,mFrontWidth+mBackWidth,getMeasuredHeight());
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        mBackWidth=mBack.getMeasuredWidth();
        mFrontWidth=mFront.getMeasuredWidth();
        super.onSizeChanged(w, h, oldw, oldh);
    }


    然后我们需要初始化一下ViewDragHelper,并且重写上面提到的除了垂直滑动的所有方法。在重写这些方法之前,我们还需要重写VDCLayout里的方法onInterceptTouchEvent()和OnTouchEvent()。

@Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        return mDragHelper.shouldInterceptTouchEvent(ev);
    }
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        mDragHelper.processTouchEvent(event);
        return true; }

   

    这个总的意思就是拦截触摸事件,将触摸事件交给mDragHelper处理。获得了触摸事件后,就可以开始重写ViewDragHelper的方法

mDragHelper=ViewDragHelper.create(this, 1.0f, new ViewDragHelper.Callback() {//第二个参数是响应的敏感程度,1.0f是正常
            
            @Override
            public boolean tryCaptureView(@NonNull View child, int pointerId) {
                return true;
            }
           
            @Override
            public int clampViewPositionHorizontal(@NonNull View child, int left, int dx) {
                //front滑动,left不能小于-mBackWidth,不能大于0
                if (child==mFront){
                    if (left<=-mBackWidth)
                        return -mBackWidth;
                    else if (left>=0)
                        return 0;
                    return left;
                }else {//back滑动,left不能小于mFrontWidth-mBackWidth,不能大于mFrontWidth
                    if (left>=mFrontWidth)
                        return mFrontWidth;
                    else if (left<=mFrontWidth-mBackWidth)
                        return mFrontWidth-mBackWidth;
                    return left;
                }

            }
            //为了不卡在中间,要么完全显示,要么完全隐藏
            @Override
            public void onViewReleased(@NonNull View releasedChild, float xvel, float yvel) {              
                if (mFront.getLeft()<=-mBackWidth/2){
                    mDragHelper.smoothSlideViewTo(mFront,-mBackWidth,mFront.getTop());
                }else if (mFront.getLeft()<0&&mFront.getLeft()>-mBackWidth/2){
                    mDragHelper.smoothSlideViewTo(mFront,0,mFront.getTop());
                }
                invalidate();
            }

            @Override
            public void onViewPositionChanged(@NonNull View changedView, int left, int top, int dx, int dy) {
                //如果front滑动了,back的位置也要随之变化,反之亦然
                if (changedView==mFront){
                    mBack.offsetLeftAndRight(dx);
                }else if (changedView==mBack){
                    mFront.offsetLeftAndRight(dx);
                }
               
            }
        });

    这里要注意,mDragHelper.smoothSlideViewTo()中调用了Scroller的startScroll()方法,所以我们需要重写computeScroll()方法。

 @Override
    public void computeScroll() {
        if (mDragHelper.continueSettling(true)){
            invalidate();
        }
    }

    

    完整的代码如下:

public class VDHLayout extends FrameLayout{
    private ViewDragHelper mDragHelper;
    private View mFront;
    private View mBack;
    private int mBackWidth;
    private int mFrontWidth;
    public VDHLayout(Context context) {
        super(context);
        init();
    }

    public VDHLayout(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public VDHLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    public VDHLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        init();
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);
        //参数为距离父控件的上下左右距离
        mBack.layout(mFrontWidth,0,mFrontWidth+mBackWidth,getMeasuredHeight());
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        mBackWidth=mBack.getMeasuredWidth();
        mFrontWidth=mFront.getMeasuredWidth();
        super.onSizeChanged(w, h, oldw, oldh);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        return mDragHelper.shouldInterceptTouchEvent(ev);
    }
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        mDragHelper.processTouchEvent(event);
        return true;
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        mFront=findViewById(R.id.front);
        mBack=findViewById(R.id.back);
    }

    private void init(){

        mDragHelper=ViewDragHelper.create(this, 1.0f, new ViewDragHelper.Callback() {
            //会在触碰到View的时候调用一次,松开,再触碰才会调用第二次
            @Override
            public boolean tryCaptureView(@NonNull View child, int pointerId) {
                return true;
            }

            //left:控件的X坐标   dx:水平滑动的增量
            @Override
            public int clampViewPositionHorizontal(@NonNull View child, int left, int dx) {

                if (child==mFront){
                    if (left<=-mBackWidth)
                        return -mBackWidth;
                    else if (left>=0)
                        return 0;
                    return left;
                }else {
                    if (left>=mFrontWidth)
                        return mFrontWidth;
                    else if (left<=mFrontWidth-mBackWidth)
                        return mFrontWidth-mBackWidth;
                    return left;
                }

            }
            //top:控件的Y坐标    dy:垂直滑动的增量

            @Override
            public int clampViewPositionVertical(@NonNull View child, int top, int dy) {
                return super.clampViewPositionVertical(child, top, dy);
            }

            //触碰到边缘的时候调用一次,松开,再触碰才会调用第二次
            @Override
            public void onEdgeDragStarted(int edgeFlags, int pointerId) {

            }
            //手指松开的时候调用
            @Override
            public void onViewReleased(@NonNull View releasedChild, float xvel, float yvel) {
                Log.v("松开","xvel:"+xvel+"  yvel:"+yvel);
                if (mFront.getLeft()<=-mBackWidth/2){
                    mDragHelper.smoothSlideViewTo(mFront,-mBackWidth,mFront.getTop());
                }else if (mFront.getLeft()<0&&mFront.getLeft()>-mBackWidth/2){
                    mDragHelper.smoothSlideViewTo(mFront,0,mFront.getTop());
                }
                invalidate();
            }

            @Override
            public void onViewPositionChanged(@NonNull View changedView, int left, int top, int dx, int dy) {

                if (changedView==mFront){
                    mBack.offsetLeftAndRight(dx);
                }else if (changedView==mBack){
                    mFront.offsetLeftAndRight(dx);
                }

            }
        });

    }

    @Override
    public void computeScroll() {
        if (mDragHelper.continueSettling(true)){
            invalidate();
        }
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值