Android自定义view之View的测量过程全解析

本文详细介绍了Android自定义View中的测量过程,从View的测量、布局、绘制三个步骤出发,重点剖析了测量阶段。文章阐述了测量阶段的重要性,并通过源码分析展示了View的测量流程,包括MeasureSpec类的作用、测量模式以及如何生成测量规格。此外,还探讨了ViewGroup如何测量其子View,特别以AbsoluteLayout为例进行了讲解。
摘要由CSDN通过智能技术生成

Android 应用层开发中绕不开自定义 View 这个话题,虽然现在 Github 上有形形色色的开源库供大家使用,

但是作为一名开发者而言,虽然不提倡重复造轮子,但是轮子都是造出来的。碰到一些新鲜的 UI 效果时,

如果现有的控件无法完成任务,那么我们就应该想到要自定义一个 View 了。 

我们知道,在 Android 中 View 绘制流程有测量、布局、绘制三个步骤,它们分别对应 3 个 API :onMeasure()、onLayout()、onDraw()。 

- 测量 onMeasure() :测量View的尺寸,决定View的大小

- 布局 onLayout() :通过设置l,t,r,b确定view在父容器中的位置

- 绘制 onDraw():通过canvas绘制我们想要展示的内容

没有办法说这三个阶段,哪个阶段最重要,只是相对而言,测量阶段对于开发者而言难度相对其它两个要大,处理的细节也要多得多,

自定义一个 View,正确的测量是第一步,正因为如此今天我将从源码的角度来学习View的测量过程.

View在本质上是一个Rect矩形的区域.

在Android中View的测量是从View树的根节点开始的,一步一步的往下测量而成的.

那么,首先我们来了解下View树的结构:

我们在Activity中一般通过setContentView()方法来设置我们的View视图:

public void setContentView(@LayoutRes int layoutResID) {
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
    }

在setContentView()方法中,我们调用了getWindow().setContentView()方法,而这里的

getWindow()就是PhoneWindow,因此我们转到PhoneWindow的setContentView()方法:

public void setContentView(int layoutResID) {
          //如果顶层容器FrameLayout为空的话,需要从xml加载顶层容器
        if (mContentParent == null) {
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }

        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                    getContext());
            transitionTo(newScene);
        } else {
            //将我们通过setContentView设置的布局资源加载到顶层容器mContentParent中
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
        mContentParent.requestApplyInsets();
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
        mContentParentExplicitlySet = true;
    }

在PhoneWindow的setContentView()中会调用installDecor()方法(部分代码省略):

 private void installDecor() {
        mForceDecorInstall = false;
        if (mDecor == null) {
            //生成DecorView
            mDecor = generateDecor(-1);
        } else {
            //将DecorView关联到PhoneWindow上
            mDecor.setWindow(this);
        }
        if (mContentParent == null) {
            //同时生成Activity的setContentView方法需要加载的View的顶层容器,并添加到DecorView中去
            mContentParent = generateLayout(mDecor);
            }
        }

分别调用generateDecor()方法生成DecorView:

protected DecorView generateDecor(int featureId) {
        //生成顶层的DecorView
        return new DecorView(context, featureId, this, getAttributes());
    }

同时我们会看到在installDecor()方法中,也会通过generateLayout方法生成mContentParent对象:

 protected ViewGroup generateLayout(DecorView decor) {
        
        int layoutResource;
        
        // Embedded, so no decoration is needed.
        layoutResource = R.layout.screen_simple;
        //在该方法中最终将mContentParent顶层view添加到DecorView中
        mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
        //mContentParent顶层容器View的布局id为com.android.internal.R.id.content
        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
        if (contentParent == null) {
            throw new RuntimeException("Window couldn't find content container view");
        }

        return contentParent;
    }

以上的流程中最终会将资源id为:R.layout.screen_simple的布局加载DecorView中:

<LinearLayout xmlns:android="http
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值