android的RecylerView基本使用

一、RecylerView基本使用

1. 添加依賴

  1. 直接添加依賴:implementation 'androidx.recyclerview:recyclerview:1.2.0-alpha01'
  2. File ->Project Structure ->Dependencies
  3. image.png
  4. image.png

2. 添加布局

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">
    
    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recyclerListView"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    </androidx.recyclerview.widget.RecyclerView>
    
</LinearLayout>

3. 添加adapter

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;

import java.util.List;

public class RecyclerAdapter extends RecyclerView.Adapter<RecyclerAdapter.MyViewHolder> {

    private List<String> mList;
    private Context mContext;

    public RecyclerAdapter(Context context, List<String> list) {
        this.mContext = context;
        this.mList = list;
    }

    public void removeData(int position) {
        mList.remove(position);
        notifyItemRemoved(position);
    }

    /**
     * 該方法會在RecyclerView需要展示一個item的時候回調,
     * 重寫該方法,使ViewHolder加載item的佈局,佈局複用,提高性能,
     * 就ListView的優化一樣,只不過RecyclerView把這個集成到官方方法中了。
     * */
    @NonNull
    @Override
    public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {

        MyViewHolder holder = new MyViewHolder(
                LayoutInflater.from(mContext)
                        .inflate(
                                R.layout.item_recycler,
                                parent,
                                false));
        return holder;
    }

    /**
     * 該方法是填充綁定item數據的
     * */
    @Override
    public void onBindViewHolder(@NonNull MyViewHolder holder, int position) {
        holder.textView.setText(mList.get(position));
    }

    /**
     * 該方法是返回item的數量。
     * */
    @Override
    public int getItemCount() {
        return mList.size();
    }

    class MyViewHolder extends RecyclerView.ViewHolder {

        TextView textView;

        public MyViewHolder(@NonNull View itemView) {
            super(itemView);
            textView = itemView.findViewById(R.id.tv);
        }
    }
}

4. 添加item_recycler.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">
    <TextView
        android:id="@+id/tv"
        android:gravity="center"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
    </TextView>
</LinearLayout>

5. MainActivity代碼

import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.DefaultItemAnimator;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

import android.os.Bundle;

import java.util.ArrayList;
import java.util.List;


public class MainActivity extends AppCompatActivity {

    private RecyclerView mRecyclerView;
    private RecyclerAdapter mRecyclerAdapter;
    private List<String> mList = new ArrayList<>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initView();
    }

    public void initView() {
        //添加測試數據
        for (int i = 0; i < 100; i++) {
            mList.add(i + " ");
        }
        //佈局關聯
        mRecyclerView = findViewById(R.id.recyclerListView);
        //設置佈局方向
        mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
        //設置item增加和刪除時的動畫,這裏設置默認動畫
        mRecyclerView.setItemAnimator(new DefaultItemAnimator());
        //設置adapter關聯的list
        mRecyclerAdapter = new RecyclerAdapter(this, mList);
        //數據更新
        mRecyclerAdapter.notifyDataSetChanged();
        //關聯adapter
        mRecyclerView.setAdapter(mRecyclerAdapter);
    }
}


6. 效果

RecyclerView效果圖.png

7. 代碼地址

地址:https://github.com/lanjiabin/RecyclerListView
版本選擇RecyclerView(一)這個版本

image.png

二、RecyclerView-setLayoutManager

1. 效果1

setLayoutManager的用法是掌握RecyclerView是一個重要過程。可以設置列表的更多的靈活性。
首先要把item_recycler.xml裏的TextView中的android:layout_width="match_parent"改成android:layout_width="wrap_content",不然很難看出效果。

先來看看默認效果:
代碼:

 		//設置佈局方向
        mRecyclerView.setLayoutManager(new LinearLayoutManager(this));

效果:垂直往下
默認效果.png

2. 效果2

代碼:隨後我們修改LinearLayoutManager(this)裏面的參數,第二個參數表示水平佈局,第三個參數表示是否反轉,就會呈現出另一個效果。

 		//設置佈局方向-水平佈局-不反轉
        mRecyclerView.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL,false));

效果:可以左右滑動,第一個數據在左邊開始
image.png

看看反轉的情況:

  		//設置佈局方向-水平佈局-反轉
        mRecyclerView.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL,true));

效果:第一個數據是從右邊開始的,可以左右滑動。
image.png

3. 效果3

表格佈局,第一個參數表示上下文,第二個參數表示表格有多少列。
代碼:

		//設置佈局方向-表格佈局
        mRecyclerView.setLayoutManager(new GridLayoutManager(this,5));

效果:分爲5列,可以上下滑動
image.png

4. 效果4

上面實現了列,那在這裏也可以實現多少行。
代碼:

  		//設置佈局方向-表格佈局-行
        mRecyclerView.setLayoutManager(new GridLayoutManager(this,5,GridLayoutManager.HORIZONTAL,false));

效果:可以發現,有5列,而且數據還是從豎直來排過去的。
image.png

三、分割線

1. 佈局中添加分割線

