目录
RelativeLayout绘制流程解析
RecyclerView绘制流程解析
絮絮叨叨
从年初到现在,一直积攒了好多知识点没有学习和总结,待办越积越多,此时此刻立下flag,要在这个可爱的八月把所有的待办解决掉。
---- 鲁迅:不,我没说过
背景
在我刚开始学习Android的时候,ListView是列表实现的首选项,现在因为效率等问题慢慢地被RecyclerView替代了,当然,很多简单的业务还是用listview实现更方便。抛开具体现实,光谈理论也是耍流氓的。
为了后面更好的学习RecyclerView,很有必要好好学习了解ListView的绘制机制。
分析
在开始LIstView开始之前,先看下整体重要的数据结构。
AbsListView 是 ListView的父类,也是GridView的父类,当然ListView和GridView都是列表,有很多地方是相似的,因此提取了很多同样的流程到AbsListView中。
AbsListView有一个重要的内部类: RecycleBin
RecycleBin 是 ListView绘制机制的基石,顾名思义,是回收桶的意思。负责专门回收和提供、复用ItemView(ItemView就是ListView中每个滑动的子View)。
RecycleBin
看RecycleBin的注释有这么一段说明它的作用
/**
* The RecycleBin facilitates reuse of views across layouts. The RecycleBin has two levels of
* storage: ActiveViews and ScrapViews. ActiveViews are those views which were onscreen at the
* start of a layout. By construction, they are displaying current information. At the end of
* layout, all views in ActiveViews are demoted to ScrapViews. ScrapViews are old views that
* could potentially be used by the adapter to avoid allocating views unnecessarily.
**/
简单来说,RecycleBin负责回收复用view,其中有两个复用存储空间:
一个是ActiveView负责回收当前界面的活跃的View,他们保留着当前显示的信息。另外一个ScrapViews,ActiveView当被滑出屏幕会被回收到到ScrapViews,ScrapViews里面的View可以被复用,重新绑定,这样可以避免listView的Adpater重新生成新的View。
翻译一下,就是ActiveViews是负责保存当前屏幕活跃的View,而ScrapViews是保存滑出屏幕非活跃的View。
二者的区别有一个在于,ActiveViews取出来的View可以直接拿来绘制显示,不需要重新绑定,就是调用Adapter的onCreateView(其中,onCreateView具备了创建View和绑定View的逻辑,而RecyclerView将二者分离开了),而ScrapViews取出来的需要重新绑定。
RecyleBin的数据结构
private View[] mActiveViews = new View[0];
// 缓存当前界面显示的子View
private ArrayList<View>[] mScrapViews;
private ArrayList<View> mCurrentScrap;
// 存储已经废弃的View,mCurrentScrap在viewTypeCount为1下使用,mScrapViews数组对应viewTypeCount种view
private int mViewTypeCount;
// View种类数量,对应Adapter的getItemType()
private ArrayList<View> mSkippedScrap;
private SparseArray<View> mTransientStateViews;
private LongSparseArray<View> mTransientStateViewsById;
// mTransientStateViews/mTransientStateViewsById 缓存Transient瞬态的view,若hasStableId=true,则key为itemId
上面其中的mTransientStateViews缓存的是transient状态的view,transient表示瞬间临时状态,通过View的hasTransientState函数来判断,当View处理动画时候,会处于transient状态。
void fillActiveViews(int childCount, int firstActivePosition);
// 将ListView指定元素存储到mActiveViews
View getActiveView(int position)
// 从ActiveViews中取出,但只能取一次
void addScrapView(View scrap, int position)
// 添加ScrapView
View getScrapView(int position)
// 从ScrapViews中取出
void setViewTypeCount(int viewTypeCount)
// 设置ViewTypeCount,子View种类
void scrapActiveViews()
// 将ActiveViews移动到ScrapViews种
在布局之前,如果数据集没有变化,会将显示的子View存到mActivieViews,若数据集发生了变化,将显示的子View添加到mScrapViews,布局完成之后,将mActiveViews剩下的View放到mScrapViews中。
获取子View过程中,如果数据集没有变化,则从mActiveViews直接取出布局子View,若数据集发生了变化,则从mScrapViews中获取,如果匹配到了会重新绑定,如果没匹配到,则创建一个子View绑定。
RecyleBin的大部分的方法都是从上述中获取出View或者存储View的逻辑,在后面源码的过程中再好好具体学习分析。
Measure
之前有分析了ViewGroup/View的绘制流程,其中三个最重要的流程:
measure 测量、layout 布局、draw 绘制
按照这个流程继续来学习ListView,Measure的流程相对比较简单。
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// Sets up mListPadding
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int childWidth = 0;
int childHeight = 0;
int childState = 0;
mItemCount = mAdapter == null ? 0 : mAdapter.getCount();
if (mItemCount > 0 && (widthMode == MeasureSpec.UNSPECIFIED
|| heightMode == MeasureSpec.UNSPECIFIED)) {
// 存在子View,无法确定高度时候
final View child = obtainView(0, mIsScrap);
// Lay out child directly against the parent measure spec so that
// we can obtain exected minimum width and height.
measureScrapChild(child,