当前位置:   article > 正文

android view绘制源码分析_android view的绘制 源码分析

android view的绘制 源码分析

在开发过程中我们经常要进行view的自定义。如果熟练掌握自定义技巧的话就能做出很多控件出来。这篇文章来讲讲view绘制背后发生的那些事。

一、view的基础知识

1.view的绘制概括

首先先说说view绘制的整体过程。

View绘制的源码分析 ,它的三大流程都是在ViewRootImpl中完成的,从ViewRootImpl中的performTraversals开始,有三个方法performMeasure, performLayout, prformDraw分别对measure,layout,draw三个方法。在onMeasure对所有子元素进行measure过程 ,这时measure就从父容器传递到子元素。子元素重复父元素的过程。

layout与draw类似,只是draw通过diapatchDraw来实现。measure完成后可以通过getMeasureWidth,getMeasureHeight分别获取View测量后的宽高。在实际情况下几乎所有情况它都等于最终宽高。layout过程决定view的四个顶点的坐标和实际view的宽高,完成之后可以通过getTop,getBottom,getLeft,getRight来拿 到view的四个顶点位置。并通过getWidth()和getHeight()来拿到最终宽高。draw决定了view的显示,只有完成才能显示在屏幕上。

所以,view的生命周期,从构造方法开始,依次执行

onAttachToWindow——>onMeasure——>onSizeChanged——>onLayout——> onMeasure——>onLayout——>onDraw——>onDetachedFromWindow

View与ViewGroup里的方法调用过程总结如下:

1.首先,Activity onCreate并初始化view

2.然后,Activity onResume后调用view的onAttachedToWindow,因此常常在onAttachedToWindow方法中做初始化工作,比如注册一些广播、开始动画等等……

3.接下来,如果设置了背景则调用onDraw,之后倘若是viewgroup则调用dispatchDraw。

说明:dispatchDraw()主要是分发给子组件进行绘制,我们通常定制组件的时候重写的是onDraw()方法。值得注意的是ViewGroup容器组件的绘制,当它没有背景时直接调用的是dispatchDraw()方法, 而绕过了draw()方法,当它有背景的时候就调用draw()方法,而draw()方法里包含了dispatchDraw()方法的调用。因此要在ViewGroup上绘制东西的时候往往重写的是dispatchDraw()方法而不是onDraw()方法,或者自定制一个Drawable,重写它的draw(Canvas c)和 getIntrinsicWidth(),getIntrinsicHeight()方法,然后设为背景。

4.最后,当点击回退键时,Activity onDestroy后才调用onDetachedFromWindow,这时我们就在这个方法做一些收尾工作,如:取消广播注册、停止动画等等。

说明:onDraw与dispatchDraw将可能由于setVisible或onresume调用多次,而onAttachedToWindow与onDetachedFromWindow在创建与销毁view的过程中只会调用一次。

5.此外在创建期间还会多次调用onMeasure和onLayout

注:onFinishInflate 的调用,当View中所有的子控件均被映射成xml后触发,也就是onFinishInflate ——>onAttachToWindow......


MeasureSpec

在测量过程中系统会将View的LayoutParams根据容器所施加的规则转换成对应的MeasureSpec,然后再根据这个测量出view。 

Measure是一个32位的int,高2位代表SpecMode,低30位代表SpecSize。SpecMode表示测量模式,SpecSize指在某种测量模式下规格的大小。其代码如下:

  1. public static class MeasureSpec {
  2. private static final int MODE_SHIFT = 30;
  3. private static final int MODE_MASK = 0x3 << MODE_SHIFT;
  4. public static final int UNSPECIFIED = 0 << MODE_SHIFT;
  5. public static final int EXACTLY = 1 << MODE_SHIFT;
  6. public static final int AT_MOST = 2 << MODE_SHIFT;
  7. public static int makeMeasureSpec(int size, int mode) {
  8. if (sUseBrokenMakeMeasureSpec) {
  9. return size + mode;
  10. } else {
  11. return (size & ~MODE_MASK) | (mode & MODE_MASK);
  12. }
  13. }
  14. public static int getMode(int measureSpec) {
  15. return (measureSpec & MODE_MASK) ;
  16. }
  17. public static int getSize(int measureSpec) {
  18. return (measureSpec & ~MODE_MASK) ;
  19. }
  20. }

