1. 程式人生 > >左滑操作(刪除,置頂等。。。)

左滑操作(刪除,置頂等。。。)

  • 在滑動列表中,常常會有左滑出現刪除,置頂操作的需求,如下qq的左滑效果:
    qq
今天也來實現下類似的效果,可供大家參考:
  • 1.實現原理

    • 原理圖
      原理圖
  • 2.實現自定義左滑View

    • 1.新增View
      首先在自定義View中通用getChildAt來獲取左邊顯示內容的View和右邊的操作View,這裡通過getChildAt可以更方便的定製按鈕的個數,大小。
// 左邊顯示內容的View
private View leftContentView;
// 右邊操作的View
private View rightActionView;

@Override
protected void onFinishInflate() { super.onFinishInflate(); if (getChildCount() < 2) { throw new IllegalArgumentException("child view less than 2!!"); } leftContentView = getChildAt(0); rightActionView = getChildAt(1); }
  • 2.測量佈局
@Override
protected void onMeasure
(int widthMeasureSpec, int heightMeasureSpec) { // 測量子View的寬高 measureChildren(widthMeasureSpec, heightMeasureSpec); super.onMeasure(widthMeasureSpec, heightMeasureSpec); } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { /* *(0,0) (左width,0) * -------------- -------------- * | 左 | | 右 | * -------------- (左width,左height)-------------- (左width+右width,右height) */
int leftLeft = 0; int leftTop = 0; int leftRight = leftContentView.getMeasuredWidth(); int leftBottom = leftContentView.getMeasuredHeight(); leftContentView.layout(leftLeft, leftTop, leftRight, leftBottom); int rightLeft = leftContentView.getMeasuredWidth(); int rightTop = 0; int rightRight = leftContentView.getMeasuredWidth() + rightActionView.getMeasuredWidth(); int rightBottom = rightActionView.getMeasuredHeight(); rightActionView.layout(rightLeft, rightTop, rightRight, rightBottom); }
  • 寫上我們需要的xml佈局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.welcom.slide.action.view.SlideActionView
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:background="@android:color/white">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="center_vertical"
            android:orientation="horizontal">

            <ImageView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:src="@mipmap/ic_launcher" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="小馬哥" />
        </LinearLayout>

        <LinearLayout
            android:layout_width="100dp"
            android:layout_height="match_parent">

            <TextView
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_weight="1"
                android:background="#ff0000"
                android:gravity="center"
                android:text="刪除" />

            <TextView
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_weight="1"
                android:background="#ff0000"
                android:gravity="center"
                android:text="拉黑" />
        </LinearLayout>
    </com.welcom.slide.action.view.SlideActionView>
</LinearLayout>

執行效果:
執行效果
可以看到和我們的原理圖一樣了,左邊顯示內容區域,右邊因為在螢幕外,暫時看不到。

  • 3.實現滑動
// 是否滑動到右邊
private boolean isShowRightView = false;
// 滑動輔助類
private ViewDragHelper helper;

public SlideActionView(Context context) {
    this(context, null);
}

public SlideActionView(Context context, AttributeSet attrs) {
    this(context, attrs, 0);
}

public SlideActionView(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    initView();
}

private void initView() {
    helper = ViewDragHelper.create(this, new ViewDragHelper.Callback() {
        @Override
        public boolean tryCaptureView(@NonNull View child, int pointerId) {
            //捕獲需要滑動的View,這裡返回true
            return true;
        }

        @Override
        public void onViewPositionChanged(@NonNull View changedView, int left, int top, int dx, int dy) {
            //如果左邊控制元件拖動,我們要讓右邊控制元件也重新佈局,反之
            if (changedView == leftContentView) {
                rightActionView.layout(rightActionView.getLeft() + dx, 0, rightActionView.getRight() + dx, rightActionView.getBottom() + dy);
            } else if (changedView == rightActionView) {
                leftContentView.layout(leftContentView.getLeft() + dx, 0, leftContentView.getRight() + dx, leftContentView.getBottom() + dy);
            }
        }

        @Override
        public int clampViewPositionHorizontal(@NonNull View child, int left, int dx) {
            //對左右越界問題的處理
            if (child == leftContentView) {
                //處理兩邊的越界問題
                if (left >= 0) {
                    left = 0;
                } else if (left <= -rightActionView.getMeasuredWidth()) {
                    left = -rightActionView.getMeasuredWidth();
                }
            } else if (child == rightActionView) {
                if (left <= leftContentView.getMeasuredWidth() - rightActionView.getMeasuredWidth()) {
                    left = leftContentView.getMeasuredWidth() - rightActionView.getMeasuredWidth();
                } else if (left >= leftContentView.getMeasuredWidth()) {
                    left = leftContentView.getMeasuredWidth();
                }
            }
            return left;
        }

        @Override
        public void onViewReleased(View releasedChild, float xvel, float yvel) {
            //鬆開後,什麼時候開啟rightActionView,什麼時候關閉leftContentView
            //臨界值,rightActionView.getLeft() 和 螢幕的寬度-rightActionView.getWidth()/2
            if (releasedChild == leftContentView) {
                if (rightActionView.getLeft() < getMeasuredWidth() - rightActionView.getMeasuredWidth() / 2) {
                    //使用ViewDragHelper來滑動
                    helper.smoothSlideViewTo(rightActionView, getMeasuredWidth() - rightActionView.getMeasuredWidth(), 0);
                    isShowRightView = true;
                    invalidate();
                } else {
                    helper.smoothSlideViewTo(rightActionView, getMeasuredWidth(), 0);
                    isShowRightView = false;
                    invalidate();
                }
            } else if (releasedChild == rightActionView) {
                if (rightActionView.getLeft() < getMeasuredWidth() - rightActionView.getMeasuredWidth() / 2) {
                    helper.smoothSlideViewTo(rightActionView, getMeasuredWidth() - rightActionView.getMeasuredWidth(), 0);
                    isShowRightView = true;
                    invalidate();
                } else {
                    helper.smoothSlideViewTo(rightActionView, getMeasuredWidth(), 0);
                    isShowRightView = false;
                    invalidate();
                }
            }
        }
    });
}

