RecyclerView 的使用(androidx)

一、导包

 implementation 'androidx.recyclerview:recyclerview:1.1.0'

二、基本使用

2.1、首先是两个布局文件

Activity 的布局文件

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recyclerView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
</LinearLayout>

item 的布局文件

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

    <TextView
        android:id="@+id/tvItem"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:gravity="center" />

</LinearLayout>

2.2、Adapter 代码

public class HomeAdapter extends RecyclerView.Adapter<HomeAdapter.MyViewHolder> {
    private List<String> mList;
    private Context mContext;

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

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

    @NonNull
    @Override
    public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View itemView = LayoutInflater.from(mContext).inflate(R.layout.item_recycler, parent, false);
        MyViewHolder holder = new MyViewHolder(itemView);
        return holder;
    }

    @Override
    public void onBindViewHolder(@NonNull MyViewHolder holder, int position) {
        holder.tv.setText(mList.get(position));
    }

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

    class MyViewHolder extends RecyclerView.ViewHolder {
        private TextView tv;

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

2.3、Activity 中的代码

public class MainActivity extends AppCompatActivity {
    private RecyclerView mRecyclerView;
    private HomeAdapter mHomeAdapter;
    private List<String> mList;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mRecyclerView = this.findViewById(R.id.recyclerView);
        // 设置布局管理器
        LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
        linearLayoutManager.setOrientation(RecyclerView.VERTICAL);
        mRecyclerView.setLayoutManager(linearLayoutManager);
        // 设置 item 增加和删除时的动画
        mRecyclerView.setItemAnimator(new DefaultItemAnimator());
        mList = getList();
        mHomeAdapter = new HomeAdapter(this, mList);
        mRecyclerView.setAdapter(mHomeAdapter);
    }

    private List<String> getList() {
        List<String> list = new ArrayList<>();
        for (int i = 0; i < 100; i++) {
            list.add(i + "");
        }
        return list;
    }
}

2.4、排列方向

垂直排列(默认): linearLayoutManager.setOrientation(RecyclerView.VERTICAL);
水平排列: linearLayoutManager.setOrientation(RecyclerView.HORIZONTAL);

运行效果

在这里插入图片描述

三、设置分割线

参考博客:
RecyclerView之ItemDecoration由浅入深
RecyclerView 之 ItemDecoration 讲解及高级特性实践
正确使用RecyclerView分割线

3.1、默认分割线。

 mRecyclerView.addItemDecoration(new DividerItemDecoration(this, RecyclerView.VERTICAL));

效果图:

在这里插入图片描述
3.2、复制 DividerItemDecoration 的源码进行修改。参考 5.2 中示例

我们可以根据默认分割线的源码,继承 RecyclerView.ItemDecoration 来自定义分割线。里面核心的方法就是 onDraw 方法,它根据传进来的 orientation 来判断是绘制横向 item 的分割线还是纵向 item 的分割线。getItemOffsets 方法则用于设置 item 的 padding 属性。

四、自定义点击事件

1、自定义接口并提供回调方法

    private OnItemClickListener mOnItemClickListener;

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

        void onItemLongClick(View view, int position);
    }

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

2、Adapter 继承 View.OnClickListener 和 View.OnLongClickListener 并实现其中的方法。

3、OnBindViewHolder 方法中给 item 设置 tag。

    @Override
    public void onBindViewHolder(@NonNull MyViewHolder holder, int position) {
        holder.itemView.setTag(position);
        holder.tv.setText(mList.get(position));
    }

4、监听 item 的点击事件并回调给我们自定义的监听。

    @Override
    public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View itemView = LayoutInflater.from(mContext).inflate(R.layout.item_recycler, parent, false);
        MyViewHolder holder = new MyViewHolder(itemView);
        itemView.setOnClickListener(this);
        itemView.setOnLongClickListener(this);
        return holder;
    }
    @Override
    public void onClick(View v) {
        if (mOnItemClickListener != null) {
            mOnItemClickListener.onItemClick(v, (int) v.getTag());
        }
    }

    @Override
    public boolean onLongClick(View v) {
        if (mOnItemClickListener != null) {
            mOnItemClickListener.onItemLongClick(v, (int) v.getTag());
        }
        return false;
    }