item_recycler.xml添加一個佈局作爲分割線。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">
    <TextView
        android:id="@+id/tv"
        android:gravity="center"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
    </TextView>
    <View
        android:background="@color/colorAccent"
        android:layout_width="match_parent"
        android:layout_height="2dp">
    </View>
</LinearLayout>

效果:
image.png

2. 默認分割線

 		//添加Android自帶的分割線
        mRecyclerView.addItemDecoration(new DividerItemDecoration(this,DividerItemDecoration.VERTICAL));

效果:
image.png

3. 自定義Drawable分割線

查看DividerItemDecoration ``extends RecyclerView.ItemDecoration源碼有setDrawable這個方法,我們只需要傳入一個Drawable對象就可以了。

 /**
     * Sets the {@link Drawable} for this divider.
     *
     * @param drawable Drawable that should be used as a divider.
     */
    public void setDrawable(@NonNull Drawable drawable) {
        if (drawable == null) {
            throw new IllegalArgumentException("Drawable cannot be null.");
        }
        mDivider = drawable;
    }

在drawable文件夾中添加一個xml文件:recyclerview_divideritem.xml
代碼:

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:shape="rectangle">

    <gradient
        android:centerColor="#ff00ff00"
        android:endColor="#ff0000ff"
        android:startColor="#ffff0000"
        android:type="linear" />
    <size android:height="3dp" />
</shape>

然後使用這個文件:

		DividerItemDecoration divider=new DividerItemDecoration(this,DividerItemDecoration.VERTICAL);
        divider.setDrawable(ContextCompat.getDrawable(this,R.drawable.recyclerview_divideritem));
        //添加Android自定分割線
        mRecyclerView.addItemDecoration(divider);

效果:
image.png

4.自定義分割線

這個類放上了前面的幾個方法,做成一個自定義類,方便統一管理和使用。

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.view.View;

import androidx.core.content.ContextCompat;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

public class RecycleViewDivider extends RecyclerView.ItemDecoration {

    private Paint mPaint;
    private Drawable mDivider;
    private int mDividerHeight = 2;//分割線高度,默認爲1px
    private int mOrientation;//列表的方向:LinearLayoutManager.VERTICAL或LinearLayoutManager.HORIZONTAL
    private static final int[] ATTRS = new int[]{android.R.attr.listDivider};//使用系統自帶的listDivider

    /**
     * 默認分割線:高度爲2px,顏色爲灰色
     *
     * @param context
     * @param orientation 列表方向
     */
    public RecycleViewDivider(Context context, int orientation) {
        if (orientation != LinearLayoutManager.VERTICAL && orientation != LinearLayoutManager.HORIZONTAL) {
            throw new IllegalArgumentException("請輸入正確的參數!");
        }
        mOrientation = orientation;

        final TypedArray a = context.obtainStyledAttributes(ATTRS);//使用TypeArray加載該系統資源
        mDivider = a.getDrawable(0);
        a.recycle();//緩存
    }

    /**
     * 自定義分割線
     *
     * @param context
     * @param orientation 列表方向
     * @param drawableId  分割線圖片
     */
    public RecycleViewDivider(Context context, int orientation, int drawableId) {
        this(context, orientation);
        mDivider = ContextCompat.getDrawable(context, drawableId);
        mDividerHeight = mDivider.getIntrinsicHeight();
    }

    /**
     * 自定義分割線
     *
     * @param context
     * @param orientation   列表方向
     * @param dividerHeight 分割線高度
     * @param dividerColor  分割線顏色
     */
    public RecycleViewDivider(Context context, int orientation, int dividerHeight, int dividerColor) {
        this(context, orientation);
        mDividerHeight = dividerHeight;
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setColor(dividerColor);
        mPaint.setStyle(Paint.Style.FILL);
    }


    //獲取分割線尺寸
    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
        super.getItemOffsets(outRect, view, parent, state);
        //設置偏移的高度是mDivider.getIntrinsicHeight,該高度正是分割線的高度
        outRect.set(0, 0, 0, mDividerHeight);
    }

    //繪製分割線
    @Override
    public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
        super.onDraw(c, parent, state);
        if (mOrientation == LinearLayoutManager.VERTICAL) {
            drawVertical(c, parent);
        } else {
            drawHorizontal(c, parent);
        }
    }

    //繪製橫向 item 分割線
    private void drawHorizontal(Canvas canvas, RecyclerView parent) {
        final int left = parent.getPaddingLeft();//獲取分割線的左邊距,即RecyclerView的padding值
        final int right = parent.getMeasuredWidth() - parent.getPaddingRight();//分割線右邊距
        final int childSize = parent.getChildCount();
        //遍歷所有item view,爲它們的下方繪製分割線
        for (int i = 0; i < childSize; i++) {
            final View child = parent.getChildAt(i);
            RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) child.getLayoutParams();
            final int top = child.getBottom() + layoutParams.bottomMargin;
            final int bottom = top + mDividerHeight;
            if (mDivider != null) {
                mDivider.setBounds(left, top, right, bottom);
                mDivider.draw(canvas);
            }
            if (mPaint != null) {
                canvas.drawRect(left, top, right, bottom, mPaint);
            }
        }
    }

    //繪製縱向 item 分割線
    private void drawVertical(Canvas canvas, RecyclerView parent) {
        final int top = parent.getPaddingTop();
        final int bottom = parent.getMeasuredHeight() - parent.getPaddingBottom();
        final int childSize = parent.getChildCount();
        for (int i = 0; i < childSize; i++) {
            final View child = parent.getChildAt(i);
            RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) child.getLayoutParams();
            final int left = child.getRight() + layoutParams.rightMargin;
            final int right = left + mDividerHeight;
            if (mDivider != null) {
                mDivider.setBounds(left, top, right, bottom);
                mDivider.draw(canvas);
            }
            if (mPaint != null) {
                canvas.drawRect(left, top, right, bottom, mPaint);
            }
        }
    }
}

