13问13答全面学习Android View绘制

13问13答全面学习Android View绘制,第1张

概述本文通过13问13答学习AndroidView绘制,供大家参考,具体内容如下1.View的绘制流程分几步,从哪开始?哪个过程结束以后能看到view?

本文通过13问13答学习AndroID VIEw绘制,供大家参考,具体内容如下

1.VIEw的绘制流程分几步,从哪开始?哪个过程结束以后能看到vIEw?

答:从VIEwRoot的performTraversals开始,经过measure,layout,draw 三个流程。draw流程结束以后就可以在屏幕上看到vIEw了。

 2.vIEw的测量宽高和实际宽高有区别吗?

答:基本上百分之99的情况下都是可以认为没有区别的。有两种情况,有区别。第一种 就是有的时候会因为某些原因 vIEw会多次测量,那第一次测量的宽高 肯定和最后实际的宽高 是不一定相等的,但是在这种情况下

最后一次测量的宽高和实际宽高是一致的。此外,实际宽高是在layout流程里确定的,我们可以在layout流程里 将实际宽高写死 写成硬编码,这样测量的宽高和实际宽高就肯定不一样了,虽然这么做没有意义 而且也不好。

 3.vIEw的measureSpec 由谁决定?顶级vIEw呢?

答:由vIEw自己的layoutparams和父容器  一起决定自己的measureSpec。一旦确定了spec,onMeasure中就可以确定vIEw的宽高了。

顶级vIEw就稍微特殊一点,对于decorVIEw的测量在VIEwRootImpl的源码里。

//desire的这2个参数就代表屏幕的宽高, chilDWIDthMeasureSpec = getRootMeasureSpec(desireDWindowWIDth,lp.wIDth); childHeightmeasureSpec = getRootMeasureSpec(desireDWindowHeight,lp.height); performMeasure(chilDWIDthMeasureSpec,childHeightmeasureSpec); //decorVIEw的measureSpec就是在这里确定的,其实比普通vIEw的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;} 

4.对于普通vIEw来说,他的measure过程中,与父vIEw有关吗?如果有关,这个父vIEw也就是vIEwgroup扮演了什么角色?

答:看源码:

