android View的探索(二) : View的形成过程

我们在设计项目的时候,经常会遇到一些奇奇怪怪的需求,在这些需求中,对于UI的需求更是占了大半部分,这时候我们就避免不了与View打交道,熟悉自定义View的旁友应该会将onMeasure()->onLayout->onDraw的调用过程烂熟于心。因此,为了在撸代码的时候更加的对VIew得心应手,我们有必要对其进行一个深入的了解,下面就从构造方法看起吧。

View的构造方法如下:

public View(Context context) {

       mContext = context;
       mResources = context != null ? context.getResources() : null;
       mViewFlags = SOUND_EFFECTS_ENABLED | HAPTIC_FEEDBACK_ENABLED;
       ....

           sCompatibilityDone = true;
       }
   }
​
 public View(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
       this(context);
​
       final TypedArray a = context.obtainStyledAttributes(
               attrs, com.android.internal.R.styleable.View, defStyleAttr, defStyleRes);
​
       if (mDebugViewAttributes) {
           saveAttributeData(attrs, a);
       }
​
       Drawable background = null;
​
       ...

       final int N = a.getIndexCount();
       for (int i = 0; i < N; i++) {
           int attr = a.getIndex(i);
           switch (attr) {
               //读取attr的参数
               ...
           }
  //设置View的滚动模式
       setOverScrollMode(overScrollMode);
​
       // Cache start/end user padding as we cannot fully resolve padding here (we dont have yet
       // the resolved layout direction). Those cached values will be used later during padding
       // resolution.
       mUserPaddingStart = startPadding;
       mUserPaddingEnd = endPadding;
  //设置背景
       if (background != null) {
           setBackground(background);
       }
​
       // setBackground above will record that padding is currently provided by the background.
       // If we have padding specified via xml, record that here instead and use it.
       mLeftPaddingDefined = leftPaddingDefined;
       mRightPaddingDefined = rightPaddingDefined;
​
       ...
   }

对于View的构造方法而言,在其中只是做了相关参数的初始化以及传入参数attr的数据的获取的操作,相对来说,并没有什么特别需要注意的地方,那我们直接查看到onMeasure中:

 /**

     * <p>
     * Measure the view and its content to determine the measured width and the
     * measured height. This method is invoked by {@link #measure(int, int)} and
     * should be overridden by subclasses to provide accurate and efficient
     * measurement of their contents.
     * </p>
     *
     * <p>
     * <strong>CONTRACT:</strong> When overriding this method, you
     * <em>must</em> call {@link #setMeasuredDimension(int, int)} to store the
     * measured width and height of this view. Failure to do so will trigger an
     * <code>IllegalStateException</code>, thrown by
     * {@link #measure(int, int)}. Calling the superclass'
     * {@link #onMeasure(int, int)} is a valid use.
     * </p>
     *
     * <p>
     * The base class implementation of measure defaults to the background size,
     * unless a larger size is allowed by the MeasureSpec. Subclasses should
     * override {@link #onMeasure(int, int)} to provide better measurements of
     * their content.
     * </p>
     *
     * <p>
     * If this method is overridden, it is the subclass's responsibility to make
     * sure the measured height and width are at least the view's minimum height
     * and width ({@link #getSuggestedMinimumHeight()} and
     * {@link #getSuggestedMinimumWidth()}).
     * </p>
     *
   */
     
   protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
       setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
               getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
   }
   

在View注释中已经把onMeasure()解释得相当的完善了,我们可以在注释中获取一些信息:

  • onMeasure()方法中会测量其本身View以及包含内容的宽高

  • 当我们在子类中重写onMeasure()方法时,我们需要调用setMeasuredDimension(int, int)为这个View进行保存宽高

  • View默认的是background的大小


/**

     * Utility to return a default size. Uses the supplied size if the
     * MeasureSpec imposed no constraints. Will get larger if allowed
     * by the MeasureSpec.
     *
     * @param size Default size for this view
     * @param measureSpec Constraints imposed by the parent
     * @return The size this view should be.
     */
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;
   }
protected int getSuggestedMinimumWidth() {
       return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
   }
protected int getSuggestedMinimumHeight() {
       return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight());
​
   }

