RecyclerView使用概述

443 阅读7分钟

标签(空格分隔): 安卓UI


一、RecyclerView的基本用法

1、设置布局

RecyclerView相对于ListView而言提供设置整个数据的展示布局,对应方法源码:

/**
 * Set the {@link LayoutManager} that this RecyclerView will use.
 *
 * <p>In contrast to other adapter-backed views such as {@link android.widget.ListView}
 * or {@link android.widget.GridView}, RecyclerView allows client code to provide custom
 * layout arrangements for child views. These arrangements are controlled by the
 * {@link LayoutManager}. A LayoutManager must be provided for RecyclerView to function.</p>
 *
 * <p>Several default strategies are provided for common uses such as lists and grids.</p>
 *
 * @param layout LayoutManager to use
 */
public void setLayoutManager(LayoutManager layout) {
......
}

目前实现RecyclerView.LayoutManager的LayoutManager有: LinerLayoutManager:内部元素以垂直或者水平列表方式展示Item; GridLayoutManager:以网格方式展示Item; StaggeredGridLayoutManager:以瀑布流方式展示Item; 因此可以看到RecyclerView比ListView固定的垂直布局要灵活的多;

2、设置动画和Item间隔

RecyclerView支持对于Item的特殊设定,比如支持item添加和移除的动画、比如支持设置item之间间隔样式;

/**
 * Sets the {@link ItemAnimator} that will handle animations involving changes
 * to the items in this RecyclerView. By default, RecyclerView instantiates and
 * uses an instance of {@link DefaultItemAnimator}. Whether item animations are
 * enabled for the RecyclerView depends on the ItemAnimator and whether
 * the LayoutManager {@link LayoutManager#supportsPredictiveItemAnimations()
 * supports item animations}.
 *
 * @param animator The ItemAnimator being set. If null, no animations will occur
 * when changes occur to the items in this RecyclerView.
 */
public void setItemAnimator(ItemAnimator animator) {
    if (mItemAnimator != null) {
        mItemAnimator.endAnimations();
        mItemAnimator.setListener(null);
    }
    mItemAnimator = animator;
    if (mItemAnimator != null) {
        mItemAnimator.setListener(mItemAnimatorListener);
    }
}

/**
 * Add an {@link ItemDecoration} to this RecyclerView. Item decorations can
 * affect both measurement and drawing of individual item views.
 *
 * <p>Item decorations are ordered. Decorations placed earlier in the list will
 * be run/queried/drawn first for their effects on item views. Padding added to views
 * will be nested; a padding added by an earlier decoration will mean further
 * item decorations in the list will be asked to draw/pad within the previous decoration's
 * given area.</p>
 *
 * @param decor Decoration to add
 * @param index Position in the decoration chain to insert this decoration at. If this value
 *              is negative the decoration will be added at the end.
 */
public void addItemDecoration(ItemDecoration decor, int index) {
    if (mLayout != null) {
        mLayout.assertNotInLayoutOrScroll("Cannot add item decoration during a scroll  or"
                + " layout");
    }
    if (mItemDecorations.isEmpty()) {
        setWillNotDraw(false);
    }
    if (index < 0) {
        mItemDecorations.add(decor);
    } else {
        mItemDecorations.add(index, decor);
    }
    markItemDecorInsetsDirty();
    requestLayout();
}

3、设置适配器

RecyclerView本身就是用来展示集合数据源的View,适配器本质是一个支持范型的设计模式,即实现将不同的数据和展示(逻辑)做适配,因此适配器的关键在于提供一些方法来绑定数据和展示的特殊关系;每一个具体的使用者都必须自己重写这些方法,来表达如何匹配; RecyclerView.Adapter这个抽象类定义如下:

/**
 * Base class for an Adapter
 *
 * <p>Adapters provide a binding from an app-specific data set to views that are displayed
 * within a {@link RecyclerView}.</p>
 *
 * @param <VH> A class that extends ViewHolder that will be used by the adapter.
 */
public abstract static class Adapter<VH extends ViewHolder> {
    ......
}

在RecyclerView.Adapter会需要用到RecyclerView.ViewHolder,ViewHolder有什么作用?参考Google官方说明:

/**
 * A ViewHolder describes an item view and metadata about its place within the RecyclerView.
 *
 * <p>{@link Adapter} implementations should subclass ViewHolder and add fields for caching
 * potentially expensive {@link View#findViewById(int)} results.</p>
 *
 * <p>While {@link LayoutParams} belong to the {@link LayoutManager},
 * {@link ViewHolder ViewHolders} belong to the adapter. Adapters should feel free to use
 * their own custom ViewHolder implementations to store data that makes binding view contents
 * easier. Implementations should assume that individual item views will hold strong references
 * to <code>ViewHolder</code> objects and that <code>RecyclerView</code> instances may hold
 * strong references to extra off-screen item views for caching purposes</p>
 */
public abstract static class ViewHolder {
    ......
}

ViewHolder实际上就是提供将每个Item的View和数据源Data关联起来的桥梁,ViewHolder内部会定义Item View的子元素,后续再根据Adapter提供的方法,将数据源的数据关联到ViewHolder的View子元素 如下使用:

public static class MyViewHolder extends RecyclerView.ViewHolder {
        TextView show_tv;

        public MyViewHolder(View itemView) {
            super(itemView);
            show_tv = (TextView) itemView.findViewById(R.id.item_show_tv);
        }
    }

Adapater提供了两个方法,一个用来表示每个Item对应的View展示,一个用来表示每个Item如何展示数据源的数据:

3.1、onCreateViewHolder方法(将每个Item关联到View展示对应的ViewHolder)

