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

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