在上述代码中,我们可以看到在getDefaultSize()方法中通过MeasureSpec来获得测量模式和大小(MeasureSpec代表一个32位的int值,高两位代表了specMode,低30位specSize)。specMode总共有三种()

  • MeasureSpec.UNSPECIFIED:父容器不对View有任何的影响,要多大就给多大

  • MeasureSpec.EXACTLY:父容器检测出View所需要的精确大小,这是View的最终大小就是specSize的值,其对应了match_parent模式

  • MeasureSpec.AT_MOST:父容器制定了可用大小,即specSize,View的大小不能大于这个值,对应wrap_content模式

上述三种模式的说明来自于《android艺术开发探索》。在View的测量中,我们可以看到View的大小的测量跟设置的模式以及父布局是息息相关的,因此我们在进行自定义View的时候需要考虑到这两方面的因素。
我们重新回溯到setMeasuredDimension()方法中:

protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {

       boolean optical = isLayoutModeOptical(this);
       if (optical != isLayoutModeOptical(mParent)) {
           Insets insets = getOpticalInsets();
           int opticalWidth = insets.left + insets.right;
           int opticalHeight = insets.top + insets.bottom;
​
           measuredWidth += optical ? opticalWidth : -opticalWidth;
           measuredHeight += optical ? opticalHeight : -opticalHeight;
       }
       setMeasuredDimensionRaw(measuredWidth, measuredHeight);
   }
 private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
       mMeasuredWidth = measuredWidth;
       mMeasuredHeight = measuredHeight;
​
       mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
   }

在其中我们看到最终会将计算得到的宽高传给mMeasuredHeight和mMeasuredWidth。
接下来在看看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) {
   }

onLayout()是一个空实现的方法,从注释当中我们可以看到此方法更适用于ViewGroup,只有View是一个ViewGroup才需要考虑到layout的位置的问题。

往下走,onDraw():

/**

     * Implement this to do your drawing.
     *
     * @param canvas the canvas on which the background will be drawn
     */
   protected void onDraw(Canvas canvas) {
   }

同样是空实现,这里自定义View通过重写该方法来进行绘制的操作。

ViewRootIpml
分析完上面的三个方法,我们更多地是看到一些赋值和空实现的方法,那么到底View的怎么开始绘制的过程的呢?这时候涉及到ViewRootIpml这个类了,它是连接View和WindowManager的桥梁,具体的作用是什么呢,我们从构建开始说起,一步步往下学习,首先我们找到其初始化的地方:

//WindowManagerGlobal

   public void addView(View view, ViewGroup.LayoutParams params,

           Display display, Window parentWindow) {
           ...

​

           ViewRootImpl root;
           View panelParentView = null;

           ...

​

           root = new ViewRootImpl(view.getContext(), display);
​
           view.setLayoutParams(wparams);
​
           mViews.add(view);
           mRoots.add(root);
           mParams.add(wparams);

       }
  try {

           root.setView(view, wparams, panelParentView);
       } catch (RuntimeException e) {
           // BadTokenException or InvalidDisplayException, clean up.
           synchronized (mLock) {
               final int index = findViewLocked(view, false);
               if (index >= 0) {
                   removeViewLocked(index, true);
               }
           }
           throw e;
       }

​
       ...

   }
​

我们在WindowManager的实现类WindowManagerGlobal找到了其ViewRootImpl初始化的地方(关于WindowManager在以后会进行深入探讨,现在主要知道它是window窗口的管理类即可),从addView()方法中我们推测其是将View添加到window的一个过程(addView这个方法在ActivityThread的handleResumeActivity()中实现),然后又调用了ViewRootImpl的setView()方法:

