美文网首页android 自学
android列表View,ListView源码分析

android列表View,ListView源码分析

作者: Bean的世界 | 来源:发表于2017-06-06 10:45 被阅读650次

    ListView结构关系
    首先理清listview的层级关系,

    271423506402853.jpg

    了解一下 AdapterView

    public abstract class AdapterView<T extends Adapter> 
               extends ViewGroup {
                省略部分代码
        public void setOnItemClickListener(@Nullable OnItemClickListener listener)  
        public void setOnItemLongClickListener(OnItemLongClickListener listener)
        public void setOnItemSelectedListener(@Nullable OnItemSelectedListener listener)
        public abstract T getAdapter();
        public abstract void setAdapter(T adapter);
        public int getCount()
        @Override
        public void addView(View child) {
            throw new UnsupportedOperationException("addView(View) is not supported in AdapterView");
        }
    
    //在AdapterView的addView方法中会抛出异常,
    //也就是说AdapterView禁用了addView方法。
        @Override
        public void addView(View child, int index) {
            throw new UnsupportedOperationException("addView(View, int) is not supported in AdapterView");
        }
          省略部分代码
        class AdapterDataSetObserver extends DataSetObserver {
            private Parcelable mInstanceState = null;
             //当adapter数据发生改变的时候申请重绘
             //DataSetObserver 用到了观察者模式
            @Override
            public void onChanged() {
            省略部分代码
            }
            @Override
            public void onInvalidated() {
             省略部分代码
        }
    }
    
    

    首先需要说一下RecycleBin的基本原理,这个类也是实现复用的关键类。RecleBin是AbsListView内部类。AbsListView中有一个RecycleBin的对象mRecycler

    AbsListView.java

    //用于存储不用的view,以便在下次layout中使用来避免创建新的
    final RecycleBin mRecycler = new RecycleBin();
    

    RecycleBin使用两级view来进行回收:
    ActiveView
    :激活view,当前显示在屏幕上的激活view。
    ScrapView
    :废弃view,被删除的ActiveView会被自动加入ScrapView。

    RecycleBin变量说明
    AbsListView.java

    private RecyclerListener mRecyclerListener;
    
    //存储在mActiveViews中的第一个view的位置
    private int mFirstActivePosition;
    
    //布局开始时屏幕显示的view,这个数组会在布局开始时填充,布局结束后所有view被移至mScrapViews。
    private View[] mActiveViews = new View[0];
    //可以被适配器用作convert view的无序view数组。 这个ArrayList就是adapter中getView方法中的
    //参数convertView的来源。注意:这里是一个数组,因为如果adapter中数据有多种类型,
    //那么就会有多个ScrapViews。
    private ArrayList<View>[] mScrapViews;
    
    //view类型总数,列表中可能有多种数据类型,比如内容数据和分割符
    private int mViewTypeCount;
    
    //跟mScrapViews的却别是,mScrapViews是个队列数组,ArrayList<View>[]类型,
    //数组长度为mViewTypeCount,而默认ViewTypeCount = 1
    //的情况下mCurrentScrap=mScrapViews[0]。
    private ArrayList<View> mCurrentScrap;
    
    

    RecycleBin 方法说明

    //为每个子类(子对象)调用forceLayout()。将mScrapView中回收回来的View设置一样标志,
    //在下次被复用到ListView中时,告诉viewroot重新layout该view。
    //forceLayout()方法只是设置标志,并不会通知其parent来重新layout
    public void markChildrenDirty()
    
    //判断给定的view的viewType指明是否可以回收回。
    //指定忽略的( ITEM_VIEW_TYPE_IGNORE = -1),
    //或者是 HeaderView / (FootViewITEM_VIEW_TYPE_HEADER_OR_FOOTER = -2)是不被回收的。
    //如有特殊需要可以将自己定义的viewType设置为-1,否则,将会浪费内存,导致OOM。
     public boolean shouldRecycleViewType(int viewType) {
                return viewType >= 0;
            }
    
    //Clears the scrap heap.清空废弃view堆,并将这些View从窗口中Detach。
    void clear()
    
    //获取mActiveViews中指定位置的view,如果找到会将该view从mActiveViews中移除
    View getActiveView(int position)
    
    //用AbsListView.的所有子view填充ActiveViews,
    //其中childCount是mActiveViews应该保存的最少的view数,
    //firstActivePosition是mActiveViews中存储的首个view的位置。
    void fillActiveViews(int childCount, int firstActivePosition)
    
    //清掉当前处于transient(转换)状态的所有保存的view
    void clearTransientStateViews()
    
    //将view放入scrapview list中
    //
    void addScrapView(View scrap, int position) 
    
    //
    View getScrapView(int position)
    
    //
    private View retrieveFromScrap(ArrayList<View> scrapViews, int position)
    
    //
    void removeSkippedScrap()
    //
    private void pruneScrapViews()
    //
    void reclaimScrapViews(List<View> views)
    
    //
    void scrapActiveViews()
    //
    void setCacheColorHint(int color)
    

    ActivityView就是在UI屏幕上可见的视图(onScreenView),也是与用户进行交互的View。这些View会通过RecycleBin直接存储到mActivityView数组当中。
    当滑动ListView的时候,有些View被滑动到屏幕之外(offScreen) View,这些View就成为了ScrapView,就是废弃的View,已经无法与用户进行交互了,此时在UI视图改变的时候就没有绘制视图的必要。这些Veiw被RecycleBin存在mScrapView数组当中,但是没有被销毁掉,目的是为了二次复用,也就是间接复用。

    选区_131.png

    当新的View需要显示的时候,先判断mActiveViews中是否存在,如果存在那么就可以从mActivityView数组当中直接取出复用,直接复用,否则从mScrapView数组当中进行判断,存在,二次复用当前的视图,不存在,就需要inflate View了。

    选区_132.png

    ListView比如第一次绘制 会执行 onMeasure->onLayout->onDraw
    比如数据改变,申请requestLayout

    调用的函数的堆栈图如下。(这里只是选择的特殊的一条函数执行路径)

    选区_133.png

    AbsListView.java

        /**
         * 子类不必覆写这个方法,覆写layoutChildren代替
         *  
         */
        @Override
        protected void onLayout(boolean changed, int l, int t, int r, int b) {
            super.onLayout(changed, l, t, r, b);
    
            mInLayout = true;
    
            final int childCount = getChildCount();
            if (changed) {
                for (int i = 0; i < childCount; i++) {
                    getChildAt(i).forceLayout();
                }
                mRecycler.markChildrenDirty();
            }
            //对item进行布局的流程
            layoutChildren();
            mInLayout = false;
    
            省略部分代码
        }
    

    fillFromTop 如下

    ListView.java

    
        private View fillDown(int pos, int nextTop) {
            View selectedView = null;
        /**
         * end用来判断Item是否已经将ListView填充满
         */
            int end = (mBottom - mTop);
            if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
                end -= mListPadding.bottom;
            }
    
             /**
              * nextTop < end确保了我们只要将新增的子View能够覆盖ListView的界面就可以了
              *pos < mItemCount确保了我们新增的子View在Adapter中都有对应的数据源item
              */
            while (nextTop < end && pos < mItemCount) {
                // is this the selected item?
                boolean selected = pos == mSelectedPosition;
                View child = makeAndAddView(pos, nextTop, true, mListPadding.left, selected);
    
                nextTop = child.getBottom() + mDividerHeight;
                if (selected) {
                    selectedView = child;
                }
                pos++;
            }
    
            setVisibleRangeHint(mFirstPosition, mFirstPosition + getChildCount() - 1);
            return selectedView;
        }
    

    上面函数的理解

    选区_134.png

    makeAndAddView如下

    ListView.java

        private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,
                boolean selected) {
    
           
            if (!mDataChanged) {
                // Try to use an existing view for this position.
                final View activeView = mRecycler.getActiveView(position);
                if (activeView != null) {
                     //如果存在activeView,使用activeView
                    // Found it. We're reusing an existing child, so it just needs
                    // to be positioned like a scrap view.
                    setupChild(activeView, position, y, flow, childrenLeft, selected, true);
                    return activeView;
                }
            }
    
           
            // Make a new view for this position, or convert an unused view if
            // possible.
            final View child = obtainView(position, mIsScrap);
    
            // This needs to be positioned and measured.
            setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);
    
            return child;
        }
    
    

    obtainView的如下

    AbListView.java

        View obtainView(int position, boolean[] outMetadata) {
            省略部分代码
            final View scrapView = mRecycler.getScrapView(position);
            // mAdapter.getView登场
            final View child = mAdapter.getView(position, scrapView, this);
            if (scrapView != null) {
                if (child != scrapView) {
                    // Failed to re-bind the data, return scrap to the heap.
                    mRecycler.addScrapView(scrapView, position);
                } else if (child.isTemporarilyDetached()) {
                    outMetadata[0] = true;
    
                    // Finish the temporary detach started in addScrapView().
                    child.dispatchFinishTemporaryDetach();
                }
            }
            省略部分代码
            return child;
        }
    

    相关文章

      网友评论

        本文标题:android列表View,ListView源码分析

        本文链接:https://www.haomeiwen.com/subject/wmjgfxtx.html