您的位置:首页 > 移动开发 > Android开发

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

2016-02-27 03:12 483 查看
今天我继续总结前面没有讲完的自定义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】有具体方法,与常用的误区提醒
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  android