//ViewRootImpl

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {

       synchronized (this) {
           if (mView == null) {

                 ...

               requestLayout();

              ...

               }

       }
   }
  @Override

   public void requestLayout() {

       if (!mHandlingLayoutInLayoutRequest) {
           checkThread();
           mLayoutRequested = true;
           scheduleTraversals();

       }

   }

   void scheduleTraversals() {

           if (!mTraversalScheduled) {

               mTraversalScheduled = true;
               mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
               mChoreographer.postCallback(
                       Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
               if (!mUnbufferedInputDispatch) {
                   scheduleConsumeBatchedInput();
               }
               notifyRendererOfFramePending();
               pokeDrawLockIfNeeded();
           }
       }
   final class TraversalRunnable implements Runnable {

           @Override
           public void run() {
               doTraversal();

           }
       }
   void doTraversal() {
           if (mTraversalScheduled) {
               mTraversalScheduled = false;
               mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
​
               if (mProfile) {
                   Debug.startMethodTracing("ViewAncestor");
               }
​
               performTraversals();

​
               if (mProfile) {
                   Debug.stopMethodTracing();
                   mProfile = false;
               }
           }
       }
  private void performTraversals() {

        ...

        performMeasure();
       ...
       performLayout();

        ...

        performDraw();

     }

​

在这里我们可以看到当第一次进来的时候会设置mView的值,在其中调用了requestLayout()进行布局,然后依次是scheduleTraversals()->doTraversal()->performTraversals()的过程,在performTraversals()方法中调用了performMeasure(),performLayout(),performDraw()方法:

private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {

       Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
       try {
           mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
       } finally {
           Trace.traceEnd(Trace.TRACE_TAG_VIEW);
       }
   }
  private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
           int desiredWindowHeight) {
         //当前mView是由WindowsManagerGlobal.addView传递过来,在ViewRootImpl中
         //通过setView()获得到的。
         final View host = mView;
         ...
           host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
   ...
           
        mInLayout = false;
   }
 private void performDraw() {
       if (mAttachInfo.mDisplayState == Display.STATE_OFF && !mReportNextDraw) {
           return;
       }
​
       final boolean fullRedrawNeeded = mFullRedrawNeeded;
       mFullRedrawNeeded = false;
​
       mIsDrawing = true;
       Trace.traceBegin(Trace.TRACE_TAG_VIEW, "draw");
       try {
           draw(fullRedrawNeeded);
       } finally {
           mIsDrawing = false;
           Trace.traceEnd(Trace.TRACE_TAG_VIEW);
       }

           try {
               mWindowSession.finishDrawing(mWindow);
           } catch (RemoteException e) {
           }
       }
   }
 private void draw(boolean fullRedrawNeeded) {

       

       ....

         if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {

           return;
         }

               

       }

       if (animating) {
           mFullRedrawNeeded = true;
           scheduleTraversals();
       }
   }
​
   /**
     * @return true if drawing was successful, false if an error occurred
     */
   private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
           boolean scalingRequired, Rect dirty) {
​
     

       mView.draw(canvas);

​
         drawAccessibilityFocusedDrawableIfNeeded(canvas);

           

       return true;
   }
​

在上面我们最终可以看到,measure->layout->draw的过程最终回调到了View中的对应的是三个方法,所以我们重新回到View中研究一下这些方法,从measure()看起:

/**

     * <p>
     * This is called to find out how big a view should be. The parent
     * supplies constraint information in the width and height parameters.
     * </p>
     *
     * <p>
     * The actual measurement work of a view is performed in
     * {@link #onMeasure(int, int)}, called by this method. Therefore, only
     * {@link #onMeasure(int, int)} can and must be overridden by subclasses.
     * </p>
     *
     *
     * @param widthMeasureSpec Horizontal space requirements as imposed by the
     *       parent
     * @param heightMeasureSpec Vertical space requirements as imposed by the
     *       parent
     *
     * @see #onMeasure(int, int)
     */

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);
       }

        ···

       onMeasure(widthMeasureSpec, heightMeasureSpec);

       ···     

   }

从注释中我们可以了解到,其实该方法是计算View树的宽高的最终方法,宽高由父布局和子布局共同决定,且该方法不允许子类进行相关操作,因为其是final的,子类想要设置宽高需要通过onMeasure()方法来进行。

接下来我们再看下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>
     *
     * @param l Left position, relative to parent
     * @param t Top position, relative to parent
     * @param r Right position, relative to parent
     * @param b Bottom position, relative to parent
     */
   @SuppressWarnings({"unchecked"})
   public void layout(int l, int t, int r, int b) {
       if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
           onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
           mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
       }
​
       int oldL = mLeft;
       int oldT = mTop;
       int oldB = mBottom;
       int oldR = mRight;