其实MeasureSpec中源码很值得我们学习。他用一个32位的int来表示模式和大小,节省了空间,也更直观。MeasureSpec通过将specMode和specSize打包成一个int来避免过多的对象内存分配。以上是MeasureSpec的打包和解包过程。 

specMode有三种状态:UNSPECIFIED,EXACTLY(相当于matchparent和精确值这两种模式),ATMOST(wrap_content)。

LayoutParams

对于一般容器,它的MeasureSpec是由父容器的MeasureSpec和自身的LayoutParams共同决定的。PhoneWindow包了一层DecorView,DecorView里才是title和我们的content view。所以行分析DecorView。

先来看下DecorView的产生源码:

  1. childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width);
  2. childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
  3. performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);

再看下getRootMeasureSpec方法:

  1. private static int getRootMeasureSpec(int windowSize, int rootDimension) {
  2. int measureSpec;
  3. switch (rootDimension) {
  4. case ViewGroup.LayoutParams.MATCH_PARENT:
  5. // Window can't resize. Force root view to be windowSize.
  6. measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
  7. break;
  8. case ViewGroup.LayoutParams.WRAP_CONTENT:
  9. // Window can resize. Set max size for root view.
  10. measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
  11. break;
  12. default:
  13. // Window wants to be an exact size. Force root view to be that size.//自定义
  14. measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
  15. break;
  16. }
  17. return measureSpec;
  18. }

这里很清楚,分别分MatchPraent和wrap_content和自定义来计算宽高。再来看下普通的view,在ViewGroup的measureChildWIthMargins中:

  1. protected void measureChildWithMargins (View child,
  2. int parentWidthMeasureSpec , int widthUsed,
  3. int parentHeightMeasureSpec , int heightUsed) {
  4. final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams() ;
  5. final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec ,
  6. mPaddingLeft + mPaddingRight + lp. leftMargin + lp.rightMargin
  7. + widthUsed, lp.width) ;
  8. final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec ,
  9. mPaddingTop + mPaddingBottom + lp. topMargin + lp.bottomMargin
  10. + heightUsed, lp.height) ;
  11. child.measure(childWidthMeasureSpec , childHeightMeasureSpec);
  12. }

再看下getChildMeasureSpec:

  1. public static int getChildMeasureSpec(int spec, int padding , int childDimension) {
  2. int specMode = MeasureSpec.getMode(spec);
  3. int specSize = MeasureSpec. getSize(spec) ;
  4. int size = Math. max( 0, specSize - padding) ;
  5. int resultSize = 0;
  6. int resultMode = 0;
  7. switch (specMode) {
  8. // Parent has imposed an exact size on us
  9. case MeasureSpec.EXACTLY:
  10. if (childDimension >= 0) {
  11. resultSize = childDimension;
  12. resultMode = MeasureSpec. EXACTLY;
  13. } else if (childDimension == LayoutParams.MATCH_PARENT) {
  14. // Child wants to be our size. So be it.
  15. resultSize = size ;
  16. resultMode = MeasureSpec. EXACTLY;
  17. } else if (childDimension == LayoutParams.WRAP_CONTENT) {
  18. // Child wants to determine its own size. It can't be
  19. // bigger than us.
  20. resultSize = size ;
  21. resultMode = MeasureSpec. AT_MOST;
  22. }
  23. break;
  24. // Parent has imposed a maximum size on us
  25. case MeasureSpec.AT_MOST:
  26. if (childDimension >= 0) {
  27. // Child wants a specific size... so be it
  28. resultSize = childDimension ;
  29. resultMode = MeasureSpec. EXACTLY;
  30. } else if (childDimension == LayoutParams.MATCH_PARENT) {
  31. // Child wants to be our size, but our size is not fixed.
  32. // Constrain child to not be bigger than us.
  33. resultSize = size ;
  34. resultMode = MeasureSpec. AT_MOST;
  35. } else if (childDimension == LayoutParams.WRAP_CONTENT) {
  36. // Child wants to determine its own size. It can't be
  37. // bigger than us.
  38. resultSize = size ;
  39. resultMode = MeasureSpec. AT_MOST;
  40. }
  41. break;
  42. // Parent asked to see how big we want to be
  43. case MeasureSpec.UNSPECIFIED:
  44. if (childDimension >= 0) {
  45. // Child wants a specific size... let him have it
  46. resultSize = childDimension ;
  47. resultMode = MeasureSpec. EXACTLY;
  48. } else if (childDimension == LayoutParams.MATCH_PARENT) {
  49. // Child wants to be our size... find out how big it should
  50. // be
  51. resultSize = 0;
  52. resultMode = MeasureSpec. UNSPECIFIED;
  53. } else if (childDimension == LayoutParams.WRAP_CONTENT) {
  54. // Child wants to determine its own size.... find out how
  55. // big it should be
  56. resultSize = 0;
  57. resultMode = MeasureSpec. UNSPECIFIED;
  58. }
  59. break;
  60. }
  61. return MeasureSpec.makeMeasureSpec(resultSize, resultMode) ;
  62. }

