Android Recycleview的侧滑删除加上拉加载下拉刷新

前言

最近写了很多有关Recycleview的东西。然后在iOS那边列表有个侧滑删除,我问了公司的iOS的大神,他说那是iOS原生就有的东西。Android这边是没有这个东西的,于是就想写一下。

名言

只要你不认输,就有机会!


先来看下效果:
这里写图片描述

效果的话就是大概这个样子。

我们还是先一步一步的来看。
先说一说怎么实现侧滑删除这个功能的吧。
考虑这个问题 因为Android没有侧滑删除这个东西,所以用Recycleview做的话只能考虑自定义,然后侧滑,侧滑,手势有关,onTouch事件。既然思路有了就向下面写。


先看item是怎么布局的

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="100dp"
    android:background="#ffffff">

    <LinearLayout
        android:id="@+id/item_layout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true"
        android:layout_alignParentTop="true"
        android:orientation="horizontal">

        <TextView
            android:id="@+id/item_content"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:gravity="center"
            android:text="0"
            android:textColor="#000000"
            android:textSize="20sp" />

        <TextView
            android:id="@+id/item_delete"
            android:layout_width="100dp"
            android:layout_height="match_parent"
            android:background="#88ff0000"
            android:gravity="center"
            android:text="删除"
            android:textColor="#ffffff"
            android:textSize="20sp" />
    </LinearLayout>

    <View
        android:layout_width="match_parent"
        android:layout_height="1dp"
        android:layout_alignParentBottom="true"
        android:background="#55000000" />

</RelativeLayout>

还有就是有一个数据类

public class Mogu {
    private int type = 0;
    private String name = null;

    public Mogu(int type, String name) {
        this.type = type;
        this.name = name;
    }

    public int getType() {
        return type;
    }

    public void setType(int type) {
        this.type = type;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

然后item布局有了recycleview重要的是什么?—————>adapter

public class MyAdapter extends RecyclerView.Adapter {
    private Context mContext;
    private LayoutInflater mInflater;
    private ArrayList<Mogu> list;
    private  int item_type = 0;

    public MyAdapter(Context context) {
        mContext = context;
        mInflater = LayoutInflater.from(context);
        list = new ArrayList<>();
    }

    public ArrayList<Mogu> getList() {
        return list;
    }

    public void setList(ArrayList<Mogu> list) {
        this.list = list;
    }


//找到布局
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = mInflater.inflate(R.layout.item, parent, false);
        MyViewHolder holder = new MyViewHolder(view);
        return holder;
    }

//绑定数据
    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, final int position) {
        if (holder instanceof MyViewHolder) {
            MyViewHolder myViewHolder = (MyViewHolder) holder;
            if (list != null) {
                myViewHolder.content.setText(list.get(position).getName());
                item_type = list.get(position).getType();
                if (item_type ==1){
                    myViewHolder.delete.setText("删除");
                    myViewHolder.delete.setBackgroundColor(Color.RED);
                }else {
                    myViewHolder.delete.setText("取消");
                    myViewHolder.delete.setBackgroundColor(Color.BLACK);
                }
            }
        }
    }

    @Override
    public int getItemCount() {
        return list.size();
    }

    //viewholder
    class MyViewHolder extends RecyclerView.ViewHolder {
        public TextView content;
        public TextView delete;
        public LinearLayout layout;

        public MyViewHolder(View itemView) {
            super(itemView);
            content = (TextView) itemView.findViewById(R.id.item_content);
            delete = (TextView) itemView.findViewById(R.id.item_delete);
            layout = (LinearLayout) itemView.findViewById(R.id.item_layout);
        }
    }

    //删除一行item的方法。
     public void removeItem(int position) {
            list.remove(position);
            notifyDataSetChanged();
        }
}

这里adapter的写法和原来是相同的我就不解释了。

说一下删除的这个方法:

 public void removeItem(int position) {
            list.remove(position);
            notifyDataSetChanged();
        }

