自定义View(四)View的生命周期

 更多内容可参考
       (一)自定义View的分类点击打开链接
       (二)自定义View的构造方法及自定义属性点击打开链接

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

       (四)View的生命周期

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

       (六)事件分发机制

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

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



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

        窗口布局ViewDecorView的子View

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

      android:theme="@android:style/Theme.NoTitleBar"

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

       requestWindowFeature(Window.FEATURE_NO_TITLE);

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

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

        源码如下

    //1、根据requestFreature()和Activity节点的android:theme="" 设置好 features值    
    //2 根据设定好的 features值,即特定风格属性,选择不同的窗口修饰布局文件  
    int layoutResource;  //窗口修饰布局文件    
    int features = getLocalFeatures();  
    // System.out.println("Features: 0x" + Integer.toHexString(features));  
    if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {  
        if (mIsFloating) {  
            layoutResource = com.android.internal.R.layout.dialog_title_icons;  
        } else {  
            layoutResource = com.android.internal.R.layout.screen_title_icons;  
        }  
        // System.out.println("Title Icons!");  
    } else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0) {  
        // Special case for a window with only a progress bar (and title).  
        // XXX Need to have a no-title version of embedded windows.  
        layoutResource = com.android.internal.R.layout.screen_progress;  
        // System.out.println("Progress!");  
    }   
    //...     
    //3 选定了窗口修饰布局文件 ,添加至DecorView对象里,并且指定mcontentParent值  
    View in = mLayoutInflater.inflate(layoutResource, null);  
    decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));  
    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三个过程

  private void performTraversals() {
        final View host = mView;
        ……
        if (layoutRequested) {
            windowSizeMayChange |= measureHierarchy(host, lp, res,
                    desiredWindowWidth, desiredWindowHeight);///
        }
        ……
        if (mFitSystemWindowsRequested) {
            if (mLayoutRequested) {
                windowSizeMayChange |= measureHierarchy(host, lp,///
                        mView.getContext().getResources(),
                        desiredWindowWidth, desiredWindowHeight);
            }
        }
     if (!mStopped) {
             if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
                        || mHeight != host.getMeasuredHeight() || contentInsetsChanged) {            
                    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);///
                    }
             if (measureAgain) {
                        performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);///
                    }
     }
     ……
    if(didLayout){
        performLayout(lp, desiredWindowWidth, desiredWindowHeight);///
    }
     ……
    if(!cancelDraw&&!newSurface)
    {
        performDraw();///
    }
  }
            源码中有几个比较重要的方法,他们在满足条件时会被调用

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

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

          

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

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

     

     一、View

       measure过程

      View的该方法由父View调用

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

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
       // super.onMeasure(widthMeasureSpec, heightMeasureSpec);//调用基类的onMeasure方法
        setMeasuredDimension(10, 10);//设置新的长宽值
    }
      如果我们重写了onMeasure方法,通常要调用setMeasuredDimension方法设置我们指定的长宽值

 protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
        ……
        mMeasuredWidth = measuredWidth;
        mMeasuredHeight = measuredHeight;
        ……
    }
     如果我们没有重写onMeasure方法,则默认调用View类的onMeasure方法,该方法为我们的控件指定了默认长宽值

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }
     继承View的自定义View不需要测量子View长宽

    Layout过程

 /**
     * Assign a size and position to a view and all of its
     * descendants
     *
     * <p>This is the second phase of the layout mechanism.
     * (The first is measuring). In this phase, each parent calls
     * layout on all of its children to position them.
     * This is typically done using the child measurements
     * that were stored in the measure pass().</p>
     *
     * <p>Derived classes should not override this method.
     * Derived classes with children should override
     * onLayout. In that method, they should
     * call layout on each of their children.</p>
     */
    public void layout(int l, int t, int r, int b) {
       ……
        boolean changed = isLayoutModeOptical(mParent) ?
                setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);//放置自己的位置

       ……
            onLayout(changed, l, t, r, b);
       ……
    }
      源码的注释说的很清楚,这个方法由View的父View调用。是布局机制的第二步。

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

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

 /**
     * Called from layout when this view should
     * assign a size and position to each of its children.
     *
     * Derived classes with children should override
     * this method and call layout on each of
     * their children.
     * @param changed This is a new size or position for this view
     * @param left Left position, relative to parent
     * @param top Top position, relative to parent
     * @param right Right position, relative to parent
     * @param bottom Bottom position, relative to parent
     */
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    }

     draw过程

/**
     * Manually render this view (and all of its children) to the given Canvas.
     * The view must have already done a full layout before this function is
     * called.  When implementing a view, implement
     * {@link #onDraw(android.graphics.Canvas)} instead of overriding this method.
     * If you do need to override this method, call the superclass version.
     *
     * @param canvas The Canvas to which the View is rendered.
     */
    public void draw(Canvas canvas) {
        /*
         * Draw traversal performs several drawing steps which must be executed
         * in the appropriate order:
         *
         *      1. Draw the background 绘制背景
         *      2. If necessary, save the canvas' layers to prepare for fading 如果需要保持画板图层
         *      3. Draw view's content 绘制内容
         *      4. Draw children 绘制子View
         *      5. If necessary, draw the fading edges and restore layers 如果需要绘制边并恢复图层
         *      6. Draw decorations (scrollbars for instance)绘制装饰(如滚动条)
         */
        // Step 1, draw the background, if needed
        ……
        // skip step 2 & 5 if possible (common case)
        ……
        // Step 3, draw the content
           onDraw(canvas);
        ……
        // Step 4, draw the children
            dispatchDraw(canvas);

       // Step 6, draw decorations (scrollbars)
            onDrawScrollBars(canvas);
        ……
       // we're done...
            return;
        }
        注释里说,draw方法用来绘制view。在调用之前必须保证布局过程已经完成。继承View时,重写onDraw方法,而不是本方法,如果一定要重写此方法,一定记得调用super.draw( )。上述代码第三步,调用了onDraw方法。
/**
     * Implement this to do your drawing.
     *
     * @param canvas the canvas on which the background will be drawn
     */
    protected void onDraw(Canvas canvas) {
    }
      重写onDraw方法,绘制我们自己的View
/**
     * Called by draw to draw the child views. This may be overridden
     * by derived classes to gain control just before its children are drawn
     * (but after its own view has been drawn).
     * @param canvas the canvas on which to draw the view
     */
    protected void dispatchDraw(Canvas canvas) {

    }
      重写dispatchDraw方法绘制子View

      二、 ViewGroup

       measure过程

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

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
      int count = getChildCount();
      for (int i = 0; i < count; i++) {
        View child = getChildAt(i);
        child.measure(widthMeasureSpec, heightMeasureSpec);//方法1
       // measureChild(child, widthMeasureSpec, heightMeasureSpec);//方法2
    }
      //  measureChildren(widthMeasureSpec, heightMeasureSpec);  //方法3    
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }
      测量子view一共有三个方法

     ①child.measure(widthMeasureSpec, heightMeasureSpec)

     ②measureChild(child,widthMeasureSpec, heightMeasureSpec)

     ③measureChildren(widthMeasureSpec, heightMeasureSpec)

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

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


    Layout过程

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

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

@Override
    protected abstract void onLayout(boolean changed,
            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调用几次完全取决于它有多少外层的控件,以及这些控件会调用测量其子控件多少次。






阅读更多

没有更多推荐了,返回首页