@Override
public void computeScroll() {
    if (helper.continueSettling(true)) {
        ViewCompat.postInvalidateOnAnimation(this);
    }
}

@SuppressLint("ClickableViewAccessibility")
@Override
public boolean onTouchEvent(MotionEvent event) {
    //處理父檢視接收的觸控事件。此方法將排程回撥事件。
    helper.processTouchEvent(event);
    return true;
}

執行程式碼,效果:
執行效果

  • 到這裡就完了嗎?沒有,接著我們將這個放入的RecyclerView中,這裡直接說問題
    • 1.在RecyclerView中會出現左右滑動的時候也可以上下滑動
    • 2.顯示某條item右邊時,再次滑動因為未復位右邊顯示區域導致複用顯示問題。
    • 3.點選回撥未處理
      解決方法,繼承RecyclerView,重寫dispatchTouchEvent中判斷
      完整程式碼如下:
public class SlideRecyclerView extends RecyclerView {
    public SlideRecyclerView(Context context) {
        super(context);
    }

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

    public SlideRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        try {
            if (SlideActionView.isShowRightView()) {
                if (SlideActionView.disTouchArea(ev)) {
                    return super.dispatchTouchEvent(ev);
                } else {
                    SlideActionView.restorePosition();
                    return true;
                }
            }
        } catch (Exception e) {
            //ignore
        }
        return super.dispatchTouchEvent(ev);
    }
}
public class SlideActionView extends ViewGroup {

    private static SlideActionView slideActionView = null;

    // 左邊顯示內容的View
    private View leftContentView;
    // 右邊操作的View
    private View rightActionView;
    // 是否滑動到右邊
    private boolean isShowRightView = false;
    // 滑動輔助類
    private ViewDragHelper helper;

    private ISlideRightActionOnClickListener rightActionOnClickListener;

    public void setRightActionOnClickListener(ISlideRightActionOnClickListener rightActionOnClickListener) {
        this.rightActionOnClickListener = rightActionOnClickListener;
    }

    public SlideActionView(Context context) {
        this(context, null);
    }