删除某一item就是移除集合里面的那一项就是了所以着了给了一个position。然后adapter在刷新一下。更新一下视图。就行了。

接下来就是自定义的recycleview了。

public class ItemRemoveRecyclerView extends RecyclerView {
    private Context mContext;

    //上一次的触摸点
    private int mLastX, mLastY;
    //当前触摸的item的位置
    private int mPosition;

    //item对应的布局
    private LinearLayout mItemLayout;
    //删除按钮
    private TextView mDelete;

    //最大滑动距离(即删除按钮的宽度)
    private int mMaxLength;
    //是否在垂直滑动列表
    private boolean isDragging;
    //item是在否跟随手指移动
    private boolean isItemMoving;

    //item是否开始自动滑动
    private boolean isStartScroll;
    //删除按钮状态   0:关闭 1:将要关闭 2:将要打开 3:打开
    private int mDeleteBtnState;

    //检测手指在滑动过程中的速度
    private VelocityTracker mVelocityTracker;
    private Scroller mScroller;
    private OnItemClickListener mListener;

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

    public ItemRemoveRecyclerView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public ItemRemoveRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        mContext = context;

        mVelocityTracker = VelocityTracker.obtain();
        //LinearInterpolator());插值器控制动画播放的速度这个是先快后面,插值器还有很多种可以试一试
        mScroller = new Scroller(context, new LinearInterpolator());
    }