5、最后在 Activity 中进行监听

        mHomeAdapter.setOnItemClickListener(new HomeAdapter.OnItemClickListener() {
            @Override
            public void onItemClick(View view, int position) {
                Toast.makeText(MainActivity.this, "点击了第" + (position + 1) + "条", Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onItemLongClick(View view, final int position) {
                new AlertDialog.Builder(MainActivity.this)
                        .setTitle("确认删除吗?")
                        .setNegativeButton("取消", null)
                        .setPositiveButton("确定", new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                mHomeAdapter.removeData(position);
                            }
                        }).show();
            }
        });

长按时会弹出对话框,效果如下。

在这里插入图片描述

五、实现 GridView

5.1、布局管理器

这里设置 4 列

  GridLayoutManager gridLayoutManager = new GridLayoutManager(this,4);
  mRecyclerView.setLayoutManager(gridLayoutManager);

或者用瀑布流布局管理器

  StaggeredGridLayoutManager staggeredGridLayoutManager = new StaggeredGridLayoutManager(4, StaggeredGridLayoutManager.HORIZONTAL);
  mRecyclerView.setLayoutManager(staggeredGridLayoutManager);

注意StaggeredGridLayoutManager.VERTICAL 的情况下,4 是列数,表示只有 4 列。改为 StaggeredGridLayoutManager.HORIZONTAL 方向时,4 是行数,表示只有 4 行。

5.2、分割线

没有默认的网格布局分割线,这里我们通过修改 DividerItemDecoration 的源码自定义一个分割线。代码如下:

public class DividerGridItemDecoration extends RecyclerView.ItemDecoration {
    public static final int HORIZONTAL = LinearLayout.HORIZONTAL;
    public static final int VERTICAL = LinearLayout.VERTICAL;

    private static final String TAG = "DividerItem";
    private static final int[] ATTRS = new int[]{android.R.attr.listDivider};

    private Drawable mDivider;
    private int mOrientation;

    private final Rect mBounds = new Rect();

    public DividerGridItemDecoration(Context context) {
        final TypedArray a = context.obtainStyledAttributes(ATTRS);
        mDivider = a.getDrawable(0);
        if (mDivider == null) {
            Log.w(TAG, "@android:attr/listDivider was not set in the theme used for this "
                    + "DividerItemDecoration. Please set that attribute all call setDrawable()");
        }
        a.recycle();
    }

    public void setDrawable(@NonNull Drawable drawable) {
        if (drawable == null) {
            throw new IllegalArgumentException("Drawable cannot be null.");
        }
        mDivider = drawable;
    }

    @Nullable
    public Drawable getDrawable() {
        return mDivider;
    }

    @Override
    public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
        if (parent.getLayoutManager() == null || mDivider == null) {
            return;
        }
        drawVertical(c, parent);
        drawHorizontal(c, parent);
    }

    private void drawVertical(Canvas canvas, RecyclerView parent) {
        canvas.save();
        final int left;
        final int right;
        //noinspection AndroidLintNewApi - NewApi lint fails to handle overrides.
        if (parent.getClipToPadding()) {
            left = parent.getPaddingLeft();
            right = parent.getWidth() - parent.getPaddingRight();
            canvas.clipRect(left, parent.getPaddingTop(), right,
                    parent.getHeight() - parent.getPaddingBottom());
        } else {
            left = 0;
            right = parent.getWidth();
        }

        final int childCount = parent.getChildCount();
        for (int i = 0; i < childCount; i++) {
            final View child = parent.getChildAt(i);
            parent.getDecoratedBoundsWithMargins(child, mBounds);
            final int bottom = mBounds.bottom + Math.round(child.getTranslationY());
            final int top = bottom - mDivider.getIntrinsicHeight();
            mDivider.setBounds(left, top, right, bottom);
            mDivider.draw(canvas);
        }
        canvas.restore();
    }

    private void drawHorizontal(Canvas canvas, RecyclerView parent) {
        canvas.save();
        final int top;
        final int bottom;
        //noinspection AndroidLintNewApi - NewApi lint fails to handle overrides.
        if (parent.getClipToPadding()) {
            top = parent.getPaddingTop();
            bottom = parent.getHeight() - parent.getPaddingBottom();
            canvas.clipRect(parent.getPaddingLeft(), top,
                    parent.getWidth() - parent.getPaddingRight(), bottom);
        } else {
            top = 0;
            bottom = parent.getHeight();
        }

        final int childCount = parent.getChildCount();
        for (int i = 0; i < childCount; i++) {
            final View child = parent.getChildAt(i);
            parent.getLayoutManager().getDecoratedBoundsWithMargins(child, mBounds);
            final int right = mBounds.right + Math.round(child.getTranslationX());
            final int left = right - mDivider.getIntrinsicWidth();
            mDivider.setBounds(left, top, right, bottom);
            mDivider.draw(canvas);
        }
        canvas.restore();
    }

    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
                               RecyclerView.State state) {
        if (mDivider == null) {
            outRect.set(0, 0, 0, 0);
            return;
        }
        if (mOrientation == VERTICAL) {
            outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
        } else {
            outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);
        }
    }
}

