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树。
- measure:判断是否需要重新计算View的大小
- layout:判断是否需要重新计算View的位置
- draw:判断是否需要重新绘制View
具体步骤:
- 从上到下,有次序地测量View;
- 测量之后,根据测量的大小和规格,由layout去放置View;
- 最后,如果需要重新绘制,交给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。
- UNSPECIFIED:表示我们的父View不会对子View有任何的约束.这样子View就可以达到我们想要的最大尺寸,这个最大尺寸受屏幕大小限制
- EXACTLY:父View为子View指定了一个尺寸,无论子View想要多大都必须在这个尺寸最大值的边界内,如具体dp值,或match parent
- 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数组,分别是mActiveViews和mScrapViews。这两个数组中所存储的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增删相关的关键代码,主要分以下三个阶段:
- ListView的children->RecycleBin
- ListView清空children
- RecycleBin->ListView的children
在layout这个方法刚刚开始执行的时候,ListView中的children其实还是上一帧中需要绘制的子View的集合,在layout这个方法执行完成的时候,ListView中的children就变成了当前帧马上要进行绘制的子View的集合。
四个重要方法:
- public void setViewTypeCount(int viewTypeCount)为每一种不同类型的数据都建立一个RecycleBin机制,一般都是相同的类型
- void fillActiveView(int childCount, int firstActivePosition)将指定元素存储到mActiveView数组中
- View getActiveView(int position) 与2是成对的,2是填充数组,3是从数组中获取数据
- 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机制。
- convertView 通过缓存convertView可以复用view,不用每次都创建
- viewHolder 避免重复调用findViewById方法
- getView中尽量少做耗时操作
- listview中item元素避免半透明 半透明绘制时会集成大量的乘法计算,滑动时,不停的重新绘制肯定会带来大量的计算,可能会造成滑动卡顿
- 开启硬件加速 提升非常巨大,要避免使用某些不支持的函数,导致关闭了硬件加速
本文详细介绍了Android中View的绘制流程,包括measure、layout及draw阶段,并深入解析ListView的RecycleBin机制及其优化技巧。

被折叠的 条评论
为什么被折叠?



