《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)
widthMeasureSpec和heightMeasureSpec
两个是对测量模式和尺寸的封装,比如我们在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()