添加分割线

        DividerGridItemDecoration gridItemDecoration=new DividerGridItemDecoration(this);
        mRecyclerView.addItemDecoration(gridItemDecoration);

运行效果如下

在这里插入图片描述

5.3、每个item占用的格数

使用setSpanSizeLookup函数,其中传入一个GridLayoutManager.SpanSizeLookup对象,其内部有一个抽象函数getSpanSize(),你可以设置返回的数值,让当前的item占据几个位置,当然返回的int型数值只能小于等于GridLayoutManager设置span的个数,比如每行item的个数为4个,然后你设置返回5,就会报错。

首先我们不能再以上面的方式添加分割线了,而是通过设置item的样式来设置分割线,为了更加直观添加了一个颜色。

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <stroke
        android:width="1dp"
        android:color="#000000" />
    <solid android:color="@color/colorAccent" />
</shape>

设置每个数据占用的格数。

        GridLayoutManager gridLayoutManager = new GridLayoutManager(this, 4);
        gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
            @Override
            public int getSpanSize(int position) {
                if (position == 0) {
                    return 4;
                } else if (position == 2) {
                    return 3;
                } else {
                    return 1;
                }
            }
        });

这里设置第1个数据占用4格,第3个数据占用3格,其余的都只占用一格。效果图如下。

在这里插入图片描述

六、实现瀑布流

6.1、修改 item 布局

为了实现方便,瀑布流不用分割线,我们定义 item 的分割距离为 2dp,为了更加直观,我们还给 item 添加了一个颜色。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_margin="2dp"
    android:background="@color/colorPrimary"
    android:orientation="vertical">

    <TextView
        android:id="@+id/tvItem"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:gravity="center" />

</LinearLayout>

6.2、每个 item 的高度

通常这个高度是由服务器返回的数据高度来控制的,在这里我们写一个随机高度来控制 item。

        mHeights = new ArrayList<>();
        for (int i = 0; i < mList.size(); i++) {
            mHeights.add((int) (100 + Math.random() * 300));
        }

设置 item 的高度


    @Override
    public void onBindViewHolder(@NonNull MyViewHolder holder, int position) {
        holder.itemView.setTag(position);
        holder.tv.setText(mList.get(position));
        ViewGroup.LayoutParams lp = holder.tv.getLayoutParams();
        lp.height = mHeights.get(position);
        holder.tv.setLayoutParams(lp);
    }

运行效果如下
在这里插入图片描述

七、更多效果

Recyclerview实现滑动放大ItemView

八、RecyclerView 常见问题

  • 给 RecyclerView 设置 padding 时,滚动内容时与屏幕边缘存在一个间隙
    给 RecyclerView 设置布局属性:android:clipToPadding=“false”

九、RecyclerView 和 ScrollView 嵌套的问题

  • 滑动不流畅
    代码中:RecyclerView 使用 setNestedScrollingEnabled(false) 方法禁止嵌套滑动。
    或者布局中:android:nestedScrollingEnabled=“false”
  • 首次进入会占用焦点,导致 ScrollView 不能显示最上方
    RecyclerView 设置 setFocusable(false) 。
  • 显示不全
    外层嵌套布局并使用 android:descendantFocusability=“blocksDescendants” 属性。
            <RelativeLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:descendantFocusability="blocksDescendants">

                <android.support.v7.widget.RecyclerView
                    android:id="@+id/recyclerview"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content" />
            </RelativeLayout>
  • 11
    点赞
  • 42
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值