【Android】View绘制深度、高质量学习

本文详细介绍了Android中View的绘制流程,包括measure、layout及draw阶段,并深入解析ListView的RecycleBin机制及其优化技巧。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1 View绘制:decorview、window、surface等概念

链接:https://www.jianshu.com/p/c77082f1b30a
在这里插入图片描述
Activity
接受到焦点的时候,会请求绘制我们的UI布局(是由Android的FreamWork层来处理的)绘制从根节点开始,对我们的布局依次进行测量和绘制。

PhoneWindow
其实就是我们Window窗口的一个实现类(这个概念是在我们的Android的FreamWork当中一个Window的抽象类,这个抽象类是对我们Android系统当中窗口的一种抽象),窗口就是屏幕上位于绘制各种的UI布局元素,以及响应我们用户输入事件的矩形的区域。
在Android系统为窗口提供了一个功能,窗口独占一个surface实例的写实区域,可以把surface看作一个写实画布,用户可以通过cants可以在上面作画,最后我们可以通过servicebuilder将多块surface按照特定的顺序进行混合,然后会输出到我们的StringBuffer当中,这样用户界面就可以显示了

DecorView
是我们应用窗口的一个根容器(本质上是一个FreamLayout)。有唯一的一个子View(垂直的LinearLayout)。它包含两个元素,是用来储存我们内容的。
(TitleView—>ActionBarContainer—>ActionBar
ContentView—>FrameLayout—>RelativeLayout)

View绘制的整个流程的(由ViewRoot的实现类ViewRootimpl负责的)每一个应用程序窗口DecorView(都有一个相关联的ViewRoot对象)而这个关联的关系是通过我们的WindowsManager这个管理类来维护的

当我们建立好了DecorView和ViewRoot的关联后,会调用我们的requestLayout()这个方法用来完成我们应用程序界面布局的首次布局

ViewRootimpl

   @Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();
            mLayoutRequested = true;
            scheduleTraversals();
        }
    }

这个方法最终会调用scheduleTraversals()这个方法

scheduleTraversals

   void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }

这个方法通过主线程向主线程发送消息,然后不断的依次遍历每一次的绘制过程(因为绘制不是一次就可以完成的)。

从根结点开始,绘制View树。

  1. measure:判断是否需要重新计算View的大小
  2. layout:判断是否需要重新计算View的位置
  3. draw:判断是否需要重新绘制View

具体步骤:

  1. 从上到下,有次序地测量View;
  2. 测量之后,根据测量的大小和规格,由layout去放置View;
  3. 最后,如果需要重新绘制,交给draw方法进行View绘制。

2 View绘制:测量过程&measureSpec

链接:https://www.jianshu.com/p/b35bc86633b5

目的:计算出各个控件的大小。

进入ViewRootImpl—> measureHierarchy()

measureHierarchy()中的部分关键代码:

if (!goodMeasure) {
    childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
    childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
    if (mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight()) {
        windowSizeMayChange = true;
    }
}

measureHierarchy() — > getRootMeasureSpec()

调用getRootMeasureSpec这个方法来获取它的宽高的根的MeasureSpec。
这是个限制,所有的宽高都不能超过根的宽高。

