View的相关总结

32 篇文章 0 订阅
29 篇文章 16 订阅

View的绘制流程

View的绘制流程是从ViewRootImpl#performTraversals方法开始的,在performTraversals中会分别通过performMeasure,performLayout,performDraw这三个方法来绘制一个View
具体可以参考setContentView那些事

  • measure: 用来测量当前View的宽度和高度
  • layout: 用来确定View在父容器的存放位置
  • draw: 将View绘制到屏幕上

如下图:
这里写图片描述

measure过程决定了当前View的宽度和高度,measure完成之后,就可以通过getMeasuredWidth和getMeasuredHeight来获取其测量后的宽度和高度,layout决定了View的四个顶点的位置,
draw方法决定了View的显示。

MeasureSpec测量模式

MeasureSpec是View的一个内部类,有三种分类:

  • UNSPECIFIED

父容器不对当前View做任何限制,一般用于系统内部

  • EXACTLY

父容器检测出当前View所需要的精确大小,此时View的最终大小就是SpecSize所指定的值,对应于LayoutParams中给定确定的数值和match_parent这两种模式

  • AT_MOST

父容器指定了一个大小可用的SpecSize,View的大小不能大于这个值,对应于LayoutParams中wrap_content

View的工作过程

View的工作流程,主要指measure,layout和draw这三个流程

measure过程

measure过程分两种情况,如果是一个简单的View,那么measure方法就完成了其测量过程,如果是ViewGroup,那么除了自己的测量过程外,还需要遍历调用所有
子元素的measure方法,各个子元素再递归去执行这个流程

View的measure过程

View的measure过程从自身的measure方法开始,measure是一个final方法,所以在子View中不能复写该方法。在View的measure方法中会调用View的onMeasure方法。

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

如果在子类中复写了onMeasure方法,那么子View将负责在自己的onMeasure方法中计算自己的宽度和高度,上述通过setMeasuredDimension设置当前View宽度和高度的测量值

ViewGroup的measure过程

对于ViewGroup除了自己的测量过程外,还需要遍历调用所有子元素的measure方法,各个子元素再递归去执行这个流程,ViewGroup是一个抽象类,因此其没有重写View的onMeasure方法,
ViewGroup为我们提供了一个measureChildren

protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
        final int size = mChildrenCount;
        final View[] children = mChildren;
        for (int i = 0; i < size; ++i) {
            final View child = children[i];
            if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
                measureChild(child, widthMeasureSpec, heightMeasureSpec);
            }
        }
}

可以看到,ViewGroup在measure时候,会遍历每一个子元素进行通过measureChild测量

protected void measureChild(View child, int parentWidthMeasureSpec,
            int parentHeightMeasureSpec) {
        // 取出子元素的LayoutParams
        final LayoutParams lp = child.getLayoutParams();
        // 创建子元素的MeasureSpec
        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight, lp.width);
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                mPaddingTop + mPaddingBottom, lp.height);
        // 调用子元素的measure方法
        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}

View在measure完成之后,可以通过getMeasuredWidth和getMeasuredHeight获取其测量的宽度和高度,不过建议最好在onLayout方法中获取View测量的宽高。

Activity中获取View的宽度和高度

如果我们需要在Activity的onCreate方法中获取当前View的宽度和高度,会发现获取到的都是0,为什么会这样?这是因为View的measure过程和Activity的生命周期方法不是同步的,因此无法保证在Activity执行了onCreate,onStart,onResume时刻,View已经测量完毕,如果View还没有测量完毕,那么或得到的宽度和高度就是0.我们可以通过下面方法解决这个问题:

在Activity中复写onWindowFocusChanged

onWindowFocusChanged表示View已经初始化完毕了,此时获取宽度和高度是没有问题的,不过onWindowFocusChanged可能会多次执行,当activity获取或者失去焦点的时候,onResume和onPause方法多次被调用,那么onWindowFocusChanged也会被频繁的调用

public void onWindowFocusChanged(boolean hasFocus) {

    if(hasFocus) {
    int width = mTextView.getWidth();
        int height = mTextView.getHeight()        
    }
}

View.post(runnable)

通过post可以将一个runnable放入到消息队列的尾部,然后等待Lopper调用此runnable时候,View也已经初始化好了

mTextView.post(new Runnable() {
    @Override
    public void run() {
        int width = mTextView.getMeasuredWidth();
        int height = mTextView.getMeasuredHeight();
    }
});

ViewTreeObserver

可以在ViewTreeObserver中设置很多回调,比如可以使用OnGlobalLayoutListener,当View树的状态发生改变的时候,会回调onGlobalLayout,可以在这个方法里获取当前View的宽度和高度

ViewTreeObserver viewTreeObserver = mTextView.getViewTreeObserver();
        viewTreeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                mTextView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
                int width = mTextView.getMeasuredWidth();
                int height = mTextView.getMeasuredHeight();
            }
});

自定义View

自定义View主要分为下面四类:

  • 继承自View,重写onDraw方法
  • 继承自ViewGroup
  • 继承自特定的View(比如TextView)
  • 继承自特定的ViewGroup(比如LinearLayout)

自定义View需要注意的:

  • 让View支持wrap_content
    如果我们的控件直接继承自View或者ViewGroup,如果不在onMeasure中对wrap_content做特殊处理,那么当在布局中使用wrap_content时候,就无法达到预期效果
  • 让View支持padding
    原因同上
  • 及时停止View中的线程和动画
    如果当前View变为不可见,或者包含此View的activity退出时候,或者当前View被remove时,需要及时停止其线程和动画,否则可能造成内存泄漏
  • 当有嵌套View时候,处理好滑动冲突
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值