//重写了onTouch事件
   @Override
    public boolean onTouchEvent(MotionEvent e) {
        mVelocityTracker.addMovement(e);

        int x = (int) e.getX();
        int y = (int) e.getY();
        switch (e.getAction()) {
            //手指按下的状态可能会有2个状态一个是已经滑动过去了就是打开的 一个是没有滑动过去就是关闭的
            case MotionEvent.ACTION_DOWN:
            //假如按下是0这个状态也就是关闭的状态的
                if (mDeleteBtnState == 0) {
                //findChildViewUnder(x, y),recycleview提供的一个方法。
                //这个ChildHelper类,它会协助获取RecyclerView中的childVIew,并提供忽略隐Children的功能,也就是说,调它的getChildAt只会在当前显示的Children中去查找,如果想HiddenChildren,需要调getUnfilteredChildAt。
                    View view = findChildViewUnder(x, y);
                    if (view == null) {
                        return false;
                    }
                    //找到viewholder
                    MyAdapter.MyViewHolder viewHolder = (MyAdapter.MyViewHolder) getChildViewHolder(view);
                    //item 的外层布局
                    mItemLayout = viewHolder.layout;
                    mPosition = viewHolder.getAdapterPosition();
                    //找到删除的按钮
                    mDelete = (TextView) mItemLayout.findViewById(R.id.item_delete);
                    //滑动的宽度是mDelete控件的宽度
                    mMaxLength = mDelete.getWidth();
                    //获取mDelete控件上面的字看是什么
                    String str = mDelete.getText().toString();
                    //如果是删除的话我们就调用删除的监听,如果是取消就做取消的监听。
                    if (str.equals("删除")) {
                        mDelete.setOnClickListener(new OnClickListener() {
                            @Override
                            public void onClick(View v) {
                            //调用开始写好的删除的监听
                                mListener.onDeleteClick(mPosition);
                                //scrollTo(x,y)滚动到什么地方
                                mItemLayout.scrollTo(0, 0);
                                //然后在回到关闭的状态
                                mDeleteBtnState = 0;
                            }
                        });
                    } else {
                        mDelete.setOnClickListener(new OnClickListener() {
                            @Override
                            public void onClick(View v) {
                            //调用开始写好的取消的监听
                                mListener.onCancel(mPosition);
                                mItemLayout.scrollTo(0, 0);
                                mDeleteBtnState = 0;
                            }
                        });
                    }

                //假如是3这个状态也就是打开的状态
                } else if (mDeleteBtnState == 3) {
                //简单理解从点  A(mItemLayout.getScrollX(), 0)滑动到点B(-mMaxLength, 0)时间200
                    mScroller.startScroll(mItemLayout.getScrollX(), 0, -mMaxLength, 0, 200);
                    //刷新一下界面
                    invalidate();
                    //然后让他处于关闭状态去
                    mDeleteBtnState = 0;
                    return false;
                } else {
                    return false;
                }

                break;
            case MotionEvent.ACTION_MOVE:

                    int dx = mLastX - x;
                    int dy = mLastY - y;

                    int scrollX = mItemLayout.getScrollX();
                    if (Math.abs(dx) > Math.abs(dy)) {//左边界检测
                        isItemMoving = true;
                        if (scrollX + dx <= 0) {
                            mItemLayout.scrollTo(0, 0);
                            return true;
                        } else if (scrollX + dx >= mMaxLength) {//右边界检测
                            mItemLayout.scrollTo(mMaxLength, 0);
                            return true;
                        }
                        mItemLayout.scrollBy(dx, 0);//item跟随手指滑动
                }


                break;
            case MotionEvent.ACTION_UP:
                if (!isItemMoving && !isDragging && mListener != null) {
                    mListener.onItemClick(mItemLayout, mPosition);
                }
                isItemMoving = false;

                mVelocityTracker.computeCurrentVelocity(1000);//计算手指滑动的速度
                float xVelocity = mVelocityTracker.getXVelocity();//水平方向速度(向左为负)
                float yVelocity = mVelocityTracker.getYVelocity();//垂直方向速度

                int deltaX = 0;
                int upScrollX = mItemLayout.getScrollX();

                if (Math.abs(xVelocity) > 100 && Math.abs(xVelocity) > Math.abs(yVelocity)) {
                    if (xVelocity <= -100) {//左滑速度大于100,则删除按钮显示
                        deltaX = mMaxLength - upScrollX;
                        mDeleteBtnState = 2;
                    } else if (xVelocity > 100) {//右滑速度大于100,则删除按钮隐藏
                        deltaX = -upScrollX;
                        mDeleteBtnState = 1;
                    }
                } else {
                    if (upScrollX >= mMaxLength / 2) {//item的左滑动距离大于删除按钮宽度的一半,则则显示删除按钮
                        deltaX = mMaxLength - upScrollX;
                        mDeleteBtnState = 2;
                    } else if (upScrollX < mMaxLength / 2) {//否则隐藏
                        deltaX = -upScrollX;
                        mDeleteBtnState = 1;
                    }
                }

                //item自动滑动到指定位置
                mScroller.startScroll(upScrollX, 0, deltaX, 0, 200);
                isStartScroll = true;
                invalidate();

                mVelocityTracker.clear();
                break;
        }

        mLastX = x;
        mLastY = y;
        return super.onTouchEvent(e);
    }

//这里下面说
    @Override
    public void computeScroll() {
        if (mScroller.computeScrollOffset()) {
            mItemLayout.scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
            invalidate();
        } else if (isStartScroll) {
            isStartScroll = false;
            if (mDeleteBtnState == 1) {
                mDeleteBtnState = 0;
            }

            if (mDeleteBtnState == 2) {
                mDeleteBtnState = 3;
            }
        }
    }

    @Override
    protected void onDetachedFromWindow() {
        mVelocityTracker.recycle();
        super.onDetachedFromWindow();
    }

    @Override
    public void onScrollStateChanged(int state) {
        super.onScrollStateChanged(state);
        isDragging = state == SCROLL_STATE_DRAGGING;
    }

    public void setOnItemClickListener(OnItemClickListener listener) {
        mListener = listener;
    }
}

