该文章是一个系列文章, 是本人在 Android 开发的漫漫长途上的一点感想和记录, 我会尽量按照先易后难的顺序进行编写该系列该系列引用了 Android 开发艺术探索以及深入理解 Android 卷,, 中的相关知识, 另外也借鉴了其他的优质博客, 在此向各位大神表示感谢, 膜拜!!!
前言
上文我们很详细的分析了 ListView 的使用优化及 ListView 的 RecycleBin 机制, 读者如果对 ListView 不太清楚, 那么请参看我的上篇博文不过呢, Google Material Design 提供的 RecyclerView 已经逐渐的取代 ListViewRecyclerView 提供了一种插拔式的体验, 高度的解耦, 异常的灵活, 通过设置它提供的不同 LayoutManager,ItemDecoration , ItemAnimator 实现令人瞠目的效果
如果说上面的理由只是大而空泛的话, 那我们来看以下场景
你想控制数据的显示方式, 列表显示网格显示瀑布流显示等等, 之前你需要 ListView,GridView 和自定义 View, 而现在你可以通过 RecyclerView 的布局管理器 LayoutManager 控制
你想要控制 Item 间的间隔(可绘制), 想自定义更多样式的分割线, 之前你可以设置 divider, 那么现在你可以使用 RecyclerView 的 ItemDecoration, 想怎么画怎么画
你想要控制 Item 增删的动画, ListView 呢我们只能自己通过属性动画来操作 Item 的视图 RecyclerView 可使用 ItemAnimator
你想要局部刷新某个 Item, 对于 ListView 来说, 我们知道 notifyDataSetChanged 来通知视图更新变化, 但是该方法会重绘每个 Item, 而对于 RecyclerView.Adapter 则提供了 notifyItemChanged 用于更新单个 Item View 的刷新, 我们可以省去自己写局部更新的工作
除了上述场景外, RecyclerView 强制使用了 ViewHolder 模式, 我们知道 ListView 使用 ViewHolder 来进行性能优化, 但是这不是必须得, 但是在 RecyclerView 中是必须的, 另外 RecyclerView 还有许多优势, 这里就不一一列举了, 总体来说现在越来越多的项目使用 RecyclerView, 许多老旧项目也渐渐使用 RecyclerView 来替代 ListView
注: 当我们想要一个列表显示控件的时候, 需要支持动画, 或者频繁更新, 局部刷新, 建议使用 RecyclerView, 更加强大完善, 易扩展; 其他情况下 ListView 在使用上反而更加方便, 快捷
前言我们就讲到这, 那么我们来进入正题
RecyclerView 的使用
作为一个新控件, RecyclerView 的使用有许多需要注意的地方
RecyclerView 的简单使用
一样的我们新建一个 Demo 来演示 RecyclerView 的使用
- [RecyclerViewDemo1Activity.java]
- public class RecyclerViewDemo1Activity extends AppCompatActivity {
- @BindView(R.id.recycler_view)
- RecyclerView mRecyclerView;
- private List<String> mData;
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_recycler_demo1_view);
- ButterKnife.bind(this);
- //LayoutManager 必须指定, 否则无法显示数据, 这里指定为线性布局,
- mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
- // 虚拟数据
- mData = createDataList();
- // 设置 Adapter 必须指定, 否则数据怎么显示
- mRecyclerView.setAdapter(new RecyclerViewDemo1Adapter(mData));
- }
- protected List<String> createDataList() {
- mData = new ArrayList<>();
- for (int i=0;i<20;i++){
- mData.add("这是第"+i+"个 View");
- }
- return mData;
- }
- }
其对应的布局文件也很简单 activity_recycler_demo1_view.xml
- <?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"
- >
- <android.support.v7.widget.RecyclerView
- android:id="@+id/recycler_view"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- >
- </android.support.v7.widget.RecyclerView>
- </LinearLayout>
那么我们再来看 RecyclerViewDemo1Adapter
- /**
- * 与 ListView 的 Adapter 不同, RecyclerView 的 Adapter 需要继承 RecyclerView.Adapter<VH>(VH 是 ViewHolder 的类名)
- * 记为 RecyclerViewDemo1Adapter
- * 创建 ViewHolder: 在 RecyclerViewDemo1Adapter 中创建一个继承 RecyclerView.ViewHolder 的静态内部类, 记为 ViewHolder
- * (RecyclerView 必须使用 ViewHolder 模式, 这里的 ViewHolder 实现几乎与 ListView 优化时所使用的 ViewHolder 一致)
- * 在 RecyclerViewDemo1Adapter 中实现:
- * ViewHolder onCreateViewHolder(ViewGroup parent, int viewType): 映射 Item Layout Id, 创建 VH 并返回
- *
- * void onBindViewHolder(ViewHolder holder, int position): 为 holder 设置指定数据
- *
- * int getItemCount(): 返回 Item 的个数
- *
- * 可以看出, RecyclerView 将 ListView 中 getView()的功能拆分成了 onCreateViewHolder()和 onBindViewHolder()
- */
- public class RecyclerViewDemo1Adapter extends RecyclerView.Adapter<RecyclerViewDemo1Adapter.ViewHolder> {
- private List<String> mData;
- public RecyclerViewDemo1Adapter(List<String> data) {
- this.mData = data;
- }
- @Override
- public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
- View view = LayoutInflater
- .from(parent.getContext())
- .inflate(R.layout.item_menu_main, parent, false);
- return new ViewHolder(view);
- }
- @Override
- public void onBindViewHolder(ViewHolder holder, int position) {
- holder.setData(this.mData.get(position));
- holder.itemView.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- //item 点击事件
- }
- });
- }
- @Override
- public int getItemCount() {
- return this.mData != null ? this.mData.size() : 0;
- }
- static class ViewHolder extends RecyclerView.ViewHolder{
- private TextView mTextView;
- public ViewHolder(View itemView) {
- super(itemView);
- mTextView = (TextView) itemView.findViewById(R.id.tv_title);
- }
- public void setData(String title) {
- this.mTextView.setText(title);
- }
- }
- }
需要注意的是 RecyclerView 没有提供如 ListView 的 setOnItemClickListener 或者 setOnItemLongClickListener 之类的 Item 点击事件, 我们必须自己去实现该部分功能, 实现的方法有很多种, 也比较容易, 本例中采用在 Adapter 中 BindViewHolder 绑定数据的时候为 item 设置了点击事件
小结
RecyclerView 的四大组成分别是:
Adapter: 为 Item 提供数据必须提供, 关于 Adapter 我们上面的代码注释已经说的很明白了
Layout Manager:Item 的布局必须提供, 我们需要为 RecyclerView 指定一个布局管理器
Item Animator: 添加删除 Item 动画可选提供, 默认是 DefaultItemAnimator
Item Decoration:Item 之间的 Divider 可选提供, 默认是空
所以上面代码的运行结果看起来像是是一个没有分割线的 ListView
RecyclerView 的进阶使用
上面的基本使用我们是会了, 而且点击 Item 也有反应了, 不过巨丑无比啊有木有起码的分割线都没有, 真无语
为 RecyclerView 添加分割线
那么如何创建分割线呢,
创建一个类并继承 RecyclerView.ItemDecoration, 重写以下两个方法:
onDraw()或者 onDrawOver: 绘制分割线
getItemOffsets(): 设置分割线的宽高
然后使用 RecyclerView 通过 addItemDecoration()方法添加 item 之间的分割线
我们来看一下代码
- public class RecyclerViewDemo2Activity extends AppCompatActivity {
- @BindView(R.id.recycler_view)
- RecyclerView mRecyclerView;
- private List<String> mData;
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_recycler_demo1_view);
- ButterKnife.bind(this);
- //LayoutManager 必须指定, 否则无法显示数据, 这里指定为线性布局,
- mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
- // 虚拟数据
- mData = createDataList();
- // 设置 Adapter 必须指定, 否则数据怎么显示
- mRecyclerView.setAdapter(new RecyclerViewDemo2Adapter(mData));
- // 设置分割线
- mRecyclerView.addItemDecoration(
- new DividerItemDecoration(this,DividerItemDecoration.VERTICAL));
- }
- protected List<String> createDataList() {
- mData = new ArrayList<>();
- for (int i=0;i<20;i++){
- mData.add("这是第"+i+"个 View");
- }
- return mData;
- }
- }
布局文件还跟上面的一致, 代码也大致相同, 不过我们多了一行
- // 设置分割线
- mRecyclerView.addItemDecoration(
- new DividerItemDecoration(this,DividerItemDecoration.VERTICAL));
这里的 DividerItemDecoration 是 Google 给了一个参考的实现类, 这里我们通过分析这个例子来看如何自定义 Item Decoration
- [DividerItemDecoration.java]
- public class DividerItemDecoration extends RecyclerView.ItemDecoration {
- public static final int HORIZONTAL = LinearLayout.HORIZONTAL;
- public static final int VERTICAL = LinearLayout.VERTICAL;
- private static final int[] ATTRS = new int[]{ android.R.attr.listDivider };
- private Drawable mDivider;
- private int mOrientation;
- private final Rect mBounds = new Rect();
- /**
- * 创建一个可使用于 LinearLayoutManager 的分割线
- *
- */
- public DividerItemDecoration(Context context, int orientation) {
- final TypedArray a = context.obtainStyledAttributes(ATTRS);
- mDivider = a.getDrawable(0);
- a.recycle();
- setOrientation(orientation);
- }
- @Override
- public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
- if (parent.getLayoutManager() == null) {
- return;
- }
- if (mOrientation == VERTICAL) {
- drawVertical(c, parent);
- } else {
- drawHorizontal(c, parent);
- }
- }
- @SuppressLint("NewApi")
- private void drawVertical(Canvas canvas, RecyclerView parent) {
- canvas.save();
- final int left;
- final int right;
- 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(ViewCompat.getTranslationY(child));
- final int top = bottom - mDivider.getIntrinsicHeight();
- 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 (mOrientation == VERTICAL) {
- outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
- } else {
- outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);
- }
- }
- }
首先看构造函数, 构造函数中获得系统属性 android:listDivider, 该属性是一个 Drawable 对象接着设置 mOrientation, 我们这里传入的是 DividerItemDecoration.VERTICAL
上面我们就说了如何添加分割线, 那么作为实例, 我们先看 DividerItemDecoration 的 getItemOffsets 方法
- @Override
- public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
- RecyclerView.State state) {
- if (mOrientation == VERTICAL) {
- outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
- } else {
- outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);
- }
- }
outRect 是当前 item 四周的间距, 类似 margin 属性, 现在设置了该 item 下间距为 mDivider.getIntrinsicHeight()
那么 getItemOffsets()是怎么被调用的呢?
RecyclerView 继承了 ViewGroup, 并重写了 measureChild(), 该方法在 onMeasure()中被调用, 用来计算每个 child 的大小, 计算每个 child 大小的时候就需要加上 getItemOffsets()设置的外间距:
- public void measureChild(View child, int widthUsed, int heightUsed) {
- final LayoutParams lp = (LayoutParams) child.getLayoutParams();
- final Rect insets = mRecyclerView.getItemDecorInsetsForChild(child);
- widthUsed += insets.left + insets.right;
- heightUsed += insets.top + insets.bottom;
- ......
- }
也就是说 getItemOffsets()方法是确定分割线的大小的(这个大小指的是高度, 宽度)
那么接着 onDraw()以及 onDrawOver(), 两者的作用是什么呢以及两者之间有什么关系呢?
- public class RecyclerView extends ViewGroup {
- @Override
- public void draw(Canvas c) {
- super.draw(c);
- final int count = mItemDecorations.size();
- for (int i = 0; i < count; i++) {
- mItemDecorations.get(i).onDrawOver(c, this, mState);
- }
- ......
- }
- @Override
- public void onDraw(Canvas c) {
- super.onDraw(c);
- final int count = mItemDecorations.size();
- for (int i = 0; i < count; i++) {
- mItemDecorations.get(i).onDraw(c, this, mState);
- }
- }
- }
根据 View 的绘制流程, 首先调用 RecyclerView 重写的 draw()方法, 随后 super.draw()即调用 View 的 draw(), 该方法会先调用 onDraw()(这个方法在 RecyclerView 重写了), 再调用 dispatchDraw()绘制 children 因此: ItemDecoration 的 onDraw()在绘制 Item 之前调用, ItemDecoration 的 onDrawOver()在绘制 Item 之后调用
在 RecyclerView 的 onDraw()方法中会得到分割线的数目, 并循环调用其 onDraw()方法, 我们再来看分割线实例 DividerItemDecoration 的 onDraw()方法
- @Override
- public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
- if (parent.getLayoutManager() == null) {
- return;
- }
- if (mOrientation == VERTICAL) {
- drawVertical(c, parent);
- } else {
- drawHorizontal(c, parent);
- }
- }
这里调用了 drawVertical
- @SuppressLint("NewApi")
- private void drawVertical(Canvas canvas, RecyclerView parent) {
- canvas.save();
- final int left;
- final int right;
- 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(ViewCompat.getTranslationY(child));
- final int top = bottom - mDivider.getIntrinsicHeight();
- mDivider.setBounds(left, top, right, bottom);
- mDivider.draw(canvas);
- }
- canvas.restore();
- }
drawVertical 的逻辑比较简单, 重要的代码
- // 为分割线设置 bounds
- mDivider.setBounds(left, top, right, bottom);
- // 画出来
- mDivider.draw(canvas);
小结
在 RecyclerView 中添加分割线需要的操作已经在上文中比较详细的说明了, 这里再总结一下我们在为 RecyclerView 添加分割线的时候使用
- // 设置分割线
- mRecyclerView.addItemDecoration(
- new DividerItemDecoration(this,DividerItemDecoration.VERTICAL));
其中 addItemDecoration 方法的参数即为分割线的实例, 那么如何创建分割线呢,
创建一个类并继承 RecyclerView.ItemDecoration, 重写以下两个方法:
onDraw()或者 onDrawOver: 绘制分割线
getItemOffsets(): 设置分割线的宽高
为 RecyclerView 添加 HeaderView 以及 FooterView
基本功能设计
RecyclerView 没有提供类似 ListView 的 addHeaderView 或者 addFooterView 方法, 所以我们要自己实现关于实现的方法也有很多种目前网上能搜到的主流解决办法是在 Adapter 中重写 getItemViewType 方法为头部或者底部布局生成特定的 item 从而实现头部布局以及底部布局
本篇的解决办法与上面的并无本质上的不同, 只是我们在 Adapter 的外面再包上一层, 以类似装饰者设计模式的方式对 Adapter 进行无侵入式的包装
我们希望使用的方式比较简单
- // 这个是真正的 Adapter, 在本例中不需要对其改变
- mAdapter = new RecyclerViewDemo2Adapter(mData);
- // 包装的 wrapper, 对 Adapter 进行包装实现添加 Header 以及 Footer 等的功能
- mHeaderAndFooterWrapper = new HeaderAndFooterWrapper(mAdapter);
- TextView t1 = new TextView(this);
- t1.setText("Header 1");
- TextView t2 = new TextView(this);
- t2.setText("Header 2");
- mHeaderAndFooterWrapper.addHeaderView(t1);
- mHeaderAndFooterWrapper.addHeaderView(t2);
- mRecyclerView.setAdapter(mHeaderAndFooterWrapper);
- mHeaderAndFooterWrapper.notifyDataSetChanged();
我们下面先对 HeaderAndFooterWrapper 基本功能
- public class HeaderAndFooterWrapper<T> extends RecyclerView.Adapter<RecyclerView.ViewHolder>
- {
- // 以较高的数值作为基数, 每一个 Header 或者 Footer 对应不同的数值
- private static final int BASE_ITEM_TYPE_HEADER = 100000;
- private static final int BASE_ITEM_TYPE_FOOTER = 200000;
- // 存储 Header 和 Footer 的集合
- private SparseArrayCompat<View> mHeaderViews = new SparseArrayCompat<>();
- private SparseArrayCompat<View> mFootViews = new SparseArrayCompat<>();
- // 内部的真正的 Adapter
- private RecyclerView.Adapter mInnerAdapter;
- public HeaderAndFooterWrapper(RecyclerView.Adapter adapter)
- {
- mInnerAdapter = adapter;
- }
- private boolean isHeaderViewPos(int position)
- {
- return position < getHeadersCount();
- }
- private boolean isFooterViewPos(int position)
- {
- return position >= getHeadersCount() + getRealItemCount();
- }
- public void addHeaderView(View view)
- {
- mHeaderViews.put(mHeaderViews.size() + BASE_ITEM_TYPE_HEADER, view);
- }
- public void addFootView(View view)
- {
- mFootViews.put(mFootViews.size() + BASE_ITEM_TYPE_FOOTER, view);
- }
- public int getHeadersCount()
- {
- return mHeaderViews.size();
- }
- public int getFootersCount()
- {
- return mFootViews.size();
- }
- }
我们这里使用 SparseArrayCompat 作为存储 Header 和 Footer 的集合, SparseArrayCompat 有什么特点呢? 它类似于 Map, 只不过在某些情况下比 Map 的性能要好, 并且只能存储 key 为 int 的情况
我们这里可以看到 HeaderAndFooterWrapper 是继承于 RecyclerView.Adapter
- // 真正进行数据处理以及展示的 Adapter
- mAdapter = new RecyclerViewDemo2Adapter(mData);
- // 添加 Header 以及 Footer 的 wrapper
- mHeaderAndFooterWrapper = new HeaderAndFooterWrapper(mAdapter);
- // 设置空 View 的 wrapper
- mEmptyWrapperAdapter = new EmptyWrapper(mHeaderAndFooterWrapper);
- mRecyclerView.setAdapter(mEmptyWrapperAdapter);
重写相关方法
- public class HeaderAndFooterWrapper<T> extends
- RecyclerView.Adapter<RecyclerView.ViewHolder>
- {
- private static final int BASE_ITEM_TYPE_HEADER = 100000;
- private static final int BASE_ITEM_TYPE_FOOTER = 200000;
- //SparseArrayCompat 类似于 Map, 其用法与 map 相似
- private SparseArrayCompat<View> mHeaderViews = new SparseArrayCompat<>();
- private SparseArrayCompat<View> mFootViews = new SparseArrayCompat<>();
- private RecyclerView.Adapter mInnerAdapter;
- public HeaderAndFooterWrapper(RecyclerView.Adapter adapter)
- {
- mInnerAdapter = adapter;
- }
- /**
- * 重写 onCreateViewHolder, 创建 ViewHolder
- * @param parent 父容器, 这里指的是 RecyclerView
- * @param viewType view 的类型, 用 int 表示, 也是 SparseArrayCompat 的 key
- * @return
- */
- @Override
- public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType)
- {
- if (mHeaderViews.get(viewType) != null)
- {// 如果以 viewType 为 key 获取的 View 为 null
- // 创建 ViewHolder 并返回
- ViewHolder holder = new ViewHolder(parent.getContext(), mHeaderViews.get(viewType));
- return holder;
- } else if (mFootViews.get(viewType) != null)
- {
- ViewHolder holder = new ViewHolder(parent.getContext(), mFootViews.get(viewType));
- return holder;
- }
- return mInnerAdapter.onCreateViewHolder(parent, viewType);
- }
- /**
- * 获得对应 position 的 type
- * @param position
- * @return
- */
- @Override
- public int getItemViewType(int position)
- {
- if (isHeaderViewPos(position))
- {
- return mHeaderViews.keyAt(position);
- } else if (isFooterViewPos(position))
- {
- return mFootViews.keyAt(position - getHeadersCount() - getRealItemCount());
- }
- return mInnerAdapter.getItemViewType(position - getHeadersCount());
- }
- private int getRealItemCount()
- {
- return mInnerAdapter.getItemCount();
- }
- /**
- * 绑定数据
- * @param holder
- * @param position
- */
- @Override
- public void onBindViewHolder(RecyclerView.ViewHolder holder, int position)
- {
- if (isHeaderViewPos(position))
- {
- return;
- }
- if (isFooterViewPos(position))
- {
- return;
- }
- mInnerAdapter.onBindViewHolder(holder, position - getHeadersCount());
- }
- /**
- * 得到 item 数量 (包括头部布局数量和尾部布局数量)
- * @return
- */
- @Override
- public int getItemCount()
- {
- return getHeadersCount() + getFootersCount() + getRealItemCount();
- }
- private boolean isHeaderViewPos(int position)
- {
- return position < getHeadersCount();
- }
- private boolean isFooterViewPos(int position)
- {
- return position >= getHeadersCount() + getRealItemCount();
- }
- /**
- * 以 mHeaderViews.size() + BASE_ITEM_TYPE_HEADER 为 key, 头部布局 View 为 Value
- * 放入 mHeaderViews
- */
- public void addHeaderView(View view)
- {
- mHeaderViews.put(mHeaderViews.size() + BASE_ITEM_TYPE_HEADER, view);
- }
- public void addFootView(View view)
- {
- mFootViews.put(mFootViews.size() + BASE_ITEM_TYPE_FOOTER, view);
- }
- public int getHeadersCount()
- {
- return mHeaderViews.size();
- }
- public int getFootersCount()
- {
- return mFootViews.size();
- }
- class ViewHolder extends RecyclerView.ViewHolder {
- private View mConvertView;
- private Context mContext;
- public ViewHolder(Context context, View itemView) {
- super(itemView);
- mContext = context;
- mConvertView = itemView;
- }
- }
- }
看上面的代码, HeaderAndFooterWrapper 继承于 RecyclerView.Adapter
- /**
- * 重写 onCreateViewHolder, 创建 ViewHolder
- * @param parent 父容器, 这里指的是 RecyclerView
- * @param viewType view 的类型, 用 int 表示, 也是 SparseArrayCompat 的 key
- * @return
- */
- @Override
- public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType)
- {
- if (mHeaderViews.get(viewType) != null)
- {// 如果以 viewType 为 key 获取的 View 为 null
- // 创建 ViewHolder 并返回
- ViewHolder holder = new ViewHolder(parent.getContext(), mHeaderViews.get(viewType));
- return holder;
- } else if (mFootViews.get(viewType) != null)
- {
- ViewHolder holder = new ViewHolder(parent.getContext(), mFootViews.get(viewType));
- return holder;
- }
- return mInnerAdapter.onCreateViewHolder(parent, viewType);
- }
我们先看 onCreateViewHolder 方法, 该方法返回 ViewHolder, 我们在其中为头部以及底部布局单独创建 ViewHolder, 对于普通的 item, 我们依然调用内部的 mInnerAdapter 的 onCreateViewHolder 方法
创建好 ViewHolder 后, 便进行绑定的工作了
- /**
- * 绑定数据
- * @param holder
- * @param position
- */
- @Override
- public void onBindViewHolder(RecyclerView.ViewHolder holder, int position)
- {
- if (isHeaderViewPos(position))
- {
- return;
- }
- if (isFooterViewPos(position))
- {
- return;
- }
- mInnerAdapter.onBindViewHolder(holder, position - getHeadersCount());
- }
这里我们头部以及底部布局不进行数据的绑定, 其他普通的 item 依然调用内部真正的 mInnerAdapter.onBindViewHolder
运行结果如下
适配 GridLayoutManager
上面我们已经初步实现为 RecyclerView 添加 Header 以及 Footer 了, 不过上面的我们的布局模式是 LinearyLayoutManager, 当我们使用 GridLayoutManager 时, 效果就不是我们所想像的那样了
- // 设置 GridLayoutManager
- mRecyclerView.setLayoutManager(new GridLayoutManager(this,3));
当我们设置 GridLayoutManager 时, 可以看到头部布局所展示的样子, 头部布局还真的被当做一个普通的 item 布局了那么我们需要为这个布局做一些特殊处理我们知道使用 GridLayoutManager 的 SpanSizeLookup 设置某个 Item 所占空间
在我们的 HeaderAndFooterWrapper 中重写 onAttachedToRecyclerView 方法(该方法在 Adapter 与 RecyclerView 相关联时回调), 如下:
- @Override
- public void onAttachedToRecyclerView(RecyclerView recyclerView)
- {
- mInnerAdapter.onAttachedToRecyclerView(recyclerView);
- RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
- if (layoutManager instanceof GridLayoutManager)
- {
- final GridLayoutManager gridLayoutManager = (GridLayoutManager) layoutManager;
- final GridLayoutManager.SpanSizeLookup spanSizeLookup = gridLayoutManager.getSpanSizeLookup();
- gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup()
- {
- @Override
- public int getSpanSize(int position)
- {
- int viewType = getItemViewType(position);
- if (mHeaderViews.get(viewType) != null)
- {
- return layoutManager.getSpanCount();
- } else if (mFootViews.get(viewType) != null)
- {
- return layoutManager.getSpanCount();
- }
- if (spanSizeLookup != null)
- return spanSizeLookup.getSpanSize(position);
- return 1;
- }
- });
- gridLayoutManager.setSpanCount(gridLayoutManager.getSpanCount());
- }
- }
当发现 layoutManager 为 GridLayoutManager 时, 通过设置 SpanSizeLookup, 对其 getSpanSize 方法, 返回值设置为 layoutManager.getSpanCount();
适配 StaggeredGridLayoutManager
- mRecyclerView.setLayoutManager(new StaggeredGridLayoutManager(3,
- OrientationHelper.VERTICAL));
当我们设置 StaggeredGridLayoutManager 时, 可以看到如下效果
而针对于 StaggeredGridLayoutManager, 我们需要使用 StaggeredGridLayoutManager.LayoutParams
在我们的 HeaderAndFooterWrapper 中重写 onViewAttachedToWindow 方法(该方法在 Adapter 与 RecyclerView 相关联时回调), 如下:
- @Override
- public void onViewAttachedToWindow(RecyclerView.ViewHolder holder)
- {
- mInnerAdapter.onViewAttachedToWindow(holder);
- int position = holder.getLayoutPosition();
- if (isHeaderViewPos(position) || isFooterViewPos(position))
- {
- ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
- if (lp != null
- && lp instanceof StaggeredGridLayoutManager.LayoutParams)
- {
- StaggeredGridLayoutManager.LayoutParams p = (StaggeredGridLayoutManager.LayoutParams) lp;
- p.setFullSpan(true);
- }
- }
- }
为 RecyclerView 设置 EmptyView
上面已经详细给出了为 RecyclerView 添加 Header 以及 Footer 的例子, 关于 EmptyView 的实现方法与上面基本类似, 读者可自行实现, 当然在本篇末会给出完整的源码地址
RecyclerView 的缓存机制
RecyclerView 和 ListView 的回收机制非常相似, 但是 ListView 是以 View 作为单位进行回收, RecyclerView 是以 ViewHolder 作为单位进行回收相比于 ListView,RecyclerView 的回收机制更为完善
Recycler 是 RecyclerView 回收机制的实现类, 他实现了四级缓存:
mAttachedScrap: 缓存在屏幕上的 ViewHolder
mCachedViews: 缓存屏幕外的 ViewHolder, 默认为 2 个 ListView 对于屏幕外的缓存都会调用 getView()
mViewCacheExtensions: 需要用户定制, 默认不实现
mRecyclerPool: 缓存池, 多个 RecyclerView 共用
要想理解 RecyclerView 的回收机制, 我们就必须从其数据展示谈起, 我们都知道 RecyclerView 使用 LayoutManager 管理其数据布局的显示
注: 以下源码来自 support-v7 25.4.0
RecyclerView$LayoutManager
LayoutManager 是 RecyclerView 下的一个抽象类, Google 提供了 LinearLayoutManager,GridLayoutManager 以及 StaggeredGridLayoutManager 基本上能满足大部分开发者的需求这三个类的代码都非常长, 这要分析下来可了不得本篇文章只分析 LinearLayoutManager 的一部分内容
与分析 ListView 时类似, RecyclerView 作为一个 ViewGroup, 肯定也跑不了那几大过程, 我们依然还是只分析其 layout 过程
- [RecyclerView.java]
- @Override
- protected void onLayout(boolean changed, int l, int t, int r, int b) {
- TraceCompat.beginSection(TRACE_ON_LAYOUT_TAG);
- dispatchLayout();
- TraceCompat.endSection();
- mFirstLayoutComplete = true;
- }
- void dispatchLayout() {
- if (mAdapter == null) {
- Log.e(TAG, "No adapter attached; skipping layout");
- // leave the state in START
- return;
- }
- if (mLayout == null) {
- Log.e(TAG, "No layout manager attached; skipping layout");
- // leave the state in START
- return;
- }
- mState.mIsMeasuring = false;
- if (mState.mLayoutStep == State.STEP_START) {
- //1 没有执行过布局流程的情况
- dispatchLayoutStep1();
- mLayout.setExactMeasureSpecsFrom(this);
- dispatchLayoutStep2();
- } else if (mAdapterHelper.hasUpdates()
- || mLayout.getWidth() != getWidth() ||
- mLayout.getHeight() != getHeight()) {
- //2 执行过布局流程, 但是之后 size 又有变化的情况
- mLayout.setExactMeasureSpecsFrom(this);
- dispatchLayoutStep2();
- } else {
- //3 执行过布局流程, 可以直接使用之前数据的情况
- mLayout.setExactMeasureSpecsFrom(this);
- }
- dispatchLayoutStep3();
- }
不过, 无论什么情况, 最终都是完成 dispatchLayoutStep1,dispatchLayoutStep2 和 dispatchLayoutStep3 这三步, 这样的情况区分只是为了避免重复计算
其中第二步的 dispatchLayoutStep2 是真正的布局!
- private void dispatchLayoutStep2() {
- ...... // 设置状态
- mState.mInPreLayout = false; // 更改此状态, 确保不是会执行上一布局操作
- // 真正布局就是这一句话, 布局的具体策略交给了 LayoutManager
- mLayout.onLayoutChildren(mRecycler, mState);
- ......// 设置和恢复状态
- }
由上面的代码可以知道布局的具体操作都交给了具体的 LayoutManager, 那我们来分析其中的 LinearLayoutManager
- [LinearLayoutManager.java]
- /**
- *LinearLayoutManager 的 onLayoutChildren 方法代码也比较多, 这里也不进行逐行分析
- * 只来看关键的几个点
- */
- @Override
- public void onLayoutChildren(RecyclerView.Recycler recycler,
- RecyclerView.State state) {
- ......
- // 状态判断以及一些准备操作
- onAnchorReady(recycler, state, mAnchorInfo, firstLayoutDirection);
- /**
- *1 感觉这个函数应该跟上一篇我们所分析的 ListView 的 detachAllViewsFromParent(); 有点像
- */
- detachAndScrapAttachedViews(recycler);
- ......
- //2 感觉这个函数跟上一篇我们所分析的 ListView 的 fillUp 有点像
- fill(recycler, mLayoutState, state, false);
- }
上面已经给出了真正布局的代码我们还是按照上一篇的思路来分析, 两次 layout
第 1 次 layout
第 1 个重要函数
- [RecyclerView$LayoutManager]
- /**
- * 暂时 detach 和 scrap 所有当前附加的子视图视图将被丢弃到给定的回收器中(即参数 recycler)
- * 回收器 (即 Recycler) 可能更喜欢重用 scrap 的视图
- *
- * @param recycler 指定的回收器 Recycler
- */
- public void detachAndScrapAttachedViews(Recycler recycler) {
- final int childCount = getChildCount();
- for (int i = childCount - 1; i >= 0; i--) {
- final View v = getChildAt(i);
- scrapOrRecycleView(recycler, i, v);
- }
- }
第 1 次 layout 时, RecyclerView 并没有 Child, 所以跳过该函数, 不过我们从上面的代码注释也知道了该函数跟缓存 Recycler 有关
第 2 个重要函数
- [LinearLayoutManager.java]
- int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
- RecyclerView.State state, boolean stopOnFocusable) {
- ......
- int remainingSpace = layoutState.mAvailable + layoutState.mExtra;
- LayoutChunkResult layoutChunkResult = mLayoutChunkResult;
- while ((layoutState.mInfinite || remainingSpace > 0)
- && layoutState.hasMore(state)) {// 这里循环判断是否还有空间放置 item
- ......
- // 真正放置的代码放到了这里
- layoutChunk(recycler, state, layoutState, layoutChunkResult);
- ......
- }
- if (DEBUG) {
- validateChildOrder();
- }
- return start - layoutState.mAvailable;
- }
跟进 layoutChunk
- [LinearLayoutManager.java]
- void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
- LayoutState layoutState, LayoutChunkResult result) {
- /**
- * 获取一个 View, 这个函数应该是重点了,
- */
- View view = layoutState.next(recycler);
- ......
- // 添加 View
- addView(view);
- ......
- // 计算 View 的大小
- measureChildWithMargins(view, 0, 0);
- ......
- // 布局
- layoutDecoratedWithMargins(view, left, top, right, bottom);
- ......
- }
跟进 next()
- [LinearLayoutManager$LayoutState]
- View next(RecyclerView.Recycler recycler) {
- if (mScrapList != null) {
- return nextViewFromScrapList();
- }
- final View view = recycler.getViewForPosition(mCurrentPosition);
- mCurrentPosition += mItemDirection;
- return view;
- }
getViewForPosition 方法可以说是 RecyclerView 中缓存策略最重要的方法, 该方法是从 RecyclerView 的回收机制实现类 Recycler 中获取合适的 View, 或者新创建一个 View
- View getViewForPosition(int position, boolean dryRun) {
- /**
- * 从这个函数就能看出 RecyclerView 是以 ViewHolder 为缓存单位的些许端倪
- */
- return tryGetViewHolderForPositionByDeadline
- (position, dryRun, FOREVER_NS).itemView;
- }
跟进 tryGetViewHolderForPositionByDeadline
- /**
- * 试图获得给定位置的 ViewHolder, 无论是从
- *mAttachedScrapmCachedViewsmViewCacheExtensionsmRecyclerPool 还是直接创建
- *
- * @return ViewHolder for requested position
- */
- @Nullable
- ViewHolder tryGetViewHolderForPositionByDeadline(int position,
- boolean dryRun, long deadlineNs) {
- ......
- // 1) 尝试从 mAttachedScrap 获取
- if (holder == null) {
- holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
- ......
- }
- if (holder == null) {
- ......
- final int type = mAdapter.getItemViewType(offsetPosition);
- // 2) 尝试从 mCachedViews 获取
- if (mAdapter.hasStableIds()) {
- holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),
- type, dryRun);
- if (holder != null) {
- // update position
- holder.mPosition = offsetPosition;
- fromScrapOrHiddenOrCache = true;
- }
- }
- // 3) 尝试从 mViewCacheExtensions 获取
- if (holder == null && mViewCacheExtension != null) {
- ......
- final View view = mViewCacheExtension
- .getViewForPositionAndType(this, position, type);
- if (view != null) {
- holder = getChildViewHolder(view);
- ......
- }
- }
- // 4) 尝试从 mRecyclerPool 获取
- if (holder == null) { // fallback to pool
- holder = getRecycledViewPool().getRecycledView(type);
- if (holder != null) {
- holder.resetInternal();
- if (FORCE_INVALIDATE_DISPLAY_LIST) {
- invalidateDisplayListInt(holder);
- }
- }
- }
- if (holder == null) {
- // 5) 直接创建
- holder = mAdapter.createViewHolder(RecyclerView.this, type);
- }
- }
- ......
- // 6) 判断是否需要 bindHolder
- if (!holder.isBound()
- || holder.needsUpdate()
- || holder.isInvalid()) {
- final int offsetPosition = mAdapterHelper.findPositionOffset(position);
- bound = tryBindViewHolderByDeadline
- (holder, offsetPosition, position, deadlineNs);
- }
- ......
- return holder;
- }
那么在第 1 次 layout 时,, 前 4 步都不能获得 ViewHolder, 那么进入第 5, 直接创建
- holder = mAdapter.createViewHolder(RecyclerView.this, type);
- public final VH createViewHolder(ViewGroup parent, int viewType) {
- TraceCompat.beginSection(TRACE_CREATE_VIEW_TAG);
- // 这里终于看到我们的亲人 onCreateViewHolder
- final VH holder = onCreateViewHolder(parent, viewType);
- holder.mItemViewType = viewType;
- TraceCompat.endSection();
- return holder;
- }
这个 onCreateViewHolder 正是在 RecyclerViewDemo1Adapter 中我们重写的
- @Override
- public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
- Log.d(TAG,"onCreateViewHolder->viewtype"+viewType);
- View view = LayoutInflater
- .from(parent.getContext())
- .inflate(R.layout.item_menu_main, parent, false);
- return new ViewHolder(view);
- }
初次创建了 ViewHolder 之后, 便进入 6, 导致我们重写的 onBindViewHolder 回调, 数据与 View 绑定了
第 2 次 layout
从上一篇 ListView 中我们就知道了再简单的 View 也至少需要两次 Layout, 在 ListView 中通过把屏幕的子 View detach 并加入 mActivieViews, 以避免重复添加 item 并可通过 attach 提高性能, 那么在 RecyclerView 中, 它的做法与 ListView 十分类似, RecyclerView 也是通过 detach 子 View, 并把子 View 对应的 ViewHolder 加入其 1 级缓存 mAttachedScrap 这部分我们就不详细分析了, 读者可参照上一篇的步骤进行分析
RecyclerView 与 ListView 缓存机制对比分析
ListView 和 RecyclerView 最大的区别在于数据源改变时的缓存的处理逻辑, ListView 是一锅端, 将所有的 mActiveViews 都移入了二级缓存 mScrapViews, 而 RecyclerView 则是更加灵活地对每个 View 修改标志位, 区分是否重新 bindView
小结
在一些场景下, 如界面初始化, 滑动等, ListView 和 RecyclerView 都能很好地工作, 两者并没有很大的差异, 但是在需要支持动画, 或者频繁更新, 局部刷新, 建议使用 RecyclerView, 更加强大完善, 易扩展
本篇总结
本篇呢, 我们分析了 RecyclerView 的使用方法以及 RecyclerView 部分源码目的是为了更好的掌握 RecyclerView
这里呢再上图总结一下 RecyclerView 的 layout 流程
下篇预告
下篇呢, 也是一篇干货, 上面两篇文章, 我们的数据都是虚拟的, 静态的, 而实际开发中数据通常都是从服务器动态获得的, 这也产生了一系列问题, 如列表的下拉刷新以及上拉加载 ListVIew 异步获取图片显示错位等等问题
参考博文
http://blog.csdn.net/lmj623565791/article/details/51854533
来源: https://www.cnblogs.com/wangle12138/p/8456508.html