Android 源码 listview 重用机制 浅析

使用流程则是 初始化view,setAdapter();
那就走一下主线。

初始化view

先屡清除继承关系: listview –>AbsListView –> AdapterView< T extends Adapter> —> ViewGroup

Android 源码 listview 重用机制 浅析_第1张图片

|
|

从代码从可以看出 首先执行的是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),它可以去实现各种各样的子类,每个子类都能通过自己的逻辑来去完成特定的功能,以及与特定数据源的适配操作

Android 源码 listview 重用机制 浅析_第2张图片

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的流程图

Android 源码 listview 重用机制 浅析_第3张图片

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 ListItemAccessibilityDelegate();
       }
       if (child.getAccessibilityDelegate() == null) {
           child.setAccessibilityDelegate(mAccessibilityDelegate);
       }
   }

   Trace.traceEnd(Trace.TRACE_TAG_VIEW);

   return child;
}

到这 listview 复用机制机制 大体上也就说的差不多了; 今后 写类似view回收池时可以参考RecycleBin的写法

你可能感兴趣的:(android,ListView,RecycleBin)