RecyclerView 侧滑实现方法

RecyclerView 侧滑按钮实现方法

启发来源于此博客!
上月末由于家中有事耽搁没有更新博客,在此补上。

原理

在按下 RecyclerView 时记录手指按下的位置并记录,在滑动手指的同时滚动控件,在手指松开时判断是否打开侧滑页面还是回弹起始位置。原理就是这么简单,下面根据这个原理一步步实现。

实现步骤

首先定义基本的 Item 布局 layout_item。

<?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="wrap_content"
    android:background="@drawable/shape_white"
    android:orientation="horizontal"
    android:paddingLeft="10dp">

    <TextView
            android:id="@+id/content"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginRight="10dp"
            android:background="@drawable/shape_bg_logout_select"
            android:gravity="center"
            android:paddingBottom="5dp"
            android:paddingLeft="13dp"
            android:paddingRight="13dp"
            android:paddingTop="5dp"
            android:text="上传"
            android:textColor="@color/talk_text_white"
            android:textSize="14sp" />

    <TextView
        android:id="@+id/delete"
        android:layout_width="100dp"
        android:layout_height="match_parent"
        android:layout_gravity="center_vertical"
        android:background="@color/talk_text_red"
        android:gravity="center"
        android:text="删除"
        android:textColor="@color/talk_text_white"
        android:textSize="14sp" />
</LinearLayout>

在layout_item中,定义了 TextView content 占满横屏,定义 TextView delete 宽度为100,这样 delete 在默认的状态下是看不见的。只有当 Item 向左滑动时才能看到这个超出屏幕的 delete。

在 Item 的布局定义完成后,接下来编写基本的 XRecycleView逻辑

public class XRecyclerView extends RecyclerView {
    // 本次点击的 Item 
    private View mRootView;
    // 上次点击的 Item
    private View mPrevRootView;

    // 上次onTouchEvent 的 X,y坐标
    private int mPrevX = 0;
    private int mPrevY = 0;

    // 允许滑动的最大距离
    final int MAX_WIDTH = 100;
    // MAX_WIDTH 折算为 px的值
    private int MAX_WIDTH_DIMEN;

    public XRecyclerView(Context context) {
        super(context);
        init(context);
    }

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

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

    private void init(Context context) {
        MAX_WIDTH_DIMEN = (int) (context.getResources().getDisplayMetrics().density * MAX_WIDTH + 0.5);
        // RecyclerView 默认是没有 divider 的
        addItemDecoration(new DividerItemDecoration(getContext(), DividerItemDecoration.VERTICAL));
    }
}

上述代码中我们继承 RecyclerView 定义了一个简单的 XRecyclerView,它除了满足 RecyclerView的功能以外,其他什么都不能做。

当手指触摸到 RecyclerView 的时候,我们需要知道触摸的是哪个 Item,于是重写 onTouchEvent方法。在 onTouchEvent 触发 Action_Down 的时候,通过findChildViewUnder(x, y)找出点击的坐标属于哪个 Item,然后根据getChildViewHolder(view) 找出与该 Item 绑定的 XRecyclerViewHolder,在 XRecyclerViewHolder 中我们就能找到该 Item 的 ItemView,以及给该 Item 的 delete 按钮添加点击事件了。

@Override
    public boolean onTouchEvent(MotionEvent event) {
        int x = (int) event.getX();
        int y = (int) event.getY();

        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN: {
                mScrollTotalX = 0;
                mScrollTotalY = 0;

                //根据点击的坐标获取哪个 Item 被点击了
                View view = findChildViewUnder(x, y);
                if (view == null) {
                    // 该事件不进行消费,返回上层控件进行处理
                    return false;
                }

                //获取布局 Item 视图
                final XRecyclerViewHolder viewHolder = (XRecyclerViewHolder) getChildViewHolder(view);
                View itemView = viewHolder.itemView;

                // 保存本次点击视图的对象
                mRootView = itemView;

                if (mOnItemClickListener != null && viewHolder.mDeleteView != null) {
                    viewHolder.mDeleteView.setOnClickListener(new OnClickListener() {
                        @Override
                        public void onClick(View v) {
                            mOnItemClickListener.onItemDelete(viewHolder.getAdapterPosition());
                        }
                    });
                }
            }
            break;

            case MotionEvent.ACTION_MOVE:
                ***
            break;

            case MotionEvent.ACTION_UP: 
                ***
            break;
        }
        mPrevX = x;
        mPrevY = y;
        return super.onTouchEvent(event);
    }

