您的位置:首页 > 移动开发 > Android开发

Android 源码 listview 重用机制 浅析

2016-03-24 18:00 537 查看
使用流程则是 初始化view,setAdapter();

那就走一下主线。

初始化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 浅析

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最终会调用这个函数

@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的写法
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息