美文网首页
UI绘制流程

UI绘制流程

作者: shixinBook | 来源:发表于2017-10-30 17:12 被阅读21次

    一、从setContentView(R.layout.activity_main);入手了解UI的绘制起始过程

    1.Activity.java

    public void setContentView(@LayoutRes int layoutResID) {
    
    getWindow().setContentView(layoutResID);//①
    
     initWindowDecorActionBar();
    
    }
    

    PhoneWindow中的窗体类型,自定义窗体可以用到这些类型

    WindowManager 中窗体的类型:
    
    * @see #TYPE_BASE_APPLICATION
    
    * @see #TYPE_APPLICATION
    
    * @see #TYPE_APPLICATION_STARTING
    
    * @see #TYPE_DRAWN_APPLICATION
    
    * @see #TYPE_APPLICATION_PANEL
    
    * @see #TYPE_APPLICATION_MEDIA
    
    * @see #TYPE_APPLICATION_SUB_PANEL
    
    * @see #TYPE_APPLICATION_ABOVE_SUB_PANEL
    
    * @see #TYPE_APPLICATION_ATTACHED_DIALOG
    
    * @see #TYPE_STATUS_BAR
    
    * @see #TYPE_SEARCH_BAR
    
    * @see #TYPE_PHONE
    
    * @see #TYPE_SYSTEM_ALERT
    
    * @see #TYPE_TOAST
    
    * @see #TYPE_SYSTEM_OVERLAY
    
    * @see #TYPE_PRIORITY_PHONE
    
    * @see #TYPE_STATUS_BAR_PANEL
    
    * @see #TYPE_SYSTEM_DIALOG
    
    * @see #TYPE_KEYGUARD_DIALOG
    
    * @see #TYPE_SYSTEM_ERROR
    
    * @see #TYPE_INPUT_METHOD //输入法
    
    * @see #TYPE_INPUT_METHOD_DIALOG
    
    */
    

    2.getWindow()拿到的是Window的实现类PhoneWindow

    mWindow = new PhoneWindow(this, window);
    

    PhoneWindow源码:

    在 com.android.internal.policy包下面

    @Override
    
    public void setContentView(int layoutResID) {
    
    // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
    // decor, when theme attributes and the like are crystalized. Do not check the feature
    // before this happens.
    if (mContentParent == null) {
          installDecor();//②
    }
    
    ……
    
    mLayoutInflater.inflate(layoutResID,mContentParent//⑥最后
        将布局渲染到帧布局当中。
    }
    
    private void installDecor() {
    
    if (mDecor == null) {
    
        mDecor = generateDecor();//③生成一个DecorView(继承的FrameLayout)
      mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
    
      mDecor.setIsRootNamespace(true);
    
    if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
          mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
      }
    }
    if (mContentParent == null) {
          mContentParent = generateLayout(mDecor);//④
    }
    
        protected ViewGroup generateLayout(DecorView decor) {//⑤
        
            if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
                  layoutResource = R.layout.screen_swipe_dismiss;
          } 
    .............
          上面判断加载哪一种布局,有的有actionBar 有的没有,还有其他种类的布局,然后加载添加到decor中。
    下图中就是一种简单的布局。不含有ActionBar,但是有一个ViewStub方便以后添加标题栏
    
    View in = mLayoutInflater.inflate(layoutResource, null);
    
    decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
    
    mContentRoot = (ViewGroup) in;
    
    //然后将我们的布局填充到这个布局当中
            View in = mLayoutInflater.inflate(layoutResource, null);
            decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
            mContentRoot = (ViewGroup) in;
        }
    }
    
    1_Activity加载UI-类图关系和视图结构.png

    D:\Software\Android\SDK\platforms\android-23\data\res ,查看系统的资源文件

    这里我们来看一下snackbar添加到布局中的原理

     private static ViewGroup findSuitableParent(View view) {
            ViewGroup fallback = null;
            do {
                if (view instanceof CoordinatorLayout) {
                    // We've found a CoordinatorLayout, use it
                    return (ViewGroup) view;
                } else if (view instanceof FrameLayout) {
                    if (view.getId() == android.R.id.content) {
                         //见上图中的id/content 将SnackBar添加到DecorView中FrameLayout
                        // If we've hit the decor content view, then we didn't find a CoL in the
                        // hierarchy, so use it.
                        return (ViewGroup) view;
                    } else {
                        // It's not the content view but we'll use it as our fallback
                        fallback = (ViewGroup) view;
                    }
                }
    
                if (view != null) {
                    // Else, we will loop and crawl up the view hierarchy and try to find a parent
                    final ViewParent parent = view.getParent();
                    view = parent instanceof View ? (View) parent : null;
                }
            } while (view != null);
    
            // If we reach here then we didn't find a CoL or a suitable content view so we'll fallback
            return fallback;
        }
    

    二、measure、layout、draw的三个执行流程

    View.java类

    measure:测量,测量自己有多大,如果是ViewGroup的话会同时测量里面的子控件的大小

    layout:摆放里面的子控件bounds(left,top,right,bottom)

    draw:绘制 (直接继承了view一般都会重写onDraw)

    ViewGroup.java

    看View.java类的源码:

    1.view的requestLayout()方法开始,递归地不断往上找父容器,最终找到DecorView

    @CallSuper
        public void requestLayout() {
            if (mMeasureCache != null) mMeasureCache.clear();
    
            if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == null) {
                // Only trigger request-during-layout logic if this is the view requesting it,
                // not the views in its parent hierarchy
                ViewRootImpl viewRoot = getViewRootImpl();
                if (viewRoot != null && viewRoot.isInLayout()) {
                    if (!viewRoot.requestLayoutDuringLayout(this)) {
                        return;
                    }
                }
                mAttachInfo.mViewRequestingLayout = this;
            }
    
            mPrivateFlags |= PFLAG_FORCE_LAYOUT;
            mPrivateFlags |= PFLAG_INVALIDATED;
    
            if (mParent != null && !mParent.isLayoutRequested()) {
                mParent.requestLayout();//不断的递归往上查找,一直到找到DecorView
            }
            if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {
                mAttachInfo.mViewRequestingLayout = null;
            }
        }
    

    2.执行了DecorView的ViewRootImp类的performTranversal()方法 (ViewRootImp类:是PhoneWindow和DecorView的桥梁)

    3.performTranversal里面会调用以下三个方法

    performTranversal(){
    // Ask host how big it wants to be
    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
    performLayout(lp, desiredWindowWidth, desiredWindowHeight);
    performDraw();
    }
    
    performTraversals方法控制View绘制流程图.jpg

    4.performMeasure会执行View的measure方法,从这里开始测量View

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

    5.View的measure方法里面会调用onMeasure方法

      public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
        
            ............
            if (forceLayout || needsLayout) {
                // first clears the measured dimension flag
                mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;
                resolveRtlPropertiesIfNeeded();
                int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
                if (cacheIndex < 0 || sIgnoreMeasureCache) {
                    // measure ourselves, this should set the measured dimension flag back
                    onMeasure(widthMeasureSpec, heightMeasureSpec);
                    mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
                } else {
                    long value = mMeasureCache.valueAt(cacheIndex);
                    // Casting a long to int drops the high 32 bits, no mask needed
                    setMeasuredDimensionRaw((int) (value >> 32), (int) value);
                    mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
                }
            }   
          .........
        }
    

    onMeasure根据传入父类的MeasureSpec,得到自身的大小。

     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                    getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
        }
    

    子类的MeasureSpec由父类和LayoutParams一起决定。


    getChildMeasureSpec方法分析.png

    下面我们再来看看getDefaultSize这个方法:

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

    结合上图可以知道如何测量出View的大小。
    我们再来看看:getSuggestedMinimumWidth()方法。

      protected int getSuggestedMinimumWidth() {
            return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
        }
      public int getMinimumWidth() {
            final int intrinsicWidth = getIntrinsicWidth();
            return intrinsicWidth > 0 ? intrinsicWidth : 0;
        }
       public int getIntrinsicWidth() {
            return -1;
        }
    

    这种情况是确保View设置了背景也能准确的测量出View的大小。

    三 ViewGroup的测量流程

    View树的源码measure流程图.png

    在View的测量流程中,通过makeMeasureSpec来保存宽高信息,在其他流程通过getMode或getSize得到模式和宽高。那么问题来了,上面提到MeasureSpec是LayoutParams和父容器的模式所共同影响的,那么,对于DecorView来说,它已经是顶层view了,没有父容器,那么它的MeasureSpec怎么来的呢?
    阅读源码的时候一定要带着疑问,这时候我们提出了问题,那么接下来就要在源码中找出答案。
    MeasureSpec肯定是在测量之前就已经准备好,这时候我们就想什么时候开始测量的呢?上面已经提到过ViewRootImpl#PerformTraveals这个方法是UI绘制的起点,那我们就去那里看一下。果不其然,看到了我们想看的东西。

    private void performTraversals() {
                ...
    
            if (!mStopped) {
                int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);  // 1
                int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
                performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);       
                }
            } 
    ·······
    }
    
    /**
     * @param windowSize
     *            The available width or height of the window
     *
     * @param rootDimension
     *            The layout params for one dimension (width or height) of the
     *            window.
     *
     * @return The measure spec to use to measure the root view.
     */
    private static int getRootMeasureSpec(int windowSize, int rootDimension) {
        int measureSpec;
        switch (rootDimension) {
    
        case ViewGroup.LayoutParams.MATCH_PARENT:
            // Window can't resize. Force root view to be windowSize.
            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
            break;
        //省略...
    
        }
        return measureSpec;
    }
    

    getRootMeasureSpec(desiredWindowWidth,lp.width)方法,其中desiredWindowWidth就是屏幕的尺寸,并把返回结果赋值给childWidthMeasureSpec成员变量(childHeightMeasureSpec同理),因此childWidthMeasureSpec(childHeightMeasureSpec)应该保存了DecorView的MeasureSpec。

    下面我们来看一下ViewGroup的测量过程,ViewGroup是继承了View,调用的是View的measure方法,我们主要看一下ViewGroup的onMeasure方法,就以LinearLayout为例:

        void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
            mTotalLength = 0;
            int maxWidth = 0;
            int childState = 0;
            int alternativeMaxWidth = 0;
            int weightedMaxWidth = 0;
            boolean allFillParent = true;
            float totalWeight = 0;
    
            final int count = getVirtualChildCount();
            
            final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
            final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
    
            boolean matchWidth = false;
            boolean skippedMeasure = false;
    
            final int baselineChildIndex = mBaselineAlignedChildIndex;        
            final boolean useLargestChild = mUseLargestChild;
    
            int largestChildHeight = Integer.MIN_VALUE;
            int consumedExcessSpace = 0;
    
            // See how tall everyone is. Also remember max width.
            for (int i = 0; i < count; ++i) {
                final View child = getVirtualChildAt(i);
                if (child == null) {
                    mTotalLength += measureNullChild(i);
                    continue;
                }
    
                if (child.getVisibility() == View.GONE) {
                   i += getChildrenSkipCount(child, i);
                   continue;
                }
    
                if (hasDividerBeforeChildAt(i)) {
                    mTotalLength += mDividerHeight;
                }
    
                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
    
                totalWeight += lp.weight;
    
                final boolean useExcessSpace = lp.height == 0 && lp.weight > 0;
                if (heightMode == MeasureSpec.EXACTLY && useExcessSpace) {
                    // Optimization: don't bother measuring children who are only
                    // laid out using excess space. These views will get measured
                    // later if we have space to distribute.
                    final int totalLength = mTotalLength;
                    mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);
                    skippedMeasure = true;
                } else {
                    if (useExcessSpace) {
                        // The heightMode is either UNSPECIFIED or AT_MOST, and
                        // this child is only laid out using excess space. Measure
                        // using WRAP_CONTENT so that we can find out the view's
                        // optimal height. We'll restore the original height of 0
                        // after measurement.
                        lp.height = LayoutParams.WRAP_CONTENT;
                    }
    
                    // Determine how big this child would like to be. If this or
                    // previous children have given a weight, then we allow it to
                    // use all available space (and we will shrink things later
                    // if needed).
                    final int usedHeight = totalWeight == 0 ? mTotalLength : 0;
                    measureChildBeforeLayout(child, i, widthMeasureSpec, 0,
                            heightMeasureSpec, usedHeight);
    
                    final int childHeight = child.getMeasuredHeight();
                    if (useExcessSpace) {
                        // Restore the original height and record how much space
                        // we've allocated to excess-only children so that we can
                        // match the behavior of EXACTLY measurement.
                        lp.height = 0;
                        consumedExcessSpace += childHeight;
                    }
    
                    final int totalLength = mTotalLength;
                    mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin +
                           lp.bottomMargin + getNextLocationOffset(child));
    
                    if (useLargestChild) {
                        largestChildHeight = Math.max(childHeight, largestChildHeight);
                    }
                }
    
                /**
                 * If applicable, compute the additional offset to the child's baseline
                 * we'll need later when asked {@link #getBaseline}.
                 */
                if ((baselineChildIndex >= 0) && (baselineChildIndex == i + 1)) {
                   mBaselineChildTop = mTotalLength;
                }
    
                // if we are trying to use a child index for our baseline, the above
                // book keeping only works if there are no children above it with
                // weight.  fail fast to aid the developer.
                if (i < baselineChildIndex && lp.weight > 0) {
                    throw new RuntimeException("A child of LinearLayout with index "
                            + "less than mBaselineAlignedChildIndex has weight > 0, which "
                            + "won't work.  Either remove the weight, or don't set "
                            + "mBaselineAlignedChildIndex.");
                }
    
                boolean matchWidthLocally = false;
                if (widthMode != MeasureSpec.EXACTLY && lp.width == LayoutParams.MATCH_PARENT) {
                    // The width of the linear layout will scale, and at least one
                    // child said it wanted to match our width. Set a flag
                    // indicating that we need to remeasure at least that view when
                    // we know our width.
                    matchWidth = true;
                    matchWidthLocally = true;
                }
    
                final int margin = lp.leftMargin + lp.rightMargin;
                final int measuredWidth = child.getMeasuredWidth() + margin;
                maxWidth = Math.max(maxWidth, measuredWidth);
                childState = combineMeasuredStates(childState, child.getMeasuredState());
    
                allFillParent = allFillParent && lp.width == LayoutParams.MATCH_PARENT;
                if (lp.weight > 0) {
                    /*
                     * Widths of weighted Views are bogus if we end up
                     * remeasuring, so keep them separate.
                     */
                    weightedMaxWidth = Math.max(weightedMaxWidth,
                            matchWidthLocally ? margin : measuredWidth);
                } else {
                    alternativeMaxWidth = Math.max(alternativeMaxWidth,
                            matchWidthLocally ? margin : measuredWidth);
                }
    
                i += getChildrenSkipCount(child, i);
            }
    
            if (mTotalLength > 0 && hasDividerBeforeChildAt(count)) {
                mTotalLength += mDividerHeight;
            }
    
            if (useLargestChild &&
                    (heightMode == MeasureSpec.AT_MOST || heightMode == MeasureSpec.UNSPECIFIED)) {
                mTotalLength = 0;
    
                for (int i = 0; i < count; ++i) {
                    final View child = getVirtualChildAt(i);
                    if (child == null) {
                        mTotalLength += measureNullChild(i);
                        continue;
                    }
    
                    if (child.getVisibility() == GONE) {
                        i += getChildrenSkipCount(child, i);
                        continue;
                    }
    
                    final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams)
                            child.getLayoutParams();
                    // Account for negative margins
                    final int totalLength = mTotalLength;
                    mTotalLength = Math.max(totalLength, totalLength + largestChildHeight +
                            lp.topMargin + lp.bottomMargin + getNextLocationOffset(child));
                }
            }
    
            // Add in our padding
            mTotalLength += mPaddingTop + mPaddingBottom;
    
            int heightSize = mTotalLength;
    
            // Check against our minimum height
            heightSize = Math.max(heightSize, getSuggestedMinimumHeight());
            
            // Reconcile our calculated size with the heightMeasureSpec
            int heightSizeAndState = resolveSizeAndState(heightSize, heightMeasureSpec, 0);
            heightSize = heightSizeAndState & MEASURED_SIZE_MASK;
            
            // Either expand children with weight to take up available space or
            // shrink them if they extend beyond our current bounds. If we skipped
            // measurement on any children, we need to measure them now.
            int remainingExcess = heightSize - mTotalLength
                    + (mAllowInconsistentMeasurement ? 0 : consumedExcessSpace);
            if (skippedMeasure || remainingExcess != 0 && totalWeight > 0.0f) {
                float remainingWeightSum = mWeightSum > 0.0f ? mWeightSum : totalWeight;
    
                mTotalLength = 0;
    
                for (int i = 0; i < count; ++i) {
                    final View child = getVirtualChildAt(i);
                    if (child == null || child.getVisibility() == View.GONE) {
                        continue;
                    }
    
                    final LayoutParams lp = (LayoutParams) child.getLayoutParams();
                    final float childWeight = lp.weight;
                    if (childWeight > 0) {
                        final int share = (int) (childWeight * remainingExcess / remainingWeightSum);
                        remainingExcess -= share;
                        remainingWeightSum -= childWeight;
    
                        final int childHeight;
                        if (mUseLargestChild && heightMode != MeasureSpec.EXACTLY) {
                            childHeight = largestChildHeight;
                        } else if (lp.height == 0 && (!mAllowInconsistentMeasurement
                                || heightMode == MeasureSpec.EXACTLY)) {
                            // This child needs to be laid out from scratch using
                            // only its share of excess space.
                            childHeight = share;
                        } else {
                            // This child had some intrinsic height to which we
                            // need to add its share of excess space.
                            childHeight = child.getMeasuredHeight() + share;
                        }
    
                        final int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
                                Math.max(0, childHeight), MeasureSpec.EXACTLY);
                        final int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
                                mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin,
                                lp.width);
                        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    
                        // Child may now not fit in vertical dimension.
                        childState = combineMeasuredStates(childState, child.getMeasuredState()
                                & (MEASURED_STATE_MASK>>MEASURED_HEIGHT_STATE_SHIFT));
                    }
    
                    final int margin =  lp.leftMargin + lp.rightMargin;
                    final int measuredWidth = child.getMeasuredWidth() + margin;
                    maxWidth = Math.max(maxWidth, measuredWidth);
    
                    boolean matchWidthLocally = widthMode != MeasureSpec.EXACTLY &&
                            lp.width == LayoutParams.MATCH_PARENT;
    
                    alternativeMaxWidth = Math.max(alternativeMaxWidth,
                            matchWidthLocally ? margin : measuredWidth);
    
                    allFillParent = allFillParent && lp.width == LayoutParams.MATCH_PARENT;
    
                    final int totalLength = mTotalLength;
                    mTotalLength = Math.max(totalLength, totalLength + child.getMeasuredHeight() +
                            lp.topMargin + lp.bottomMargin + getNextLocationOffset(child));
                }
    
                // Add in our padding
                mTotalLength += mPaddingTop + mPaddingBottom;
                // TODO: Should we recompute the heightSpec based on the new total length?
            } else {
                alternativeMaxWidth = Math.max(alternativeMaxWidth,
                                               weightedMaxWidth);
    
    
                // We have no limit, so make all weighted views as tall as the largest child.
                // Children will have already been measured once.
                if (useLargestChild && heightMode != MeasureSpec.EXACTLY) {
                    for (int i = 0; i < count; i++) {
                        final View child = getVirtualChildAt(i);
                        if (child == null || child.getVisibility() == View.GONE) {
                            continue;
                        }
    
                        final LinearLayout.LayoutParams lp =
                                (LinearLayout.LayoutParams) child.getLayoutParams();
    
                        float childExtra = lp.weight;
                        if (childExtra > 0) {
                            child.measure(
                                    MeasureSpec.makeMeasureSpec(child.getMeasuredWidth(),
                                            MeasureSpec.EXACTLY),
                                    MeasureSpec.makeMeasureSpec(largestChildHeight,
                                            MeasureSpec.EXACTLY));
                        }
                    }
                }
            }
    
            if (!allFillParent && widthMode != MeasureSpec.EXACTLY) {
                maxWidth = alternativeMaxWidth;
            }
            
            maxWidth += mPaddingLeft + mPaddingRight;
    
            // Check against our minimum width
            maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
            
            setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
                    heightSizeAndState);
    
            if (matchWidth) {
                forceUniformWidth(count, heightMeasureSpec);
            }
        }
    

    这个源码是比较复杂的,但是我们可以看出的是,如果自身大小是确定的,那么高度就是自身的大小,否则的话需要测量所有的子View,同时还有分割线之类的,将他们的高度叠加。设置自身的大小。

    总结

    一、measure的过程

    如何去合理的测量一颗View树?

    如果ViewGroup和View都是直接指定的宽高,我还要测量吗?

    正是因为谷歌设计的自适应尺寸机制(比如Match_parent,wrap_content),造成了宽高不确定,所以就需要进程测量measure过程。

    measure过程会遍历整颗View树,然后依次测量每一个View的真实的尺寸。(树的遍历--先序遍历)

    MeasureSpec:测量规格

    int 32位:010111100011100

    拿前面两位当做mode,后面30位当做值。

    1.mode:

    1) EXACTLY: 精确的。比如给了一个确定的值 100dp

    1. AT_MOST: 根据父容器当前的大小,结合你指定的尺寸参考值来考虑你应该是多大尺寸,需要计算(Match_parent,wrap_content就是属于这种)

    2. UNSPECIFIED: 最多的意思。根据当前的情况,结合你制定的尺寸参考值来考虑,在不超过父容器给你限定的只存的前提下,来测量你的一个恰好的内容尺寸。

    用的比较少,一般见于ScrollView,ListView(大小不确定,同时大小还是变的。会通过多次测量才能真正决定好宽高。)

    2.value:宽高的值。

    经过大量测量以后,最终确定了自己的宽高,需要调用:setMeasuredDimension(w,h)

    写自定义控件的时候,我们要去获得自己的宽高来进行一些计算,必须先经过measure,才能获得到宽高---不是getWidth(),而是getMeasuredWidth()

    也就是当我们重写onMeasure的时候,我们需要在里面调用child.measure()才能获取child的宽高。

    从规格当中获取mode和value:

    final int widthMode = MeasureSpec.getMode(widthMeasureSpec);

    final int heightMode = MeasureSpec.getMode(heightMeasureSpec);

    int widthSize = MeasureSpec.getSize(widthMeasureSpec);

    int heightSize = MeasureSpec.getSize(heightMeasureSpec);

    反过来将mode和value合成一个规格呢:

    MeasureSpec.makeMeasureSpec(resultSize, resultMode);

    ViewGroup:

    设计它的目的是什么?

    1)作为容器处理焦点问题。

    2)作为容器处理事件分发问题;

    3)控制容器添加View的流程:addView(),removeView()

    4)抽出了一些容器的公共的工具方法:measureChildren,measureChild,measureChildWidthMargins方法。

    -------------------重点:-----------------------

    玩自定义控件的时候,需要进行测量measure,如何做好这件事?

    两种情况:

    1.继承自View的子类

    只需要重写onMeasure测量好自己的宽高就可以了。

    最终调用setMeasuredDimension()保存好自己的测量宽高。

    套路:

        int mode = MeasureSpec.getMode(widthMeasureSpec);
        
        int Size = MeasureSpec.getSize(widthMeasureSpec);
        
        int viewSize = 0;
        
        switch(mode){
                case MeasureSpec.EXACTLY:
                viewSize = size;//当前view的尺寸就为父容器的尺寸
                break;
            case MeasureSpec.AT_MOST:
                viewSize = Math.min(size, getContentSize());//当前view的尺寸就为内容尺寸和费容器尺寸当中的最小值。
                break;
            
            case MeasureSpec.UNSPECIFIED:
                viewSize = getContentSize();//内容有多大,久设置多大尺寸。
                break;
            default:
                break;
        }
        
        //setMeasuredDimension(width, height);
        
        setMeasuredDimension(size);
    

    2.继承自ViewGroup的子类:

    不但需要重写onMeasure测量自己,还要测量子控件的规格大小。

    可以直接使用ViewGroup的工具方法来测量里面的子控件,也可以自己来实现这一套子控件的测量(比如:RelativeLayout)

    套路:

    //1.测量自己的尺寸

    ViewGroup.onMeasure();

    //1.1 为每一个child计算测量规格信息(MeasureSpec)

    ViewGroup.getChildMeasureSpec();

    //1.2 将上面测量后的结果,传给每一个子View,子view测量自己的尺寸

    child.measure();

    //1.3 子View测量完,ViewGroup就可以拿到这个子View的测量后的尺寸了

    child.getChildMeasuredSize();//child.getMeasuredWidth()和child.getMeasuredHeight()

    //1.4ViewGroup自己就可以根据自身的情况(Padding等等),来计算自己的尺寸

    ViewGroup.calculateSelfSize();

    //2.保存自己的尺寸

    ViewGroup.setMeasuredDimension(size);


    二、layout的过程
    ViewGroup才有摆放的过程,有兴趣的同学可以看一下,继承自ViewGroup控件的源码。
    三、draw的过程

    ViewGroup的onDraw方法默认是不会调用的,因为在ViewGroup构造方法里面就默认设置了

    setFlags(WILL_NOT_DRAW, DRAW_MASK);//原因是因为ViewGroup本来就没东西显示,除了设置了背景,这样就是为了效率。

    如果需要它执行onDraw可以,设置背景或者如下:

    setWillNotDraw(false);

    2.自绘控件View/ViewGroup

    主要重写onDraw方法绘制,还会要处理onMeasure,onLayout

    3.组合控件。

    比如封装通用的标题栏控件。

    并不需要自己去绘制视图上面显示的内容,而只是用系统原生的控件就可以了。

    但是我们可以将几个原生控件组合到一起,可以创建出的控件就是组合控件。

    比如:在构造方法里面LayoutInflater.from(context).inflate(R.layout.title,this);然后再加上业务逻辑。

    最后的话:有些地方还有点粗糙,后面会继续补充。

    相关文章

      网友评论

          本文标题:UI绘制流程

          本文链接:https://www.haomeiwen.com/subject/sehmpxtx.html