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

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

程序员文章站 2022-05-15 16:57:31
...

    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的消息滑动效果,下面是效果图

ViewDragHelper详解及实现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();
        }
    }
}