使用方式:


        //1.添加默認分割線:高度爲2px,顏色爲灰色
        mRecyclerView.addItemDecoration(new RecycleViewDivider(this, LinearLayoutManager.HORIZONTAL));

        //2.添加自定義分割線:可自定義分割線drawable
        mRecyclerView.addItemDecoration(new RecycleViewDivider(
                this, LinearLayoutManager.HORIZONTAL, R.drawable.recyclerview_divideritem));

        //3.添加自定義分割線:可自定義分割線高度和顏色
        mRecyclerView.addItemDecoration(new RecycleViewDivider(
                this, LinearLayoutManager.HORIZONTAL, 3, getResources().getColor(R.color.colorPrimaryDark)));

四、點擊事件

1. 利用回調機制實現

1.1 添加兩個內部接口:

 	public interface OnItemClickListener {
        void onItemClick(View view, int position);
    }

    public interface OnItemLongClickListener {
        void onItemLongClick(View view, int position);
    }

1.2 添加變量:

 	private OnItemClickListener mOnItemClickListener;
    private OnItemLongClickListener mOnItemLongClickListener;

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

    public void setmOnItemLongClickListener(OnItemLongClickListener mOnItemLongClickListener) {
        this.mOnItemLongClickListener = mOnItemLongClickListener;
    }

1.3 在onBindViewHolder添加實現回調的方法:

 /**
     * 該方法是填充綁定item數據的
     * */
    @Override
    public void onBindViewHolder(@NonNull final MyViewHolder holder, int position) {

        holder.textView.setText(mList.get(position));

        //判斷是否設置了監聽器
        if(mOnItemClickListener != null){
            //爲ItemView設置監聽器
            holder.itemView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    int position = holder.getLayoutPosition(); // 1
                    mOnItemClickListener.onItemClick(holder.itemView,position); // 2
                }
            });
        }
        if(mOnItemLongClickListener != null){
            holder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
                @Override
                public boolean onLongClick(View v) {
                    int position = holder.getLayoutPosition();
                    mOnItemLongClickListener.onItemLongClick(holder.itemView,position);
                    //返回true 表示消耗了事件 事件不會繼續傳遞
                    return true;
                }
            });
        }
    }

1.4 實現接口:


        //item單擊
        mRecyclerAdapter.setmOnItemClickListener(new RecyclerAdapter.OnItemClickListener() {
            @Override
            public void onItemClick(View view, int position) {
                Toast.makeText(MainActivity.this,"點擊了:"+position,Toast.LENGTH_SHORT).show();

            }
        });

        //item長按監聽
        mRecyclerAdapter.setmOnItemLongClickListener(new RecyclerAdapter.OnItemLongClickListener() {
            @Override
            public void onItemLongClick(View view, int position) {
                Toast.makeText(MainActivity.this,"長點擊了:"+position,Toast.LENGTH_SHORT).show();

            }
        });

效果:
1.gif

2. 利用內部接口OnItemTouchListener實現

這其實就是利用View 的事件分發與攔截機制。

2.1 新建一個 RecyclerViewClickListener 類

import android.content.Context;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;

import androidx.recyclerview.widget.RecyclerView;

public class RecyclerViewClickListener implements RecyclerView.OnItemTouchListener {

    private int mLastDownX,mLastDownY;
    //該值記錄了最小滑動距離
    private int touchSlop ;
    private OnItemClickListener mListener;
    //是否是單擊事件
    private boolean isSingleTapUp = false;
    //是否是長按事件
    private boolean isLongPressUp = false;
    private boolean isMove = false;
    private long mDownTime;

    //內部接口,定義點擊方法以及長按方法
    public interface OnItemClickListener {
        void onItemClick(View view, int position);

        void onItemLongClick(View view, int position);
    }

