반사 시리즈 블로그는 새로운 방법을 배울 수있는 내 시도, 내용의 시리즈와 테이블의 기원은, 참조하십시오 여기 .
개요
Android
자신의 View
시스템이 매우 큰 소스 코드 가치가 생각하고 많은 배우 View
의를 통해, 예를 들어, 프로세스 자체를 그릴 measure
측정, layout
레이아웃 draw
세 가지 프로세스를 그리기, 궁극적으로 사용자의 앞에 그것을 끌어와 설명 할 수.
비교 과정을 측정 , 레이아웃 과정은 독자가 이해하지 않는 경우, 상대적으로 훨씬 간단 측정 과정을 , 나는이 기사를 읽어 보시기 바랍니다 :
반사 | 안드로이드보기 메커니즘의 설계 및 구현 : 측정 프로세스
전체 아이디어
측정 유량 을 목적으로한다 너비와 높이 제어를 측정 ,하지만 사실 컨트롤의 폭과 높이를 얻는 것은 충분하지 않습니다에 대한 ViewGroup
목적은 로직의 추가 설정이 필요, 그 자식 컨트롤 모두에 해당하는 정책의 레이아웃에 대한 책임, 이것은이다 레이아웃 과정 ( 레이아웃).
- 리프 노드의 경우 1.
View
그는 컨트롤의 논리적 서브 레이아웃의 처리를 필요로하지 않는, 자식 컨트롤을 가지고, 따라서 일반적으로 단지 부모 컨트롤에 자신의 위치를 기록 할 필요가 없습니다; - 하여 프로세스의 전체 레이아웃, 반드시 부모 컨트롤에 넘겨 자식 제어 장치의 위치, 그리고 2. 측정 과정 으로,
Android
레이아웃 과정은 또한 재귀 아이디어를 사용에서 : 완전한 인터페이스의 경우, 각 페이지 매핑View
는 트리 구조 제어를 보장하기 위해 다음 사항을 언급 할 가치가있다 - 나무, 부모 컨트롤의 위쪽은 차례로 자신의 유통 전략을 통해 각 자식 컨트롤의 위치를 계산, 레이아웃으로 시작 내부 자율성 각 자식 컨트롤의를 같은 위치 부모 제어의 좌표계에 대한 상대 위치 가 아니라 절대 위치 중 화면 좌표보다. 위치를 계산 한 후, 하위 레이아웃을 제어 시작되도록 하위 제어 파라미터로서 등등의 컨트롤의 바닥까지 때 완료 레이아웃 모든 제어, 전체 배치 프로세스 종료한다.
캐릭터의 측면에서 개발자 익숙하지 않은 레이아웃 프로세스에 대한 모호한 것 같다,하지만 설계 과정의 전체 레이아웃, 이들 문자의 본질을 요약 독자가 소스 코드 분석으로 해석하지 말아야하며, 자신에 대입한다 설계 과정에 깊은 디자인 아이디어, 레이아웃, 공정 설계의 전 과정의 이해와 하나가 자연의 이동에서의 격차를 채우기 위해 코드를 작성.
하나의 레이아웃 과정을보기
첫째, 최대 해당 위치에 대한 각 자식 컨트롤을 할당 문제, 측정 종료 후 레이아웃 과정의 본질에 대해 생각 - 자식 컨트롤이 있기 때문에, 그것은 메인 레이아웃 과정이 있어야 보여줍니다 ViewGroup
하나의 리프 노드로 다음 View
의 왜 레이아웃 과정이있을 것입니다?
독자 중시을 그릴 수 있고, 레이아웃 프로세스가 실제로 복잡한 과정이며, 다음과 같이 기본 프로세스의 전체 논리 시퀀스이다 :
- 당신이 재 측정 과정에 필요 여부를 결정하십시오
onMeasure()
; - 2. 정보 자체가 저장되는 위치;
- 3. 레이아웃은 개시제의 레이아웃을 변경하는 프로세스의 여부를 판단한다;
- 4. 레이아웃이 변경된 경우, 그래서 모든 자식 컨트롤 레이아웃을 다시 것을;
- 레이아웃이 변경되는 경우 5. 레이아웃 변경 알림 리스너는 전송 된 모든 알림을 관찰합니다.
전체 레이아웃 과정은 또한 4는 ViewGroup
그 자체가 수행해야 할 다른위한 논리 View
및 ViewGroup
조건은 공개됩니다 - 그 개인을 나타내는 것은 View
레이아웃 과정을 필요로한다.
이제 전체 레이아웃 과정, 즉, 세 가지 중요한 기능을 정의 :
void layout(int l, int t, int r, int b)
: 자신의 기능 전체 레이아웃 프로세스를 제어;void onLayout(boolean changed, int left, int top, int right, int bottom)
: 뷰 그룹 레이아웃 로직 기능은 개발자는 사용자 정의 레이아웃 논리를 구현해야;void setFrame(int left, int top, int right, int bottom)
: 함수의 최신 레이아웃 위치 정보 저장;
이유는 세 가지 기능을 정의하는이 필요?
시작 플래그 레이아웃 : 1.layout 기능
이제 우리는 하나의 스탠드 View
관점을 먼저 부모 컨트롤이 자식 컨트롤의 호출에 의해 요구되는 layout()
자식 컨트롤의 위치 (동안, 함수를 left、right、top、bottom
매개 변수로는) 레이아웃 과정 자체 자식 컨트롤의 표시를 시작합니다 :
// 伪代码实现
public void layout(int l, int t, int r, int b) {
// 1.决定是否需要重新进行测量流程(onMeasure)
if(needMeasureBeforeLayout) {
onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec)
}
// 先将之前的位置信息进行保存
int oldL = mLeft;
int oldT = mTop;
int oldB = mBottom;
int oldR = mRight;
// 2.将自身所在的位置信息进行保存;
// 3.判断本次布局流程是否引发了布局的改变;
boolean changed = setFrame(l, t, r, b);
if (changed) {
// 4.若布局发生了改变,令所有子控件重新布局;
onLayout(changed, l, t, r, b);
// 5.若布局发生了改变,通知所有观察布局改变的监听发送通知
mOnLayoutChangeListener.onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
}
}
여기에 의사 코드 레이아웃 과정의 설명, 사실의 방법으로 저자 View
자신의 layout()
- 내부 기능, 비록 여러 가지 있지만, 핵심 아이디어는 동일합니다 layout()
전체 프로세스가 실제로 자신의 레이아웃을 나타내는 기능 컨트롤 setFrame()
및 onLayout()
기능은 다음 layout()
에 한 단계.
2.setFrame 기능이 레이아웃 정보 저장
이유는 레이아웃 정보를 저장해야합니까? 우리는 항상 폭이 높은 수요가 컨트롤을 가져옵니다 때문에 - 예를 들면 다음과 같이 onDraw()
그리기 단계; 및 레이아웃 정보 저장, 우리는 컨트롤 자체의 폭과 높이를 계산하기 위해이 값을 통과 할 수있을 것입니다 :
public final int getWidth() { return mWidth; }
public final int getHeight() { return mHeight; }
따라서, 제어 레이아웃 정보의 보존이 실제로 매우 필요하다, 안드로이드는 것 layout()
네 개의 매개 변수의 위치 정보 기능이에 나타내는 setFrame()
저장 기능 :
protected boolean setFrame(int left, int top, int right, int bottom) {
// 布局是否发生了改变
boolean changed = false;
// 若最新的布局信息和之前的布局信息不同,则保存最新的布局信息
if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
changed = true;
mLeft = left;
mTop = top;
mRight = right;
mBottom = bottom;
}
return changed;
}
setFrame()
함수가되어 protected
있는 개발자 정의하는이 기능을 대체 할 수 있음을 의미 변성 View
지금까지 회전 로직의 레이아웃 정보를 저장하는 자체 mLeft、mTop、mRight、mBottom
네 변수.
이름이 자연스럽게 해당,이 네 가지 변수를 암시 하듯이 View
자신의 위치를,이 View
위치 정보는 네 가지 변수들에 의해 설명 제어하는 방법입니까?
3. 상대 위치 및 절대 위치
지도보기로 표현이 네 가지 변수의 의미 :
이 시간은 필연적 있다는 또 다른 문제에 직면 mLeft、mTop、mRight、mBottom
좌표 시스템의 대응 값이 어디 있는지?
이는 트리 컨트롤 보장하기 위해, 그 주목해야 내부 자율성 의 각 자식 컨트롤의 위치 부모 제어의 좌표계의 상대 위치 가 아니라 대상의 절대 위치를 화면 좌표 이하인 :
상기 위치 정보는 좌표 우선 화면에있는 경우, 다른 방법으로 라운드는, 그 후에는 각 리프 노드는 것을 의미
View
보유 할 루트에서 저장 될ViewGroup
부모까지ViewGroup
레이아웃 계산시 각 제어 위치 정보를 더 복잡 분명히 불합리한 디자인입니다.
때문에 View
이러한 위치 정보를 자신의 보유, 그 폭 및 높이의 제어가 실제로 이미 취득 getWidth()
하고 getHeight()
상기 방법은이 방법을 다시 정의 할 수있다 :
public final int getWidth() { return mRight - mLeft; }
public final int getHeight() { return mBottom - mTop; }
이것은 또한 레이아웃의 처리에 대해 설명 setFrame()
하는 기능이 완료된 후에 실행 (및 레이아웃 변경 않음) 개발자 전달할 수 getWidth()
및 getHeight()
제어 방법의 정확한 폭과 높이의 값을 얻는다.
4.onLayout 기능 : 위치 산출 자식 컨트롤
리프 노드의 경우 View
, 자식 컨트롤과하지 않았다 따라서 일반적으로하지 (특별한 경우를 참조하십시오 자식 컨트롤 레이아웃을 의미하는 것으로 AppCompatTextView
, 그래서 같은 클래스로) 기능이 빈을 달성하기 위해 설계되었습니다 :View
onLayout()
protected void onLayout(boolean changed, int left, int top, int right, int bottom) { }
에서 ViewGroup
, 다른 유형은 ViewGroup
다른 레이아웃 정책이, 서로 다른 전략의 논리적 레이아웃,이 방법은 개발자가 정의하는 방법을 구현해야합니다, 추상적 인 인터페이스를 설계 ViewGroup
레이아웃 정책 :
@Override
protected abstract void onLayout(boolean changed,int l, int t, int r, int b);
以
LinearLayout
为例,其布局策略为 根据排布方向,将其所有子控件按照指定方向依次排列布局
至此单个View
的测量流程结束,关于ViewGroup
的onLayout
函数细节将在下文进行描述。
完整布局流程
相比较测量流程,布局流程相对比较简单,整体思路是,对于一个完整的界面而言,每个页面都映射了一个View
树,最顶端的父控件开始布局时,会通过自身的布局策略依次计算出每个子控件的位置。位置计算完毕后,作为参数交给子控件,令子控件开始布局;如此往复一直到最底层的控件,当所有控件都布局完毕,整个布局流程结束。
ViewGroup
虽然重写了View
的layout()
函数,但实质上并未进行大的变动,我们大抵可以认为ViewGroup
和View
的layout()
逻辑一致:
@Override
public final void layout(int l, int t, int r, int b) {
if (!mSuppressLayout && (mTransition == null || !mTransition.isChangingLayout())) {
if (mTransition != null) {
mTransition.layoutChange(this);
}
// 仍然是执行View层的layout函数
super.layout(l, t, r, b);
} else {
mLayoutCalledWhileSuppressed = true;
}
}
唯一需要注意的是,开发者必须实现onLayout()
函数以定义ViewGroup
的布局策略,这里以 竖直布局 的LinearLayout
的伪代码为例:
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int childTop;
int childLeft;
// 遍历所有子View
for (int i = 0; i < count; i++) {
// 获取子View
final View child = getVirtualChildAt(i);
// 获取子View宽高,注意这里使用的是 getMeasuredWidth 而不是 getWidth
final int childWidth = child.getMeasuredWidth();
final int childHeight = child.getMeasuredHeight();
// 令所有子控件开始布局
setChildFrame(child, childLeft, childTop, childWidth, childHeight);
// 高度累加,下一个子View的 top 就等于上一个子View的 bottom ,符合竖直线性布局从上到下的布局策略
childTop += childHeight;
}
}
private void setChildFrame(View child, int left, int top, int width, int height) {
// 这里可以看到,子控件的mRight实际上就是 mLeft + getMeasuredWidth()
// 而在getWidth()函数中,mRight-mLeft的结果就是getMeasuredWidth()
// 因此,getWidth() 和 getMeasuredWidth() 是一致的
child.layout(left, top, left + width, top + height);
}
读者需要注意到一个细节,子控件的宽度的获取,我们并未使用getWidth()
,而是使用了getMeasuredWidth()
,这就引发了另外一个疑问,这两个函数的区别在哪里。
getWidth 和 getMeasuredWidth 的区别
首先,从上文中我们得知,getWidth()
和getHeight()
函数的相关信息实际上是在setFrame()
函数执行完毕才准备完毕的——我们大致可以认为是这两个函数 只有布局流程(layout)执行完毕才能调用,而在父控件的onLayout()
函数中,获取子控件宽度和高度时,子控件还并未开始进行布局流程,因此此时不能调用getWidth()
函数,而只能通过getMeasuredWidth()
函数获取控件测量阶段结果的宽度。
那么当控件绘制流程执行完毕后,getWidth()
和getMeasuredWidth()
函数的值有什么区别呢?从上述setChildFrame()
函数中的源码可以得知,布局流程执行后,getWidth()
返回值的本质其实就是getMeasuredWidth()
——因此本质上,当我们没有手动调用layout()
函数强制修改控件的布局信息的话,两个函数的返回值大小是完全一致的。
整体流程小结
在整个布局流程的设计中,设计者将流程中公共的业务逻辑(保存布局信息、通知布局发生改变的监听等)通过layout()
函数进行了整合,同时,将ViewGroup
额外需要的自定义布局策略通过onLayout()
函数向外暴露出来,针对组件中代码的可复用性和可扩展性进行了合理的设计。
至此,布局流程整体实现完毕。借用 carson_ho 绘制的流程图对整体布局流程做一个总结:
参考
关于我
Hello,我是 却把清梅嗅 ,如果您觉得文章对您有价值,欢迎 ❤️,也欢迎关注我的 博客 或者 Github。
如果您觉得文章还差了那么点东西,也请通过关注督促我写出更好的文章——万一哪天我进步了呢?