2018-12-26view 宽高获取

关于在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 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();
        }
        //将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 listeners = mOnGlobalLayoutListeners;
        if (listeners != null && listeners.size() > 0) {
            CopyOnWriteArray.Access 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 中,这时就可以获取到控件的实际宽高

你可能感兴趣的:(2018-12-26view 宽高获取)