一. 注意点
Activity的onCreate方法内获得的View的width可能为0
因为在onCreate时还没有对整棵View树遍历.
去进行测量布局从而对mRight和mLeft进行赋值;
实际得到的width= mRight-mLeft; 因mRight和mLeft初始值为0而得到0.
二. 案例
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
refreshInputPhoneView(mPhoneIsoView);
}
private void refreshInputPhoneView(final View view) {
if (view != null) {
view.post(new Runnable() {
@Override
public void run() {
// 此处获取的宽度
// 若该Activity的ViewTree还没有进行遍历就会是0;
// 而该post消息的执行不能保证一定在ViewTree的遍历之后
// 进而产生偶现的重叠问题
int x = view.getWidth();
int top = mInputPhoneView.getPaddingTop();
int end = mInputPhoneView.getPaddingEnd();
mInputPhoneView.getPaddingStart();
int bottom = mInputPhoneView.getPaddingBottom();
if (!SysHelper.isRtl()) {
mInputPhoneView.setPadding(x, top, end, bottom);
} else {
mInputPhoneView.setPadding(end, top, x, bottom);
}
}
});
}
三. 原因
- ViewTree 的建立流程中
(a) performLaunchActivity方法(将导致Activity#onCreate调用)
只是完成了Activity内部Window和DecorView的创建
生成了ViewTree
(b)handleResumeActivity方法
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
String reason) {
...
final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason); // 这将导致Activity#onResume方法被调
...
wm.addView(decor, l); // 这将导致注册到WMS
会将该ViewTree添加到WindowManagerGlobal中,继而注册到WMS里
- 将ViewTree注册到WMS中的方法调用root.setView
会调用requestLayout()方法,进而引发遍历过程.
ViewRootImpl.java中
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals(); // 安排一次遍历
}
}
void scheduleTraversals() {
if (!mTraversalScheduled) { // 当前是否已经在做遍历了
mTraversalScheduled = true;
...
// 这里通过向mChoreographer注册mTraversalRunnable
// 在等到VSYNC信号来临时会调用执行run方法进而做遍历
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
...
}
}
-
这样就可以看出引发首次遍历的关键在于VSYNC信号的到来
由于VSYNC信号的到来和上述案例中发到主线程消息队列的执行体何时执行没有必然联系.因此不能保证案例中消息体run方法内获得的是遍历执行以后的width. -
同时也可以看出ViewTree的遍历和Activity的生命周期方法没有必然的强联系; 不是说Activity在执行到哪个生命周期方法;
ViewTree就一定会遍历开始或完成.
四. 案例问题解决方法
重写mPhoneIsoView的上一层ViewGroup的onLayout方法,在该方法内去
获得mPhoneIsoView遍历得到的实际width
// mPhoneIsoView 以FramLayout布局方式存在与AreaView中
public class AreaView extends FrameLayout {
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom); // 此调用以后实际上已经执行完了layout
int phoneIsoViewWidth = mPhoneIsoView.getWidth(); // 此时可获取实际width
因为 View真实大小的确定在layout之后,draw之前
参考:https://blog.csdn.net/ly969434341/article/details/106268087
/**
* Called from layout when this view should
* assign a size and position to each of its children.
*
* Derived classes with children should override
* this method and call layout on each of
* their children.
* @param changed This is a new size or position for this view
* @param left Left position, relative to parent
* @param top Top position, relative to parent
* @param right Right position, relative to parent
* @param bottom Bottom position, relative to parent
*/
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}