//对于普通vIEw的measure来说 是由这个vIEw的 父vIEw ,也就是vIEwgroup来触发的。//也就是下面这个measureChilDWithmargins方法protected voID measureChilDWithmargins(VIEw child,int parentWIDthMeasureSpec,int wIDthUsed,int parentHeightmeasureSpec,int heightUsed) {   //第一步 先取得子vIEw的 layoutParams 参数值   final marginLayoutParams lp = (marginLayoutParams) child.getLayoutParams();  //然后开始计算子vIEw的spec的值,注意这里看到 计算的时候除了要用子vIEw的 layoutparams参数以外  //还用到了父vIEw 也就是vIEwgroup自己的spec的值  final int chilDWIDthMeasureSpec = getChildMeasureSpec(parentWIDthMeasureSpec,mpaddingleft + mpaddingRight + lp.leftmargin + lp.rightmargin      + wIDthUsed,lp.wIDth);  final int childHeightmeasureSpec = getChildMeasureSpec(parentHeightmeasureSpec,mpaddingtop + mpaddingBottom + lp.topmargin + lp.bottommargin      + heightUsed,lp.height);  child.measure(chilDWIDthMeasureSpec,childHeightmeasureSpec);}//这个算vIEw的spec的方法 看上去一大串 但是真的逻辑非常简单 就是根据父亲vIEwgroup//的meaurespec 同时还有vIEw自己的params来确定 vIEw自己的measureSpec。//注意这里的参数是padding,这个值的含义是 父容器已占用的控件的大小 所以vIEw的Specsize//的值 你们可以看到 是要减去这个padding的值的。总大小-已经用的 =可用的。 很好理解。//然后就是下面的switch逻辑 要自己梳理清楚。其实也不难,主要是下面几条原则//如果vIEw采用固定宽高,也就是写死的数值那种。那就不管父亲的spec的值了,vIEw的spec 就肯定是exactly 并且大小遵循layout参数里设置的大小。//如果vIEw的宽高是match_parent ,那么就要看父容器vIEwgroup的 spec的值了,如果父vIEw的spec是exactly模式,//那vIEw也肯定是exactly,并且大小就是父容器剩下的空间。如果父容器是at_most模式,那vIEw也是at_most 并且不会超过剩余空间大小//如果vIEw的宽高是wrap_content,那就不管父容器的spec了,vIEw的spec一定是at_most 并且不会超过父vIEw 剩余空间的大小。public static int getChildMeasureSpec(int spec,int padding,int childDimension) {  int specMode = MeasureSpec.getMode(spec);  int specsize = MeasureSpec.getSize(spec);  int size = Math.max(0,specsize - padding);  int resultSize = 0;  int resultMode = 0;  switch (specMode) {  // Parent has imposed an exact size on us  case MeasureSpec.EXACTLY:   if (childDimension >= 0) {    resultSize = childDimension;    resultMode = MeasureSpec.EXACTLY;   } else if (childDimension == LayoutParams.MATCH_PARENT) {    // Child wants to be our size. So be it.    resultSize = size;    resultMode = MeasureSpec.EXACTLY;   } else if (childDimension == LayoutParams.WRAP_CONTENT) {    // Child wants to determine its own size. It can't be    // bigger than us.    resultSize = size;    resultMode = MeasureSpec.AT_MOST;   }   break;  // Parent has imposed a maximum size on us  case MeasureSpec.AT_MOST:   if (childDimension >= 0) {    // Child wants a specific size... so be it    resultSize = childDimension;    resultMode = MeasureSpec.EXACTLY;   } else if (childDimension == LayoutParams.MATCH_PARENT) {    // Child wants to be our size,but our size is not fixed.    // Constrain child to not be bigger than us.    resultSize = size;    resultMode = MeasureSpec.AT_MOST;   } else if (childDimension == LayoutParams.WRAP_CONTENT) {    // Child wants to determine its own size. It can't be    // bigger than us.    resultSize = size;    resultMode = MeasureSpec.AT_MOST;   }   break;  // Parent asked to see how big we want to be  case MeasureSpec.UnspecIFIED:   if (childDimension >= 0) {    // Child wants a specific size... let him have it    resultSize = childDimension;    resultMode = MeasureSpec.EXACTLY;   } else if (childDimension == LayoutParams.MATCH_PARENT) {    // Child wants to be our size... find out how big it should    // be    resultSize = VIEw.sUseZeroUnspecifIEdMeasureSpec ? 0 : size;    resultMode = MeasureSpec.UnspecIFIED;   } else if (childDimension == LayoutParams.WRAP_CONTENT) {    // Child wants to determine its own size.... find out how    // big it should be    resultSize = VIEw.sUseZeroUnspecifIEdMeasureSpec ? 0 : size;    resultMode = MeasureSpec.UnspecIFIED;   }   break;  }  return MeasureSpec.makeMeasureSpec(resultSize,resultMode); } 

5.vIEw的meaure和onMeasure有什么关系?

答:看源码:

//vIEw的measure是final 方法 我们子类无法修改的。 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);  if ((mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ||    wIDthMeasureSpec != molDWIDthMeasureSpec ||    heightmeasureSpec != moldHeightmeasureSpec) {   // first clears the measured dimension flag   mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;   resolveRtlPropertIEsIfNeeded();   int cacheIndex = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ? -1 :     mMeasureCache.indexOfKey(key);   if (cacheIndex < 0 || sIgnoreMeasureCache) {    // measure ourselves,this should set the measured dimension flag back    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 }//不过可以看到的是在measure方法里调用了onMeasure方法//所以就能知道 我们在自定义view的时候一定是重写这个方法! protected voID onMeasure(int wIDthMeasureSpec,int heightmeasureSpec) {  setMeasuredDimension(getDefaultSize(getSuggestedMinimumWIDth(),wIDthMeasureSpec),getDefaultSize(getSuggestedMinimumHeight(),heightmeasureSpec)); }

6.简要分析vIEw的measure流程?

答:先回顾问题4,vIEwgroup 算出子vIEw的spec以后 会调用子vIEw的measure方法,而子vIEw的measure方法 我们问题5也看过了实际上是调用的onMeasure方法

