• 第四章 View的工作原理


    知识准备

        

    ViewRoot

    • ViewRoot对应ViewRootImpl类,是连接WindowManager与DecorView的纽带。
    • View的三大流程都是通过ViewRoot完成的。
    • ActivityThread中,Activity对象被创建完毕时,会将DecorView添加到Window中,同时创建ViewRootImpl对象,并将ViewRootImpl对象和DecorView对象建立关联。
    1. //创建ViewRootImpl对象
    2. root = new ViewRootImpl(view.getContext(),display);
    3. //添加关联
    4. root.setView(view,wparams,panelparentView);
    1. View绘制流程从ViewRoot的performTraversals方法开始,经过measure、layout、draw三大流程后才将View绘制出来。
    2. performTraversals方法(8W多行代码),会依次调用performMeasure、performLayout、performDraw方法(这三个方法分别完成顶级View - DecorView 的measure、layout、draw方法)
    3. 其中performMeasure方法会调用measure方法,在measure方法中又会调用onMeasure方法,onMeasure方法则会对所有子元素进行measure,从而达到measure流程从父容器传递到子元素中的目的。接着子元素会重复父容器的measure过程,如此反复完成整个View树的遍历。
    4. 最后,perfromLayout、performDraw的传递过程也是类似的,唯一不同,而performDraw的传递过程在draw方法中通过dispatchDraw来实现。


    MeasureSpec:
            
    1. MeasureSpec代表一个32位int值,高2位代表SpecMode(测量模式),低30位代表SpecSize(规格大小)
    2. 小白科普:Java中int为4个字节,Android使用第一个高位字节存储Mode,剩下三个字节存储Size
    系统内部是通过MeasureSpec进行View测量的,但正常情况下,都是使View指定MeasureSpec。 
    View测量时候系统会将LayoutParams在父容器约束下转换成对应的MeasureSpec,然后再根据这个MeasureSpec来确定View测量后的宽高。因此,Measure需要由LayoutParams和父容器一起决定。 



    另外,对于顶级View(DecorView),其MeasureSpec由窗口尺寸和其自身的LayoutParams共同决定。Measure一旦决定后,onMeasure中即可获得View的测量宽高。
    以下是DecorView获得MeasureSpec的过程
        其中rootDimension是DecorView的LayoutParam指定的宽高。
    1. /**
    2. * DecorView中,MeasureSpec产生过程
    3. * 根据LayoutParams划分,并产生MeasureSpec
    4. */
    5. private static int getRootMeasureSpec(int windowSize, int rootDimension) {
    6. int measureSpec;
    7. switch (rootDimension) {
    8. case ViewGroup.LayoutParams.MATCH_PARENT:
    9. // Window can't resize. Force root view to be windowSize.
    10. measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
    11. break;
    12. case ViewGroup.LayoutParams.WRAP_CONTENT:
    13. // Window can resize. Set max size for root view.
    14. measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
    15. break;
    16. default:
    17. // Window wants to be an exact size. Force root view to be that size.
    18. measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
    19. break;
    20. }
    21. return measureSpec;
    22. }
    上述代码很明确的可以看出DecorView的SpecMeasure的创建过程,如果是MATCH_PARENT那它的大小就是当前窗口的大小,
    如果是WRAP_CONTENT:最大模式,大小是不定的,但是最大不能超过windowSize。


    我们关心的是普通的View的绘制过程。下面就来分析一下普通View的measure过程:
    1. 对于普通View(布局中的View),View的measure方法需要由ViewGroup传递过来
    2. 所以我们先看看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. }
    很明显,childMeasureSpec的值和父容器的MeasureSpec和自身的LayoutParam,以及自身的padding,margin相关。
    从这一行中可以很明显的看出
    1. final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
    2. mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
    3. + widthUsed, lp.width);

    那到底是如何使用协调这些参数的呢? 其具体情况可以看一下getChildMeasureSpec的实现


    代码如下:
        先解释一下padding其实是父容器已经占用了的空间,很明显其就是所设置的子view padding和margin的和。
    则第五行这个size就是父容器剩下可以使用的size。
            在Switch代码中可以看到子view的MeasureSpec是由ViewGroup的MODEL和子View的LayoutParams共同决定的。
    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 = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
    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 = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
    57. resultMode = MeasureSpec.UNSPECIFIED;
    58. }
    59. break;
    60. }
    61. return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
    62. }
    这个是上面这段代码的总结图:
            
    表格中parentSize就是代码里的size也就是父容器目前可用的Size。

    接下来的分析情况下文


    View的工作过程:
        主要流程是measure、layout、draw这三大过程。
    • measure 确定View宽高
    • layout确定View最终宽高和四个顶点位置
    • draw将View绘制到屏幕


    我们继续上面的measure。
            

    探究Measure过程

    • 两种情况,若只是一个原始的View,通过measure方法就完成了测量过程;如果是ViewGroup,除了完成自身的measure过程,还需要遍历子元素的measure方法,各个子元素递归去执行这个部分。
    • View的measure方法是一个final类型的方法 - 意味着子类不能重写该方法,因此仔细研究onMeasure方法的实现效果会更好。

    1. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    2. setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
    3. getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    4. }
    setMeasuredDimension 方法会设置view宽高的测量值,因此我们只需看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. }
    1. getDefaultSize方法直接就是根据测量模式返回measureSpec中的specSize,而这个specSize就是View测量后的大小。
    2. 注意:View测量后大小 与 View最终大小 需要区分,是两个东西,因为View最终大小是在layout阶段确定的,但两者几乎所有情况都是相等的。
    1. 接下来,再继续探究getDefaultSize方法的第一个参数,从onMeasure方法中可知,该参数来源于下面两个方法
    2. protected int getSuggestedMinimumWidth() {
    3. return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
    4. }
    5. protected int getSuggestedMinimumHeight() {
    6. return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight());
    7. }
    8. 上述两个方法实现原理都一致,判断有没有背景,如果有,返回两者较大的宽高,没有则返回自己的宽高(android:minwith这个属性指定的值)。
    9. 那么,问题来了,背景最小宽高原理是什么?
    10. public int getMinimumWidth() {
    11. final int intrinsicWidth = getIntrinsicWidth();
    12. return intrinsicWidth > 0 ? intrinsicWidth : 0;
    13. }
    14. 上述代码中可见,Drawable的原始宽度,如果没有原始宽度,则返回0
    15. 小白科普:ShapeDrawable无原始宽高,而BimapDrawable有原始宽高(即图片尺寸)

    通过上面的分析,我们是不是发现了一个问题:既WRAP_CONTENT是不是起不到warp_content的作用?
            我们看,View的绘制和其LayoutParam与父容器的MeasureSpec有关,查看那张表,我们看到当view设定为Wrap_content的时候,其View的Measure MODEL为 AT_MOST,其size为parentsize(也就是父容器可用的sieze)。也就是说onMeasure方法中得到的测量大小其实和match_parent没有任何区别(都是parentsize)

    其实解决也很简单
    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. }
    对于Wrap_cont的我们自己指定一个height或者weight即可,当然这个宽高要自己算。

    1. 再谈谈ViewGroup的measure过程
    2. 与上面的主要区别:
    3. 1. 除了完成自己measure过程还要遍历调用子元素的measure方法,各个子元素再递归执行该过程。
    4. 2. ViewGroup是抽象类,没有重写View的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. }
      11. protected void measureChild(View child, int parentWidthMeasureSpec,
      12. int parentHeightMeasureSpec) {
      13. final LayoutParams lp = child.getLayoutParams();
      14. final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
      15. mPaddingLeft + mPaddingRight, lp.width);
      16. final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
      17. mPaddingTop + mPaddingBottom, lp.height);
      18. child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
      19. }
      View在measure过程中会对每一个子元素进行measure。 
      再细说下,measureChild方法的思路: 
      1. 取出子元素的LayoutParams 
      2. 通过getChildMeasureSpec创建子元素的MeasureSpec 
      3. 将MeasureSpec传递给View的Measure方法进行测量。

           问题:为什么ViewGroup不像View一样对其onMeasure方法做统一实现? 
           因为不同的ViewGroup子类会有不同的特性,因此其中的onMeasure细节不相同。
    引申:
       获取View宽高方法不当,可能会获取错误。 
           原因:View的measure过程Activity生命周期方法执行顺序是不确定的,无法保证Activity执行了onCreate、onStart、onReasume时,View测量完毕。 
       如果View还没有完成测量,则获取的宽高会是0. 

       给出四种方法解决:
            1、onWindowFocusChanged - 该方法被调用时候,View已经测量完毕,能够正确获取View宽高。 
    注意:该方法会被调用多次,Activity窗口得到焦点与失去焦点时均会被调用一次(继续执行,暂停执行)。
            
    1. public void onWindowFocusChanged(boolean hasWindowFocus) {
    2. super.onWindowFocusChanged(hasWindowFocus);
    3. if(hasWindowFocus){
    4. //获取宽高
    5. int with = view.getMeasuredWidth();
    6. int height = view.getMeasuredHeight();
    7. }
    8. }
    2、view.post(runnable) 
    通过post将一个runnable投递到消息队列队尾,等待Looper调用此runnable时,View已初始化完毕。
    1. protected void onStart(){
    2. super.onStart();
    3. view.post(new Runnable(){
    4. @override
    5. public void run(){
    6. //获取宽高
    7. int with = view.getMeasuredWidth();
    8. int height = view.getMeasuredHeight();
    9. }
    10. })
    11. }
    3、ViewTreeObserver 该类有众多回调接口,其中的OnGlobalLayoutListener接口,当View树状态发生变化或者View树内部的View的可见性发生改变,该方法都会被回调,利用此特性,可获得宽高。
    1. protected void onStart(){
    2. super.onStart();
    3. viewTreeObserver observer = view.getViewTreeObserver();
    4. observer.addOnGlobalLayoutListener(new OnGlobalListener(){
    5. public void onGlobalLayout(){
    6. view.getViewTreeObserver().removeGlobalOnlayoutListener(this);
    7. //获取宽高
    8. int with = view.getMeasuredWidth();
    9. int height = view.getMeasuredHeight();
    10. }
    11. })
    12. }
    1. view.measure(int widthMeasureSpec,int heightMeasureSpec)
    2. 手动对View进行获取,根据ViewLayoutParams不同,而采取不同手段。(因不常用,这里就不详细说明)
    4、view.measure(int widthMeasureSpec,int heightMeasureSpec) 手动对View进行获取,根据View的LayoutParams不同,而采取不同手段。(因不常用,这里就不详细说明)


    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. //设定四个顶点位置
    7. int oldL = mLeft;
    8. int oldT = mTop;
    9. int oldB = mBottom;
    10. int oldR = mRight;
    11. boolean changed = isLayoutModeOptical(mParent) ?
    12. setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
    13. if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
    14. onLayout(changed, l, t, r, b);
    15. mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;
    16. ListenerInfo li = mListenerInfo;
    17. if (li != null && li.mOnLayoutChangeListeners != null) {
    18. ArrayList<OnLayoutChangeListener> listenersCopy =
    19. (ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();
    20. int numListeners = listenersCopy.size();
    21. for (int i = 0; i < numListeners; ++i) {
    22. listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
    23. }
    24. }
    25. }
    26. mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
    27. mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
    28. }
    1. setFrame方法设定View四个顶点位置 
    2. 调用onLayout方法,父容器确定子元素位置 
    另外,与onMeasure方法相似,onLayout方法也是各不相同的。

    draw过程:
           draw主要作用是将View绘制到屏幕上 
           绘制过程: 
    1. 绘制背景(background.draw(canvas)) 
    2. 绘制自己(onDraw) 
    3. 绘制children(dispatchDraw) 
    4. 绘制装饰(onDrawScrollBars)

    1. View的绘制过程传递是通过dispatchDraw来实现,dispatchDraw会遍历所有子元素的draw方法,另外,View还有一个特殊的方法setWillNotDraw
    2. public void setWillNotDraw(boolean willNotDraw) {
    3. setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK);
    4. }
    5. setFlags - 该方法可设置优化标记
    6. 如果View不需要绘制任何内容,那么将设置标记为true,系统会相应优化。默认情况下,View不启用这个标记位,但ViewGroup会默认启动该优化标记。
    7. 而实际开发意义:当我们自定义控件继承于ViewGroup并且本身不具备绘制功能,则开启标记。而如果明确知道一个ViewGroup需要通过onDraw来绘制内容时候,则需要显式关闭WILL_NOT_DRAW这个标记位。

       



    下面的流程图便于方便理解View的工作过程
    View生命周期


        
     

    View工作流程




    自定义VIEW:
        

    1. 分类:

    1. 继承View从写onDraw()方法 
      采用这种方式需要自己支持wrap_content,并且padding也需要自己处理。
    2. 继承ViewGroup派生特殊的Layout 
      当效果看起来很像集中View组合在一起的时候,可以采用这种方法来实现。
    3. 继承特定的View 
      一般用于扩展某种已有的View的功能。比如继承TextView进行增强等。
    4. 继承特定的ViewGroup 
      比如继承LinearLayout进行增强等,或者继承RelativeLayout写组合控件。

    2. 自定义View注意事项:

    1. 让View支持wrap_content 
      直接继承View或ViewGroup的控件,如果不支持wrap_content,则该控件的表现效果和match_parent一样。
    2. 如果有必要,支持padding 
      直接继承View的控件,不在draw()方法中处理padding,则padding属性失效。继承ViewGroup的控件必须要在onMeasure()和onLayout()中考虑padding和margin,否则这两属性失效。
    3. 尽量不要在View中使用Handler 
      View本身就提供了post系列方法,完全可以替代handler。除非很明确地要使用handler。
    4. View中如果有线程或动画,需要及时停止 
      View.onDetachedFromWindow()中停止。如果不及时处理,会造成内存溢出。当包含此View的Activity退出或者此View被remove()时,就会调用该方法。
    5. View带有滑动嵌套情形时,需要处理好滑动冲突

    举一个很简单的例子:
            
    1. package com.ryg.chapter_4.ui;
    2. import com.ryg.chapter_4.R;
    3. import android.content.Context;
    4. import android.content.res.TypedArray;
    5. import android.graphics.Canvas;
    6. import android.graphics.Color;
    7. import android.graphics.Paint;
    8. import android.util.AttributeSet;
    9. import android.view.View;
    10. public class CircleView extends View {
    11. private int mColor = Color.RED;
    12. private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    13. public CircleView(Context context) {
    14. super(context);
    15. init();
    16. }
    17. public CircleView(Context context, AttributeSet attrs) {
    18. this(context, attrs, 0);
    19. }
    20. public CircleView(Context context, AttributeSet attrs, int defStyleAttr) {
    21. super(context, attrs, defStyleAttr);
    22. TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CircleView);
    23. mColor = a.getColor(R.styleable.CircleView_circle_color, Color.RED);
    24. a.recycle();
    25. init();
    26. }
    27. private void init() {
    28. mPaint.setColor(mColor);
    29. }
    30. @Override
    31. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    32. super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    33. int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
    34. int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
    35. int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
    36. int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
    37. if (widthSpecMode == MeasureSpec.AT_MOST
    38. && heightSpecMode == MeasureSpec.AT_MOST) {
    39. setMeasuredDimension(200, 200);
    40. } else if (widthSpecMode == MeasureSpec.AT_MOST) {
    41. setMeasuredDimension(200, heightSpecSize);
    42. } else if (heightSpecMode == MeasureSpec.AT_MOST) {
    43. setMeasuredDimension(widthSpecSize, 200);
    44. }
    45. }
    46. @Override
    47. protected void onDraw(Canvas canvas) {
    48. super.onDraw(canvas);
    49. final int paddingLeft = getPaddingLeft();
    50. final int paddingRight = getPaddingLeft();
    51. final int paddingTop = getPaddingLeft();
    52. final int paddingBottom = getPaddingLeft();
    53. int width = getWidth() - paddingLeft - paddingRight;
    54. int height = getHeight() - paddingTop - paddingBottom;
    55. int radius = Math.min(width, height) / 2;
    56. canvas.drawCircle(paddingLeft + width / 2, paddingTop + height / 2,
    57. radius, mPaint);
    58. }
    59. }
    如何定义属性呢?
        1、新建一个xml用来配置属性:
        
    1. <?xml version="1.0" encoding="utf-8"?>
    2. <resources>
    3. <declare-styleable name="CircleView">
    4. <attr name="circle_color" format="color" />
    5. </declare-styleable>
    6. </resources>
    <declare-styleable name="CircleView"> 这句表示自定义一个属性集合“CircleView”

    2、在View的构造方法中解析自定义属性的值并作出相应处理。
        
    1. public CircleView(Context context, AttributeSet attrs, int defStyleAttr) {
    2. super(context, attrs, defStyleAttr);
    3. TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CircleView);
    4. mColor = a.getColor(R.styleable.CircleView_circle_color, Color.RED);
    5. a.recycle();
    6. init();
    7. }

        更详细的例子:
                待补。。。  好困
     







  • 相关阅读:
    POJ_1485_dp
    POJ_1376_bfs
    [noi1994]海盗
    [noi1755]Trie
    [luogu3733]八纵八横
    [noi1774]array
    [noi1773]function
    [noi1754]SA
    [noi1779]D
    [bzoj4873]寿司餐厅
  • 原文地址:https://www.cnblogs.com/You0/p/5984470.html
Copyright © 2020-2023  润新知