自定义view

本文转自 http://blog.csdn.net/u011791526/article/details/53925370

更多内容可参考

       (一)自定义View的分类点击打开链接
       (二)自定义View的构造方法及自定义属性点击打开链接

       (三)自定义View的常用方法(测量、绘制、位置)点击打开链接

       (四)View的生命周期

       (五)自定义View的具体实现

       (六)事件分发机制

       上一节中,我们讨论了自定义View的几个常用方法,掌握这些方法的使用,是自定义View的核心。那么这些方法是怎么调度的?我们一起从源码来看看吧。

        我们平时看到的android屏幕,它的层次是这样的



         DecorView窗口的根View,即整个窗口的根视图,它继承了FrameLayout

        窗口布局ViewDecorView的子View

        ①它的风格属性由application或者activity中的android:Theme = “”指定,比如为Activity配置xml属性:

[java]  view plain  copy
  1. <span style="font-size:14px;">      android:theme="@android:style/Theme.NoTitleBar"</span>  

         也可以由activity的requestWindowFeature方法(最终调用了PhoneWindow.requestFeature方法)指定,比如

[java]  view plain  copy
  1. <span style="font-size:14px;">       requestWindowFeature(Window.FEATURE_NO_TITLE);</span>  

        上述两种方式指定风格属性以后,系统会根据他们拿到相应的窗口布局文件(下面源码中的features就是风格属性,layoutResource就是根据风格属性指定的窗口布局文件

       ②窗口布局文件中包含一个存放Activity自定义布局文件的ViewGroup视图,一般为FrameLayout 其id 为: android:id="@android:id/content"

        源码如下

[java]  view plain  copy
  1. //1、根据requestFreature()和Activity节点的android:theme="" 设置好 features值      
  2. //2 根据设定好的 features值,即特定风格属性,选择不同的窗口修饰布局文件    
  3. int layoutResource;  //窗口修饰布局文件      
  4. int features = getLocalFeatures();    
  5. // System.out.println("Features: 0x" + Integer.toHexString(features));    
  6. if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {    
  7.     if (mIsFloating) {    
  8.         layoutResource = com.android.internal.R.layout.dialog_title_icons;    
  9.     } else {    
  10.         layoutResource = com.android.internal.R.layout.screen_title_icons;    
  11.     }    
  12.     // System.out.println("Title Icons!");    
  13. else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0) {    
  14.     // Special case for a window with only a progress bar (and title).    
  15.     // XXX Need to have a no-title version of embedded windows.    
  16.     layoutResource = com.android.internal.R.layout.screen_progress;    
  17.     // System.out.println("Progress!");    
  18. }     
  19. //...       
  20. //3 选定了窗口修饰布局文件 ,添加至DecorView对象里,并且指定mcontentParent值    
  21. View in = mLayoutInflater.inflate(layoutResource, null);    
  22. decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));    
  23. ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);   
                contentParent:就是上面窗口布局文件中id为"@android:id/content"的View,他就是我们activity的布局文件的父布局
            Activity的布局文件:在activity中,我们通过setContentView方法指定Activity的布局文件,这个文件就是被存放到了contentParent中

         以上就是android系统屏幕视图的层次了,这里推荐一篇文章里面的讲解更加详细View添加到窗口的过程


         接下来讲一讲DecorView的绘制过程,这里有兴趣的可以了解一下

          DecorView是ViewRoot类调用View的生命周期方法绘制完成的

          \frameworks\base\core\java\android\view\ViewRootImpl.java

          通过ViewRoot调用performTranversals开始绘制View,依次通过measure、layout、draw三个过程