所以我们只要分析好onMeasure方法即可,注意onMeasure方法的参数 正是他的父vIEw算出来的那2个spec的值(这里vIEw的measure方法会把这个spec里的specsize值做略微的修改 这个部分 不做分析 因为measure方法修改specsize的部分很简单)。

//可以看出来这个就是setMeasuredDimension方法的调用 这个方法看名字就知道就是确定vIEw的测量宽高的//所以我们分析的重点就是看这个getDefaultSize 方法 是怎么确定vIEw的测量宽高的 protected voID onMeasure(int wIDthMeasureSpec,heightmeasureSpec)); }//这个方法特别简单 基本可以认为就是近似的返回spec中的specsize,除非你的specMode是UnspecIFIED//UnspecIFIED 这个一般都是系统内部测量才用的到,这种时候返回size 也就是getSuggestedMinimumWIDth的返回值 public static int getDefaultSize(int size,int measureSpec) {  int result = size;  int specMode = MeasureSpec.getMode(measureSpec);  int specsize = MeasureSpec.getSize(measureSpec);  switch (specMode) {  case MeasureSpec.UnspecIFIED:   result = size;   break;  case MeasureSpec.AT_MOST:  case MeasureSpec.EXACTLY:   result = specsize;   break;  }  return result;}//跟vIEw的背景相关 这里不多做分析了protected int getSuggestedMinimumWIDth() {  return (mBackground == null) ? mMinWIDth : max(mMinWIDth,mBackground.getMinimumWIDth()); } 

7.自定义view中 如果onMeasure方法 没有对wrap_content 做处理 会发生什么?为什么?怎么解决?

答:如果没有对wrap_content做处理 ,那即使你在xml里设置为wrap_content.其效果也和match_parent相同。看问题4的分析。我们可以知道vIEw自己的layout为wrap,那mode就是at_most(不管父亲vIEw是什么specmode).

这种模式下宽高就是等于specsize(getDefaultSize函数分析可知),而这里的specsize显然就是parentSize的大小。也就是父容器剩余的大小。那不就和我们直接设置成match_parent是一样的效果了么?

解决方式就是在onMeasure里 针对wrap 来做特殊处理 比如指定一个默认的宽高,当发现是wrap_content 就设置这个默认宽高即可。

 8.VIEwGroup有onMeasure方法吗?为什么?

答:没有,这个方法是交给子类自己实现的。不同的vIEwgroup子类 肯定布局都不一样,那onMeasure索性就全部交给他们自己实现好了。

 9.为什么在activity的生命周期里无法获得测量宽高?有什么方法可以解决这个问题吗?

答:因为measure的过程和activity的生命周期  没有任何关系。你无法确定在哪个生命周期执行完毕以后 vIEw的measure过程一定走完。可以尝试如下几种方法 获取vIEw的测量宽高。

//重写activity的这个方法public voID onWindowFocusChanged(boolean hasFocus) {  super.onWindowFocusChanged(hasFocus);  if (hasFocus) {   int wIDth = tv.getMeasureDWIDth();   int height = tv.getMeasuredHeight();   Log.v("burning","wIDth==" + wIDth);   Log.v("burning","height==" + height);  } }

或者重写这个方法

@OverrIDe protected voID onStart() {  super.onStart();  tv.post(new Runnable() {   @OverrIDe   public voID run() {    int wIDth = tv.getMeasureDWIDth();    int height = tv.getMeasuredHeight();   }  }); }

再或者:

@OverrIDe protected voID onStart() {  super.onStart();  VIEwTreeObserver observer = tv.getVIEwTreeObserver();  observer.addOnGlobalLayoutListener(new VIEwTreeObserver.OnGlobalLayoutListener() {   @OverrIDe   public voID onGlobalLayout() {    int wIDth = tv.getMeasureDWIDth();    int height = tv.getMeasuredHeight();    tv.getVIEwTreeObserver().removeOnGlobalLayoutListener(this);   }  }); }

10.layout和onLayout方法有什么区别?

答:layout是确定本身vIEw的位置 而onLayout是确定所有子元素的位置。layout里面 就是通过serFrame方法设设定本身vIEw的 四个顶点的位置。这4个位置以确定 自己vIEw的位置就固定了

然后就调用onLayout来确定子元素的位置。vIEw和vIEwgroup的onlayout方法都没有写。都留给我们自己给子元素布局 