private static int getRootMeasureSpec(int windowSize, int rootDimension) {
        int measureSpec;
        switch (rootDimension) {

        case ViewGroup.LayoutParams.MATCH_PARENT:
            // Window can't resize. Force root view to be windowSize.
            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
            break;
        case ViewGroup.LayoutParams.WRAP_CONTENT:
            // Window can resize. Set max size for root view.
            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
            break;
        default:
            // Window wants to be an exact size. Force root view to be that size.
            measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
            break;
        }
        return measureSpec;
    }
 public static class MeasureSpec {
 private static final int MODE_SHIFT = 30;
 private static final int MODE_MASK  = 0x3 << MODE_SHIFT;

 /** @hide */
 @IntDef({UNSPECIFIED, EXACTLY, AT_MOST})
 @Retention(RetentionPolicy.SOURCE)
 public @interface MeasureSpecMode {}

 /**
  * Measure specification mode: The parent has not imposed any constraint
  * on the child. It can be whatever size it wants.
  */
 public static final int UNSPECIFIED = 0 << MODE_SHIFT;

MeasureSpec是定义在一个View类当中的一个静态内部类,它其实就是代表了一个限定值,同时它是一个32位整数,由mode和size两部分组成,高二位代表mode,低三十位代表size。

mode有三种类型的可选值:UNSPECIFIED, EXACTLY, AT_MOST

  1. UNSPECIFIED:表示我们的父View不会对子View有任何的约束.这样子View就可以达到我们想要的最大尺寸,这个最大尺寸受屏幕大小限制
  2. EXACTLY:父View为子View指定了一个尺寸,无论子View想要多大都必须在这个尺寸最大值的边界内,如具体dp值,或match parent
  3. AT_MOST:为子View指定了一个最大尺寸,那么就要确保他所有的子子View都在最大范围内,如wrap content,父View无法得知子View的大小,只有子View自己知道自己多大

measureHierarchy()—>performMeasure()

通过performMeasure() 进行下一步的测量计算。

    private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {

mView 就是 DecroView
        if (mView == null) {
            return;
        }
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
        try {

调用DecroView的measure方法来进行具体的测量过程
            mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
    }

performMeasure() — > measuer()

这个measure方法,会从上到下,依次测量View树中每一个控件的大小,然后进行计算和整合。

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
        boolean optical = isLayoutModeOptical(this);
        if (optical != isLayoutModeOptical(mParent)) {
            Insets insets = getOpticalInsets();
            int oWidth  = insets.left + insets.right;
            int oHeight = insets.top  + insets.bottom;
            widthMeasureSpec  = MeasureSpec.adjust(widthMeasureSpec,  optical ? -oWidth  : oWidth);
            heightMeasureSpec = MeasureSpec.adjust(heightMeasureSpec, optical ? -oHeight : oHeight);
        }

        // Suppress sign extension for the low bytes
        long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL;
        if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2);

        final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;

        // Optimize layout by avoiding an extra EXACTLY pass when the view is
        // already measured as the correct size. In API 23 and below, this
        // extra pass is required to make LinearLayout re-distribute weight.
        final boolean specChanged = widthMeasureSpec != mOldWidthMeasureSpec
                || heightMeasureSpec != mOldHeightMeasureSpec;
        final boolean isSpecExactly = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY
                && MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY;
        final boolean matchesSpecSize = getMeasuredWidth() == MeasureSpec.getSize(widthMeasureSpec)
                && getMeasuredHeight() == MeasureSpec.getSize(heightMeasureSpec);
        final boolean needsLayout = specChanged
                && (sAlwaysRemeasureExactly || !isSpecExactly || !matchesSpecSize);

        if (forceLayout || needsLayout) {
            // first clears the measured dimension flag
            mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;

            resolveRtlPropertiesIfNeeded();

            int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
            if (cacheIndex < 0 || sIgnoreMeasureCache) {
                // measure ourselves, this should set the measured dimension flag back

measure()最终会调用onMeasure()方法
                onMeasure(widthMeasureSpec, heightMeasureSpec);
                mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
            } else {
                long value = mMeasureCache.valueAt(cacheIndex);
                // Casting a long to int drops the high 32 bits, no mask needed
                setMeasuredDimensionRaw((int) (value >> 32), (int) value);
                mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
            }

            // flag not set, setMeasuredDimension() was not invoked, we raise
            // an exception to warn the developer
            if ((mPrivateFlags & PFLAG_MEASURED_DIMENSION_SET) != PFLAG_MEASURED_DIMENSION_SET) {
                throw new IllegalStateException("View with id " + getId() + ": "
                        + getClass().getName() + "#onMeasure() did not set the"
                        + " measured dimension by calling"
                        + " setMeasuredDimension()");
            }

            mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
        }

        mOldWidthMeasureSpec = widthMeasureSpec;
        mOldHeightMeasureSpec = heightMeasureSpec;

        mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |
                (long) mMeasuredHeight & 0xffffffffL); // suppress sign extension
    }

measuer() —> onMeasure(int , int )

measure方法最终会调用一个onMeasure(int , int )方法,measure方法是final的,不能被重写,但是onMeasure方法可以被重写。

根据这两个限定好的值去计算View的宽和高,要根据不同的模式进行不同的计算
  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

内部会调用setMeasedDimension(),
这个方法是测量阶段最终的一个方法,它会得到一个具体的尺寸.
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
        getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }

onMeasure()这个方法是可以被覆写的,所以说当我们在自定义View的时候直接覆写这个方法就好了。

3 view绘制:layout布局阶段&FrameLayout

链接:https://www.jianshu.com/p/27f5e3b0f340

Layout阶段,主要根据得到View的测量的宽高,来确定我们View最终摆放的位置。

View — > Layout(int l,int t,int r,int b)

public void layout(int l, int t, int r, int b) {
        if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
            onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
            mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
        }

        int oldL = mLeft;
        int oldT = mTop;
        int oldB = mBottom;
        int oldR = mRight;