    public RecyclerViewClickListener(Context context, OnItemClickListener listener){
        touchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
        mListener = listener;
    }
    @Override
    public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
        int x = (int) e.getX();
        int y = (int) e.getY();
        switch (e.getAction()){
            /**
             *  如果是ACTION_DOWN事件,那麼記錄當前按下的位置,
             *  記錄當前的系統時間。
             */
            case MotionEvent.ACTION_DOWN:
                mLastDownX = x;
                mLastDownY = y;
                mDownTime = System.currentTimeMillis();
                isMove = false;
                break;
            /**
             *  如果是ACTION_MOVE事件,此時根據TouchSlop判斷用戶在按下的時候是否滑動了,
             *  如果滑動了,那麼接下來將不處理點擊事件
             */
            case MotionEvent.ACTION_MOVE:
                if(Math.abs(x - mLastDownX)>touchSlop || Math.abs(y - mLastDownY)>touchSlop){
                    isMove = true;
                }
                break;
            /**
             *  如果是ACTION_UP事件,那麼根據isMove標誌位來判斷是否需要處理點擊事件;
             *  根據系統時間的差值來判斷是哪種事件,如果按下事件超過1s,則認爲是長按事件,
             *  否則是單擊事件。
             */
            case MotionEvent.ACTION_UP:
                if(isMove){
                    break;
                }
                if(System.currentTimeMillis()-mDownTime > 1000){
                    isLongPressUp = true;
                }else {
                    isSingleTapUp = true;
                }
                break;
        }
        if(isSingleTapUp ){
            //根據觸摸座標來獲取childView
            View childView = rv.findChildViewUnder(e.getX(),e.getY());
            isSingleTapUp = false;
            if(childView != null){
                //回調mListener#onItemClick方法
                mListener.onItemClick(childView,rv.getChildLayoutPosition(childView));
                return true;
            }
            return false;
        }
        if (isLongPressUp ){
            View childView = rv.findChildViewUnder(e.getX(),e.getY());
            isLongPressUp = false;
            if(childView != null){
                mListener.onItemLongClick(childView, rv.getChildLayoutPosition(childView));
                return true;
            }
            return false;
        }
        return false;
    }

    @Override
    public void onTouchEvent(RecyclerView rv, MotionEvent e) {

    }

    @Override
    public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {

    }
}

2.2 MainActivity的調用

 		//點擊事件,單擊和長擊。缺陷:長按的時候,只有把手放起來,纔會觸發長按事件。
        mRecyclerView.addOnItemTouchListener(new RecyclerViewClickListener(this, new RecyclerViewClickListener.OnItemClickListener() {
            @Override
            public void onItemClick(View view, int position) {
                Toast.makeText(MainActivity.this,"點擊了:"+position,Toast.LENGTH_SHORT).show();

            }

            @Override
            public void onItemLongClick(View view, int position) {
                Toast.makeText(MainActivity.this,"長點擊了:"+position,Toast.LENGTH_SHORT).show();
            }
        }));

2.3 提示

方法一和方法二的區別:

首先,方法一我們是直接在MyAdapter數據適配器中,爲itemview設置了內置監聽器,再通過這個監聽器實現我們的回調方法,相當於回調了兩次,同時這個方法與MyAdapter的耦合度比較高,也違反了單一職責原則,當然其簡易性也是突出的優點。
而方法二,我們利用了onTouchListener接口對事件進行了攔截,在攔截中處理我們的點擊事件,實現了與適配器的解耦,但是複雜程度會比方法一大。總地來說,如果RecyclerView需要處理的點擊事件邏輯很簡單,那麼可以使用方法一;如果需要處理比較複雜的點擊事件,比如說,雙擊、長按等點擊事件,則需要使用方法二去實現各種複雜的邏輯。

3. 利用GestureDetector(手勢檢測類)

在實現方法二的RecyclerViewClickListener的時候,在內部對事件的實現了單擊、長按的判斷,但是這個長按事件不是標準的,只有鬆開手指的時候纔會觸發長按事件,這也算是一點瑕疵,同時如果要增加別的事件,比如說雙擊事件,則需要增加相應的邏輯,如果需要判斷的事件種類變多則會給我們的代碼編寫帶來困難,那麼有沒有更加簡便的方法呢?
其實安卓SDK爲我們提供了一個手勢檢測類:GestureDetector來處理各種不同的手勢,那麼我們完全可以利用GestureDetector來對方法二進行改進。

3.1 修改RecyclerViewClickListener 類

import android.content.Context;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;

import androidx.recyclerview.widget.RecyclerView;

public class RecyclerViewClickListener implements RecyclerView.OnItemTouchListener {

    private GestureDetector mGestureDetector;
    private OnItemClickListener mListener;

    //內部接口,定義點擊方法以及長按方法
    public interface OnItemClickListener {
        void onItemClick(View view, int position);

        void onItemLongClick(View view, int position);
    }

    public RecyclerViewClickListener(Context context, final RecyclerView recyclerView, OnItemClickListener listener) {

        mListener = listener;
        /**
         * 這裏選擇SimpleOnGestureListener實現類,可以根據需要選擇重寫的方法
         * */
        mGestureDetector = new GestureDetector(context,
                new GestureDetector.SimpleOnGestureListener() {
                    //單擊事件
                    @Override
                    public boolean onSingleTapUp(MotionEvent e) {
                        View childView = recyclerView.findChildViewUnder(e.getX(), e.getY());
                        if (childView != null && mListener != null) {
                            mListener.onItemClick(childView, recyclerView.getChildLayoutPosition(childView));
                            return true;
                        }
                        return false;
                    }

                    //長按事件
                    @Override
                    public void onLongPress(MotionEvent e) {
                        View childView = recyclerView.findChildViewUnder(e.getX(), e.getY());
                        if (childView != null && mListener != null) {
                            mListener.onItemLongClick(childView, recyclerView.getChildLayoutPosition(childView));
                        }
                    }
                });
    }