以上表明,如果父是EXACTLY,parentSize,那么子如果是EXACTLY,

  • 具体的值size:那子的MeasureSpec就是EXACTLY,size; 

  • MATCH_PARENT:那子的MeasureSpec就是EXACTLY,parentSize; 

  • WRAPCONTENT:那子的MeasureSpec就是ATMOST,parentSize;

如果父是ATMOST,parentSize,那么子如果是EXACTLY,

  • 具体的值size:那子的MeasureSpec就是EXACTLY,size; 

  • MATCHPARENT:那子的MeasureSpec就是ATMOST,parentSize; 

  • WRAPCONTENT:那子的MeasureSpec就是ATMOST,parentSize; 

总结:对于普通View的MeasureSpec由父容器的MeasureSpec和自身的LayoutParams来共同决定。

二、三大过程源码分析

OnMeasure

measure。如果是view,measure绘制其自身。如果是VIewGroup,measure绘制自身外,还要绘制其子元素。先看View的measure方法,measure是一个final方法,不能重写:

  1. if (cacheIndex < 0 |if (cacheIndex < 0 || sIgnoreMeasureCache ) {
  2. // measure ourselves, this should set the measured dimension flag back
  3. onMeasure(widthMeasureSpec , heightMeasureSpec);
  4. mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
  5. }| sIgnoreMeasureCache ) {
  6. // measure ourselves, this should set the measured dimension flag back
  7. onMeasure(widthMeasureSpec , heightMeasureSpec);
  8. mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
  9. }

调用了onMeasure(),来看下它的源码:

  1. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  2. setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth() , widthMeasureSpec) ,
  3. getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)) ;
  4. }

看下getSuggestedMinimumWidth(),它就是获取背景大小和mMinWidth的较大值:

  1. protected int getSuggestedMinimumWidth () {
  2. return (mBackground == null) ? mMinWidth : max(mMinWidth , mBackground .getMinimumWidth());
  3. }

那么mMinWidth是什么呢,mMinWidth就是设置的android:minWidth的属性,没设置就等于0。不信,看如下代码:

  1. case R.styleable.View_minWidth:
  2. mMinWidth = a.getDimensionPixelSize(attr , 0) ;
  3. break;

getMinimumWidth()表示的是获取背景图大小,它位于Drawable下:

  1. public int getMinimumHeight() {
  2. final int intrinsicHeight = getIntrinsicHeight() ;
  3. return intrinsicHeight > 0 ? intrinsicHeight : 0 ;
  4. }

看下getDefaultSize方法:

  1. public static int getDefaultSize(int size, int measureSpec) {
  2. int result = size;
  3. int specMode = MeasureSpec. getMode(measureSpec) ;
  4. int specSize = MeasureSpec. getSize(measureSpec) ;
  5. switch (specMode) {
  6. case MeasureSpec.UNSPECIFIED:
  7. result = size;
  8. break;
  9. case MeasureSpec. AT_MOST:
  10. case MeasureSpec.EXACTLY:
  11. result = specSize;
  12. break;
  13. }
  14. return result;
  15. }