setOpticalFrame(l,t,r,b)setFrame(l,t,r,b):
判断位置是否发生改变,如果发生改变就在这两个方法当中选择其中一个进行重新布局
        boolean changed = isLayoutModeOptical(mParent) ?
                setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);

        if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
            onLayout(changed, l, t, r, b);

            if (shouldDrawRoundScrollbar()) {
                if(mRoundScrollbarRenderer == null) {
                    mRoundScrollbarRenderer = new RoundScrollbarRenderer(this);
                }
            } else {
                mRoundScrollbarRenderer = null;
            }

            mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;

            ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnLayoutChangeListeners != null) {
                ArrayList<OnLayoutChangeListener> listenersCopy =
                        (ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();
                int numListeners = listenersCopy.size();
                for (int i = 0; i < numListeners; ++i) {
                    listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
                }
            }
        }

        mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
        mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;

        if ((mPrivateFlags3 & PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT) != 0) {
            mPrivateFlags3 &= ~PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT;
            notifyEnterOrExitForAutoFillIfNeeded(true);
        }
    }

View — > onLayout(changed,l,t,r,b)

对View进行局部变换

protected void onLayout(boolean changed, int left, int top, int right, int bottom) 
{
}

ViewGroup —> OnLayout()

在这里插入图片描述

点开这个方法发现它是空的所以我们去ViewGroup当中找到OnLayout()发现ViewGroup当中这个方法是抽象的,因为不同的布局管理器有着不同的布局方式,所以需要我们不同的Layout类去继承重写ViewGroup中的OnLayout()方法。

FreamLayout —> onLayout()
在这个父View当中摆放所有子View的一个过程

在这里插入图片描述
onLayout()—> layoutChidren()
在这里插入图片描述

parentLeft:表示当前的View为我们View显示的左边界(子View显示区域的左边到我们父View左边的距离)

parentLeft/right/top/bottom一样

会通过for(int i=0;i<count;i++)这个循环来完成子View的布局
然后通过 child.getVisibility() 来判断这个View是否为可见的 如果这个子View不为空的话就会执行下面的方法 对子View进行布局
child.getLayoutParams() :获取View的LayoutParams
child.getMeasuredWidth():获取宽
childLeft:最终子View的左边缘距离我们父边缘的距离
childTop:最终子View的上边缘距离我们父边缘的距离

for(int i=0;i<count;i++)
根据子View的LayoutGravity对childLeft和childTop做出不同的调整

switch(absoluteGravity&…
child.layout(l,t,r,b):调用子View的layout对子View进行参数设置
child.layout()

4 view绘制:draw阶段&dispatchDraw实现

链接:https://www.jianshu.com/p/d4c809852155

如何将View绘制到屏幕上?
View — > darw(Canvas canvas)

Draw the background 绘制背景
Draw view’s content 绘制自己的内容
Draw chidren 绘制子View的内容
Draw decorations(scrollbars for instance) 给View添加自己的装饰
在这里插入图片描述

one Draw the background 首先绘制背景,根据View的不同做不同的处理
在这里插入图片描述

当点击进去发现在这个View类当中这个dispatchDraw是一个空实现.因为他是View中子View的所以不需要实现dispatchDraw这个方法.但ViewGroup不一样有子View所以我们去ViewGroup中看一下

他会调用cihld.draw()这个方法,它会遍历我们的子View,去调用我们子View的这个方法.来去进行绘制

当我们点击draw()这个方法的时候会发现我们又回到了View里的draw()方法 所以说dispatchDraw()这个方法遍历调用我们所有的元素,然后调用这个子元素的draw()方法,这样我们的绘制事件他就是一层一层的传递下去的.回到View的draw()方法中重新走一遍方法 绘制好自定义View

在这里插入图片描述

5 listview的绘制和缓存原理:convertview/viewHolder

链接:https://blog.csdn.net/Bill_Ming/article/details/8817172

listview数据集合以动态滚动的方式,展示到用户界面上。

listview === Adapter === 数据源

Adapter作为listview和数据源之间的桥梁。

工作原理:
1.ListView针对List中每个item,要求adapter给我一个视图(getView)
2.一个新的视图被返回并显示

如果我们有上亿个item要显示怎么办?为每个项目创建一个新视图?NO!这不可能~~~Android实际上为你缓存了视图

Android中有个叫做Recycler( 反复循环器 )的构件,下图是它的工作原理:
在这里插入图片描述
1.如果你有10亿个项目(item),其中只有可见的项目存在内存中,其他的在Recycler中
2. ListView先请求一个type1视图(getView),然后请求其他可见的项目。conVertView在getView中时null的
3. 当item1滚出屏幕,并且一个新的项目从屏幕地段上来时,ListView再请求一个type1视图。convertView此时不是空值了,它的值是item1.你只需要设定新的数据返回convertView,不必重新创建一个视图。这样直接使用convertView从而减少了很不不必要view的创建

!!!!!!更快的方式是定义一个ViewHolder,将convertView的tag设置为ViewHolder,不为空是重新使用

ViewHolder只是将需要缓存的那些view封装好,convertView的setTag才是将这些缓存起来供下次调用
当你的listview里布局多样化的时候 viewholder的作用就有比较明显的体现了。 当然了,单一模式的布局一样有性能优化的作用 只是不直观。 假如你2种模式的布局 当发生回收的时候 你会用setTag分别记录是哪两种 这两种模式会被封装到viewholder中进行保存方便你下次使用。 VH就是个静态类 与缓存无关的。

public class MultipleItemsList extends ListActivity {
  
    private MyCustomAdapter mAdapter;
  
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mAdapter = new MyCustomAdapter();
        for (int i = 0; i < 50; i++) {
            mAdapter.addItem("item " + i);
        }
        setListAdapter(mAdapter);
    }
  
    private class MyCustomAdapter extends BaseAdapter {
  
        private ArrayList mData = new ArrayList();
        private LayoutInflater mInflater;
  
        public MyCustomAdapter() {
            mInflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        }
  
        public void addItem(final String item) {
            mData.add(item);
            notifyDataSetChanged();
        }
  
        @Override
        public int getCount() {
            return mData.size();
        }
  
        @Override
        public String getItem(int position) {
            return mData.get(position);
        }
  
        @Override
        public long getItemId(int position) {
            return position;
        }
  
        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            System.out.println("getView " + position + " " + convertView);
            ViewHolder holder = null;
            if (convertView == null) {
                convertView = mInflater.inflate(R.layout.item1, null);
                holder = new ViewHolder();
                holder.textView = (TextView)convertView.findViewById(R.id.text);
                convertView.setTag(holder);
            } else {
                holder = (ViewHolder)convertView.getTag();
            }
            holder.textView.setText(mData.get(position));
            return convertView;
        }
  
    }
  
    public static class ViewHolder {
        public TextView textView;
    }
}