在触发Action_Move 时,ItemView根据手指滑动距离滚动。

@Override
public boolean onTouchEvent(MotionEvent event) {
    ***
    switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN: 
                ***
            break;

            case MotionEvent.ACTION_MOVE: {
                if (mRootView == null)
                    return false;

                if (Math.abs(mPrevX - x) > 0 && Math.abs(mPrevX - x) > Math.abs(mPrevY - y)) {
                    int scrollX = mRootView.getScrollX();
                    int newScrollX = scrollX + mPrevX - x;

                    if (newScrollX < 0)
                        newScrollX = 0;
                    else if (newScrollX > MAX_WIDTH_DIMEN)
                        newScrollX = MAX_WIDTH_DIMEN;

                    mRootView.scrollTo(newScrollX, 0);
                }
            }
            break;

            case MotionEvent.ACTION_UP: 
                ***
            break;
        }
        mPrevX = x;
        mPrevY = y;
        return super.onTouchEvent(event);
}

在触发 Action_Up 时,根据 ItemView滑动的距离判断是否打开侧滑部分,还是恢复原始位置。

@Override
public boolean onTouchEvent(MotionEvent event) {
    ***
    switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN: 
            ***
        break;

        case MotionEvent.ACTION_MOVE: 
            ***
        break;

        case MotionEvent.ACTION_UP: {
            if (mRootView == null)
                return false;

            int newScrollX;
            if (scrollX > MAX_WIDTH_DIMEN / 2) {
                newScrollX = MAX_WIDTH_DIMEN;
                mPrevRootView = mRootView;
            } else
                newScrollX = 0;
            mRootView.scrollTo(newScrollX, 0);
            invalidate();
        }
        break;
    }
    mPrevX = x;
    mPrevY = y;
    return super.onTouchEvent(event);
}

优化

上述通过代码阐述了侧滑实现的基本原理,但是在使用的时候会比较尴尬,比如说,恢复上次滑出的菜单、自然的滑出或回弹菜单、触发 ItemClick点击事件等。这些部分我们都可以在 onTouchEvent 中进行优化。

private void init(Context context) {
    mOpenScroller = new OverScroller(context, new LinearInterpolator());
    mCloseScroller = new OverScroller(context, new AccelerateInterpolator());

    MAX_WIDTH_DIMEN = (int) (context.getResources().getDisplayMetrics().density * MAX_WIDTH + 0.5);

    addItemDecoration(new DividerItemDecoration(getContext(), DividerItemDecoration.VERTICAL));
}

