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() 방법을 기록하는 읽기
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
[Android] 둥글게 펼쳐지는 Ripple을, 바삭하게 구현간이적으로 터치 피드백이 없는 버튼이나 레이아웃, 탭 범위가 좁아져 버린 버튼 등에, 범위 밖으로 둥글게 퍼지는 Ripple로 탭감, 영역을 조금 늘립니다. 이런 느낌 (화질 나쁘고 미안해..) Ripple을 내고 ...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.