获取View宽高
我们应用在获取控件宽高的时候,都是调用控件View.getWidth()、View.getHeight()获取,但是我们真正获取的时机是在什么时候呢?如果在onCreate()、onResume()的生命周期获取会怎么样呢??
class MainActivity : AppCompatActivity() {
val TAG = "LeonMainActivity"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
Log.d(TAG,"onCreate获取view的高度==="+tv_name.height)
tv_name.post {
Log.d(TAG,"post获取view的高度==="+tv_name.height)
}
}
override fun onResume() {
super.onResume()
Log.d(TAG,"onResume获取view的高度==="+tv_name.height)
}
}
运行结果如下:
2019-12-13 16:31:09.672 com.leon.view D/LeonMainActivity: onCreate获取view的高度===0
2019-12-13 16:31:09.694 com.leon.view D/LeonMainActivity: onResume获取view的高度===0
2019-12-13 16:31:10.017 com.leon.view D/LeonMainActivity: post获取view的高度===57
看到结果是不是很意外,我们再onCreate()、onResume()获取到的控件高度都是0,而使用post可以获取到正确的高度。下面我们根据源码来看为什么会这样。
我们知道View的显示经历了测量(measure)、布局(layout)、绘制(draw)这三步,控制只有测量完成以后才被设置宽高属性,所有我们在onCreate()和onResume()获取高度为0是因为没有进行绘制,那布局的绘制是在什么时候进行的我们要看下系统源码。
我们在android.app.ActivityThread.java 文件里面看到handleResumeActivity方法,先不管这个方法是什么时候调的,这一步后面在Activity的启动流程分析时再看
android.app.ActivityThread.java
@Override
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward, String reason) {
//Activity的启动流程,会回调到Activity的onResume()生命周期
final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
//省略。。。。。。
if (r.window == null && !a.mFinished && willBeVisible) {
r.window = r.activity.getWindow();
//获取到DecorView,DecorView是页面的根布局包裹一个FrameLayout,我们的布局就是添加到这个布局的R.id.content
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
//获取到WindowManager
ViewManager wm = a.getWindowManager();
//省略。。。。。。
if (r.mPreserveWindow) {
//ViewRootImpl是连接DecorView和WindowManager的桥梁,测量绘制都是在这个里面处理的
ViewRootImpl impl = decor.getViewRootImpl();
}
if (a.mVisibleFromClient) {
if (!a.mWindowAdded) {
a.mWindowAdded = true;
wm.addView(decor, l);
} else {
a.onWindowAttributesChanged(l);
}
}
//省略。。。。。。
WindowManager.LayoutParams l = r.window.getAttributes();
if ((l.softInputMode
& WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION)
!= forwardBit) {
//省略。。。。。。
l.softInputMode = (l.softInputMode
& (~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION))
| forwardBit;
if (r.activity.mVisibleFromClient) {
ViewManager wm = a.getWindowManager();
View decor = r.window.getDecorView();
//此处就是更新布局的地方
wm.updateViewLayout(decor, l);
}
}
//省略。。。。。。
}
执行完onResume方法或会调用WindowManager的updateViewLayout方法,此处的WindowManager是WindowManagerImpl,我们到WindowManagerImpl里面查看updateViewLayout方法。
android.view.WindowManagerImpl
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
@Override
public void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.updateViewLayout(view, params);
}
接下来到WindowManagerGlobal的updateViewLayout方法
android.view.WindowManagerGlobal.java
public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
if (view == null) {
throw new IllegalArgumentException("view must not be null");
}
if (!(params instanceof WindowManager.LayoutParams)) {
throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
}
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;
view.setLayoutParams(wparams);
synchronized (mLock) {
int index = findViewLocked(view, true);
ViewRootImpl root = mRoots.get(index);
mParams.remove(index);
mParams.add(index, wparams);
//ViewRootImpl设置
root.setLayoutParams(wparams, false);
}
}
看到最后 ViewRootImpl类的setLayoutParams(wparams, false)
android.view.ViewRootImpl
1.进入ViewRootImpl
void setLayoutParams(WindowManager.LayoutParams attrs, boolean newView) {
applyKeepScreenOnFlag(mWindowAttributes);
if (newView) {
mSoftInputMode = attrs.softInputMode;
requestLayout();
}
// Don't lose the mode we last auto-computed.
if ((attrs.softInputMode & WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST)
== WindowManager.LayoutParams.SOFT_INPUT_ADJUST_UNSPECIFIED) {
mWindowAttributes.softInputMode = (mWindowAttributes.softInputMode
& ~WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST)
| (oldSoftInputMode & WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST);
}
mWindowAttributesChanged = true;
//开始进入绘制流程
scheduleTraversals();
}
}
2.调用mTraversalRunnable
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
3.执行doTraversal()方法
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
4.执行doTraversal()方法
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}
performTraversals();
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}
到最后的关键一步了performTraversals()
private void performTraversals() {
// cache mView since it is used so much below...
final View host = mView;
//省略。。。。。。
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
//省略。。。。。。
performLayout(lp, mWidth, mHeight);
//省略。。。。。。
performDraw();
}
进行测量、布局、绘制三部曲,所以我们再onCreate()、onResume()获取不到宽高,因为测量就是在这两个生命周期之后,至于view.post(Runnable)为什么可以获取,下篇文章再分析