@Override
public boolean onTouchEvent(MotionEvent event) {
    int x = (int) event.getX();
    int y = (int) event.getY();
    LogMgr.e("", "x = " + x);

    switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN: {
            mScrollTotalX = 0;
            mScrollTotalY = 0;

            //根据点击的坐标获取哪个 Item 被点击了
            View view = findChildViewUnder(x, y);
            if (view == null) {
                // 该事件不进行消费,返回上层控件进行处理
                return false;
            }

            //获取布局 Item 视图
            final XRecyclerViewHolder viewHolder = (XRecyclerViewHolder) getChildViewHolder(view);
            View itemView = viewHolder.itemView;

            // 判断本次点击视图是否为上次点击视图,如果是,则重置位置,并清空本次及上次的点击视图
            if (itemView.equals(mPrevRootView)) {
                mScrolledView = mPrevRootView;
                mPrevRootView = mRootView = null;
                mCloseScroller.startScroll(mScrolledView.getScrollX(), 0, -mScrolledView.getScrollX(), 0);
                invalidate();
                return true;
            }

            // 如果本次点击的视图,不是上次点击的视图,则恢复上一次点击的视图
            if (mPrevRootView != null) {
                mScrolledView = mPrevRootView;
                mPrevRootView = null;
                mCloseScroller.startScroll(mScrolledView.getScrollX(), 0, -mScrolledView.getScrollX(), 0);
                invalidate();
            }

            // 保存本次点击视图的对象
            mRootView = itemView;

            if (mOnItemClickListener != null && viewHolder.mDeleteView != null) {
                viewHolder.mDeleteView.setOnClickListener(new OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        mOnItemClickListener.onItemDelete(viewHolder.getAdapterPosition());
                    }
                });
            }
        }
        break;

        case MotionEvent.ACTION_MOVE: {
            if (mRootView == null)
                return false;

            mScrollTotalX += Math.abs(mPrevX - x);
            mScrollTotalY += Math.abs(mPrevY - y);

            if (Math.abs(mPrevX - x) > 0 && Math.abs(mPrevX - x) > Math.abs(mPrevY - y)) {
                int scrollX = mRootView.getScrollX();
                int newScrollX = scrollX + mPrevX - x;

                if (newScrollX < 0)
                    newScrollX = 0;
                else if (newScrollX > MAX_WIDTH_DIMEN)
                    newScrollX = MAX_WIDTH_DIMEN;

                mRootView.scrollTo(newScrollX, 0);
            }
        }
        break;

        case MotionEvent.ACTION_UP: {
            if (mRootView == null)
                return false;

            // 判断是否有滑动,如果没有滑动则触发点击事件
            int scrollX = mRootView.getScrollX();

            if (mScrollTotalX < 1 && mScrollTotalY < 1) {
                // 触发点击事件
                int position = getChildAdapterPosition(mRootView);
                if (mOnItemClickListener != null && position >= 0) {
                    mOnItemClickListener.onItemClickLister(mRootView, position);
                }

                performClick();
                return true;
            }

            /*if (scrollX - 1 <= 0 && Math.abs(mPrevY - y) < 1) {
                // 触发点击事件
                int position = getChildAdapterPosition(mRootView);
                LogMgr.e("", "点了" + position + " 在 scrollX = " + scrollX + " 触发");
                if (mOnItemClickListener != null && position >= 0) {
                    mOnItemClickListener.onItemClickLister(mRootView, position);
                }

                performClick();
                return true;
            }*/

            int newScrollX;
            if (scrollX > MAX_WIDTH_DIMEN / 2) {
                newScrollX = MAX_WIDTH_DIMEN;
                mPrevRootView = mRootView;
            } else
                newScrollX = 0;
            mOpenScroller.startScroll(scrollX, 0, newScrollX - scrollX, 0);
            invalidate();
        }
        break;
    }
    mPrevX = x;
    mPrevY = y;
    return super.onTouchEvent(event);
}

@Override
public void computeScroll() {
    if (mOpenScroller.computeScrollOffset()) {
        if (mRootView != null)
            mRootView.scrollTo(mOpenScroller.getCurrX(), mOpenScroller.getCurrY());
    }

    if (mCloseScroller.computeScrollOffset()) {
        if (mScrolledView != null)
            mScrolledView.scrollTo(mCloseScroller.getCurrX(), mCloseScroller.getCurrY());
    }

    invalidate();
}

@Override
public boolean performClick() {
    return super.performClick();
}

private OnItemClickListener mOnItemClickListener;

public interface OnItemClickListener {
    void onItemClickLister(View itemView, int position);
    void onItemDelete(int position);
}

public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
    this.mOnItemClickListener = onItemClickListener;
}

原理和代码实现就介绍到这里。
源码请见提交记录– f2dacabb560d6e95c3e8a7656ce6da5f6c5cf124

扫描二维码关注公众号,回复: 2342294 查看本文章

由于该 XRecyclerView 与布局文件耦合性太强,所以后续还需继续优化,优化方法在下篇博客中给出( 因为我也要想想该怎么弄O(∩_∩)O哈哈~ )。

猜你喜欢

转载自blog.csdn.net/wxpqqa/article/details/78958978