    public SlideActionView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public SlideActionView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initView();
    }

    private void initView() {
        helper = ViewDragHelper.create(this, new ViewDragHelper.Callback() {
            @Override
            public boolean tryCaptureView(@NonNull View child, int pointerId) {
                //捕獲需要滑動的View,這裡返回true
                return true;
            }

            @Override
            public void onViewPositionChanged(@NonNull View changedView, int left, int top, int dx, int dy) {
                //如果左邊控制元件拖動,我們要讓右邊控制元件也重新佈局,反之
                if (changedView == leftContentView) {
                    rightActionView.layout(rightActionView.getLeft() + dx, 0, rightActionView.getRight() + dx, rightActionView.getBottom() + dy);
                } else if (changedView == rightActionView) {
                    leftContentView.layout(leftContentView.getLeft() + dx, 0, leftContentView.getRight() + dx, leftContentView.getBottom() + dy);
                }
            }

            @Override
            public int clampViewPositionHorizontal(@NonNull View child, int left, int dx) {
                //對左右越界問題的處理
                if (child == leftContentView) {
                    //處理兩邊的越界問題
                    if (left >= 0) {
                        left = 0;
                    } else if (left <= -rightActionView.getMeasuredWidth()) {
                        left = -rightActionView.getMeasuredWidth();
                    }
                } else if (child == rightActionView) {
                    if (left <= leftContentView.getMeasuredWidth() - rightActionView.getMeasuredWidth()) {
                        left = leftContentView.getMeasuredWidth() - rightActionView.getMeasuredWidth();
                    } else if (left >= leftContentView.getMeasuredWidth()) {
                        left = leftContentView.getMeasuredWidth();
                    }
                }
                return left;
            }

            @Override
            public void onViewReleased(View releasedChild, float xvel, float yvel) {
                //鬆開後,什麼時候開啟rightActionView,什麼時候關閉leftContentView
                //臨界值,rightActionView.getLeft() 和 螢幕的寬度-rightActionView.getWidth()/4
                if (releasedChild == leftContentView) {
                    if (rightActionView.getLeft() < getMeasuredWidth() - rightActionView.getMeasuredWidth() / 4) {
                        //使用ViewDragHelper來滑動
                        helper.smoothSlideViewTo(rightActionView, getMeasuredWidth() - rightActionView.getMeasuredWidth(), 0);
                        isShowRightView = true;
                        invalidate();
                    } else {
                        helper.smoothSlideViewTo(rightActionView, getMeasuredWidth(), 0);
                        isShowRightView = false;
                        invalidate();
                    }
                } else if (releasedChild == rightActionView) {
                    if (rightActionView.getLeft() < getMeasuredWidth() - rightActionView.getMeasuredWidth() / 2) {
                        helper.smoothSlideViewTo(rightActionView, getMeasuredWidth() - rightActionView.getMeasuredWidth(), 0);
                        isShowRightView = true;
                        invalidate();
                    } else {
                        helper.smoothSlideViewTo(rightActionView, getMeasuredWidth(), 0);
                        isShowRightView = false;
                        invalidate();
                    }
                }
            }
        });
    }

    @Override
    public void computeScroll() {
        if (helper.continueSettling(true)) {
            ViewCompat.postInvalidateOnAnimation(this);
        } else {
            if (isShowRightView) {
                slideActionView = this;
            } else {
                slideActionView = null;
            }
        }
    }

    @SuppressLint("ClickableViewAccessibility")
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        //處理父檢視接收的觸控事件。此方法將排程回撥事件。
        helper.processTouchEvent(event);
        return true;
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        if (getChildCount() < 2) {
            throw new IllegalArgumentException("child view less than 2!!");
        }
        leftContentView = getChildAt(0);
        rightActionView = getChildAt(1);
        if (rightActionView instanceof ViewGroup) {
            for (int i = 0; i < ((ViewGroup) rightActionView).getChildCount(); i++) {
                ((ViewGroup) rightActionView).getChildAt(i).setOnClickListener(new OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        if (rightActionOnClickListener != null) {
                            rightActionOnClickListener.onClick(v);
                        }
                    }
                });
            }
        } else {
            rightActionView.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View v) {
                    if (rightActionOnClickListener != null) {
                        rightActionOnClickListener.onClick(v);
                    }
                }
            });
        }
    }

    public static boolean disTouchArea(MotionEvent ev) {
        if (slideActionView == null) return false;
        float rawX = ev.getRawX();
        float rawY = ev.getRawY();
        int[] location = new int[2];
        slideActionView.getLocationOnScreen(location);
        Log.i("wxq", "rawX->" + rawX);
        Log.i("wxq", "rawY->" + rawY);
        Log.i("wxq", "locationX->" + location[0]);
        Log.i("wxq", "locationY->" + location[1]);
        return rawX > location[0] && rawX < (slideActionView.getMeasuredWidth() + location[0])
                && rawY > location[1] && rawY < (slideActionView.getMeasuredHeight() + location[1]);
    }

    public static boolean isShowRightView() {
        return slideActionView != null;
    }

    //還原位置
    public static void restorePosition() {
        if (!isShowRightView()) {
            return;
        }
        try {
            if (slideActionView.helper.continueSettling(true)) return;
            slideActionView.helper.smoothSlideViewTo(slideActionView.rightActionView, slideActionView.getMeasuredWidth(), 0);
            slideActionView.isShowRightView = false;
            slideActionView.invalidate();
        } catch (Exception e) {
            //ignore
        }
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        // 測量子View的寬高
        measureChildren(widthMeasureSpec, heightMeasureSpec);
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        /*
         *(0,0)                              (左width,0)
         * --------------                    --------------
         * |    左      |                    |      右    |
         * --------------  (左width,左height)-------------- (左width+右width,右height)
         */
        int leftLeft = 0;
        int leftTop = 0;
        int leftRight = leftContentView.getMeasuredWidth();
        int leftBottom = leftContentView.getMeasuredHeight();
        leftContentView.layout(leftLeft, leftTop, leftRight, leftBottom);

        int rightLeft = leftContentView.getMeasuredWidth();
        int rightTop = 0;
        int rightRight = leftContentView.getMeasuredWidth() + rightActionView.getMeasuredWidth();
        int rightBottom = rightActionView.getMeasuredHeight();
        rightActionView.layout(rightLeft, rightTop, rightRight, rightBottom);
    }

    public interface ISlideRightActionOnClickListener {
        void onClick(View view);
    }
}
傳送門git示例程式碼