    @Override
    public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
        //把事件交給GestureDetector處理
        if (mGestureDetector.onTouchEvent(e)) {
            return true;
        } else
            return false;
    }

    @Override
    public void onTouchEvent(RecyclerView rv, MotionEvent e) {
    }

    @Override
    public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
    }
}

3.2 修改調用方法

 		//利用GestureDetector手勢檢測類來實現回調
        mRecyclerView.addOnItemTouchListener(new RecyclerViewClickListener(this, mRecyclerView, new RecyclerViewClickListener.OnItemClickListener() {
            @Override
            public void onItemClick(View view, int position) {
                Toast.makeText(MainActivity.this,"單擊了:"+position,Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onItemLongClick(View view, int position) {
                Toast.makeText(MainActivity.this,"長擊了:"+position,Toast.LENGTH_SHORT).show();
            }
        }));

效果和方法一是一樣的。

3.3 github代碼

image.png

五、修改數據和背景

先認識官方提供的方法

//該方法用於當增加一個數據的時候,position表示新增數據顯示的位置
final void notifyItemInserted(int position)
 
//該方法用於刪除一個數據的時候,position表示數據刪除的位置
final void notifyItemRemoved(int position)
 
//該方法表示所在position對應的item位置不會改變,但是該item內容發生變化
final void notifyItemChanged(int position)
 
//該方法一般用於:適配器之前裝載的數據大部分已經過時了,需要重新更新數據
//調用該方法的時候,recyclerView會重新計算子item及所有子item重新佈局
//出於效率考慮,官方建議用更加精確的方法(比如上面三個方法)來取代這個方法
final void notifyDataSetChanged()

1. 在RecyclerAdapter添加修改方法

 	//記錄用戶選擇了RecyclerListView哪個item
    //賦值-1是爲了防止默認選中position=0的item
    private int mPosition = -1;

    //獲取當前點擊item的position
    public void getCurrentPosition(int position) {
        this.mPosition = position;
    }

    //移除數據
    public void removeData(int position) {
        mList.remove(position);
        notifyItemRemoved(position);
    }

    //新增數據
    public void addData(int position) {
        mList.add(position, "Add One" + position);
        notifyItemInserted(position);
    }

    //更改某個位置的數據
    public void changeData(int position) {
        mList.set(position, "Item " + position + " has changed");
        notifyItemChanged(position);
    }
 /**
     * 該方法是填充綁定item數據的
     */
    @Override
    public void onBindViewHolder(@NonNull final MyViewHolder holder, int position) {

        holder.textView.setText(mList.get(position));

        //修改選中item的顏色
        if (mPosition == position) {
            holder.textView.setBackgroundColor(mContext.getResources().getColor(R.color.colorPrimaryDark));
        } else {
            holder.textView.setBackgroundColor(mContext.getResources().getColor(R.color.white));
        }

    }

2. 添加數據按鈕和修改按鈕

在activity_main.xml中加入


    <LinearLayout
        android:orientation="horizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <Button
            android:id="@+id/addData"
            android:text="addData"
            android:layout_weight="1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content">
        </Button>
        <Button
            android:layout_weight="1"
            android:id="@+id/changeData"
            android:text="changeData"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content">
        </Button>
    </LinearLayout>

3. MainActivity.java添加代碼

 	private Button addDataButton, changeData;
    //記錄用戶選擇了RecyclerListView哪個item
    private int mPosition;
 public void initView() {
        addDataButton = findViewById(R.id.addData);
        changeData = findViewById(R.id.changeData);

        addDataButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //添加數據到選擇item的上方
                mRecyclerAdapter.addData(mPosition);
            }
        });

        changeData.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //修改選中item的數據
                mRecyclerAdapter.changeData(mPosition);
            }
        });


    }

 		//利用GestureDetector手勢檢測類來實現回調
        mRecyclerView.addOnItemTouchListener(new RecyclerViewClickListener(this, mRecyclerView, new RecyclerViewClickListener.OnItemClickListener() {
            @Override
            public void onItemClick(View view, int position) {

                //顏色改變了,更新Adapter
                mRecyclerAdapter.notifyDataSetChanged();

                //記錄用戶選擇了哪個item
                mPosition = position;

                //傳遞點擊了哪個item,以此用來更新選中item的顏色
                mRecyclerAdapter.getCurrentPosition(position);

                Toast.makeText(MainActivity.this, "單擊了:" + position, Toast.LENGTH_SHORT).show();

            }

            @Override
            public void onItemLongClick(View view, int position) {
                Toast.makeText(MainActivity.this, "長擊了:" + position, Toast.LENGTH_SHORT).show();
                //刪除長按的item
                mRecyclerAdapter.removeData(position);
            }
        }));