11.draw方法 大概有几个步骤?

答: 一共是4个步骤, 绘制背景---------绘制自己--------绘制chrildren----绘制装饰。 

12.setwillNotDraw方法有什么用?

答:这个方法在vIEw里。

/**  * If this vIEw doesn't do any drawing on its own,set this flag to  * allow further optimizations. By default,this flag is not set on  * VIEw,but Could be set on some VIEw subclasses such as VIEwGroup.  *  * Typically,if you overrIDe {@link #onDraw(androID.graphics.Canvas)}  * you should clear this flag.  *  * @param willNotDraw whether or not this VIEw draw on its own  */ public voID setwillNotDraw(boolean willNotDraw) {  setFlags(willNotDraw ? WILL_NOT_DRAW : 0,DRAW_MASK); }

用于设置标志位的 也就是说 如果你的自定义view 不需要draw的话,就可以设置这个方法为true。这样系统知道你这个vIEw 不需要draw 可以优化执行速度。vIEwgroup 一般都默认设置这个为true,因为vIEwgroup多数都是只负责布局

不负责draw的。而vIEw 这个标志位 默认一般都是关闭的。

13.自定义view 有哪些需要注意的点?

答:主要是要处理wrap_content 和padding。否则xml 那边设置这2个属性就根本没用了。还有不要在vIEw中使用handler 因为人家已经提供了post方法。如果是继承自vIEwGroup,那在onMeasure和onLayout里面 也要考虑

padding和layout的影响。也就是说specsize 要算一下 。最后就是如果vIEw的动画或者线程需要停止,可以考虑在onDetachedFromWindow里面来做。

针对上述的几点,给出几个简单的自定义view 供大家理解。

给出一个圆形的vIEw 范例:

