美文网首页
获取View宽高

获取View宽高

作者: Leon_hy | 来源:发表于2019-12-25 22:36 被阅读0次

    我们应用在获取控件宽高的时候,都是调用控件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)为什么可以获取,下篇文章再分析

    相关文章

      网友评论

          本文标题:获取View宽高

          本文链接:https://www.haomeiwen.com/subject/mlmhoctx.html