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中改成才上面那樣。
假如豎向滑動就不讓他橫向滑動就解決了。

好了這篇也就差不多了,假如有講的不對的地方,還望提出。謝謝~~~~

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