这里说一下computeScroll

 @Override
    public void computeScroll() {
        if (mScroller.computeScrollOffset()) {
            mItemLayout.scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
            invalidate();
        } else if (isStartScroll) {
            isStartScroll = false;
            if (mDeleteBtnState == 1) {
                mDeleteBtnState = 0;
            }

            if (mDeleteBtnState == 2) {
                mDeleteBtnState = 3;
            }
        }
    }

这个computeScroll和Scroller之间的纠葛。
1、说要有什么关系也没得什么直接关系。但并不意味着没有关系。
2、Scroller说白了是个计算器,是为滑动提供计算。他不是让滑动改变的。
3、computeScroll也是计算,看是如何滑动的,他也不是滑动的更本原因。
4、真正让其实现滑动的是scrollTo,scrollBy这2个东西。
5、非要说computeScroll和Scroller的关系的话那就是computeScroll可以参考Scroller计算结果来影响scrollTo,scrollBy,从而使得滑动发生改变。也就是Scroller不会调用computeScroll,反而是computeScroll调用Scroller。
6、连续滑动的时候就好比上面的例子假如我一直划过去划过来他的状态是怎么保持的,omputeScroll调用Scroller,只要computeScroll调用连续,Scroller也会连续。

其他的感觉没得什么可以说的。
我把监听的回调接口贴出来

public interface OnItemClickListener {
    /**
     * item点击回调
     *
     * @param view
     * @param position
     */
    void onItemClick(View view, int position);

    /**
     * 删除按钮回调
     *
     * @param position
     */
    void onDeleteClick(int position);


    /**
     * 取消回调
     *
     * @param position
     */
    void onCancel(int position);
}

接下来就是Activity中了
先看看布局

   <com.example.beiduo.testmyrecycle.removeitemrecyclerview.ItemRemoveRecyclerView
            android:id="@+id/recycle"
            android:layout_width="match_parent"
            android:layout_height="match_parent">
        </com.example.beiduo.testmyrecycle.removeitemrecyclerview.ItemRemoveRecyclerView>

