关于在onCreate或onResume中获取控件的实际宽高
我们知道在onCreate或onResume 中获取控件的宽高都为0,但是可以通过以下代码来获取
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_animator58);
mShowBt = findViewById(R.id.show);
mShowBt.post(new Runnable() {
@Override
public void run() {
Log.e("TAG", "run: ---->" + mShowBt.getWidth());
}
});
mShowBt.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
mShowBt.getViewTreeObserver().removeOnGlobalLayoutListener(this);
Log.e("TAG", "onGlobalLayout: ----->" + mShowBt.getWidth());
}
});
}
这两个地方都能输出控件的实际宽高。今天就来分析为什么这样可以获取到宽高
post 的方式
进入源码
public boolean post(Runnable action) {
final AttachInfo attachInfo = mAttachInfo;
//该值在onCreate 中是 ==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);
return true;
}
首先判断 mAttachInfo 是否为null 可以通过反射来测试在onCreate中为null:
Class<? extends Button> aClass = mShowBt.getClass();
try {
//该属性在view 中,获取button 的父类
Class<?> superclass = aClass.getSuperclass();
Field mAttachInfo = superclass.getDeclaredField("mAttachInfo");
mAttachInfo.setAccessible(true);
//这里输出null
Log.e("TAG", "onCreate: ---->" + mAttachInfo.get(mShowBt));
} catch (Exception e) {
e.printStackTrace();
}
通过测试可以知道 mAttachInfo 为null,来看 getRunQueue().post(action);
private HandlerAction[] mActions;
private int mCount;
public void post(Runnable action) {
postDelayed(action, 0);
}
public void postDelayed(Runnable action, long delayMillis) {
//HandlerAction 是一个内部类,用于保存action和delayMillis
final HandlerAction handlerAction = new HandlerAction(action, delayMillis);
synchronized (this) {
if (mActions == null) {
mActions = new HandlerAction[4];
}
//将post 中的Runnable 保存到 mActions 数组中
mActions = GrowingArrayUtils.append(mActions, mCount, handlerAction);
mCount++;
}
}
根据上面,当调用view.post 方法是,只是将 Runnable 保存到 mActions 数组中,那么这个Runnable中的run 方法什么时候回调呢?
需要了解,只有当view 执行了 onMeasure 方法设置了自身的大小才能获取到实际的宽高
由此可以知道在 performTraversals 方法中开始view的onMeasure,来到ViewRootImp 中的performTraversals
private void performTraversals() {
//根view DecorView
final View host = mView;
//..........省略代码
//为view mAttachInfo 赋值
mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(true);
//..........省略代码
//执行保存的Runnable
getRunQueue().executeActions(mAttachInfo.mHandler);
//..........省略代码
//开始测量,最终会调用 onMeasure 并设置宽高
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
//..........省略代码
//执行layout
performLayout(lp, mWidth, mHeight);
//..........省略代码
//执行draw
performDraw();
}
//getRunQueue().executeActions(mAttachInfo.mHandler);
public void executeActions(Handler handler) {
synchronized (this) {
final HandlerAction[] actions = mActions;
//循环执行保存在mActions 中的 Runnable 就能回调到 onCreate 中的run方法
for (int i = 0, count = mCount; i < count; i++) {
final HandlerAction handlerAction = actions[i];
handler.postDelayed(handlerAction.action, handlerAction.delay);
}
mActions = null;
mCount = 0;
}
}
为什么getRunQueue().executeActions(mAttachInfo.mHandler); 在performMeasure 方法前调用还能获取到宽高呢?
来看 performTraversals 方法调用的时机
//ViewRootImpl 中的 scheduleTraversals 方法,具体可见我上一篇内容
void scheduleTraversals() {
//是通过handler 的方式来执行
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
}
performTraversals 方法是通过handler 的方式来执行 getRunQueue().executeActions(mAttachInfo.mHandler);也是
通过handler 的方式来执行,通过handler 的源码不难知道getRunQueue().executeActions(mAttachInfo.mHandler) 中
的handler 会在performTraversals 方法执行完成后执行,类似如下代码
mHandler.post(new Runnable() {
@Override
public void run() {
Log.e("TAG", "run: 1----->第一个handler 开始执行");
sHandler.post(new Runnable() {
@Override
public void run() {
Log.e("TAG", "run: 3----->第二个handler 开始执行");
}
});
Log.e("TAG", "run: 2----->第一个handler 执行完成");
}
});
一个handler 的post 中执行另一个handler 的post 执行顺序为 1 -> 2 -> 3
所以getRunQueue().executeActions(mAttachInfo.mHandler); 后执行
post 总结:在onCreate 中调用view.post 时,会将当前的 Runnable 保存,当view 执行了onMeasure 得到实际的宽高后会再执行保存的Runnable
ViewTreeObserver() 方式
根据名字大概可以才出来是类似观察这模式,当控件宽高设置后回调。来看源码
public ViewTreeObserver getViewTreeObserver() {
//通过上面分析可知 mAttachInfo == null
if (mAttachInfo != null) {
return mAttachInfo.mTreeObserver;
}
//为 null 则直接 new ViewTreeObserver 返回
if (mFloatingTreeObserver == null) {
mFloatingTreeObserver = new ViewTreeObserver(mContext);
}
return mFloatingTreeObserver;
}
其中 ViewTreeObserver 包含各类回调接口和赋值方法,有兴趣可以自行了解
public void addOnGlobalLayoutListener(OnGlobalLayoutListener listener) {
//判断是否可用,不可用会直接抛出异常
checkIsAlive();
if (mOnGlobalLayoutListeners == null) {
//CopyOnWriteArray 是内部的一个类似List 的集合
mOnGlobalLayoutListeners = new CopyOnWriteArray<OnGlobalLayoutListener>();
}
//将listener 添加到mOnGlobalLayoutListeners 中
mOnGlobalLayoutListeners.add(listener);
}
执行完上面的方法只是在 mOnGlobalLayoutListeners 添加了一个listener 什么时候回调呢?
来跟踪 mFloatingTreeObserver 看哪里还有使用,发现只有一处地方有使用
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
//将 mAttachInfo 赋值
mAttachInfo = info;
if (mOverlay != null) {
mOverlay.getOverlayView().dispatchAttachedToWindow(info, visibility);
}
mWindowAttachCount++;
// We will need to evaluate the drawable state at least once.
mPrivateFlags |= PFLAG_DRAWABLE_STATE_DIRTY;
if (mFloatingTreeObserver != null) {
//将mFloatingTreeObserver 赋值给info.mTreeObserver
info.mTreeObserver.merge(mFloatingTreeObserver);
mFloatingTreeObserver = null;
}
}
//ViewTreeObserver 中的 merge 方法
void merge(ViewTreeObserver observer) {
//将传进来的 observer 赋值到当前的observer
if (observer.mOnGlobalLayoutListeners != null) {
if (mOnGlobalLayoutListeners != null) {
mOnGlobalLayoutListeners.addAll(observer.mOnGlobalLayoutListeners);
} else {
mOnGlobalLayoutListeners = observer.mOnGlobalLayoutListeners;
}
}
}
在哪调用dispatchAttachedToWindow 方法呢?通过上面分析 performTraversals 方法可以知道
private void performTraversals() {
//该处就会调用view 中的dispatchAttachedToWindow 方法
host.dispatchAttachedToWindow(mAttachInfo, 0);
performLayout(lp, mWidth, mHeight);
//在 layout 方法调用完成后会调用
if (triggerGlobalLayoutListener) {
mAttachInfo.mRecomputeGlobalAttributes = false;
mAttachInfo.mTreeObserver.dispatchOnGlobalLayout();
}
}
//ViewTreeObser 中的dispatchOnGlobalLayout
public final void dispatchOnGlobalLayout() {
// NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to
// perform the dispatching. The iterator is a safe guard against listeners that
// could mutate the list by calling the various add/remove methods. This prevents
// the array from being modified while we iterate it.
final CopyOnWriteArray<OnGlobalLayoutListener> listeners = mOnGlobalLayoutListeners;
if (listeners != null && listeners.size() > 0) {
CopyOnWriteArray.Access<OnGlobalLayoutListener> access = listeners.start();
try {
int count = access.size();
for (int i = 0; i < count; i++) {
//循环获取lister 调用onGlobalLayout 方法
access.get(i).onGlobalLayout();
}
} finally {
listeners.end();
}
}
}
ViewTreeObserver() 方式 总结:在onCreate 方法中调用的时候 会创建 一个 ViewTreeObser mFloatingTreeObserver ;之后在performTraversals 方法中会调用dispatchAttachedToWindow 方法将 mFloatingTreeObserver 赋值到mAttachInfo.mTreeObserver 中,在performLayout 执行完毕会执行mAttachInfo.mTreeObserver.dispatchOnGlobalLayout();回调到onGlobalLayout 中,这时就可以获取到控件的实际宽高
网友评论