화면 구성하는 2가지의 pass 를 수행하게 된다. 순서는 아래와 같다.
- Measure pass
- Layout Pass
measure vs onMeasure()
measure -> onMeasure()View.java
layout -> onLayout()
measure 나 layout 이나 호출을 하는 모양새는 비슷하다. 모두 measure/layout 을 호출하고 그 내부에서 다시 onMeasure()/onLayout() 을 호출하고 있다.
measure() 내부에서 onMeasure() 를 호출한다. 이것을 design 관점에서 본다면, measure 는 필수적으로 수행돼야 하는 부분들을 모아놓은 것이고, 그 중에 override 를 해야 하거나, 할 수 있는 것들을 onMeasure() 로 분리해 놓은 듯이 보인다.
onMeasure()
View 는 layout 안에서 너비(width) 나 높이(height) 가 각 View 마다 자신의 값을 가질 수 있다. 그래서 View 는 자신의 width 와 height 의 값을 가지고 있다. 이것이 member variable 인 mMeasuredWidth / mMeasuredHeight 이다.그래서, 기본적으로 이 mMeasuredWidth / mMeasuredHeight 이 set 돼야 한다.
View 는 그래서
View.measure() >> View.onMeasure() >> View.setMeasuredDimesion()를 호출해서 이 작업을 한다.
그런데 우리가 customView 를 만들 때는 이 onMeasure() 를 override 하게 된다. 그러면 setMeausredDimesion() 이 호출이 안되기 때문에 우리가 만든 customView 의 onMeasure() 에서 setMeasuredDimension() 을 호출해 줘서 mMeasuredWidth / mMeasuredHeight 을 설정해야 한다.
그러면 onMeasure() 에서 할 일은 onMeasure() 를 가지고 있는 class 의 width 와 height 를 설정 해 주는 일을 한다고 보면 된다. View 를 extends 했다면 view 의 width 와 height 를 설정해 주는 작업을 onMeasure() 에 작성하면 되고, layout 을 extends 했다면, layout 의 width와 height 를 작성하는 코드를 만들면 된다.
간단히 얘기하면, size 계산(measure)해서 set 까지 해 주는 것이다.
onLayout()
FrameLayout 를 보자. FrameLayout 는 ViewGroup 이다.ViewGroup.layout() >> View.layout() >> View.onLayout()gravity 가 있다면 gravity 에 따라서 위치가 달라질 것이다. 그렇기 때문에 gravity 설정과 관련된 계산도 onLayout 에서 해준다.
그리고 ViewGroup() 인 경우에는 그 안에 여러 child View 들을 가지고 있다. 이 child view 들은 또 ViewGroup() 일 수도, 그냥 View 일 수도 있다. 그렇기 때문에 childView.layout() 을 호출해서 자신의 child 에게 layout 을 하라고 한다.
ViewGroup 은 자신이 가지고 있는 child View 들이 어떤 식으로 배치(layout) 되는 지를 결정해야 한다. 이런 결정은 결국 onLayout() 에서 child View 들의 구역(region) 을 정해주는 것으로 가능하다.
그러므로 현재 View 의 onLayout() 에서는 child 에게 그려지는 범위까지만 알려주면 된다. 그러면 child 는 그 범위를 자신의 범위로 인식하고 그 범위에 따른 상대적인 좌표를 계산하게 된다.
ViewGroup.layout() >> View.layout() >> View.onLayout() : empty >> ViewGroup.onLayout() : empty >> inherited class' onLayout()View.layout() 에서 setFrame() 함수를 통해 View의 left, top, right, bottom 을 set 한다. 각 View 에서 이렇게 layout() 을 호출하기 때문에 각 View 가 setFrame() 을 호출한다고 보면 된다.
mLeft = left;
mTop = top;
mRight = right;
mBottom = bottom;
Sample code[ref. 2]
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
final int heightSize = MeasureSpec.getSize(heightMeasureSpec);
if (widthMode != MeasureSpec.EXACTLY) {
throw new IllegalStateException("Width must have an exact value or MATCH_PARENT");
} else if (heightMode != MeasureSpec.EXACTLY) {
throw new IllegalStateException("Height must have an exact value or MATCH_PARENT");
}
int layoutHeight = heightSize - getPaddingTop() - getPaddingBottom();
int panelHeight = mPanelHeight;
final int childCount = getChildCount();
if (childCount > 2) {
Log.e(TAG, "onMeasure: More than two child views are not supported.");
} else if (getChildAt(1).getVisibility() == GONE) {
panelHeight = 0;
}
// We'll find the current one below.
mSlideableView = null;
mCanSlide = false;
/**
namh
<core>
for (int i = 0; i < childCount; i++)
child.measure(childWidthSpec, childHeightSpec);
</core>
*/
// First pass. Measure based on child LayoutParams width/height.
for (int i = 0; i < childCount; i++) {
final View child = getChildAt(i);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
int height = layoutHeight;
if (child.getVisibility() == GONE) {
lp.dimWhenOffset = false;
continue;
}
if (i == 1) {
lp.slideable = true;
lp.dimWhenOffset = true;
mSlideableView = child;
mCanSlide = true;
} else {
height -= panelHeight;
}
int childWidthSpec;
if (lp.width == LayoutParams.WRAP_CONTENT) {
childWidthSpec = MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.AT_MOST);
} else if (lp.width == LayoutParams.MATCH_PARENT) {
childWidthSpec = MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.EXACTLY);
} else {
childWidthSpec = MeasureSpec.makeMeasureSpec(lp.width, MeasureSpec.EXACTLY);
}
int childHeightSpec;
if (lp.height == LayoutParams.WRAP_CONTENT) {
childHeightSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST);
} else if (lp.height == LayoutParams.MATCH_PARENT) {
childHeightSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
} else {
childHeightSpec = MeasureSpec.makeMeasureSpec(lp.height, MeasureSpec.EXACTLY);
}
child.measure(childWidthSpec, childHeightSpec);
}
setMeasuredDimension(widthSize, heightSize);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
final int paddingLeft = getPaddingLeft();
final int paddingTop = getPaddingTop();
final int childCount = getChildCount();
int yStart = paddingTop;
int nextYStart = yStart;
if (mFirstLayout) {
mSlideOffset = mCanSlide && mPreservedExpandedState ? 0.f : 1.f;
}
for (int i = 0; i < childCount; i++) {
final View child = getChildAt(i);
if (child.getVisibility() == GONE) {
continue;
}
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
int childHeight = child.getMeasuredHeight();
if (lp.slideable) {
mSlideRange = childHeight - mPanelHeight;
yStart += (int) (mSlideRange * mSlideOffset);
} else {
yStart = nextYStart;
}
final int childTop = yStart;
final int childBottom = childTop + childHeight;
final int childLeft = paddingLeft;
final int childRight = childLeft + child.getMeasuredWidth();
child.layout(childLeft, childTop, childRight, childBottom);
nextYStart += child.getHeight();
}
if (mFirstLayout) {
updateObscuredViewVisibility();
}
mFirstLayout = false;
}
머리속에 정리도 안되고 많이 헷갈렸던 내용인데 잘 정리된 글이네요. 정말 감사드립니다.
답글삭제분명 좋은 글입니다.
답글삭제정말 큰 도움되었습니다 감사합니다^^
답글삭제