自定义控件总结---onMeasure onLayout onDraw深入解析

本文深入探讨了Android自定义控件的开发,特别是测量、布局和绘制流程。从启动Activity时PhoneWindow和DecorView的创建,到ViewRootImp如何启动测量、布局和绘制,再到MeasureSpec的详解,以及onMeasure、onLayout和onDraw的方法实现。文章着重分析了自定义ViewGroup时如何处理尺寸计算和子View的排列规则。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

          对于一个Android开发来说,不管你有多不喜欢自定义控件,你都得和她谈一场恋爱。所以我主动谈了这场很费脑子的恋爱,因为阅读源码的能力不好,在寻根抛底的找线索的过程中,搞得反胃,不过最后还是把来龙去脉都捋了一遍。当然今天的角不是常用且高效的组合型自定义控件,而是纯粹绘制出来的View或者重新定义规则的一个ViewGroup。

    简述1: 启动Activity的时候会创建一个PhoneWindow和DectorView,并将DectorView放进PhoneWindow中。这个DectorView(继承FrameLayout的控件View)的资源布局是根据Theme来确定的,这个布局一般是LinearLayout的上下布局,上面是一个title标题栏,下面是一个content的内容栏。我们一般在onCreate中的setContentView(resID),就是往这个内容栏里面放布局。   

    简述2:DectorView和PhoneWindow都有了,布局也setContentView了。 那么是谁启动的测量,布局,绘制这些绘制流程的呢?     答案是ViewRootImp这个类。   还是简单的捋一捋吧:

       1.ActivityThread收到RESUME_ACTIVITY消息,执行handleResumeActivity()方法:将DectorView添加进Window中。

      代码: wm.addView(decor, l);

       2.然后会在WindowManagerGlobal类中调用addView方法:创建ViewrootImp,并将DectorView传进去。

    root = new ViewRootImpl(view.getContext(), display);
    root.setView(view, wparams, panelParentView);

      3.然后在ViewRootImp的setView中触发requestLayout()方法,并执行scheduleTraversals();

     @Override
      public void requestLayout() {
         if (!mHandlingLayoutInLayoutRequest) {
           checkThread();
            mLayoutRequested = true;
            scheduleTraversals();
        }
     }

      4.然后scheduleTraversals()这个方法就是发送一个消息去执行一个线程,这个线程中便是真正开启执行测量,最终调用的是performTraversals方法:

   final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
    }
   void doTraversal() {
       if (mTraversalScheduled) {
            mTraversalScheduled = false;
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

            if (mProfile) {
                Debug.startMethodTracing("ViewAncestor");
            }

            performTraversals();

            if (mProfile) {
               Debug.stopMethodTracing();
               mProfile = false;
            }
        }
    }

5.然后在这个方法里面调用测量  布局  绘制图  这些方法。performMeasure() performLayout() performDraw();

上面说了这么多就是为了更好的理解这个流程,而不至于一头雾水的留下为啥会调用这些方法,谁最先开启的这些方法等等有头无尾的疑问。  

      简述3:MeasureSpec是Mode和Size共同产生的一个值,32位。前两位是Mode模式,后面30位才是View实实在在的尺寸值。   一般情况下 一个View或者说ViewGroup的MeasureSpec是由父类ViewGroup和自己共同决定的。如下图:

1.  EXACTLY  :这个模式,精确模式,就是我能确定了它的具体值了。

              a.给View 设置了具体的值,100dp   200dp  等

              b.match_parenter:填充父窗体意思。如果父窗体是EXACTLY模式,即是一个确定的,那这个View就和他爹一样大。

2. AT_MOST  :这个模式代表最大不超过某个值的模式。wrap_content.。自适应大小,但是最大不超过他爹的最大值。

3. UNSPECIFIED: 父容器不对View有任何限制,给它想要的任何尺寸。一般用于系统内部,表示一种测量状态。

 

  一:测量  

      1.  ViewRootImp进行测量的初始  performTraversals()此方法,这里面我们测量了根布局DectorView的宽和高childWidthMeasureSpec 和 childHeightMeasureSpec ,需要注意的是Dedctorview就是老祖宗,没有爹的限制,自己是啥就是啥。而实际上他的 宽高都是match_parent。所以就是EXACTLY+屏幕的尺寸。

          对应的源码是:

     int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
     int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);

     2.  然后  

----->  performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
----->  mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
----->  onMeasure(widthMeasureSpec, heightMeasureSpec);

      3.  onMeasure(widthMeasureSpec, heightMeasureSpec):首先判断这个View是否重写了onMeasure方法,如果重写了则调用重写的,否则就调用View.java 的。    这个方法View.java有,但是ViewGroup.java是没有的,因为继承他的自定义控件是一个容器,所以他的大小与子View的排列有关。因此继承ViewGroup 的自定义控件需要自己重写onMeasure来重新定义子View在其内部的排列规则。虽然View.java是有onMeasure方法,但是自己绘制的自定义View,他默认不处理padding设置的值,另外AT_MOST和MATCH_PARENT模式得到的尺寸是一样的,所以一般也是需要重写onmeasure()方法。下面我们就先看看View.java中的onMeasure()的源码      

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值