​
       boolean changed = isLayoutModeOptical(mParent) ?
               setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);

  //setFrame会判断是否需要重新布局,即如果我们在完成一次View展示后,重新设置View的大小,那么
      //setFrame返回tru

       if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
           onLayout(changed, l, t, r, b);

           mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;
​
           ListenerInfo li = mListenerInfo;
           if (li != null && li.mOnLayoutChangeListeners != null) {
               ArrayList<OnLayoutChangeListener> listenersCopy =
                       (ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();
               int numListeners = listenersCopy.size();
               for (int i = 0; i < numListeners; ++i) {
                   listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
               }
           }
       }
​
       mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
       mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
   }

确认过大小后,我们需要定位View最终位置,在layout中会调用到onLayout()来获得我们需要展示的childrenView的效果。
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.
     */
   @CallSuper
   public void draw(Canvas canvas) {
       final int privateFlags = mPrivateFlags;
       final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
               (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
       mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;
​
       /*
         * 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
         *     5. If necessary, draw the fading edges and restore layers
         *     6. Draw decorations (scrollbars for instance)
         */
​
       // Step 1, draw the background, if needed
       int saveCount;
​
       if (!dirtyOpaque) {
           drawBackground(canvas);
       }
​
       // skip step 2 & 5 if possible (common case)
       final int viewFlags = mViewFlags;
       boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
       boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
       if (!verticalEdges && !horizontalEdges) {
           // Step 3, draw the content
           if (!dirtyOpaque) onDraw(canvas);
​
           // Step 4, draw the children
          //递归draw

           dispatchDraw(canvas);
​
           // Overlay is part of the content and draws beneath Foreground
           if (mOverlay != null && !mOverlay.isEmpty()) {
               mOverlay.getOverlayView().dispatchDraw(canvas);
           }
​
           // Step 6, draw decorations (foreground, scrollbars)
           onDrawForeground(canvas);
​
           // we're done...
           return;
       }
​

看代码注释就可以很轻松知道draw中的步骤,这里就不详细解释了。

看到这里就对我们对View从哪里开始,并且如何一步步从测量绘制完成有了一个稍微深入的认识,画一个图先总结一下吧:

View绘制过程[1]

但是我们只看到了绘制完成的部分,并没有看到View是怎么展示在我们面前的,想要知道View如何被调用再到绘制完成显示出来,这就牵扯到atcivity的启动的过程了,这里不对这方面进行讲解,详细的过程可以大概看下:
http://www.jianshu.com/p/93c66b3f08d6
我写的这篇文章(写的比较乱,有时间整理一下= ̄ω ̄=),本身我们就知道activity启动完成显示到手机上会执行到onResume()这个方法,这个方法可硬对应在ActivityThread中的handleResumeActivity():

   final void handleResumeActivity(...){
          ....
           else if (!willBeVisible) {
               if (localLOGV) Slog.v(
                   TAG, "Launch " + r + " mStartedActivity set");
               r.hideForNow = true;
           }
​
           // Get rid of anything left hanging around.
           cleanUpPendingRemoveWindows(r);
​
           // The window is now visible if it has been added, we are not
           // simply finishing, and we are not starting another activity.
           if (!r.activity.mFinished && willBeVisible
                   && r.activity.mDecor != null && !r.hideForNow) {
               if (r.newConfig != null) {
                   r.tmpConfig.setTo(r.newConfig);
                   if (r.overrideConfig != null) {
                       r.tmpConfig.updateFrom(r.overrideConfig);
                   }
                   if (DEBUG_CONFIGURATION) Slog.v(TAG, "Resuming activity "
                           + r.activityInfo.name + " with newConfig " + r.tmpConfig);
                   performConfigurationChanged(r.activity, r.tmpConfig);
                   freeTextLayoutCachesIfNeeded(r.activity.mCurrentConfig.diff(r.tmpConfig));
                   r.newConfig = null;
               }
               if (localLOGV) Slog.v(TAG, "Resuming " + r + " with isForward="
                       + isForward);
               WindowManager.LayoutParams l = r.window.getAttributes();
               if ((l.softInputMode
                       & WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION)
                       != forwardBit) {
                   l.softInputMode = (l.softInputMode
                           & (~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION))
                           | forwardBit;
                   if (r.activity.mVisibleFromClient) {
                       ViewManager wm = a.getWindowManager();
                       View decor = r.window.getDecorView();
                       wm.updateViewLayout(decor, l);
                   }
               }
               r.activity.mVisibleFromServer = true;
               mNumVisibleActivities++;
               if (r.activity.mVisibleFromClient) {
                   r.activity.makeVisible();
               }
           }
​
           if (!r.onlyLocalRequest) {
               r.nextIdle = mNewActivities;
               mNewActivities = r;
               if (localLOGV) Slog.v(
                   TAG, "Scheduling idle handler for " + r);
               Looper.myQueue().addIdleHandler(new Idler());
           }
           r.onlyLocalRequest = false;
​
           // Tell the activity manager we have resumed.
           if (reallyResume) {
               try {
                   ActivityManagerNative.getDefault().activityResumed(token);
               } catch (RemoteException ex) {
               }
           }
​
       } else {
           // If an exception was thrown when trying to resume, then
           // just end this activity.
           try {
               ActivityManagerNative.getDefault()
                   .finishActivity(token, Activity.RESULT_CANCELED, null, false);
           } catch (RemoteException ex) {
           }
       }
   }

代码中有句 r.activity.makeVisible()就是展示的关键的代码,他会让DecorView变为Visibile状态,最终展示出来。


ViewGrop

上面通过一系列的讲解只是从最简单的角度上而言——当页面中只有一个View的时候,但是绝大部分情况下是不可能的,往往项目中VIew树的层级是很深的(这里建议使用RelativeLayout作为初始布局容器),那就涉及到ViewGroup了,其实ViewGroup也很简单,只不过多了一些递归的过程而已。

这里选择FrameLayout来进行大概的讲述吧(因为好多文章用了LinearLayout,RelativeLayout我又嫌麻烦。。),从onMeasure()开始看:

  @Override

   protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
      //获取子View的数量

       int count = getChildCount();
  
      //当前布局宽高否是match_parent
       final boolean measureMatchParentChildren =
               MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
               MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
       mMatchParentChildren.clear();
​
       int maxHeight = 0;
       int maxWidth = 0;
       int childState = 0;
​
       for (int i = 0; i < count; i++) {
           final View child = getChildAt(i);
          //GONE的布局不会进行测量

           if (mMeasureAllChildren || child.getVisibility() != GONE) {
              //测量时加上margin参数

               measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
               final LayoutParams lp = (LayoutParams) child.getLayoutParams();
               maxWidth = Math.max(maxWidth,
                       child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
               maxHeight = Math.max(maxHeight,
                       child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
               childState = combineMeasuredStates(childState, child.getMeasuredState());
               if (measureMatchParentChildren) {
                   //子布局中是否由含match_parent的布局,有则添加到mMatchParentChildren中
                   if (lp.width == LayoutParams.MATCH_PARENT ||
                           lp.height == LayoutParams.MATCH_PARENT) {
                       mMatchParentChildren.add(child);
                   }
               }
           }
       }
​
       // Account for padding too
       maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();
       maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();
​
       // Check against our minimum height and width
       maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
       maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
​
       // Check against our foreground's minimum height and width
      //计算图片宽度,取图片宽高和最大宽高两者之间最大值

       final Drawable drawable = getForeground();
       if (drawable != null) {
           maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());
           maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());
       }
​
       setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
               resolveSizeAndState(maxHeight, heightMeasureSpec,
                       childState << MEASURED_HEIGHT_STATE_SHIFT));​
       count = mMatchParentChildren.size();

      //这里对于子布局是match_parent的进行重新测量得到准确值
       if (count > 1) {
           for (int i = 0; i < count; i++) {
               final View child = mMatchParentChildren.get(i);
               final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
​
               final int childWidthMeasureSpec;
               if (lp.width == LayoutParams.MATCH_PARENT) {
                   final int width = Math.max(0, getMeasuredWidth()
                           - getPaddingLeftWithForeground() - getPaddingRightWithForeground()
                           - lp.leftMargin - lp.rightMargin);
                   childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
                           width, MeasureSpec.EXACTLY);
               } else {
                   childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
                           getPaddingLeftWithForeground() + getPaddingRightWithForeground() +
                           lp.leftMargin + lp.rightMargin,
                           lp.width);
               }
