从今天开始RecyclerView源码讲解系列正式开始更新,每周一篇。欢迎大家持续关注。
RecyclerView是Android中十分重要的视图组件,可以说基本每个APP的主页面都用到了RecyclerView,由此可见其重要性。那么问题来了,如何对RecyclerView做性能优化、并且封装成一个简单易用且功能强大的RecyclerView。那么首先第一步就是先了解RecyclerView,对它的整体设计有一个总览的概念。由此诞生了RecyclerView源码系列讲解(主要注重整体流程的讲解),后续也会去写RecyclerView性能优化系列 和 RecyclerView 封装系列文章。
RecyclerView源码讲解系列将分为以下几篇:
为了节约时间,后续文章将第一时间在微信公众号发布。CSDN延迟发布
本篇的主要内容是让大家对于RecyclerView有一个整体的认知和了解。认识一下RecyclerView家族的主要成员(静态内部类)
Adapter的作用是提供一个从数据->视图的映射关系,当数据发生变化的时候也会及时通知观察者,更新数据->视图的映射。
public abstract static class Adapter<VH extends ViewHolder> {
//存储注册的观察者对象,当数据源发生改变的时候用来去通知观察者
private final AdapterDataObservable mObservable = new AdapterDataObservable();
//根据传入鹅ViewType来创建我们想要的ViewHolder,这个ViewHolder必须是VH(泛型)的子类
public abstract VH onCreateViewHolder(@NonNull ViewGroup parent, int viewType);
//给ViewHolder绑定数据,调用ViewHolder的bind方法
public abstract void onBindViewHolder(@NonNull VH holder, int position);
//onCreateViewHolder方法中的ViewType参数就取自该方法,我们可以重写该方法。
//根据不同的model,返回不同的ViewType,再创建不同的ViewHolder。
//当我们对Adapter进行封装的时候,这里可以是封装的重点区域。让其可以根据不同的model
//自动映射并创建不同的ViewHolder
public int getItemViewType(int position) {
return 0;
}
//下面的方法都是用来通知观察者数据发生变化,视图需要更新的方法
//推荐使用notifyItem...系列方法,性能会更佳。在局部更新原理中会讲到原因
public final void notifyDataSetChanged() {
mObservable.notifyChanged();
}
public final void notifyItemChanged(int position) {
mObservable.notifyItemRangeChanged(position, 1);
}
//...... 代表还有很多其他代码,这里只讲解比较重要和常用的方法
}
ViewHolder可以看作是View的包装类,它会持有一个View,并且保存和这个View相关的参数。里面有一个比较重要的成员变量是flags,该变量会保存当前ViewHolder的状态。
public abstract static class ViewHolder {
//ViewHolder持有的View
public final View itemView;
//持有嵌套鹅RecyclerView
WeakReference<RecyclerView> mNestedRecyclerView;
//当前的对应Adapter中的位置
int mPosition = NO_POSITION;
//老位置
int mOldPosition = NO_POSITION;
long mItemId = NO_ID;
//对应的ItemType
int mItemViewType = INVALID_TYPE;
int mPreLayoutPosition = NO_POSITION;
//记录当前ViewHolder的状态,例如:数据是否失效,是否需要bind,是否回收
int mFlags;
//被添加到了哪个RecyclerView上
RecyclerView mOwnerRecyclerView;
}
RecyclerView会把measure、layout、scroll行为委托给LayoutManager管理。这也是为什么我们使用不同的LayoutManager的子类可以得到不同的布局效果。
//LayoutManager 主要关注下面四个方法
public abstract static class LayoutManager {
//...//
//用来测RecyclerView的大小
public void onMeasure(@NonNull Recycler recycler, @NonNull State state, int widthSpec,
int heightSpec) {
}
//用来对RecyclerView的子View进行布局
public void onLayoutChildren(Recycler recycler, State state) {
}
//用于处理RecyclerView垂直方向的滑动
public int scrollHorizontallyBy(int dx, Recycler recycler, State state) {
return 0;
}
//用于处理RecyclerView的水平方向滑动
public int scrollVerticallyBy(int dy, Recycler recycler, State state) {
return 0;
}
//...//
}
Recycler主要负责对ViewHolder进行回收和缓存。RecycelrView四级缓存机制就是用它来实现的。
public final class Recycler {
//一级缓存,存储屏幕上可见的ViewHolder
final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>();
//二级缓存,缓存当前不可见,滚动到下一个就可见并且不需要执行bind方法的ViewHolder
final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>();
//三级缓存,自定义缓存
private ViewCacheExtension mViewCacheExtension;
//四级缓存,对象池
RecycledViewPool mRecyclerPool;
}
RecyclerViewPool是四级缓存中的最后一级,是一个缓存ViewHolder的对象池,默认每个ViewType对应的ViewHolder最多缓存5个。
public static class RecycledViewPool {
private static final int DEFAULT_MAX_SCRAP = 5;
static class ScrapData {
final ArrayList<ViewHolder> mScrapHeap = new ArrayList<>();
int mMaxScrap = DEFAULT_MAX_SCRAP;
long mCreateRunningAverageNs = 0;
long mBindRunningAverageNs = 0;
}
//SparseArray是轻量级的HashMap,key只能为int。
//存放的是每个ViewType对应的ViewHolder
SparseArray<ScrapData> mScrap = new SparseArray<>();
}
ViewCacheExtension是四级缓存中的第三级,这是一个抽象类,里面只有一个getViewForPositionAndType方法,我们可以写一个该类的实现类,并把对象设置给RecyclerView,达到插入我们自己缓存策略的目的。
public abstract static class ViewCacheExtension {
public abstract View getViewForPositionAndType(@NonNull Recycler recycler, int position,
int type);
}
ViewFlinger负责处理当滑动列表,并且手指离开屏幕,列表依旧滚动(称为Fling状态)的行为。
class ViewFlinger implements Runnable {
//...//
//封装了具有超出滚动操作范围的滚动功能。Scroller 的直接替代品。
OverScroller mOverScroller;
//插值器,用于计算每帧需要滚动的距离,默认会用线性插值器,如果想让RecyclerView的Fling有阻尼的效果,可以采用其他插值器
Interpolator mInterpolator = sQuinticInterpolator;
ViewFlinger() {
mOverScroller = new OverScroller(getContext(), sQuinticInterpolator);
}
@Override
public void run() {
//用来执行每帧的滚动逻辑
}
//...//
}
State会保存当前 RecyclerView 状态的有用信息,比如目标滚动位置或视图焦点。状态对象还可以保存由资源 ID 标识的任意数据。
很多时候,RecyclerView 组件需要在彼此之间传递信息。为了在组件之间提供良好定义的数据总线,RecyclerView 将相同的 State 对象传递给组件回调,这些组件可以使用它来交换数据。
如果实现自定义组件,则可以使用 State 的 put/get/remove 方法在组件之间传递数据,而无需管理它们的生命周期。
public static class State {
static final int STEP_START = 1;
static final int STEP_LAYOUT = 1 << 1;
static final int STEP_ANIMATIONS = 1 << 2;
//记录要滚动到的位置
int mTargetPosition = RecyclerView.NO_POSITION;
//保存由资源 ID 标识的任意数据 key为id,value是data
private SparseArray<Object> mData;
/**
* adapter在先前布局中的Item数。
*/
int mPreviousLayoutItemCount = 0;
/**
* 上一次布局中删除的不可见Item的数量。
*/
int mDeletedInvisibleItemCountSincePreviousLayout = 0;
@IntDef(flag = true, value = {
STEP_START, STEP_LAYOUT, STEP_ANIMATIONS
})
@Retention(RetentionPolicy.SOURCE)
@interface LayoutState {}
//记录当前Laytout走到了第几步(RecyclerView Layout共三步)
@LayoutState
int mLayoutStep = STEP_START;
/**
* adapter中Item的数量
*/
int mItemCount = 0;
//标志RecyclerView的结构是否改变
boolean mStructureChanged = false;
/**
* 当前RecyclerView是否处于pre-layout状态
*/
boolean mInPreLayout = false;
boolean mTrackOldChangeHolders = false;
//RecyclerView是否正在进行measure
boolean mIsMeasuring = false;
//是否运行简单的动画
boolean mRunSimpleAnimations = false;
//是否运行预测动画
boolean mRunPredictiveAnimations = false;
/**
* 记录RecyclerView中持有焦点鹅位置和ItemId
*/
int mFocusedItemPosition;
long mFocusedItemId;
// 当一个子孩子有焦点时,记录它的id,看看我们是否可以直接请求焦点在那个上
int mFocusedSubChildId;
//记录当前剩余的滚动距离
int mRemainingScrollHorizontal;
int mRemainingScrollVertical;
}
ItemAnimator主要负责每个Item的动画展示效果,当Item被添加、删除、移动、数据变化的时候。当我们想要使用自定义动画的时候就可以继承该类实现子类,再通过setItemAnimator(RecyclerView.ItemAnimator)方法设置给RecyclerView。默认使用DefaultItemAnimator。
EdgeEffectFactory主要是让我们可以自定义当滑动到边界继续滑动时边界动画的效果。
public static class EdgeEffectFactory {
//创建一个EdgeEffect用于显示边界效果
protected @NonNull EdgeEffect createEdgeEffect(@NonNull RecyclerView view,
@EdgeDirection int direction) {
return new EdgeEffect(view.getContext());
}
}
ItemDecoration 可以让应用程序向adpater数据集中的特定项目视图添加特殊的绘图和布局偏移。 主要用在在Item突出显示、Item之间视觉分组边界绘制分隔线。
public abstract static class ItemDecoration {
/**
* 提供在 RecyclerView 的 Canvas 中绘制任何适当的装饰。
* 通过此方法绘制的任何内容都将在绘制Item视图之前绘制,
* 因此将出现在视图下方。
*
* @param c Canvas to draw into
* @param parent RecyclerView this ItemDecoration is drawing into
* @param state The current state of RecyclerView
*/
public void onDraw(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull State state) {
onDraw(c, parent);
}
/**
*这个将在Item绘制后,再绘制。会出现在Item绘制d鹅上方
*
* @param c Canvas to draw into
* @param parent RecyclerView this ItemDecoration is drawing into
* @param state The current state of RecyclerView.
*/
public void onDrawOver(@NonNull Canvas c, @NonNull RecyclerView parent,
@NonNull State state) {
onDrawOver(c, parent);
}
//我们经常使用该方法去设置Item之间的边界
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view,
@NonNull RecyclerView parent, @NonNull State state) {
getItemOffsets(outRect, ((LayoutParams) view.getLayoutParams()).getViewLayoutPosition(),
parent);
}
}
RecyclerView.LayoutParams继承自ViewGroup.MarginLayoutParams,添加了一些在ViewHolder中需要用到的参数和方法。这个LayoutParams将被使用在ViewHolder中的View身上。每个自定义LayoutManager可以再实现一个该LayoutParams的子类,用于存储每个LayoutManager需要的特定信息在ViewHolder身上。
public static class LayoutParams extends android.view.ViewGroup.MarginLayoutParams {
//绑定的ViewHolder
ViewHolder mViewHolder;
//ViewHolder的边界
final Rect mDecorInsets = new Rect();
//如果视图在与 RV 分离时被绑定,则标志设置为 true。
// 在这种情况下,我们需要在添加视图后手动调用 invalidate
// 以保证通过 View 层次结构填充失效
boolean mPendingInvalidate = false;
/**
* 如果视图应该更新其内容,则为 true
*/
public boolean viewNeedsUpdate() {
return mViewHolder.needsUpdate();
}
/**
* 如果ViewHolder已经Invalid则返回true
*/
public boolean isViewInvalid() {
return mViewHolder.isInvalid();
}
/*
* 如果这个ViewHolder对应的Item已经在Adapter中的数据集中被删除,返回true
*/
public boolean isItemRemoved() {
return mViewHolder.isRemoved();
}
/**
* 如果视图对应的项目在数据集中已更改,则为 true
*/
public boolean isItemChanged() {
return mViewHolder.isUpdated();
}
/**
* 在最新的布局中,这个View在Adapter中布局位置
*/
public int getViewLayoutPosition() {
return mViewHolder.getLayoutPosition();
}
/**
* 返回当前ViewHolder对应Adapter中的位置
*/
public int getViewAdapterPosition() {
return mViewHolder.getAdapterPosition();
}
}
典型的观察者模式。
Adapter持有AdapterDataObservable用于保存注册了的观察者对象AdapterDataObserver。当Adapter中的数据发生变化时,会通过AdapterDataObservable告知AdapterDataObserver。
static class AdapterDataObservable extends Observable<AdapterDataObserver> {
//是否存在订阅的观察者
public boolean hasObservers() {
return !mObservers.isEmpty();
}
//下面的方法都是通知已经订阅的观察者数据集发生变化
public void notifyChanged() {
for (int i = mObservers.size() - 1; i >= 0; i--) {
mObservers.get(i).onChanged();
}
}
public void notifyItemRangeChanged(int positionStart, int itemCount) {
notifyItemRangeChanged(positionStart, itemCount, null);
}
public void notifyItemRangeChanged(int positionStart, int itemCount,
@Nullable Object payload) {
for (int i = mObservers.size() - 1; i >= 0; i--) {
mObservers.get(i).onItemRangeChanged(positionStart, itemCount, payload);
}
}
public void notifyItemRangeInserted(int positionStart, int itemCount) {
for (int i = mObservers.size() - 1; i >= 0; i--) {
mObservers.get(i).onItemRangeInserted(positionStart, itemCount);
}
}
public void notifyItemRangeRemoved(int positionStart, int itemCount) {
for (int i = mObservers.size() - 1; i >= 0; i--) {
mObservers.get(i).onItemRangeRemoved(positionStart, itemCount);
}
}
public void notifyItemMoved(int fromPosition, int toPosition) {
for (int i = mObservers.size() - 1; i >= 0; i--) {
mObservers.get(i).onItemRangeMoved(fromPosition, toPosition, 1);
}
}
}
//想要订阅被观察者就继承该类并实现响应的通知方法,当被观察者数据发生变化时,相应的方法会被调用。
public abstract static class AdapterDataObserver {
public void onChanged() {
// Do nothing
}
public void onItemRangeChanged(int positionStart, int itemCount) {
// do nothing
}
public void onItemRangeChanged(int positionStart, int itemCount, @Nullable Object payload) {
onItemRangeChanged(positionStart, itemCount);
}
public void onItemRangeInserted(int positionStart, int itemCount) {
// do nothing
}
public void onItemRangeRemoved(int positionStart, int itemCount) {
// do nothing
}
public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
// do nothing
}
}
下一篇:RecyclerView的measure和layout流程(上)将于10.2日在微信公众号:"潇洒哥讲Android"准时发布,欢迎大家及时关注。