代码中

 RecyclerView.LayoutManager manager = new LinearLayoutManager(this);
        recyclerView.setLayoutManager(manager);
        adapter = new MyAdapter(this);
        adapter.setList(list);
        recyclerView.setAdapter(adapter);
        adapter.notifyDataSetChanged();
        recyclerView.setOnItemClickListener(new OnItemClickListener() {
            @Override
            public void onItemClick(View view, int position) {
                //item点击事件
                Toast.makeText(MainActivity.this, list.get(position).getName(), Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onDeleteClick(int position) {
                //删除操作
                Toast.makeText(MainActivity.this, "删除", Toast.LENGTH_SHORT).show();
                adapter.removeItem(position);
            }

            @Override
            public void onCancel(int position) {
                //取消操作
                Toast.makeText(MainActivity.this, "取消", Toast.LENGTH_SHORT).show();
            }
        });

在回调的接口里面做你该做的事情就好了。


这个地方的上拉刷新和下拉加载我直接用的是一个第三方。
为了尊敬作者我附上他文章的连接:

https://github.com/823546371/PullToRefresh

好了还是说下简单的用法
xml布局:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <com.jwenfeng.library.pulltorefresh.PullToRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/activity_recycler_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent">


        <com.example.beiduo.testmyrecycle.removeitemrecyclerview.ItemRemoveRecyclerView
            android:id="@+id/recycle"
            android:layout_width="match_parent"
            android:layout_height="match_parent">
        </com.example.beiduo.testmyrecycle.removeitemrecyclerview.ItemRemoveRecyclerView>


    </com.jwenfeng.library.pulltorefresh.PullToRefreshLayout>

</LinearLayout>

Activity中

public class MainActivity extends AppCompatActivity {

    private ItemRemoveRecyclerView recyclerView;
    private ArrayList<Mogu> list;
    private MyAdapter adapter = null;
    //上拉加载更多 下拉刷新
    private PullToRefreshLayout pullToRefreshLayout = null;
    private NormalHeadView headView = null;
    private NormalFooterView footerView = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        initData();
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_myrecycle);
        recyclerView = (ItemRemoveRecyclerView) findViewById(R.id.recycle);
        //上拉加载下拉刷星
        pullToRefreshLayout = (PullToRefreshLayout) findViewById(R.id.activity_recycler_view);
        headView = new NormalHeadView(this);
        footerView = new NormalFooterView(this);
        setView();
    }

    public void setView() {
        RecyclerView.LayoutManager manager = new LinearLayoutManager(this);
        recyclerView.setLayoutManager(manager);
        adapter = new MyAdapter(this);
        adapter.setList(list);
        recyclerView.setAdapter(adapter);
        adapter.notifyDataSetChanged();
        recyclerView.setOnItemClickListener(new OnItemClickListener() {
            @Override
            public void onItemClick(View view, int position) {
                //item点击事件
                Toast.makeText(MainActivity.this, list.get(position).getName(), Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onDeleteClick(int position) {
                //删除操作
                Toast.makeText(MainActivity.this, "删除", Toast.LENGTH_SHORT).show();
                adapter.removeItem(position);
            }

            @Override
            public void onCancel(int position) {
                //取消操作
                Toast.makeText(MainActivity.this, "取消", Toast.LENGTH_SHORT).show();
            }
        });

        //上下拉设置监听
        pullToRefreshLayout.setHeaderView(headView);
        pullToRefreshLayout.setFooterView(footerView);

        pullToRefreshLayout.setRefreshListener(new BaseRefreshListener() {
            //下拉刷新
            @Override
            public void refresh() {
                new Handler().postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        pullToRefreshLayout.finishRefresh();
                        Toast.makeText(MainActivity.this, "已经是最新的数据了", Toast.LENGTH_SHORT).show();
                    }
                }, 2000);
            }

            //加载更多
            @Override
            public void loadMore() {
                new Handler().postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        //请求接口
                        pullToRefreshLayout.finishLoadMore();
                        Toast.makeText(MainActivity.this, "加载更多", Toast.LENGTH_SHORT).show();

                    }
                }, 2000);
            }
        });
    }
    public void initData() {
        list = new ArrayList<>();
        list.add(new Mogu(1, "洒家卖蘑菇"));
        list.add(new Mogu(2, "路飞"));
        list.add(new Mogu(1, "索隆"));
        list.add(new Mogu(1, "乌索普"));
        list.add(new Mogu(2, "三治"));
        list.add(new Mogu(1, "娜美"));
        list.add(new Mogu(1, "乔巴"));
        list.add(new Mogu(1, "罗宾"));
        list.add(new Mogu(2, "弗兰奇"));
        list.add(new Mogu(1, "布鲁克"));
    }
}

PullToRefreshLayout,NormalHeadView,NormalFooterView这几个类可以到上面的连接里面找到。

但是用了这个以后会出现一个滑动冲突的问题
效果是这样的:
这里写图片描述

怎么解决呢?

 case MotionEvent.ACTION_MOVE:
              if (isDragging) {
                    int dx = mLastX - x;
                    int dy = mLastY - y;
                    int scrollX = mItemLayout.getScrollX();
                    if (Math.abs(dx) > Math.abs(dy)) {//左边界检测
                        isItemMoving = true;
                        if (scrollX + dx <= 0) {
                            mItemLayout.scrollTo(0, 0);
                            return true;
                        } else if (scrollX + dx >= mMaxLength) {//右边界检测
                            mItemLayout.scrollTo(mMaxLength, 0);
                            return true;
                        }
                        mItemLayout.scrollBy(dx, 0);//item跟随手指滑动
                    }
               }
                break;

在自定义的recycleview中的MotionEvent.ACTION_MOVE中改成才上面那样。
假如竖向滑动就不让他横向滑动就解决了。

好了这篇也就差不多了,假如有讲的不对的地方,还望提出。谢谢~~~~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章