package com.example.administrator.motioneventtest;import androID.content.Context;import androID.graphics.Canvas;import androID.graphics.color;import androID.graphics.Paint;import androID.util.AttributeSet;import androID.vIEw.VIEw;/** * Created by administrator on 2016/2/4. */public class CircleVIEw extends VIEw { private int mcolor = color.RED; private Paint mPaint = new Paint(Paint.ANTI_AliAS_FLAG); private voID init() {  mPaint.setcolor(mcolor); } @OverrIDe protected voID onMeasure(int wIDthMeasureSpec,int heightmeasureSpec) {  super.onMeasure(wIDthMeasureSpec,heightmeasureSpec);  int wIDthSpecMode = MeasureSpec.getMode(wIDthMeasureSpec);  int wIDthSpecsize = MeasureSpec.getSize(wIDthMeasureSpec);  int heightSpecMode = MeasureSpec.getMode(heightmeasureSpec);  int heightSpecsize = MeasureSpec.getSize(heightmeasureSpec);  //处理为wrap_content时的情况  if (wIDthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) {   setMeasuredDimension(200,200);  } else if (wIDthSpecMode == MeasureSpec.AT_MOST) {   setMeasuredDimension(200,heightSpecsize);  } else if (heightSpecMode == MeasureSpec.AT_MOST) {   setMeasuredDimension(wIDthSpecsize,200);  } } @OverrIDe protected voID onDraw(Canvas canvas) {  super.onDraw(canvas);  //处理padding的情况  final int paddingleft = getpaddingleft();  final int paddingRight = getpaddingRight();  final int paddingtop = getpaddingtop();  final int paddingBottom = getpaddingBottom();  int wIDth = getWIDth() - paddingleft - paddingRight;  int height = getHeight() - paddingtop - paddingBottom;  int radius = Math.min(wIDth,height) / 2;  canvas.drawCircle(paddingleft + wIDth / 2,paddingtop + height / 2,radius,mPaint); } public CircleVIEw(Context context,AttributeSet attrs,int defStyleAttr) {  super(context,attrs,defStyleAttr);  init(); } public CircleVIEw(Context context) {  super(context);  init(); } public CircleVIEw(Context context,AttributeSet attrs) {  super(context,attrs);  init(); }}

然后下面再给出一个范例,稍微复杂一点是自定义viewgroup了(主要是加强对onMeasure和onLayout的理解), 需求如下:

一个水平的vIEwgroup,内部的子元素 为了简单 我们假定他们的宽高都是一样的。来写一个这样的简单的vIEwgroup。

package com.example.administrator.motioneventtest;import androID.content.Context;import androID.util.AttributeSet;import androID.util.Log;import androID.vIEw.VIEw;import androID.vIEw.VIEwGroup;/** * Created by administrator on 2016/2/4. *///这里我们只处理了padding的状态 没有处理margin的状态,子vIEw的margin 对measure和layout的影响//就留给读者自己完成了public class CustomHorizontalLayout extends VIEwGroup { //设置默认的控件最小是多少 这里不提供自定义属性了 写死在代码里 你们可以自行拓展 final int minHeight = 0; final int minWIDth = 0; public CustomHorizontalLayout(Context context) {  super(context); } public CustomHorizontalLayout(Context context,attrs); } public CustomHorizontalLayout(Context context,defStyleAttr); } @OverrIDe protected voID onMeasure(int wIDthMeasureSpec,heightmeasureSpec);  int measureWIDth = 0;  int measureHeight = 0;  final int childCount = getChildCount();  measureChildren(wIDthMeasureSpec,heightmeasureSpec);  int wIDthSpecMode = MeasureSpec.getMode(wIDthMeasureSpec);  int wIDthSpecsize = MeasureSpec.getSize(wIDthMeasureSpec);  int heightSpecMode = MeasureSpec.getMode(heightmeasureSpec);  int heightSpecsize = MeasureSpec.getSize(heightmeasureSpec);  final VIEw childVIEw = getChildAt(0);  final int paddingleft = getpaddingleft();  final int paddingRight = getpaddingRight();  final int paddingtop = getpaddingtop();  final int paddingBottom = getpaddingBottom();  //没有子控件 时 我们的宽高要作特殊处理  if (childCount == 0) {   //当没有子控件时,如果长宽有一个为wrap 那么就让这个控件以最小的形式展现   //这里我们最小设置为0   if (wIDthSpecMode == MeasureSpec.AT_MOST || heightSpecMode == MeasureSpec.AT_MOST) {    setMeasuredDimension(minWIDth,minHeight);   } else {    //否则根据我们的layout属性来    setMeasuredDimension(getLayoutParams().wIDth,getLayoutParams().height);   }  } else if (wIDthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) {   measureWIDth = childVIEw.getMeasureDWIDth() * childCount;   measureHeight = childVIEw.getMeasuredHeight();   setMeasuredDimension(paddingleft + measureWIDth + paddingRight,paddingtop + measureHeight + paddingBottom);  } else if (heightSpecMode == MeasureSpec.AT_MOST) {   measureHeight = childVIEw.getMeasuredHeight();   setMeasuredDimension(paddingleft + paddingRight + wIDthSpecsize,paddingtop + paddingBottom + measureHeight);  } else if (wIDthSpecMode == MeasureSpec.AT_MOST) {   measureWIDth = childVIEw.getMeasureDWIDth() * childCount;   setMeasuredDimension(paddingleft + paddingRight + measureWIDth,paddingtop + paddingBottom + heightSpecsize);  } } @OverrIDe protected voID onLayout(boolean changed,int l,int t,int r,int b) {  final int paddingleft = getpaddingleft();  final int paddingRight = getpaddingRight();  final int paddingtop = getpaddingtop();  final int paddingBottom = getpaddingBottom();  //左边初始位置为0  int childleft = 0 + paddingleft;  final int childCount = getChildCount();  for (int i = 0; i < childCount; i++) {   final VIEw childVIEw = getChildAt(i);   if (childVIEw.getVisibility() != VIEw.GONE) {    final int chilDWIDth = childVIEw.getMeasureDWIDth();    childVIEw.layout(childleft,0 + paddingtop,childleft + chilDWIDth,paddingtop + childVIEw.getMeasuredHeight());    childleft += chilDWIDth;   }  } }}

以上就是本文的全部内容,希望对大家的学习有所帮助。

总结

以上是内存溢出为你收集整理的13问13答全面学习Android View绘制全部内容,希望文章能够帮你解决13问13答全面学习Android View绘制所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/1141836.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-31
下一篇 2022-05-31

发表评论

登录后才能评论

评论列表(0条)

保存