为什么在View.post()方法中可以获取View 的高度?
在平常的开发中,经常遇到要在onCreate()方法中获取某个View的高度或者是某个View获取到焦点,或者滑动到指定的位置,但是直接用view.getHeight()常常拿到的高度为0,当我们使用View.post()方法或者使用View.postDelay()即可以获得想要的结果
mTvResult.post(() -> {
int measuredHeight = mTvResult.getMeasuredHeight();
ELog.i(TAG, "onCreate: "+measuredHeight);
});
mTvResult.postDelayed(new Runnable() {
@Override
public void run() {
int measuredHeight = mTvResult.getMeasuredHeight();
ELog.i(TAG, "onCreate: "+measuredHeight);
}
},1000);
下面分析原因
首先我们看看View.post()方法的源码
/**
* Causes the Runnable to be added to the message queue.
* The runnable will be run on the user interface thread.
*
* @param action The Runnable that will be executed.
*
* @return Returns true if the Runnable was successfully placed in to the
* message queue. Returns false on failure, usually because the
* looper processing the message queue is exiting.
*
* @see #postDelayed
* @see #removeCallbacks
*/
public boolean post(Runnable action) {
final AttachInfo attachInfo = mAttachInfo; //A set of information given to a view when it is attached to its parent window.
//由于在onCreate方法中View还没有attached 此时attchInfo为null
if (attachInfo != null) {
return attachInfo.mHandler.post(action);
}
// Postpone the runnable until we know on which thread it needs to run.
// Assume that the runnable will be successfully placed after attach.
getRunQueue().post(action); //HandlerActionQueue Class used to enqueue pending work from Views when no Handler is attached.
return true;
}
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.view;
import android.os.Handler;
import com.android.internal.util.GrowingArrayUtils;
/**
* Class used to enqueue pending work from Views when no Handler is attached.
*
* @hide Exposed for test framework only.
*/
public class HandlerActionQueue {
private HandlerAction[] mActions;
private int mCount;
public void post(Runnable action) {
postDelayed(action, 0);
}
public void postDelayed(Runnable action, long delayMillis) {
final HandlerAction handlerAction = new HandlerAction(action, delayMillis);
synchronized (this) {
if (mActions == null) {
mActions = new HandlerAction[4];
}
mActions = GrowingArrayUtils.append(mActions, mCount, handlerAction);
mCount++;
}
}
public void removeCallbacks(Runnable action) {
synchronized (this) {
final int count = mCount;
int j = 0;
final HandlerAction[] actions = mActions;
for (int i = 0; i < count; i++) {
if (actions[i].matches(action)) {
// Remove this action by overwriting it within
// this loop or nulling it out later.
continue;
}
if (j != i) {
// At least one previous entry was removed, so
// this one needs to move to the "new" list.
actions[j] = actions[i];
}
j++;
}
// The "new" list only has j entries.
mCount = j;
// Null out any remaining entries.
for (; j < count; j++) {
actions[j] = null;
}
}
}
public void executeActions(Handler handler) {
synchronized (this) {
final HandlerAction[] actions = mActions;
for (int i = 0, count = mCount; i < count; i++) {
final HandlerAction handlerAction = actions[i];
handler.postDelayed(handlerAction.action, handlerAction.delay);
}
mActions = null;
mCount = 0;
}
}
public int size() {
return mCount;
}
public Runnable getRunnable(int index) {
if (index >= mCount) {
throw new IndexOutOfBoundsException();
}
return mActions[index].action;
}
public long getDelay(int index) {
if (index >= mCount) {
throw new IndexOutOfBoundsException();
}
return mActions[index].delay;
}
private static class HandlerAction {
final Runnable action;
final long delay;
public HandlerAction(Runnable action, long delay) {
this.action = action;
this.delay = delay;
}
public boolean matches(Runnable otherAction) {
return otherAction == null && action == null
|| action != null && action.equals(otherAction);
}
}
}
private void performTraversals() {
// cache mView since it is used so much below...
final View host = mView;
//注:该方法有800行代码,已省略
// Execute enqueued actions on every traversal in case a detached view enqueued an action
getRunQueue().executeActions(attachInfo.mHandler);
...
performMeasure();//从DecorView开始完成View树的测量
...
performLayout();//从DecorView开始完成View树的布局
...
performDraw();//从DecorView开始绘制View树
}
结论:view.post()方法在整个view树的performMeasure, performLayout, performDraw执行完后,才被主线程轮询到,才得到执行。
getRunQueue().executeActions(attachInfo.mHandler)里面会遍历数组然后把所有的view.post(Runnable)里的Runnable都用和主线程Looper关联的Hanlder#sendMessage出去,放到主线程Looper里轮询,等待调用。那么由于当前的performTraversals()本身就是由主线程Looper回调给刚才的Choreographer里面去执行的,因此主线程一定会等待performTraversals()整个方法执行完,才去接着执行由view.post()推送到主线程的Runnable。因此整个View树都完成了测量,布局,绘制。然后view.post()里面百分百的可以拿到view的宽高了。