Android 개발 학습의 선형 레이아웃 측정 프로세스 소스 읽기

배경.
어제 나는 안탁중의 상대적인 레이아웃의 측정 프로세스 원본을 기록한 다음에 선형 레이아웃인 Linear Layout의 측정 프로세스(onMeasure)를 읽었지만 저녁에 갑자기 수요가 생겨서 글 기록이 현재로 미뤄졌다.
onMeasure()
LinearLayout.onMeasure() 코드는 다음과 같습니다.
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        if (mOrientation == VERTICAL) {
            measureVertical(widthMeasureSpec, heightMeasureSpec);
        } else {
            measureHorizontal(widthMeasureSpec, heightMeasureSpec);
        }
    }

모두가 알다시피 선형 구조의 방향은 수직과 수평으로 나뉘는데 이 두 가지는 각각 measureVertical() 방법과 measureHorizontal() 방법에 대응한다. 두 가지 방법의 사고방식이 똑같다. 나는 수직 방향을 예로 들어 그 측정 절차를 읽어 보겠다. 주로 코드의 주석에 해석되어 있다.
measureVertical()
상대적으로 레이아웃된 onMeasure () 방법으로 읽는 것과 같이, 나는 measureVertical () 의 절차를 7단계로 나누었다
void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
        /**
         *   :
         *  1、     
         *  2、     view,       ,           
         *  3、         
         *  4、    view     ,              ,      view      ,           
         *          view    ,         ,      "     view",          ,           view   ,      view   
         *                ,          ,         =  view   *    view   +      +      ,      
         *  5、        
         *  6、      
         *  7、   view match_parent,           ,       view       ,         
         */
}

그러면 한 걸음 한 걸음 가죠.
변수 초기화
사용된 변수 초기화
        mTotalLength = 0; //    
        int maxWidth = 0; //    view     
        int childState = 0; //  view    
        int alternativeMaxWidth = 0; //         view     
        int weightedMaxWidth = 0; //     view     
        boolean allFillParent = true; //  view   fill_parent/match_parent
        float totalWeight = 0; //  view    

        final int count = getVirtualChildCount(); //  view  

        final int widthMode = MeasureSpec.getMode(widthMeasureSpec); //           
        final int heightMode = MeasureSpec.getMode(heightMeasureSpec); //           

        boolean matchWidth = false; //    view   match_parent,             
        boolean skippedMeasure = false; //     view           

        final int baselineChildIndex = mBaselineAlignedChildIndex; //        view,   -1
        final boolean useLargestChild = mUseLargestChild; //    false

        int largestChildHeight = Integer.MIN_VALUE; //    view  
        int consumedExcessSpace = 0; //             

        int nonSkippedChildCount = 0; //        view  

모든 하위 뷰를 처음으로 훑어보기
이 범람 코드는 매우 길고 140여 줄 코드인데, 나는 그래도 몇 단계로 나누어 보겠다
서브뷰가null 또는gone인 경우 처리하고 분할선 처리하기
for (int i = 0; i < count; ++i) {
            final View child = getVirtualChildAt(i); //   getChildAt(i)
            if (child == null) {
                mTotalLength += measureNullChild(i); // 0
                continue;
            }

            if (child.getVisibility() == View.GONE) {
               i += getChildrenSkipCount(child, i); // 0
               continue;
            }

            nonSkippedChildCount++;
            if (hasDividerBeforeChildAt(i)) {
                //      view   divider,         
                mTotalLength += mDividerHeight;
            }
            ....

처리 서브뷰 높이 및 가중치
            final LayoutParams lp = (LayoutParams) child.getLayoutParams();

            totalWeight += lp.weight; //    view   

            final boolean useExcessSpace = lp.height == 0 && lp.weight > 0;
            //  view   0,     0
            //              

            if (heightMode == MeasureSpec.EXACTLY && useExcessSpace) {
                //           , view  useExcessSpace,       ,       

                final int totalLength = mTotalLength;
                mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin); //          
                skippedMeasure = true;
                //     view      ,       skippedMeasure true

            } else {
                if (useExcessSpace) {
                    //             , view  useExcessSpace

                    lp.height = LayoutParams.WRAP_CONTENT;
                    //       height      ,  measureChildBeforeLayout()    
                }

                //       view     ,      view     ,         
                final int usedHeight = totalWeight == 0 ? mTotalLength : 0;
                measureChildBeforeLayout(child, i, widthMeasureSpec, 0,
                        heightMeasureSpec, usedHeight);
                //        i,  viewGroup.measureChildWithMargins()

                final int childHeight = child.getMeasuredHeight();
                if (useExcessSpace) {

                    lp.height = 0; //    view      0
                    consumedExcessSpace += childHeight; //                
                }

                final int totalLength = mTotalLength;
                mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin +
                       lp.bottomMargin + getNextLocationOffset(child));
                //         0

                if (useLargestChild) {
                    //      view   
                    largestChildHeight = Math.max(childHeight, largestChildHeight);
                }
            }

데이텀 선 업데이트하기
            //         ,         
            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; //    match_parent,           ,               
            }

            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) {

                //          
                weightedMaxWidth = Math.max(weightedMaxWidth,
                        matchWidthLocally ? margin : measuredWidth); //             
            } else {

                //           
                alternativeMaxWidth = Math.max(alternativeMaxWidth,
                        matchWidthLocally ? margin : measuredWidth);
            }
            //             

            i += getChildrenSkipCount(child, i); // 0