​
               final int childHeightMeasureSpec;
               if (lp.height == LayoutParams.MATCH_PARENT) {
                   final int height = Math.max(0, getMeasuredHeight()
                           - getPaddingTopWithForeground() - getPaddingBottomWithForeground()
                           - lp.topMargin - lp.bottomMargin);
                   childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
                           height, MeasureSpec.EXACTLY);
               } else {
                   childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,
                           getPaddingTopWithForeground() + getPaddingBottomWithForeground() +
                           lp.topMargin + lp.bottomMargin,
                           lp.height);
               }
                //调用子布局的measure()方法
               child.measure(childWidthMeasureSpec, childHeightMeasureSpec);

           }
       }
   }
​
protected void measureChildWithMargins(View child,
           int parentWidthMeasureSpec, int widthUsed,
           int parentHeightMeasureSpec, int heightUsed) {
       final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
​
       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);
​            //调用子布局的measure()方法
       child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
   }

从onMeasure()可以看到主要的方法就是child.measure()的调用,他会调用到View的measure(),measure()又会重新调用onMeasure()由此形成依次递归直到获取到正确的测量结果。

onLayout()方法:

   @Override

   protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
       layoutChildren(left, top, right, bottom, false /* no force left gravity */);
   }
​
   void layoutChildren(int left, int top, int right, int bottom,
                                 boolean forceLeftGravity) {
       final int count = getChildCount();
​
       final int parentLeft = getPaddingLeftWithForeground();
       final int parentRight = right - left - getPaddingRightWithForeground();
​
       final int parentTop = getPaddingTopWithForeground();
       final int parentBottom = bottom - top - getPaddingBottomWithForeground();
​
       for (int i = 0; i < count; i++) {
           final View child = getChildAt(i);
           if (child.getVisibility() != GONE) {
               final LayoutParams lp = (LayoutParams) child.getLayoutParams();
​
               final int width = child.getMeasuredWidth();
               final int height = child.getMeasuredHeight();
​
               int childLeft;
               int childTop;
​
               int gravity = lp.gravity;
               if (gravity == -1) {
                   gravity = DEFAULT_CHILD_GRAVITY;
               }
​
               final int layoutDirection = getLayoutDirection();
               final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
               final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;
​
               switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
                   case Gravity.CENTER_HORIZONTAL:
                       childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +
                       lp.leftMargin - lp.rightMargin;
                       break;
                   case Gravity.RIGHT:
                       if (!forceLeftGravity) {
                           childLeft = parentRight - width - lp.rightMargin;
                           break;
                       }
                   case Gravity.LEFT:
                   default:
                       childLeft = parentLeft + lp.leftMargin;
               }
​
               switch (verticalGravity) {
                   case Gravity.TOP:
                       childTop = parentTop + lp.topMargin;
                       break;
                   case Gravity.CENTER_VERTICAL:
                       childTop = parentTop + (parentBottom - parentTop - height) / 2 +
                       lp.topMargin - lp.bottomMargin;
                       break;
                   case Gravity.BOTTOM:
                       childTop = parentBottom - height - lp.bottomMargin;
                       break;
                   default:
                       childTop = parentTop + lp.topMargin;
               }
​              //调用子View的layout
               child.layout(childLeft, childTop, childLeft + width, childTop + height);
           }
       }
   }
​

可以看到layout也是一个递归调用的过程,这里就不详细讲述了,而由于ViewGroup没有onDraw()方法,这里就不需要讲了。

附上一张View的生命周期:


View的生命周期

终于写完了,脑袋都要爆炸了,View形成的过程大概就是这样的,谢谢阅读,如果觉得哪写的不对或者不好的地方欢迎留言交流,希望能够一起进步~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,921评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,635评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,393评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,836评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,833评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,685评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,043评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,694评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,671评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,670评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,779评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,424评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,027评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,984评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,214评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,108评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,517评论 2 343

推荐阅读更多精彩内容