4. github代碼

image.png

5. 效果

2.gif

六、添加header和footer

1.1 添加header和footer的佈局文件

footer_recylerview.xml和header_recylerview.xml是一樣的,不過id不同而已。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/headerLinerLayout"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">
    <TextView
        android:id="@+id/headerTextView"
        android:text="@string/header"
        android:layout_width="match_parent"
        android:layout_height="100dp">
    </TextView>

</LinearLayout>

1.2 添加屬性

 	/**
     * 記錄用戶選擇了RecyclerListView哪個item
     * 賦值-1是爲了防止默認選中position=0的item
     */
    private int mPosition = -1;

    public static final int TYPE_HEADER = 0;  //說明是帶有Header的
    public static final int TYPE_FOOTER = 1;  //說明是帶有Footer的
    public static final int TYPE_NORMAL = 2;  //說明是不帶有header和footer的

    /**
     * mHeaderView 頭部
     * mFooterView 尾部
     */
    private View mHeaderView;
    private View mFooterView;

1.3 在構造方法中添加mHeaderView和mFooterView對象

	 public RecyclerAdapter(Context context, List<String> list, RecyclerView recyclerView) {
        this.mContext = context;
        this.mList = list;
        this.mHeaderView = LayoutInflater.from(mContext).inflate(R.layout.header_recylerview, recyclerView, false);
        this.mFooterView = LayoutInflater.from(mContext).inflate(R.layout.footer_recylerview, recyclerView, false);
    }

1.4 根據view類型判斷加載佈局

 	//獲取item的view的類型
    @Override
    public int getItemViewType(int position) {

        if (mHeaderView == null && mFooterView == null) {
            return TYPE_NORMAL;
        }
        if (position == 0 && mHeaderView != null) {
            return TYPE_HEADER;
        }
        if (position == getItemCount() - 1 && mFooterView != null) {
            return TYPE_FOOTER;
        }
        return TYPE_NORMAL;
    }

1.5 返回對象

	 /**
     * 該方法會在RecyclerView需要展示一個item的時候回調,
     * 重寫該方法,使ViewHolder加載item的佈局,佈局複用,提高性能,
     * 就ListView的優化一樣,只不過RecyclerView把這個集成到官方方法中了。
     */
    @NonNull
    @Override
    public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {

        if (mHeaderView != null && viewType == TYPE_HEADER) {
            return new MyViewHolder(mHeaderView);

        }
        if (mFooterView != null && viewType == TYPE_FOOTER) {
            return new MyViewHolder(mFooterView);
        }

        MyViewHolder holder = new MyViewHolder(
                LayoutInflater.from(mContext)
                        .inflate(
                                R.layout.item_recyclerview,
                                parent,
                                false));
        return holder;
    }

1.6 包括header和footer在內的每個item的設置

	/**
     * 該方法是填充綁定item數據的
     */
    @Override
    public void onBindViewHolder(@NonNull final MyViewHolder holder, int position) {


        if (getItemViewType(position) == TYPE_NORMAL) {
            if (holder instanceof MyViewHolder) {
                holder.textView.setText(mList.get(position - 1));
                //修改選中item的顏色
                if (mPosition == position) {
                    holder.textView.setBackgroundColor(mContext.getResources().getColor(R.color.colorPrimaryDark));
                } else {
                    holder.textView.setBackgroundColor(mContext.getResources().getColor(R.color.white));
                }
                return;
            }
            return;
        } else if (getItemViewType(position) == TYPE_HEADER) {
            holder.headerTextView.setText("----我是頭部header----");
            return;
        } else if (getItemViewType(position) == TYPE_FOOTER) {
            holder.footerTextView.setText("++++我是尾部footer+++++");
            return;
        }


    }

1.7 item數目的變化