현재 배치 최대 높이 업데이트
        if (nonSkippedChildCount > 0 && hasDividerBeforeChildAt(count)) {
            //   view   ,     view   divider,              
            mTotalLength += mDividerHeight;
        }

        //      useLargetChild,    view        
        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); // 0
                    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)); //       +n*   view  
            }
        }

        //         

        // 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); //        heightSpec
        heightSize = heightSizeAndState & MEASURED_SIZE_MASK;

여유 공간 할당
여기에 두 가지 상황이 나뉘는데 서브뷰가 측정되지 않았거나 남은 공간이 있는 경우 줄에서 권중분배와useLargestChild 모드에서의 권중분배가 존재하지만 모두 남은 공간을 계산해야 한다
여유 공간 계산
        // 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.

        //     view     ,         ,         view

        int remainingExcess = heightSize - mTotalLength
                + (mAllowInconsistentMeasurement ? 0 : consumedExcessSpace);
        // sdk<=23   ,     ,                

제1종 상황
        if (skippedMeasure || remainingExcess != 0 && totalWeight > 0.0f) {
            //    view     (         ,     view    ,    ),               
            float remainingWeightSum = mWeightSum > 0.0f ? mWeightSum : totalWeight;

            mTotalLength = 0;

            for (int i = 0; i < count; ++i) {
                //      view
                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) {
                    //      view   
                    final int share = (int) (childWeight * remainingExcess / remainingWeightSum);
                    //            
                    remainingExcess -= share; //        
                    remainingWeightSum -= childWeight; //        

                    final int childHeight;
                    if (mUseLargestChild && heightMode != MeasureSpec.EXACTLY) {
                        //      view    
                        childHeight = largestChildHeight;
                    } else if (lp.height == 0 && (!mAllowInconsistentMeasurement
                            || heightMode == MeasureSpec.EXACTLY)) {
                        //  view     0,  sdk > 23              

                        // This child needs to be laid out from scratch using
                        // only its share of excess space.
                        childHeight = share; //         ,        view,          
                    } else {
                        // This child had some intrinsic height to which we
                        // need to add its share of excess space.

                        //    view     ,                  
                        childHeight = child.getMeasuredHeight() + share;
                    }

                    final int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
                            Math.max(0, childHeight), MeasureSpec.EXACTLY); //        childHeight   view    

                    final int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
                            mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin,
                            lp.width); //         、  、 view       view     

                    //    ,       view
                    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; //  view  
                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; //        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 {
            //       ,    useLargestChild  ,         view        view  

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

여기에서 만약 가장 큰 서브뷰를 사용한다면 현재 레이아웃의 최대 높이가 업데이트되지 않았습니다. 왜냐하면 제가 처음에 단계를 나누었을 때의 설명을 참고하십시오
최대 너비 저장
       //    view   fill_parent,       
        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);

서브뷰 너비 match 있음parent 처리
        if (matchWidth) {
            //  view match_parent,             
            forceUniformWidth(count, heightMeasureSpec);
        }
     private void forceUniformWidth(int count, int heightMeasureSpec) {
        // Pretend that the linear layout has an exact size.
        int uniformMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth(),
                MeasureSpec.EXACTLY); //       
        for (int i = 0; i< count; ++i) {
           final View child = getVirtualChildAt(i);
           if (child != null && child.getVisibility() != GONE) {
               LinearLayout.LayoutParams lp = ((LinearLayout.LayoutParams)child.getLayoutParams());

               if (lp.width == LayoutParams.MATCH_PARENT) {
                   // Temporarily force children to reuse their old measured height
                   // FIXME: this may not be right for something like wrapping text?
                   int oldHeight = lp.height;
                   lp.height = child.getMeasuredHeight(); //       height  view     ,        measureChildWithMargins()   

                   // Remeasue with new dimensions
                   measureChildWithMargins(child, uniformMeasureSpec, 0, heightMeasureSpec, 0);
                   //    view         ,       
                   lp.height = oldHeight;
               }
           }
        }
    }

총결산
이를 통해 알 수 있듯이 선형 레이아웃은 권중 분배를 처리할 때 비교적 큰 정력을 소모하기 때문에 우리는 권중의 설정을 최대한 피하고 ui 동료와 조율을 통해 정확한 dp폭을 확정하여 측정 효율을 높여야 한다.
상대 레이아웃과 비교해 보면 상대 레이아웃은 네 개의 단점의 좌표를 설정하여 자view와 자신의 사이즈를 확정하고, 선형 레이아웃은 높이나 너비를 직접 측정하여 자view와 자신의 사이즈를 확정하는 것을 알 수 있다.아마도 원본에서 볼 때 선형 레이아웃 코드는 좀 적지만 상대적인 레이아웃보다 유연성이 떨어지고 심지어 많은 속성이나 차원을 사용해야 효율을 떨어뜨리고 비용을 증가시킬 수 있기 때문에 구체적인 상황을 구체적으로 분석하고 상대적인 레이아웃과 선형 레이아웃을 결합시켜야 서로 잘 어울릴 수 있다.
안드로이드 개발 학습의 Linear Layout의 레이아웃 과정 1문에서 선형 레이아웃의 onLayout() 방법을 기록하는 읽기

좋은 웹페이지 즐겨찾기