[java]  view plain  copy
  1. private void performTraversals() {  
  2.       final View host = mView;  
  3.       ……  
  4.       if (layoutRequested) {  
  5.           windowSizeMayChange |= measureHierarchy(host, lp, res,  
  6.                   desiredWindowWidth, desiredWindowHeight);///  
  7.       }  
  8.       ……  
  9.       if (mFitSystemWindowsRequested) {  
  10.           if (mLayoutRequested) {  
  11.               windowSizeMayChange |= measureHierarchy(host, lp,///  
  12.                       mView.getContext().getResources(),  
  13.                       desiredWindowWidth, desiredWindowHeight);  
  14.           }  
  15.       }  
  16.    if (!mStopped) {  
  17.            if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()  
  18.                       || mHeight != host.getMeasuredHeight() || contentInsetsChanged) {              
  19.                   performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);///  
  20.                   }  
  21.            if (measureAgain) {  
  22.                       performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);///  
  23.                   }  
  24.    }  
  25.    ……  
  26.   if(didLayout){  
  27.       performLayout(lp, desiredWindowWidth, desiredWindowHeight);///  
  28.   }  
  29.    ……  
  30.   if(!cancelDraw&&!newSurface)  
  31.   {  
  32.       performDraw();///  
  33.   }  
  34. }  
              源码中有几个比较重要的方法,他们在满足条件时会被调用

         measureHierarchy——>performMeasure——>measure
         performMeasure——>measure
         performLayout——>layout
         performDraw——>draw

         因为performTranversals可能会被调用多次,所以measure方法也可能被调用多次,详情可参考大神的文章点击打开链接

          

        讲了这么多,读者你是不是一脸懵逼呀,上面的内容和View的生命周期有什么关系呢。

        我们还是来看看源码里View和ViewGroup怎么调用几个主要方法的吧

     

     一、View

       measure过程

      View的该方法由父View调用

[java]  view plain  copy
  1. public final void measure(int widthMeasureSpec, int heightMeasureSpec) { //两个参数是父控件的长和宽信息    
  2.        ……  
  3.        widthMeasureSpec = MeasureSpec.adjust(widthMeasureSpec, optical ? -oWidth : oWidth); //根据父控件长宽信息获取自己的长宽信息  
  4.        heightMeasureSpec = MeasureSpec.adjust(heightMeasureSpec, optical ? -oHeight : oHeight);   
  5.        ……   
  6.        onMeasure(widthMeasureSpec, heightMeasureSpec);//测量的是本View的信息    
  7.        ……   
  8.       }  
      onMeasure方法一般在自定义的View中重写

[java]  view plain  copy
  1. @Override  
  2.     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
  3.        // super.onMeasure(widthMeasureSpec, heightMeasureSpec);//调用基类的onMeasure方法  
  4.         setMeasuredDimension(1010);//设置新的长宽值  
  5.     }  
      如果我们重写了onMeasure方法,通常要调用setMeasuredDimension方法设置我们指定的长宽值

[java]  view plain  copy
  1. protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {  
  2.        ……  
  3.        mMeasuredWidth = measuredWidth;  
  4.        mMeasuredHeight = measuredHeight;  
  5.        ……  
  6.    }  
     如果我们没有重写onMeasure方法,则默认调用View类的onMeasure方法,该方法为我们的控件指定了默认长宽值

[java]  view plain  copy
  1. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
  2.         setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),  
  3.                 getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));  
  4.     }  
     继承View的自定义View不需要测量子View长宽

    Layout过程

[java]  view plain  copy
  1. /** 
  2.     * Assign a size and position to a view and all of its 
  3.     * descendants 
  4.     * 
  5.     * <p>This is the second phase of the layout mechanism. 
  6.     * (The first is measuring). In this phase, each parent calls 
  7.     * layout on all of its children to position them. 
  8.     * This is typically done using the child measurements 
  9.     * that were stored in the measure pass().</p> 
  10.     * 
  11.     * <p>Derived classes should not override this method. 
  12.     * Derived classes with children should override 
  13.     * onLayout. In that method, they should 
  14.     * call layout on each of their children.</p> 
  15.     */  
  16.    public void layout(int l, int t, int r, int b) {  
  17.       ……  
  18.        boolean changed = isLayoutModeOptical(mParent) ?  
  19.                setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);//放置自己的位置  
  20.   
  21.       ……  
  22.            onLayout(changed, l, t, r, b);  
  23.       ……  
  24.    }  
       源码的注释说的很清楚,这个方法由View的父View调用。是布局机制的第二步。

     在实际运用中,我们并不重写layout方法,而是重写onLayout方法,遍历每一个子View,然后子View再调用layout放置自己