它返回了specSize,它是测量后的大小。由上面的分析可知,view的宽高由specSize决定,而如果直接继承View的控件需要重写onMeasure方法并设置wrapcontent的自身大小。否则wrapcontent就相当 于Match_parent了。一般一重写方法如下:

  1. protected void onMeasure( int widthMeasureSpec, int heightMeasureSpec) {
  2. super.onMeasure(widthMeasureSpec, heightMeasureSpec);
  3. int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
  4. int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
  5. int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
  6. int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
  7. if (widthSpecMode == MeasureSpec. AT_MOST
  8. && heightSpecMode == MeasureSpec. AT_MOST) {
  9. setMeasuredDimension(200, 200);
  10. } else if (widthSpecMode == MeasureSpec. AT_MOST) {
  11. setMeasuredDimension(200, heightSpecSize);
  12. } else if (heightSpecMode == MeasureSpec. AT_MOST) {
  13. setMeasuredDimension(widthSpecSize, 200);
  14. }
  15. }

上面的200是指定的一个默认宽高。

ViewGroup的measure过程,它没有重写onMeasure,它会调用measureChildren如下:

  1. protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
  2. final int size = mChildrenCount;
  3. final View[] children = mChildren;
  4. for ( int i = 0 ; i < size; ++i) {
  5. final View child = children[i] ;
  6. if ((child. mViewFlags & VISIBILITY_MASK) != GONE) {
  7. measureChild(child, widthMeasureSpec , heightMeasureSpec);
  8. }
  9. }
  10. }

分别绘制child,进入measureChild:

  1. protected void measureChild(View child, int parentWidthMeasureSpec,
  2. int parentHeightMeasureSpec) {
  3. final LayoutParams lp = child.getLayoutParams() ;
  4. final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec ,
  5. mPaddingLeft + mPaddingRight, lp.width) ;
  6. final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec ,
  7. mPaddingTop + mPaddingBottom, lp.height) ;
  8. child.measure(childWidthMeasureSpec , childHeightMeasureSpec);
  9. }

获取LayoutParams,通过getChildMeasureSpec来创建子无素的MeasureSpec,调用child.measure,因为ViewGroup有不同的特性,所以无法实现统一的onMeasure。

Layout的过程

viewGroup会遍历所有子元素并调用 其layout方法,layout方法来确定子元素的位置。ViewGroup如下:

  1. protected abstract void onLayout(boolean changed,
  2. int l , int t, int r, int b) ;

AbsoluteLayout重载的onLayout方法:

  1. @Override
  2. protected void onLayout(boolean changed, int l, int t,
  3. int r, int b) {
  4. int count = getChildCount();
  5. for (int i = 0; i < count; i++) {
  6. View child = getChildAt(i);
  7. if (child.getVisibility() != GONE) {
  8. AbsoluteLayout.LayoutParams lp = (AbsoluteLayout.LayoutParams) child.getLayoutParams();
  9. int childLeft = mPaddingLeft + lp.x;
  10. int childTop = mPaddingTop + lp.y;
  11. child.layout(childLeft, childTop,
  12. childLeft + child.getMeasuredWidth(),
  13. childTop + child.getMeasuredHeight());
  14. }
  15. }
  16. }

需要子类自己实现。看下view的layout:

  1. public void layout(int l, int t , int r, int b) {
  2. if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT ) != 0) {
  3. onMeasure(mOldWidthMeasureSpec , mOldHeightMeasureSpec) ;
  4. mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
  5. }
  6. int oldL = mLeft;
  7. int oldT = mTop;
  8. int oldB = mBottom;
  9. int oldR = mRight;
  10. boolean changed = isLayoutModeOptical( mParent) ?
  11. setOpticalFrame(l, t , r, b) : setFrame(l, t , r, b);
  12. if (changed || ( mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED ) {
  13. onLayout(changed, l, t , r, b);
  14. mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;
  15. ListenerInfo li = mListenerInfo;
  16. if (li != null && li.mOnLayoutChangeListeners != null) {
  17. ArrayList<OnLayoutChangeListener> listenersCopy =
  18. (ArrayList<OnLayoutChangeListener>)li. mOnLayoutChangeListeners .clone();
  19. int numListeners = listenersCopy.size() ;
  20. for ( int i = 0 ; i < numListeners; ++i) {
  21. listenersCopy.get(i).onLayoutChange( this, l, t, r , b, oldL, oldT , oldR, oldB);
  22. }
  23. }
  24. }
  25. mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
  26. mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
  27. }

