个人进阶之路——自定义控件(5)

来源:互联网 发布:阿里云的免费空间 编辑:程序博客网 时间:2024/05/05 21:22

今天我继续总结前面没有讲完的自定义View的内容,坚持了那么多天了,虽然很晚睡,但是觉得这么学习还是很充实的,于是乎,趁着这股劲还没有褪去,我要继续写啦。
今天的内容依旧是自定义View,讲了那么多天了,总算是有些眉目的,然后这还是不够的。依旧是看《安卓开发艺术》这本书
今天继续View的工作流程,三大流程。measure,layout,draw
measure测量,确定View的测量宽高
layout布局,确定View的最终宽高和四个顶点的位置
draw绘制,将view绘制到屏幕上

首先详细讲一下measure过程
【1】如果是原始的View,通过measure,就完成了测量过程
【2】如果是一个ViewGroup,完成自身测量过程+遍历调用所有子元素的Measure方法,各个元素再递归去执行这个流程

【1】如果是原始的View
measure过程由其measure方法完成
measure是一个final类型的方法,子类不能重写
在View 的measure方法中会去调用View的onMeasure方法
注意onMeasure方法中的
setMeasuredDimension方法,设置View宽高的测量值
getDefaultSize,AT_MOST和EXACTLY
返回的大小是MeasureSpec中的 specSize(View测量后的大小)
View 的最终的大小是在layout阶段所确定的,几乎所有的View的测量大小等于最终大小
UNSPECIFIED,系统内部的测量过程
View的大小为getDefaultSize 的第一个参数size。
即宽高为getSuggestedMinimumWidth和~这两方法的返回值。
getSuggestedMinimumWidth,
如果View没有设置背景,View 宽度为mMinWidth(android:minWidth属性的值)该属性不指定,则mMinWidth默认为
如果View指定了背景,则View 宽度为max(mMinWidth,mBackground.getMinimumWidth()背景的最小宽度)
mBackground.getMinimumWidth返回的是drawable的原始宽度,前提drawable有原始宽度,否则返回为0
ShapeDrawable没有原始宽度
BitmapDrawable有原始宽度(图片的尺寸)
getSuggestedMinimumWidth和~的返回值就是在View在UNSPECIFIED的情况下的测量宽高

getDefaultSize方法的实现来看,View的宽高由SpecSize来决定
直接继承view的自定义控件,需要重写onMeasure方法,并设置wrap_content时的自身大小,否则在布局中wrap=match
R:使用wrap,此时specSize为AT_MOST模式,宽高=SpecSize,此时specSize=parentSize(父容器当前剩余的空间大小)
View的宽高=父容器当前剩余空间大小

【2】ViewGroup的测量过程
完成自身的测量measure+遍历所有子元素的measure方法,各个元素再递归去执行这个过程
ViewGroup是一个抽象类,没有重写View的onMeasure方法,提供一个measureChildren的方法
ViewGroup的在measure的时候,会对每一个子元素进行measure,
measureChild的思想:取出子元素的LayoutParams,通过getChildMeasureSpec来创建子元素的MeasureSpec,直接将MeasureSpec传递给measure方法来进行测量。
ViewGroup并没有定义其测量的具体过程,ViewGroup是一个抽象类,测量过程的onMeasure方法需要各个子类去具体实现
如LineaLayout和RelatedLayout等,
为什么ViewGoup不像View那样,对其onMeasure方法做统一的实现呢?
因为不同的ViewGroup子类有不同的布局特性,导致测量细节各不相同。无法统一实现
可以通过LinearLayout的onMeasure方法分析ViewGroup的measure过程
其中注意onMeasure的measureVertical方法,系统遍历子元素执行measureChildBeforeLayout方法,方法内部调用子元素的measure方法,这样各个元素就开始依次进入measure阶段,并且系统会通过mTotalLength变量来存储LinearLayout在竖直方向上的初步高度。每测量一个子元素,mTotalLength就会增加(子元素的高度以及子元素在竖直方向上的margin),当子元素测量完毕之后,LinearLayout会测量自己的大小
针对竖直方向的LinearLayout而言,在水平方向的测量过程遵循View的测量过程,在竖直方向的测量过程则和View有所不同
如果布局中高度采用的是match或具体竖直,测量结果和View一致,高度为SpecSize
如果布局中高度采用的是wrap,高度是所有子元所占用的高度总和,虽然仍然不能超过父容器剩余空间,要考虑竖直padding值
view的measure过程是三大流程最复杂的一个
getMeasuredWidth/Height方法就可以正确获取到View的测量宽高。
某些极端情况下,系统需要多次调用measure才能确定最终的测量宽高,最好在onLayout中获取view测量宽高或者最终宽高。
onCreate,onStart,onResume中均无法正确得到某个View 的宽高信息,measure和Activity的生命周期不是同步执行的
如果没有测量完,获取的宽高为0
(1)Activity/view #onWindowFocusChanged
View已经初始化完毕,宽高已经准备好了,此时可以获取
onWindowFocusChanged会被调用多次,当Activity的窗口得到焦点和失去焦点时均会调用一次
当Activity继续执行和暂停执行时,onWindowFocusChanged也会被频繁调用

public void onWindowFocusChanged(boolean hasFocus){    super.onWindowFocusChanged(hasFocus);    if(hasFocus){        //获取宽高    }}

(2)view.post(runnable)
通过post可以将一个runnable投递到消息队列的尾部,然后等待Looper调用此runnable的时候,View也已经初始化好了

protected void onStarted(){    super.onStart();    view.post(new Runnable){        public void run {            //获取宽高        }    }}

(3)ViewTreeObserver
使用它的回调,使用OnGlobalLayoutListener接口,当view树状态改变或者View树内部的View的可见性发生改变时,onGlobalLayout方法会被调用,这是获取View的宽高的好时机,伴随着、view树的状态改变该方法会被多次调用

protected void onStart( ){    super.onStart();    ViewTreeObserver observer =view.getViewTreeObserver();    observer.addOnGlobalLayoutListener(new OnGlobalLayoutListener(){        public void onGlobalLayout(){            View.getViewTreeObserver().removeGlobalOnLayoutListener(this);            //获取宽高        }    });}

(4)view.measure(int widthMeasureSpec ,int heightMeasureSpec)
通过手动的对View进行measure得到宽高
分情况处理
根据View的LayoutParams划分
【match_parent】直接放弃,无法测量出具体的宽高
View的measure过程,构造此种MeasureSpec需要知道parentSize(父容器剩余空间),无法知道parentSize大小,故无法测量出来View的大小
【具体的数值】具体有方法
【wrap_content】有具体方法,与常用的误区提醒

0 0