View.post() 获得view的宽高准确吗

2020-07-20  本文已影响0人  浪够_

在项目开发中,我们可能为了获取一个控件的尺寸,通过view.post方法去获取这个控件的大小

 override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        test_wh.post {
            Log.d("shixiangyu", test_wh.width.toString())
            Log.d("shixiangyu", test_wh.height.toString())
        }

    }

每次都好像屡试不爽,但真的就准确吗,再得出答案前,需要搞明白view.post方法做了些什么

 public boolean post(Runnable action) {
        final AttachInfo attachInfo = mAttachInfo;
        if (attachInfo != null) {//onCreate时,attachInfo还为空
            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;
    }

我们在Activity的onCreate方法调用view.post时,attachInfo还为空(为什么空,可以参考另外一篇文章),所以,我们看看getRunQueue().post(action):

 private HandlerActionQueue getRunQueue() {
        if (mRunQueue == null) {
            mRunQueue = new HandlerActionQueue();
        }
        return mRunQueue;
    }

getRunQueue获得当前View类对象中的一个全局变量mRunQueue,然后把我们传进来的Runnable交给了mRunQueue,继续跟进HandlerActionQueue的post方法

 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++;
        }
    }

GrowingArrayUtils的append方法:

 public static <T> T[] append(T[] array, int currentSize, T element) {
        assert currentSize <= array.length;

        if (currentSize + 1 > array.length) {
            @SuppressWarnings("unchecked")
            T[] newArray = ArrayUtils.newUnpaddedArray(
                    (Class<T>) array.getClass().getComponentType(), growSize(currentSize));
            System.arraycopy(array, 0, newArray, 0, currentSize);
            array = newArray;
        }
        array[currentSize] = element;
        return array;
    }

看到这,我们明白,View中有一个HandlerActionQueue类型全局变量mRunQueue,mRunQueue持有一个HandlerAction[]数组,用来存放我们post进来的Runnable,到此,似乎就结束了,那么什么时候去处理这些Runnable呢?很简单,只需要看看这个mRunQueue什么时候被使用就可以了,通过查找,我们在View的dispatchAttachedToWindow方法中知道了对它的使用:

 void dispatchAttachedToWindow(AttachInfo info, int visibility) {
        
        // ...... 无关代码删减
       
        // Transfer all pending runnables.
        if (mRunQueue != null) {
            mRunQueue.executeActions(info.mHandler);
            mRunQueue = null;
        }
        performCollectViewAttributes(mAttachInfo, visibility);
        onAttachedToWindow();
 
        }

        // Send onVisibilityChanged directly instead of dispatchVisibilityChanged.
        // As all views in the subtree will already receive dispatchAttachedToWindow
        // traversing the subtree again here is not desired.
        onVisibilityChanged(this, visibility);
    }

dispatchAttachedToWindow方法中调用了mRunQueue.executeActions方法,而executeActions方法终于把我们的Runnable交给了我们熟悉的Handler

 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;
        }
    }

所以,我们需要知道View的dispatchAttachedToWindow方法何时何地被调用,这里不再对此详细阐述,可以看另外一篇文章,dispatchAttachedToWindow在ViewRootImpl的performTraversals()方法中被首次调用:

 private void performTraversals() {
        // .......省略代码
        final View host = mView;
        host.dispatchAttachedToWindow(mAttachInfo, 0);
}

其中host就是DecorView,我们知道DecorView继承自FrameLayout,而FrameLaylat继承自ViewGroup,我们看看ViewGroup的dispatchAttachedToWindow方法:

void dispatchAttachedToWindow(AttachInfo info, int visibility) {
        mGroupFlags |= FLAG_PREVENT_DISPATCH_ATTACHED_TO_WINDOW;
        super.dispatchAttachedToWindow(info, visibility);
        mGroupFlags &= ~FLAG_PREVENT_DISPATCH_ATTACHED_TO_WINDOW;

        final int count = mChildrenCount;
        final View[] children = mChildren;
        for (int i = 0; i < count; i++) {
            final View child = children[i];
            child.dispatchAttachedToWindow(info,
                    combineVisibility(visibility, child.getVisibility()));
        }
        final int transientCount = mTransientIndices == null ? 0 : mTransientIndices.size();
        for (int i = 0; i < transientCount; ++i) {
            View view = mTransientViews.get(i);
            view.dispatchAttachedToWindow(info,
                    combineVisibility(visibility, view.getVisibility()));
        }
    }

一层层调用子View的dispatchAttachedToWindow方法。看到这,我们可以捋一捋整个逻辑了:
当我们调用了View.post(Runnable action)方法后,我们的action并没有直接进行交给Handler,而是暂存到了View的全局变量mRunQueue中,等到ViewRootImpl对View和window进行关联时才取出View中暂存在mRunQueue的任务,交给Handler,

上一篇 下一篇

猜你喜欢

热点阅读