Android自定义控件入门到精通--View树的测量流程

《Android自定义控件入门到精通》文章索引 ☞ https://blog.youkuaiyun.com/Jhone_csdn/article/details/118146683

《Android自定义控件入门到精通》所有源码 ☞ https://gitee.com/zengjiangwen/Code

文章目录

View树的测量流程

小故事:

公司计划搞团建,大狗,二狗,三狗是三个部门的领导,他们跑到财务室去跟财务妹子要经费。

财务妹子:你们各自要多少经费,提交申请给我就好了

大狗比较实诚,提交了500块的预算申请,财务妹子二话不说,直接给了大狗500块,大狗拿着500块就跟部门的人一起去吃酸辣粉了。

二狗又精又胆小,即怕要多了公司不高兴,又怕要少了底下的兄弟们不高兴。于是二狗跟财务妹子提了申请,写明:“按公司预算给经费!”,又是财务妹子也不兜着,把老板交代的单部门预算5000元都给了二狗,二狗高高兴兴的带着部门的人先去做了个大保健,又吃了个沙县小吃

三狗是公司得力干将,对地下兄弟们也比较好,胆子比较大,三狗给财务妹子提了个申请:“我也不知道兄弟们要多少经费,你先把公司预算给我,不够再说”,于是,财务妹子把5000块的预算给了三狗,三狗开开心心带着兄弟们又是大保健,又是海底捞,眼看预算都快花完了,这时候底下有个兄弟说,今天挺开心的,我们去K个歌吧,这时候三狗直接给财务妹子打了个电话:“经费不够用,再给我转一点”,财务妹子也没有办法,又给三狗转了1000块经费,三狗底下的兄弟是真能喝,没一会儿就把1000块经费干完了,三狗再次给财务打了电话,要求加经费,财务妹子又给三狗转了1000块钱,并回复三狗说:“这是公司最后底线,人要知足啊!”,三狗也知好歹,回复说道:“不会再要了”。

这个故事讲的就是View的三种测量模式以及ViewRoot的尺寸问题。

先了解下视图的加载流程

ViewManager->WindowManager->WindowManagerImpl-> WindowManagerGlobal -> ViewRootImpl

public interface ViewManager{
   
    public void addView(View view, ViewGroup.LayoutParams params);
    public void updateViewLayout(View view, ViewGroup.LayoutParams params);
    public void removeView(View view);
}
//WindowManagerGlobal.java
public void addView(View view, ViewGroup.LayoutParams params,
        Display display, Window parentWindow) {
   
   ......
    ViewRootImpl root;
    View panelParentView = null;
    synchronized (mLock) {
   
        //最终通过ViewRootImpl来发起view的测量、布局、绘图      
        root = new ViewRootImpl(view.getContext(), display);
        view.setLayoutParams(wparams);
        mViews.add(view);
        mRoots.add(root);
        mParams.add(wparams);
        // do this last because it fires off messages to start doing things
        try {
   
            //将view设置给ViewRootImpl 
            root.setView(view, wparams, panelParentView);
        } catch (RuntimeException e) {
   
            // BadTokenException or InvalidDisplayException, clean up.
            if (index >= 0) {
   
                removeViewLocked(index, true);
            }
            throw e;
        }
    }
}
//ViewRootImpl.java
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
   
    synchronized (this) {
   
        if (mView == null) {
   
            mView = view;
        }
    }
}

上面的源码大家可以去看看,我们这里只要明白,我们的根view最终会被设置在ViewRootImpl对象中,然后在ViewRootImpl中实现测量、布局、绘图。

我们知道,在自定义View时,通过重写onMeasure()方法来测量计算自己需要的尺寸:

//View.java
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
   
    setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
            getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}

通过setMeasuredDimension()方法就可以定义自己View的尺寸,比如,我定义我们的自定义View为100x100尺寸大小:

setMeasuredDimension(100,100)

widthMeasureSpecheightMeasureSpec

两个是对测量模式和尺寸的封装,比如我们在xml布局文件中定义的layou_width和layout_height

来看个例子:

我们自定义了一个ChildView并设置它的宽高为50px

<cn.code.code.wiget.ChildView
    android:layout_width="50px"
    android:layout_height="50px"
    android:background="#ff2323" />

然后我们重写onMeasure方法并获取widthMeasureSpec、heightMeasureSpec的模式和size:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
   
    int widthMode = MeasureSpec.getMode(widthMeasureSpec);
    int widthSize = MeasureSpec.getSize(widthMeasureSpec);

    int heightMode = MeasureSpec.getMode(heightMeasureSpec);
    int heightSize = MeasureSpec.getSize(heightMeasureSpec);

    Logger.e("MeasureSpec.UNSPECIFIED="+MeasureSpec.UNSPECIFIED);
    Logger.e("MeasureSpec.EXACTLY="+MeasureSpec.EXACTLY);
    Logger.e("MeasureSpec.AT_MOST="+MeasureSpec.AT_MOST);

    Logger.e(getClass().getSimpleName()+": onMeasure   widthMode="+(widthMode)+"   widthSize="+widthSize);
    Logger.e(getClass().getSimpleName()+": onMeasure   heightMode="+(heightMode)+"   heightSize="+heightSize);

    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}

日志输出:

05-21 15:20:46.950 4481-4481/? E/glacat: MeasureSpec.UNSPECIFIED=0
05-21 15:20:46.950 4481-4481/? E/glacat: MeasureSpec.EXACTLY=1073741824
05-21 15:20:46.950 4481-4481/? E/glacat: MeasureSpec.AT_MOST=-2147483648
05-21 15:20:46.950 4481-4481/? E/glacat: ChildView: onMeasure   widthMode=1073741824   widthSize=50
05-21 15:20:46.950 4481-4481/? E/glacat: ChildView: onMeasure   heightMode=1073741824   heightSize=50

可以看到,widthMeasureSpec和heightMeasureSpec就是我们在xml文件中设置的宽高属性。

我们的测量模式是EXACTLY,size=50px,说明layout_width/height给定确定数值时,它的测量模式是EXACTLY(跟大狗一样,明确知道自己要多少经费)

三种测量模式的理解:

  • UNSPECIFIED: Parent对Child没有尺寸约束,Child想要多大就给多大(这个我们一般不会用到)
  • EXACTLY: 代表尺寸是明确的,确定的数值 (match_parent和确定的数值)
  • AT_MOST: 代表Child也不知道自己需要多大尺寸,但是最大不会超过Parent的尺寸(wrap_content)

我们再来验证下我们的match_parent和wrap_content的模式和size

<cn.code.code.wiget.ChildView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="#ff2323" />

onMeasure:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
   
    int widthMode = MeasureSpec.getMode(widthMeasureSpec);
    int widthSize = MeasureSpec.getSize(widthMeasureSpec);

    int heightMode = MeasureSpec.getMode(heightMeasureSpec);
    int heightSize = MeasureSpec.getSize(heightMeasureSpec);

    Logger.e(getClass().getSimpleName()+": onMeasure   widthMode="+(widthMode>>30)+"   widthSize="+widthSize);
    Logger.e(getClass()
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一鱼浅游

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值