화면 구성하는 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; }
머리속에 정리도 안되고 많이 헷갈렸던 내용인데 잘 정리된 글이네요. 정말 감사드립니다.
답글삭제분명 좋은 글입니다.
답글삭제정말 큰 도움되었습니다 감사합니다^^
답글삭제