所以,继承View时,onLayout方法完全可以不重写,但继承ViewGroup时,onLayout方法一定必须重写(稍后会做分析)

[java]  view plain  copy
  1. /** 
  2.     * Called from layout when this view should 
  3.     * assign a size and position to each of its children. 
  4.     * 
  5.     * Derived classes with children should override 
  6.     * this method and call layout on each of 
  7.     * their children. 
  8.     * @param changed This is a new size or position for this view 
  9.     * @param left Left position, relative to parent 
  10.     * @param top Top position, relative to parent 
  11.     * @param right Right position, relative to parent 
  12.     * @param bottom Bottom position, relative to parent 
  13.     */  
  14.    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {  
  15.    }  

     draw过程

[java]  view plain  copy
  1. /** 
  2.      * Manually render this view (and all of its children) to the given Canvas. 
  3.      * The view must have already done a full layout before this function is 
  4.      * called.  When implementing a view, implement 
  5.      * {@link #onDraw(android.graphics.Canvas)} instead of overriding this method. 
  6.      * If you do need to override this method, call the superclass version. 
  7.      * 
  8.      * @param canvas The Canvas to which the View is rendered. 
  9.      */  
  10.     public void draw(Canvas canvas) {  
  11.         /* 
  12.          * Draw traversal performs several drawing steps which must be executed 
  13.          * in the appropriate order: 
  14.          * 
  15.          *      1. Draw the background 绘制背景 
  16.          *      2. If necessary, save the canvas' layers to prepare for fading 如果需要保持画板图层 
  17.          *      3. Draw view's content 绘制内容 
  18.          *      4. Draw children 绘制子View 
  19.          *      5. If necessary, draw the fading edges and restore layers 如果需要绘制边并恢复图层 
  20.          *      6. Draw decorations (scrollbars for instance)绘制装饰(如滚动条) 
  21.          */  
  22.         // Step 1, draw the background, if needed  
  23.         ……  
  24.         // skip step 2 & 5 if possible (common case)  
  25.         ……  
  26.         // Step 3, draw the content  
  27.            onDraw(canvas);  
  28.         ……  
  29.         // Step 4, draw the children  
  30.             dispatchDraw(canvas);  
  31.   
  32.        // Step 6, draw decorations (scrollbars)  
  33.             onDrawScrollBars(canvas);  
  34.         ……  
  35.        // we're done...  
  36.             return;  
  37.         }  
         注释里说,draw方法用来绘制view。在调用之前必须保证布局过程已经完成。继承View时,重写onDraw方法,而不是本方法,如果一定要重写此方法,一定记得调用super.draw( )。上述代码第三步,调用了onDraw方法。
[java]  view plain  copy
  1. /** 
  2.      * Implement this to do your drawing. 
  3.      * 
  4.      * @param canvas the canvas on which the background will be drawn 
  5.      */  
  6.     protected void onDraw(Canvas canvas) {  
  7.     }  
      重写onDraw方法,绘制我们自己的View
[java]  view plain  copy
  1. /** 
  2.      * Called by draw to draw the child views. This may be overridden 
  3.      * by derived classes to gain control just before its children are drawn 
  4.      * (but after its own view has been drawn). 
  5.      * @param canvas the canvas on which to draw the view 
  6.      */  
  7.     protected void dispatchDraw(Canvas canvas) {  
  8.   
  9.     }  
      重写dispatchDraw方法绘制子View

      二、 ViewGroup

       measure过程

         ViewGroup调用measure测量自己,过程和View一样的。但如果我们需要测量子View的大小(如果后面的过程里需要获取子View的大小,一定必须先测量)就需要重写onMeasure方法,然后遍历子View依次测量了     

