Android 源码 listview 重用机制 浅析
2016-03-24 18:00
537 查看
使用流程则是 初始化view,setAdapter();
那就走一下主线。
|
|
从代码从可以看出 首先执行的是AbsListView 中的 initAbsListView();主要对View的Clickable、Focusable、Cache等属性进行的初始设置。同时 获取了 TouchSlop(最小相应距离),Velocity(滑动速度)等变量。
上代码:
接着往下看:
之后ListView在自己的方法中实现了 setDivider(分割线)、setOverscrollHeader/setOverscrollFooter等样式上的操作。
接下来的 measure layout draw 暂时先不看 里面肯定是加入了 adapter的各种判断 那就先来看 adapter
Android 设计本身就遵循 MVC的模式 View控件就是为了 展示数据 /交互用的 ;同样ListView也只承担交互和展示工作而已,至于数据从哪里来,数据类型是啥(数组 or list 或者 Couser 都可以)都不应该关心;
于是就有了 adapter;它在ListView和数据源之间起到了一个桥梁的作用,ListView并不会直接跟数据源打交道,而是通过Adapter 这个接口去访问数据源,至此ListView不用再去担心任何适配方面的问题。而Adapter又是一个接口(interface),它可以去实现各种各样的子类,每个子类都能通过自己的逻辑来去完成特定的功能,以及与特定数据源的适配操作
RecycleBin类
主要的回收类RecycleBin位于AbsListView中。
AbsListView 中有一个 RecycleBin 对象 mRecycler;
RecycleBin 中使用 两级 View来进行回收 : ActiveView[ ](当前屏幕上的激活View)ScrapView[ ](被移除掉的View)
mRecyclerListener:RecyclerListener接口只有一个函数onMovedToScrapHeap ,指明某个view被回收到了scrap heap. 该view不再被显示。该函数是处理回收时view中的资源释放。
mFirstActivePosition:储存在mActiveViews中的第一个View的位置。
mActiveViews:布局开始时屏幕显示的view,这个数组会在布局开始时填充,布局结束后所有view被移至mScrapViews。
-ArrayList[ ] mScrapViews: 是adapter中getView方法中的参数convertView的来源(系统注释:Unsorted views that can be used by the adapter as a convert view. 未分类的views 能被复用的view)。
mViewTypeCount:类型
mCurrentScrap:就是指向当前mScrapViews中的一组,默认ViewTypeCount = 1的情况下,mCurrentScrap = mScrapViews[0];
对于mActiveViews通常的操作为,在每次layout开始,AbsListView会将位于屏幕上的view全部填充到RecycleBin的mActiveViews中去。layout过程中,将下一轮即将显示在屏幕上得view从RecycleBin中取出来,最后如果mActiveViews中还有元素,就在layout结束时将它们统统转移到mScrapView中去。这个流程可以从ListView中的layoutChildren看出,每次layout时,onlayout最终会调用这个函数
从layout的过程就可以看出来,scrapview实际上是一个存放备用view的回收池,每次layout完,有多余的view会存储到池子里,以后可能会用到。那这是layout时候做的事情,如果是scroll的情况呢,情况其实类似。我们来看看scroll的流程图
scroll事件从onTouchEvent函数发起,大家肯定知道的,中间经过一些判断,最终带着deltaY和incrementalDetalY到达trackMotionScroll函数,我们的分析从这个函数开始.
从上面的注释里,不难知晓在滚动过程中,RecycleBin所起的作用,当然函数在走到fillGap前,只是完成了一部分滚出view的回收,接下来,是利用这些view进行重用还是生成新的view就要看fillGap函数所做的操作了。
、
fillGap的实现在ListView中,它只是做了个分派操作,内部分向上滚动和向下滚动分别调用fillUp和fillDown,只需要分析其中一个就好
fillDown的两个参数分别为即将开始填充的view的item 位置 和 top坐标位置
makeAndAddView所做的操作就是获得一个view并将其添加到viewHierarchy上。
makeAndAddView在数据没有改变的情况下,会首先尝试从mActiveViews中去获取。需要注意的是,在 scroll 操作中,我们一开始并没有把屏幕上的view 充填到mActiveViews中,因此scroll逻辑走到这里的时候,从mActiveViews 是拿不到view的; 为啥还有这段代码 是因为这段代码在 layout中也会调用,从 layoutChildren 函数的重新 填充子view那一步中,会调用一系列 以fill开头的函数,最后会走到这里。 接下来看 View obtainView。
position: 要显示的位置
isScrap: 是个boolean数组, 如果view从scrap heap获取,isScrap [0]为true,否则为false。
到这 listview 复用机制机制 大体上也就说的差不多了; 今后 写类似view回收池时可以参考RecycleBin的写法
那就走一下主线。
初始化view
先屡清除继承关系: listview –>AbsListView –> AdapterView< T extends Adapter> —> ViewGroup|
|
从代码从可以看出 首先执行的是AbsListView 中的 initAbsListView();主要对View的Clickable、Focusable、Cache等属性进行的初始设置。同时 获取了 TouchSlop(最小相应距离),Velocity(滑动速度)等变量。
上代码:
private void initAbsListView() { // Setting focusable in touch mode will set the focusable property to true setClickable(true); setFocusableInTouchMode(true); setWillNotDraw(false); setAlwaysDrawnWithCacheEnabled(false); setScrollingCacheEnabled(true); final ViewConfiguration configuration = ViewConfiguration.get(mContext); mTouchSlop = configuration.getScaledTouchSlop(); mMinimumVelocity = configuration.getScaledMinimumFlingVelocity(); mMaximumVelocity = configuration.getScaledMaximumFlingVelocity(); mOverscrollDistance = configuration.getScaledOverscrollDistance(); mOverflingDistance = configuration.getScaledOverflingDistance(); mDensityScale = getContext().getResources().getDisplayMetrics().density; }
接着往下看:
mOwnerThread = Thread.currentThread(); //获取主线程 后期adapter刷新UI setVerticalScrollBarEnabled(true); //设置滚动条显示状态 TypedArray a = context.obtainStyledAttributes(R.styleable.View); initializeScrollbarsInternal(a); //拿到View的Style 初始化 Scrollbars . . . . . . //abslistview 风格的设置 a.recycle();
之后ListView在自己的方法中实现了 setDivider(分割线)、setOverscrollHeader/setOverscrollFooter等样式上的操作。
接下来的 measure layout draw 暂时先不看 里面肯定是加入了 adapter的各种判断 那就先来看 adapter
Adapter 浅析
adapterAndroid 设计本身就遵循 MVC的模式 View控件就是为了 展示数据 /交互用的 ;同样ListView也只承担交互和展示工作而已,至于数据从哪里来,数据类型是啥(数组 or list 或者 Couser 都可以)都不应该关心;
于是就有了 adapter;它在ListView和数据源之间起到了一个桥梁的作用,ListView并不会直接跟数据源打交道,而是通过Adapter 这个接口去访问数据源,至此ListView不用再去担心任何适配方面的问题。而Adapter又是一个接口(interface),它可以去实现各种各样的子类,每个子类都能通过自己的逻辑来去完成特定的功能,以及与特定数据源的适配操作
RecycleBin类
主要的回收类RecycleBin位于AbsListView中。
AbsListView 中有一个 RecycleBin 对象 mRecycler;
RecycleBin 中使用 两级 View来进行回收 : ActiveView[ ](当前屏幕上的激活View)ScrapView[ ](被移除掉的View)
mRecyclerListener:RecyclerListener接口只有一个函数onMovedToScrapHeap ,指明某个view被回收到了scrap heap. 该view不再被显示。该函数是处理回收时view中的资源释放。
mFirstActivePosition:储存在mActiveViews中的第一个View的位置。
mActiveViews:布局开始时屏幕显示的view,这个数组会在布局开始时填充,布局结束后所有view被移至mScrapViews。
-ArrayList[ ] mScrapViews: 是adapter中getView方法中的参数convertView的来源(系统注释:Unsorted views that can be used by the adapter as a convert view. 未分类的views 能被复用的view)。
mViewTypeCount:类型
mCurrentScrap:就是指向当前mScrapViews中的一组,默认ViewTypeCount = 1的情况下,mCurrentScrap = mScrapViews[0];
对于mActiveViews通常的操作为,在每次layout开始,AbsListView会将位于屏幕上的view全部填充到RecycleBin的mActiveViews中去。layout过程中,将下一轮即将显示在屏幕上得view从RecycleBin中取出来,最后如果mActiveViews中还有元素,就在layout结束时将它们统统转移到mScrapView中去。这个流程可以从ListView中的layoutChildren看出,每次layout时,onlayout最终会调用这个函数
@Override protected void layoutChildren() { //....忽略一大坨代码 // layout开始前将所有已经存在的子view放入recycleBin中 // 如果dataChanged为true,就放入mActiveViews中,否则放入mScrapViews中去 final int firstPosition = mFirstPosition; final RecycleBin recycleBin = mRecycler; if (dataChanged) { for (int i = 0; i < childCount; i++) { recycleBin.addScrapView(getChildAt(i), firstPosition+i); } } else { recycleBin.fillActiveViews(childCount, firstPosition); } // Clear out old views detachAllViewsFromParent(); recycleBin.removeSkippedScrap(); //重新填充子view... // 将所有没有被重用到的view从mActiveViews转移到mScrapViews中去 recycleBin.scrapActiveViews(); }
从layout的过程就可以看出来,scrapview实际上是一个存放备用view的回收池,每次layout完,有多余的view会存储到池子里,以后可能会用到。那这是layout时候做的事情,如果是scroll的情况呢,情况其实类似。我们来看看scroll的流程图
scroll事件从onTouchEvent函数发起,大家肯定知道的,中间经过一些判断,最终带着deltaY和incrementalDetalY到达trackMotionScroll函数,我们的分析从这个函数开始.
boolean trackMotionScroll(int deltaY, int incrementalDeltaY) { //重新计算deltay和 incrementalDeltaY,判断时候还能继续向下滚动或者向上滚动 //.....一波代码 final boolean down = incrementalDeltaY < 0;//判断是否向下滚动 final boolean inTouchMode = isInTouchMode(); if (inTouchMode) { hideSelector(); } final int headerViewsCount = getHeaderViewsCount(); final int footerViewsStart = mItemCount - getFooterViewsCount(); int start = 0; int count = 0; if (down) { int top = -incrementalDeltaY; if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) { top += listPadding.top; } //如果向下滚动,则有些view会从上方滚出 for (int i = 0; i < childCount; i++) { final View child = getChildAt(i); //判断子view是否已经滚出 if (child.getBottom() >= top) {//没有滚出 break; } else {//已经滚出 count++;//增加滚出数量 int position = firstPosition + i; if (position >= headerViewsCount && position < footerViewsStart) { // The view will be rebound to new data, clear any // system-managed transient state. child.clearAccessibilityFocus(); mRecycler.addScrapView(child, position);//加入scrap集合备用 } } } } else { //向上滚动原理类似 int bottom = getHeight() - incrementalDeltaY; if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) { bottom -= listPadding.bottom; } for (int i = childCount - 1; i >= 0; i--) { final View child = getChildAt(i); if (child.getTop() <= bottom) { break; } else { start = i; count++; int position = firstPosition + i; if (position >= headerViewsCount && position < footerViewsStart) { // The view will be rebound to new data, clear any // system-managed transient state. child.clearAccessibilityFocus(); mRecycler.addScrapView(child, position); } } } } mMotionViewNewTop = mMotionViewOriginalTop + deltaY; mBlockLayoutRequests = true; if (count > 0) { detachViewsFromParent(start, count); //将滚出的view进行detach mRecycler.removeSkippedScrap(); } // …… final int absIncrementalDeltaY = Math.abs(incrementalDeltaY); if (spaceAbove < absIncrementalDeltaY || spaceBelow < absIncrementalDeltaY) { fillGap(down); //填充滚动引起的view间隙 } // …… }
从上面的注释里,不难知晓在滚动过程中,RecycleBin所起的作用,当然函数在走到fillGap前,只是完成了一部分滚出view的回收,接下来,是利用这些view进行重用还是生成新的view就要看fillGap函数所做的操作了。
、
void fillGap(boolean down) { final int count = getChildCount(); if (down) { //如果是向下滚动,则调用fillDown int paddingTop = 0; if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) { paddingTop = getListPaddingTop(); } final int startOffset = count > 0 ? getChildAt(count - 1).getBottom() + mDividerHeight : paddingTop; fillDown(mFirstPosition + count, startOffset); correctTooHigh(getChildCount()); } else { //如果是向上滚动,则调用fillUp int paddingBottom = 0; if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) { paddingBottom = getListPaddingBottom(); } final int startOffset = count > 0 ? getChildAt(0).getTop() - mDividerHeight : getHeight() - paddingBottom; fillUp(mFirstPosition - 1, startOffset); correctTooLow(getChildCount()); } }
fillGap的实现在ListView中,它只是做了个分派操作,内部分向上滚动和向下滚动分别调用fillUp和fillDown,只需要分析其中一个就好
private View fillDown(int pos, int nextTop) { View selectedView = null; int end = (mBottom - mTop); if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) { end -= mListPadding.bottom; } //开启循环,持续填充view直到已经填满 while (nextTop < end && pos < mItemCount) { // is this the selected item? boolean selected = pos == mSelectedPosition; View child = makeAndAddView(pos, nextTop, true, mListPadding.left, selected); //更新下次填充view的top位置 nextTop = child.getBottom() + mDividerHeight; if (selected) { selectedView = child; } pos++;//更新被填充位置的position } setVisibleRangeHint(mFirstPosition, mFirstPosition + getChildCount() - 1); return selectedView; }
fillDown的两个参数分别为即将开始填充的view的item 位置 和 top坐标位置
makeAndAddView所做的操作就是获得一个view并将其添加到viewHierarchy上。
private View makeAndAddView(int position, int y, boolean flow, int childrenLeft, boolean selected) { View child; //判断数据时候已经发生变化,如果没有,就尝试从ActiveView中去获取view, if (!mDataChanged) { // Try to use an existing view for this position child = mRecycler.getActiveView(position); if (child != null) { // Found it -- we're using an existing child // This just needs to be positioned setupChild(child, position, y, flow, childrenLeft, selected, true); return child; } } // 尝试从scrap中获取可重用的view,如果没有,就创建新的view child = obtainView(position, mIsScrap); // 设置子view的位置,如果有必要,会去重新measure子view,并添加到父view上 setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]); return child; }
makeAndAddView在数据没有改变的情况下,会首先尝试从mActiveViews中去获取。需要注意的是,在 scroll 操作中,我们一开始并没有把屏幕上的view 充填到mActiveViews中,因此scroll逻辑走到这里的时候,从mActiveViews 是拿不到view的; 为啥还有这段代码 是因为这段代码在 layout中也会调用,从 layoutChildren 函数的重新 填充子view那一步中,会调用一系列 以fill开头的函数,最后会走到这里。 接下来看 View obtainView。
obtainView
当这个方法被调用时,说明Recycle bin中的view已经不可用了,那么,现在唯一的方法就是,convert一个老的view,或者构造一个新的view。其中position: 要显示的位置
isScrap: 是个boolean数组, 如果view从scrap heap获取,isScrap [0]为true,否则为false。
View obtainView(int position, boolean[] isScrap) { Trace.traceBegin(Trace.TRACE_TAG_VIEW, "obtainView"); isScrap[0] = false; // 检查是否有一个对应的处于 transient state 的view. 如果有尝试重新绑定Data final View transientView = mRecycler.getTransientStateView(position); if (transientView != null) { //此时说明可以从回收站中重新使用scrapView。 final LayoutParams params = (LayoutParams) transientView.getLayoutParams(); // 如果view type没有变化,尝试重新绑定数据 if (params.viewType == mAdapter.getItemViewType(position)) { final View updatedView = mAdapter.getView(position, transientView, this); // 如果两者不相等,表示重绑定数据失败,生成了新的view, //但是我们依然使用transientView,将updatedView入回收池 if (updatedView != transientView) { setItemViewLayoutParams(updatedView, position); mRecycler.addScrapView(updatedView, position); } } // Scrap view implies temporary detachment. isScrap[0] = true; // Finish the temporary detach started in addScrapView(). transientView.dispatchFinishTemporaryDetach(); return transientView; } //找不到transientview的情况下,就从回收池中去取, final View scrapView = mRecycler.getScrapView(position); //重新绑定数据, final View child = mAdapter.getView(position, scrapView, this); if (scrapView != null) { //如果重绑定失败,将scrapView重新入回收池,采用新生成的view if (child != scrapView) { // Failed to re-bind the data, return scrap to the heap. mRecycler.addScrapView(scrapView, position); } else { isScrap[0] = true; child.dispatchFinishTemporaryDetach(); } } //设置子view的一些属性 if (mCacheColorHint != 0) { child.setDrawingCacheBackgroundColor(mCacheColorHint); } if (child.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) { child.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); } setItemViewLayoutParams(child, position); if (AccessibilityManager.getInstance(mContext).isEnabled()) { if (mAccessibilityDelegate == null) { mAccessibilityDelegate = new< 9f98 /span> ListItemAccessibilityDelegate(); } if (child.getAccessibilityDelegate() == null) { child.setAccessibilityDelegate(mAccessibilityDelegate); } } Trace.traceEnd(Trace.TRACE_TAG_VIEW); return child; }
到这 listview 复用机制机制 大体上也就说的差不多了; 今后 写类似view回收池时可以参考RecycleBin的写法
相关文章推荐
- 使用C++实现JNI接口需要注意的事项
- Android IPC进程间通讯机制
- Android Manifest 用法
- [转载]Activity中ConfigChanges属性的用法
- Android之获取手机上的图片和视频缩略图thumbnails
- Android之使用Http协议实现文件上传功能
- Android学习笔记(二九):嵌入浏览器
- android string.xml文件中的整型和string型代替
- i-jetty环境搭配与编译
- android之定时器AlarmManager
- android wifi 无线调试
- Android Native 绘图方法
- Android java 与 javascript互访(相互调用)的方法例子
- android 代码实现控件之间的间距
- android FragmentPagerAdapter的“标准”配置
- Android"解决"onTouch和onClick的冲突问题
- android:installLocation简析
- android searchView的关闭事件
- SourceProvider.getJniDirectories