由於添加了header和footer,所以item會發生變化。

 	/**
     * 該方法是返回item的數量。
     */
    @Override
    public int getItemCount() {
        if (mHeaderView == null && mFooterView == null) {
            return mList.size();
        } else if (mHeaderView == null && mFooterView != null) {
            return mList.size() + 1;
        } else if (mHeaderView != null && mFooterView == null) {
            return mList.size() + 1;
        } else {
            return mList.size() + 2;
        }

1.8 ViewHolder中關聯佈局

	class MyViewHolder extends RecyclerView.ViewHolder {

        TextView textView, headerTextView, footerTextView;

        public MyViewHolder(@NonNull View itemView) {
            super(itemView);

            /**
             * 如果是headerView或者是footerView,直接返回
             * */
            if (itemView == mHeaderView) {
                headerTextView = mHeaderView.findViewById(R.id.headerTextView);
                return;
            }
            if (itemView == mFooterView) {
                footerTextView = mFooterView.findViewById(R.id.footerTextView);
                return;
            }
            textView = itemView.findViewById(R.id.tv);
        }
    }

1.9 完整的adapter

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;

import java.util.List;

public class RecyclerAdapter extends RecyclerView.Adapter<RecyclerAdapter.MyViewHolder> {

    private List<String> mList;
    private Context mContext;
    /**
     * 記錄用戶選擇了RecyclerListView哪個item
     * 賦值-1是爲了防止默認選中position=0的item
     */
    private int mPosition = -1;

    public static final int TYPE_HEADER = 0;  //說明是帶有Header的
    public static final int TYPE_FOOTER = 1;  //說明是帶有Footer的
    public static final int TYPE_NORMAL = 2;  //說明是不帶有header和footer的

    /**
     * mHeaderView 頭部
     * mFooterView 尾部
     */
    private View mHeaderView;
    private View mFooterView;

    public RecyclerAdapter(Context context, List<String> list, RecyclerView recyclerView) {
        this.mContext = context;
        this.mList = list;
        this.mHeaderView = LayoutInflater.from(mContext).inflate(R.layout.header_recylerview, recyclerView, false);
        this.mFooterView = LayoutInflater.from(mContext).inflate(R.layout.footer_recylerview, recyclerView, false);
    }

    //獲取當前點擊item的position
    public void getCurrentPosition(int position) {
        this.mPosition = position;
    }

    //移除數據
    public void removeData(int position) {
        /**
         * 這裏要重點講解一下;
         * 添加了header和footer之後;
         * header的位置就是recyclerView是在position爲0位置,footer是在position爲recyclerView.size()-1的位置;
         * 那數據list真正佔用的位置就是recyclerView的 1 --> recyclerView.size()-2 位置
         * 當點擊recyclerView的footer的前一個位置的時候(recyclerView.size()-2)
         * 得到的recyclerView的position爲recyclerView.size()+1,而list的position爲list.size()
         * 位置差了一個1,所以在傳遞最後一個數據點擊刪除的時候,會數組越界
         * 舉例:position指的是下標
         * list的長度爲10,那數據的position就是 0 ~ 9;
         * 加入header和footer後,recyclerView的position=0 的位置就是header;
         * position=11 的位置就是footer;
         * list的 position=0 的數據放在recyclerView的 position=1 的位置;
         * list的 position=9 的數據放在recyclerView的 position=10 的位置;
         * 長按recyclerView的 position=10的位置的時候,傳遞的方法remove(position)中的position=10;
         * 傳遞的是recyclerView的position,就成了 mList.remove(10);
         * mList.remove(10),明顯數組越界了,因爲mList最大的position爲9;
         * 所以在刪除最後一個數據的時候,要特別處理,就是下面的方法。
         * 重點是notifyItemRemoved(position);方法
         * 刪除的position - 1處的list數據
         * 而更新的是recyclerView的position的位置
         * 因爲他們錯位了 1
         * */
        mList.remove(position - 1);
        notifyItemRemoved(position);
    }

    //新增數據
    public void addData(int position) {
        mList.add(position, "Add One" + position);
        notifyItemInserted(position);
    }

    //更改某個位置的數據
    public void changeData(int position) {
        mList.set(position, "Item " + position + " has changed");
        notifyItemChanged(position);
    }

    //獲取item的view的類型
    @Override
    public int getItemViewType(int position) {

        if (mHeaderView == null && mFooterView == null) {
            return TYPE_NORMAL;
        }
        if (position == 0 && mHeaderView != null) {
            return TYPE_HEADER;
        }
        if (position == getItemCount() - 1 && mFooterView != null) {
            return TYPE_FOOTER;
        }
        return TYPE_NORMAL;
    }

    /**
     * 該方法會在RecyclerView需要展示一個item的時候回調,
     * 重寫該方法,使ViewHolder加載item的佈局,佈局複用,提高性能,
     * 就ListView的優化一樣,只不過RecyclerView把這個集成到官方方法中了。
     */
    @NonNull
    @Override
    public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {

        if (mHeaderView != null && viewType == TYPE_HEADER) {
            return new MyViewHolder(mHeaderView);

        }
        if (mFooterView != null && viewType == TYPE_FOOTER) {
            return new MyViewHolder(mFooterView);
        }

        MyViewHolder holder = new MyViewHolder(
                LayoutInflater.from(mContext)
                        .inflate(
                                R.layout.item_recyclerview,
                                parent,
                                false));
        return holder;
    }

    /**
     * 該方法是填充綁定item數據的
     */
    @Override
    public void onBindViewHolder(@NonNull final MyViewHolder holder, int position) {


        if (getItemViewType(position) == TYPE_NORMAL) {
            if (holder instanceof MyViewHolder) {
                holder.textView.setText(mList.get(position - 1));
                //修改選中item的顏色
                if (mPosition == position) {
                    holder.textView.setBackgroundColor(mContext.getResources().getColor(R.color.colorPrimaryDark));
                } else {
                    holder.textView.setBackgroundColor(mContext.getResources().getColor(R.color.white));
                }
                return;
            }
            return;
        } else if (getItemViewType(position) == TYPE_HEADER) {
            holder.headerTextView.setText("----我是頭部header----");
            return;
        } else if (getItemViewType(position) == TYPE_FOOTER) {
            holder.footerTextView.setText("++++我是尾部footer+++++");
            return;
        }


    }

    /**
     * 該方法是返回item的數量。
     */
    @Override
    public int getItemCount() {
        if (mHeaderView == null && mFooterView == null) {
            return mList.size();
        } else if (mHeaderView == null && mFooterView != null) {
            return mList.size() + 1;
        } else if (mHeaderView != null && mFooterView == null) {
            return mList.size() + 1;
        } else {
            return mList.size() + 2;
        }
    }

    class MyViewHolder extends RecyclerView.ViewHolder {

        TextView textView, headerTextView, footerTextView;

        public MyViewHolder(@NonNull View itemView) {
            super(itemView);

            /**
             * 如果是headerView或者是footerView,直接返回
             * */
            if (itemView == mHeaderView) {
                headerTextView = mHeaderView.findViewById(R.id.headerTextView);
                return;
            }
            if (itemView == mFooterView) {
                footerTextView = mFooterView.findViewById(R.id.footerTextView);
                return;
            }
            textView = itemView.findViewById(R.id.tv);
        }
    }
}

1.10 MainActivity中使用

這裏有一些細節更新,就是數組越界問題,具體看GitHub代碼。

 		//設置adapter關聯的list
        mRecyclerAdapter = new RecyclerAdapter(this, mList,mRecyclerView);

完整的MainActivity.java

import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.DefaultItemAnimator;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

import java.util.ArrayList;
import java.util.List;


public class MainActivity extends AppCompatActivity {

    private RecyclerView mRecyclerView;
    private RecyclerAdapter mRecyclerAdapter;
    private List<String> mList = new ArrayList<>();
    private Button addDataButton, changeData;

    /**
     * 記錄用戶選擇了RecyclerListView哪個item
     * 賦值-1是爲了防止默認選中position=0的item
     */
    private int mPosition=-1;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initView();
        initRecyclerView();
    }

    public void initView() {
        addDataButton = findViewById(R.id.addData);
        changeData = findViewById(R.id.changeData);
        mRecyclerView = findViewById(R.id.recyclerListView);

        //添加數據到選擇item的上方
        addDataButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                /**
                 * 因爲添加了header和footer,所以mPosition的值是從-1到 mList.size()+1
                 * 頭部和尾部的item是不應該支持修改數據的,因爲用的是不同的佈局,數據也不是相同的
                 * */
                if (mPosition>=0 && mPosition<mList.size()){
                    mRecyclerAdapter.addData(mPosition);
                    mRecyclerAdapter.notifyDataSetChanged();
                }

            }
        });

        //修改選中item的數據
        changeData.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                /**
                 * 因爲添加了header和footer,所以mPosition的值是從-1到 mList.size()+1
                 * 頭部和尾部的item是不應該支持修改數據的,因爲用的是不同的佈局,數據也不是相同的
                 * */
                if (mPosition>=0 && mPosition<mList.size()){
                    mRecyclerAdapter.changeData(mPosition);
                    mRecyclerAdapter.notifyDataSetChanged();
                }

            }
        });


    }

    public void initRecyclerView() {

        //添加測試數據
        for (int i = 0; i < 20; i++) {
            mList.add(i + "-----");
        }
        //設置佈局方向-默認佈局
        mRecyclerView.setLayoutManager(new LinearLayoutManager(this));

        //設置item增加和刪除時的動畫,這裏設置默認動畫
        mRecyclerView.setItemAnimator(new DefaultItemAnimator());

        //1.添加默認分割線:高度爲2px,顏色爲灰色
        mRecyclerView.addItemDecoration(new RecycleViewDivider(this, LinearLayoutManager.HORIZONTAL));

        //設置adapter關聯的list
        mRecyclerAdapter = new RecyclerAdapter(this, mList,mRecyclerView);

        //利用GestureDetector手勢檢測類來實現回調
        mRecyclerView.addOnItemTouchListener(new RecyclerViewClickListener(this, mRecyclerView, new RecyclerViewClickListener.OnItemClickListener() {
            @Override
            public void onItemClick(View view, int position) {

                //顏色改變了,更新Adapter
                mRecyclerAdapter.notifyDataSetChanged();

                //記錄用戶選擇了哪個item
                mPosition = position-1;

                //傳遞點擊了哪個item,以此用來更新選中item的顏色
                mRecyclerAdapter.getCurrentPosition(position);

                Toast.makeText(MainActivity.this, "單擊了:" + position, Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onItemLongClick(View view, int position) {
                /**
                 * 因爲添加了header和footer,所以mPosition的值是從-1到 mList.size()+1
                 * 頭部和尾部的item是不應該支持修改數據的,因爲用的是不同的佈局,數據也不是相同的
                 * */
                if (position>0 && position<=mList.size()){
                    //長按刪除的item
                    mRecyclerAdapter.removeData(position);
                }

                Toast.makeText(MainActivity.this, "長擊了:" + (position), Toast.LENGTH_SHORT).show();
            }
        }));

        //關聯adapter
        mRecyclerView.setAdapter(mRecyclerAdapter);
    }

}

1.11 github代碼

image.png

1.12 效果

3.gif

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