在setFrame中确定了view的四个顶点坐标。mleft等。onLayout view也没有具体实现,要看具体的。以LinearLayout为例:

  1. protected void onLayout(boolean changed, int l , int t, int r, int b) {
  2. if (mOrientation == VERTICAL) {
  3. layoutVertical(l, t, r , b);
  4. } else {
  5. layoutHorizontal(l, t, r , b);
  6. }
  7. }

以layoutVertical为例:

  1. void layoutVertical(int left, int top , int right, int bottom) {
  2. final int paddingLeft = mPaddingLeft ;
  3. int childTop ;
  4. int childLeft ;
  5. // Where right end of child should go
  6. final int width = right - left;
  7. int childRight = width - mPaddingRight ;
  8. // Space available for child
  9. int childSpace = width - paddingLeft - mPaddingRight ;
  10. final int count = getVirtualChildCount() ;
  11. final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
  12. final int minorGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;
  13. switch (majorGravity) {
  14. case Gravity.BOTTOM:
  15. // mTotalLength contains the padding already
  16. childTop = mPaddingTop + bottom - top - mTotalLength;
  17. break;
  18. // mTotalLength contains the padding already
  19. case Gravity.CENTER_VERTICAL:
  20. childTop = mPaddingTop + (bottom - top - mTotalLength) / 2;
  21. break;
  22. case Gravity.TOP:
  23. default :
  24. childTop = mPaddingTop;
  25. break;
  26. }
  27. for (int i = 0; i < count ; i++) {
  28. final View child = getVirtualChildAt(i) ;
  29. if (child == null) {
  30. childTop += measureNullChild(i);
  31. } else if (child.getVisibility() != GONE) {
  32. final int childWidth = child.getMeasuredWidth() ;
  33. final int childHeight = child.getMeasuredHeight();
  34. final LinearLayout.LayoutParams lp =
  35. (LinearLayout.LayoutParams) child.getLayoutParams() ;
  36. int gravity = lp. gravity;
  37. if (gravity < 0) {
  38. gravity = minorGravity;
  39. }
  40. final int layoutDirection = getLayoutDirection() ;
  41. final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection) ;
  42. switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
  43. case Gravity. CENTER_HORIZONTAL :
  44. childLeft = paddingLeft + ((childSpace - childWidth) / 2)
  45. + lp. leftMargin - lp.rightMargin ;
  46. break;
  47. case Gravity.RIGHT:
  48. childLeft = childRight - childWidth - lp. rightMargin;
  49. break;
  50. case Gravity.LEFT:
  51. default:
  52. childLeft = paddingLeft + lp. leftMargin;
  53. break;
  54. }
  55. if (hasDividerBeforeChildAt(i)) {
  56. childTop += mDividerHeight;
  57. }
  58. childTop += lp.topMargin;
  59. setChildFrame(child , childLeft, childTop + getLocationOffset(child) ,
  60. childWidth, childHeight);
  61. childTop += childHeight + lp. bottomMargin + getNextLocationOffset(child);
  62. i += getChildrenSkipCount(child , i);
  63. }
  64. }
  65. }

主要看以下代码:

  1. final int childWidth = child.getMeasuredWidth() ;
  2. final int childHeight = child.getMeasuredHeight() ;
  3. setChildFrame(child , childLeft, childTop + getLocationOffset(child) ,
  4. childWidth , childHeight);
  5. childTop += childHeight + lp. bottomMargin + getNextLocationOffset(child);

top会逐渐增大,所以会往下排。setChildFrame仅仅是调用子元素的layout方法

  1. private void setChildFrame(View child, int left, int top , int width, int height) {
  2. child.layout(left, top, left + width , top + height);
  3. }

通过子元素的layout来确定自身


draw过程