6 listview的RecycleBin机制:缓存scrapView、fillActiveViews等

参考:https://blog.csdn.net/iispring/article/details/50967445
https://blog.csdn.net/yumaoda/article/details/80763241

在自定义Adapter时,我们常常会重写Adapter的getView方法,该方法的签名如下所示:

public abstract View getView (int position, View convertView, ViewGroup parent) 

此处会传入一个convertView变量,它的值有可能是null,也有可能不是null,如果不为null,我们就可以复用该convertView,对convertView里面的一些控件赋值后可以将convertView作为getView的返回值返回,这么做的目的是减少LayoutInflater.inflate()的调用次数,从而提升了性能(LayoutInflater.inflate()比较消耗性能)。

下面将介绍ListView中的RecycleBin机制,让大家对ListView中的优化机制有个概括的了解,同时也说明convertView的来龙去脉。

首先,我们知道,Adapter是数据源,AdapterView是展示数据源的UI控件,Adapter是给AdapterView使用的,通过调用AdapterView的setAdapter方法就可以让一个AdapterView绑定Adapter对象,从而AdapterView会将Adapter中的数据展示出来。

AdapterView的子类有AbsListView和AbsSpinner等,其中AbsListView的子类又有ListView、GridView等,所以ListView继承自AdapterView。

如果Adapter中有10000条数据,将这个Adapter对象赋给ListView,如果ListView创建10000个子View,那么App肯定崩溃了,因为Android没有能力同时绘制这么多的子View。而且,即便能同时绘制这10000个子View也没什么意义,因为手机的屏幕大小是有限的,有可能ListView的高度只能最多显示10个子View。基于此,Android在设计ListView这个类的时候,引入了RecycleBin机制—–对子View进行回收利用,RecycleBin直译过来就是回收站的意思。

6.1 RecycleBin基本原理

在这里插入图片描述

在某一时刻,我们看到ListView中有许多View呈现在UI上,这些View对我们来说是可见的,这些可见的View可以称作OnScreen的View,即在屏幕中能看到的View,也可以叫做ActiveView,因为它们是在UI上可操作的。

当触摸ListView并向上滑动时,ListView上部的一些OnScreen的View位置上移,并移除了ListView的屏幕范围,此时这些OnScreen的View就变得不可见了,不可见的View叫做OffScreen的View,即这些View已经不在屏幕可见范围内了,也可以叫做ScrapView,Scrap表示废弃的意思,ScrapView的意思是这些OffScreen的View不再处于可以交互的Active状态了。ListView会把那些ScrapView(即OffScreen的View)删除,这样就不用绘制这些本来就不可见的View了,同时,ListView会把这些删除的ScrapView放入到RecycleBin中存起来,就像把暂时无用的资源放到回收站一样。