[java]  view plain  copy
  1. @Override  
  2.     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
  3.       int count = getChildCount();  
  4.       for (int i = 0; i < count; i++) {  
  5.         View child = getChildAt(i);  
  6.         child.measure(widthMeasureSpec, heightMeasureSpec);//方法1  
  7.        // measureChild(child, widthMeasureSpec, heightMeasureSpec);//方法2  
  8.     }  
  9.       //  measureChildren(widthMeasureSpec, heightMeasureSpec);  //方法3      
  10.         super.onMeasure(widthMeasureSpec, heightMeasureSpec);  
  11.     }  
      测量子view一共有三个方法

     ①child.measure(widthMeasureSpec, heightMeasureSpec)

     ②measureChild(child,widthMeasureSpec, heightMeasureSpec)

     ③measureChildren(widthMeasureSpec, heightMeasureSpec)

      在ViewGroup的源码里。measureChild最终调用了measure方法

[java]  view plain  copy
  1. /** 
  2.     * Ask one of the children of this view to measure itself, taking into 
  3.     * account both the MeasureSpec requirements for this view and its padding. 
  4.     * The heavy lifting is done in getChildMeasureSpec. 
  5.     * 
  6.     * @param child The child to measure 
  7.     * @param parentWidthMeasureSpec The width requirements for this view 
  8.     * @param parentHeightMeasureSpec The height requirements for this view 
  9.     */  
  10.    protected void measureChild(View child, int parentWidthMeasureSpec,  
  11.            int parentHeightMeasureSpec) {  
  12.      ……  
  13.        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);  
  14.    }  
       在ViewGroup的源码里。 measureChildren遍历子View最终也调用了measure方法
[java]  view plain  copy
  1. /** 
  2.     * Ask all of the children of this view to measure themselves, taking into 
  3.     * account both the MeasureSpec requirements for this view and its padding. 
  4.     * We skip children that are in the GONE state The heavy lifting is done in 
  5.     * getChildMeasureSpec. 
  6.     * 
  7.     * @param widthMeasureSpec The width requirements for this view 
  8.     * @param heightMeasureSpec The height requirements for this view 
  9.     */  
  10.    protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {  
  11.        final int size = mChildrenCount;  
  12.        final View[] children = mChildren;  
  13.        for (int i = 0; i < size; ++i) {  
  14.            final View child = children[i];  
  15.            if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {  
  16.                measureChild(child, widthMeasureSpec, heightMeasureSpec);  
  17.            }  
  18.        }  
  19.    }  


    Layout过程

        继承ViewGroup时,onLayout方法必须重写

        在ViewGroup中,onlayout方法是抽象方法

[java]  view plain  copy
  1. @Override  
  2.     protected abstract void onLayout(boolean changed,  
  3.             int l, int t, int r, int b);  
      重写onlayout方法,遍历子View,每个子View依次调用layout方法放置自己

    draw过程

          ViewGroup绘制自身的过程和View的过程是一样的

          到这里View和ViewGroup的几个主要方法我们已经都了解了


       三、生命周期

        放一张网上比较流行的View的生命周期图,实际调用是不是这种情况呢?

         


         
讲道理的话,这幅图描绘的生命周期正是View的生命周期,但实际运用中,我们常常可以看到自定义View的onMeasure被调用很多次,这是什么原因导致的呢?

         按照我们上面所讲述的层次关系,Activity的布局是被放置在一个id为content的窗口控件中的,通常这个控件是FrameLayout,它将会两次测量子控件的长宽,举个最简单的例子,如果我们的项目只有一个activity,而这个activity的布局文件只放置我们的自定义View,那么我们可以看到这个View的onMeasure方法被调用了两次,原因是它的父控件(FrameLayout)让它进行了两次measure。如果我们在自定义View外再套一层RelativeLayout,因为RelativeLayout也会测量两次子view的长宽值,所以此时的自定义View的onMeasure方法会被调用四次。再套一层RelativeLayout,则自定义View的onMeasure被调用8次,以此类推。因此,我们自定义View的onMeasure和onLayout调用几次完全取决于它有多少外层的控件,以及这些控件会调用测量其子控件多少次。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值