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에 따라 라이센스가 부여됩니다.