当ListView的底部需要显示新的View的时候,会从RecycleBin中取出一个ScrapView,将其作为convertView参数传递给Adapter的getView方法,从而达到View复用的目的,这样就不必在Adapter的getView方法中执行LayoutInflater.inflate()方法了。

RecycleBin中有两个重要的View数组,分别是mActiveViewsmScrapViews。这两个数组中所存储的View都是用来复用的,只不过mActiveViews中存储的是OnScreen的View,这些View很有可能被直接复用;而mScrapViews中存储的是OffScreen的View,这些View主要是用来间接复用的。

上面对mActiveViews和mScrapViews的说明比较笼统,其实在细节上还牵扯到Adapter的数据源发生变化的情况,具体细节后面会讲解。

6.2 源码解析

AdapterView是继承自ViewGroup的,ViewGroup中有addView方法可以向ViewGroup中添加子View,但是AdapterView重写了addView方法,如下所示:

@Override
    public void addView(View child) {
        throw new UnsupportedOperationException("addView(View) is not supported in AdapterView");
    }

@Override
    public void addView(View child, int index) {
        throw new UnsupportedOperationException("addView(View, int) is not supported in AdapterView");
    }

在AdapterView的addView方法中会抛出异常,也就是说AdapterView禁用了addView方法。

在具体讲解之前,我们还是先花一点时间简要说一下View的每一帧的显示流程,当然,ListView也肯定遵循此流程。一个View要想在界面上呈现出来,需要经过三个阶段:measure->layout->draw

View是一帧一帧绘制的,每一帧绘制都经历了measure->layout->draw这三个阶段,绘制完一帧之后,如果UI需要更新,比如用户滚动了ListView,那么又会绘制下一帧,再次经历measure->layout->draw方法。

我们上面说了,AdapterView把addView方法给禁用了,那么ListView怎么向其中添加child呢?奥秘就在layout中,在布局的时候,ListView会执行layoutChildren方法,该方法是ListView对View进行添加以及回收的关键方法,RecycleBin的很多方法都在layoutChildren方法中被调用。在layoutChildren方法中实现对子View的增删,经过layoutChildren方法之后,ListView中所有的子View都是在屏幕中可见的,也就是说layoutChildren方法为接下来的帧绘制把子View准备完善了,这就保证了在后面的draw方法的执行过程中能够正确绘制ListView。

ListView的layoutChildren方法代码比较多,我们只研究和View增删相关的关键代码,主要分以下三个阶段:

  1. ListView的children->RecycleBin
  2. ListView清空children
  3. RecycleBin->ListView的children

在layout这个方法刚刚开始执行的时候,ListView中的children其实还是上一帧中需要绘制的子View的集合,在layout这个方法执行完成的时候,ListView中的children就变成了当前帧马上要进行绘制的子View的集合。

四个重要方法:

  1. public void setViewTypeCount(int viewTypeCount)为每一种不同类型的数据都建立一个RecycleBin机制,一般都是相同的类型
  2. void fillActiveView(int childCount, int firstActivePosition)将指定元素存储到mActiveView数组中
  3. View getActiveView(int position) 与2是成对的,2是填充数组,3是从数组中获取数据
  4. void addScrapView(View scrap, int position) 将废弃的移出屏幕外的view缓存起来:当有item要移动到屏幕外,就调用这个方法缓存该view

整个流程:
当listview滑动时,listview会将移出屏幕外的元素0删除,这样就不用绘制元素0,提高新能。
但它会将元素0放到RecycleBin当中保存起来(通过addScrapView方法),这样,当元素6需要展示的时候,它会从RecycleBin中获取一个scrapView,将它作为convertView传递给Adapter的getView方法,这样就达到了View的复用目的,就不用再创建view了。

7 listview性能优化5个关键点

listview不会导致oom的原因之一:使用RecycleBin机制。

  1. convertView 通过缓存convertView可以复用view,不用每次都创建
  2. viewHolder 避免重复调用findViewById方法
  3. getView中尽量少做耗时操作
  4. listview中item元素避免半透明 半透明绘制时会集成大量的乘法计算,滑动时,不停的重新绘制肯定会带来大量的计算,可能会造成滑动卡顿
  5. 开启硬件加速 提升非常巨大,要避免使用某些不支持的函数,导致关闭了硬件加速
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值