先参考官方定义: /**

  • Called when RecyclerView needs a new {@link ViewHolder} of the given type to represent
  • an item.
  • This new ViewHolder should be constructed with a new View that can represent the items
  • of the given type. You can either create a new View manually or inflate it from an XML
  • layout file.
  • The new ViewHolder will be used to display items of the adapter using
  • {@link #onBindViewHolder(ViewHolder, int, List)}. Since it will be re-used to display
  • different items in the data set, it is a good idea to cache references to sub views of
  • the View to avoid unnecessary {@link View#findViewById(int)} calls. */ public abstract VH onCreateViewHolder(ViewGroup parent, int viewType); 具体使用参考:
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    // 每个Item展示的view
    View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.rv_item_view, parent, false);
    // 持有展示View的viewholder,viewholder后续会和数据源对应
    MyViewHolder viewHolder = new MyViewHolder(v);
    return viewHolder;
}

onCreateViewHolder方法需要定义每个Item对应展示的View,同时将View映射到ViewHolder对象

3.2、onBindViewHolder方法(将每个Item的ViewHolder和数据源关联,每个Item要展示的数据逻辑)

先参考官方定义:

/**
 * Called by RecyclerView to display the data at the specified position. This method should
 * update the contents of the {@link ViewHolder#itemView} to reflect the item at the given
 * position.
 * Note that unlike {@link android.widget.ListView}, RecyclerView will not call this method
 * again if the position of the item changes in the data set unless the item itself is
 * invalidated or the new position cannot be determined. For this reason, you should only
 * use the <code>position</code> parameter while acquiring the related data item inside
 * this method and should not keep a copy of it. If you need the position of an item later
 * on (e.g. in a click listener), use {@link ViewHolder#getAdapterPosition()} which will
 * have the updated adapter position.
 * Override {@link #onBindViewHolder(ViewHolder, int, List)} instead if Adapter can
 * handle efficient partial bind.
 */
public abstract void onBindViewHolder(VH holder, int position);

官方文档已经做了说明,onBindViewHolder提供了将对应某个位置的数据源更新到对应到对应的ViewHolder,进而更新到对应的Item View; 具体使用参考:

@Override
public void onBindViewHolder(ViewHolder holder, int position) {
    // 将数据绑定到对应位置的ViewHolder
    holder.show_tv.setText(mData.get(position));
}

显而易见,onBindViewHolder方法就是将数据源数据绑定更新到ViewHolder,进而将数据映射到每个Item View; onBindViewHolder方法需要解决两个关键问题:1、当数据源更新的时候,每个元素都会再调用这个方法更新样式吗?2、当数据源不变,只需要更新某个Item UI展示,如何操作? 具体的操作可以参考onBindViewHolder的源码实现,即参考方法bindViewHolder、tryBindViewHolderByDeadline的具体实现来解答此问题; 除了以上方法,在具体实现过程中需要设置Adapter的数据源和重写getItemCount方法,来实现Adapter更新数据; RecyclerView的item不像ListView那样提供点击监听,我们需要自定义Item的监听时间;Item本质是一个View,可以自定义实现各种各样的监听事件,比如单击、长按、手势划动等等; 另外上面提供的重写方法是最基本的,针对不同使用场景,各位做不同的定制化;

二、RecyclerView更丰富的功能

2.1、ItemTouchHelper的使用

ItemTouchHelper是一个处理RecyclerView的滑动删除和拖拽的辅助类,RecyclerView 的item拖拽移动和滑动删除就靠它来实现;官方文档介绍如下:

/**
* This is a utility class to add swipe to dismiss and drag & drop support to RecyclerView.
* It works with a RecyclerView and a Callback class, which configures what type of interactions
* are enabled and also receives events when user performs these actions.
* Depending on which functionality you support, you should override
* {@link Callback#onMove(RecyclerView, ViewHolder, ViewHolder)} and / or
* {@link Callback#onSwiped(ViewHolder, int)}.
* This class is designed to work with any LayoutManager but for certain situations, it can be
* optimized for your custom LayoutManager by extending methods in the
* {@link ItemTouchHelper.Callback} class or implementing {@link ItemTouchHelper.ViewDropHandler}
* interface in your LayoutManager.
* By default, ItemTouchHelper moves the items' translateX/Y properties to reposition them. You can
* customize these behaviors by overriding {@link Callback#onChildDraw(Canvas, RecyclerView,
* ViewHolder, float, float, int, boolean)}
* or {@link Callback#onChildDrawOver(Canvas, RecyclerView, ViewHolder, float, float, int,
* boolean)}.
* Most of the time you only need to override <code>onChildDraw</code>.
*/
public class ItemTouchHelper extends RecyclerView.ItemDecoration
    implements RecyclerView.OnChildAttachStateChangeListener {
    ......
    }

itemTouchHelper需要与recyclerView绑定才有效果,参考方法attachToRecyclerView:

/**
 * Attaches the ItemTouchHelper to the provided RecyclerView. If TouchHelper is already
 * attached to a RecyclerView, it will first detach from the previous one. You can call this
 * method with {@code null} to detach it from the current RecyclerView.
 * @param recyclerView The RecyclerView instance to which you want to add this helper or {@code null} if you want to remove ItemTouchHelper from the current RecyclerView.
 */
public void attachToRecyclerView(@Nullable RecyclerView recyclerView) {
    ......
}

根据ItemTouchHelper.Callback可以实现对Item的添加、删除的处理;

2.2、addOnScrollListener结合RecyclerView.LayoutManager实现滑动过多UI展示

addOnScrollListener用来监听Scroll滑动,根据对应的LayoutManager可以实现滑动时候UI的各种展示效果;