它有以下几步:

  • 绘制背景,(canvas)

  • 绘制自己。(onDraw)

  • 绘制children(dispatchDraw)

  • 绘制装饰(onDrawScrollBars)

  • 看下view的draw源码:

  1. public void draw(Canvas canvas) {
  2. final int privateFlags = mPrivateFlags;
  3. final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK ) == PFLAG_DIRTY_OPAQUE &&
  4. (mAttachInfo == null || !mAttachInfo .mIgnoreDirtyState );
  5. mPrivateFlags = (privateFlags & ~ PFLAG_DIRTY_MASK ) | PFLAG_DRAWN;
  6. /*
  7. * Draw traversal performs several drawing steps which must be executed
  8. * in the appropriate order:
  9. *
  10. * 1. Draw the background
  11. * 2. If necessary, save the canvas' layers to prepare for fading
  12. * 3. Draw view's content
  13. * 4. Draw children
  14. * 5. If necessary, draw the fading edges and restore layers
  15. * 6. Draw decorations (scrollbars for instance)
  16. */
  17. // Step 1, draw the background, if needed
  18. int saveCount;
  19. if (!dirtyOpaque) {
  20. drawBackground(canvas);
  21. }
  22. // skip step 2 & 5 if possible (common case)
  23. final int viewFlags = mViewFlags;
  24. boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL ) != 0;
  25. boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL ) != 0;
  26. if (!verticalEdges && !horizontalEdges) {
  27. // Step 3, draw the content
  28. if (!dirtyOpaque) onDraw(canvas) ;
  29. // Step 4, draw the children
  30. dispatchDraw(canvas) ;
  31. // Step 6, draw decorations (scrollbars)
  32. onDrawScrollBars(canvas) ;
  33. if ( mOverlay != null && !mOverlay.isEmpty()) {
  34. mOverlay .getOverlayView().dispatchDraw(canvas) ;
  35. }
  36. // we're done...
  37. return;
  38. }

viewgroup中的dispatchDraw用于遍历子view并调用子view的draw方法。这样就一层层的传下去。到此源码分析就结束了。在绘制view的时候经常会在activity中获得view的宽高,因为activity的生命周期和view不同步,在oncreate中无法获取到view的宽高,接下来讲讲activity中如何获取view。


三、view宽高确定


  • onWindowFocusChanged:view己经初始化完毕,宽高己经准备好。当Activity得到焦点和失去焦点均会被调用,所以它会调用多次。

  • 通过view.post,将一个runnable投弟到消息队列尾部,等待looper调用时,view己经初始化好

    1. protected void onStart(){
    2. super.onStart();
    3. view.post(new Runnable(){
    4. public void run(){
    5. int width = view.getMeasuredWidth();
    6. int height = new .getMeasuredHeight();
    7. }
    8. })
    9. }

  • ViewTreeObserver: 

  • 使用ViewTreeObserver众多回调接口来完成,如OnGlobalLayoutListener,当view树状态发生改变时或内部view可见性发生改变时会回调

    1. ViewObserver obserber = view.getViewObserver ();
    2. obserber.addOnGlobalLayoutListener(new OnGlobalLayoutListener(){
    3. public void onGlobalLayout(){
    4. obserber.removeOnGlobalLayoutListener(this);
    5. int width = view.getMeasuredWidth();
    6. int height = new .getMeasuredHeight();
    7. }
    8. })

  • 通过view进行measure来得到view的宽高


    1. int width = MeasureSpec.makeMeasureSpec(100,Measure.EXACTLY);//确定值
    2. int height= MeasureSpec.makeMeasureSpec(100,Measure.EXACTLY);//确定值
    3. view.measure(width,height);
    4. //对于wrap_content:
    5. int width = MeasureSpec.makeMeasureSpec((1<<30)-1,Measure.AT_MOST);//wrap_content
    6. int height= MeasureSpec.makeMeasureSpec((1<<30)-1,Measure.AT_MOST);//wrap_content
    7. view.measure(width,height);


    四、自定义view中注意事项


自定义View需要注意的事项:

  • 如果是继承view或者viewGroup,让view支持wrap_content。

  • 如果有必要,让view支持padding。

  • View中如果有动画或者线程,要在onDetachedFromWindow中及时停止。当view的Activity退出或者当前view被remove时,调用它。


声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小舞很执着/article/detail/738038
推荐阅读
相关标签
  

闽ICP备14008679号