Android基础(83)

一、简介

在自定义view的时候,其实很简单,只需要知道3步骤:

1.测量——onMeasure:决定View的大小,关于此请阅读《Android自定义控件之onMeasure》

2.布局——onLayout决定View在ViewGroup中的位置

3.绘制——onDraw:如何绘制这个View。

这篇文章主要来谈谈第二步布局(Layout)

二、View视图结构

View视图可以是单一的一个如TextView,也可以是一个视图组(ViewGroup)如LinearLayout

如图:对于多View的视图他的结构是树形结构,最顶层是ViewGroup,ViewGroup下可能有多个ViewGroup或View。





这个树的概念很重要,因为无论我们是在测量大小或是调整布局的时候都是从树的顶端开始一层一层,一个分支一个分支的进行(树形递归)。

三、onLayout函数

measure的作用就是为整个View树计算实际的大小,而通过刚才对View树的介绍知道,想计算整个View树的大小,就需要递归的去计算每一个子视图的大小(Layout同理)。

Layout的作用就是为整个View树计算实际的位置,而通过刚才对View树的介绍知道,想计算整个View树的位置,就需要递归的去计算每一个子视图的位置(Measure同理)。

而确定这个位置很简单,只需要mLeft,mTop,mRight,mBottom四个值(注意:这4个值是子View相对于父View的值,下面会详细介绍)。

在代码中如何设置这4个值呢?

首先,无论是系统提供的LinearLayout还是我们自定义的View视图,他都需要继承自ViewGroup类,之后必须要做的就是重写onLayout方法(因为在onLayout在ViewGroup中被定义为抽象方法)。

onLayout被定义为抽象方法,所以在继承ViewGroup时必须要重写该方法(onMeasure不需要)。另外这个方法也被override标注,所以也是重写的方法,他重写的是其父类view中的onLayout方法。

View.java的onLayout:

View.java的onLayout是一个空实现,当这个view和其子view被分配一个大小和位置时,被layout调用。所以我们去看看layout中做了什么

View的layout:



在这段代码中我们只要知道:如果视图的大小和位置发生变化后,会调用我们前面分析过的onLayout方法。

对于onLayout方法的最终实现全部依靠我们在自定义ViewGroup类中重写的onLayout去实现。

ViewGroup.java的layout函数
 /** * {@inheritDoc} */ @Override public final  
void  layout(int l, int t, int r, int b) { if (!mSuppressLayout && (m 
Transition  == null || !mTransition.isChangingLayout)) { if (mTransition != null) { mTransition.layoutChange(this); } super.layout(l, t, r, b); } else { // record the fact that we noop'd it; request layout when  
transition finishes mLayoutCalledWhileSuppressed = true; } }

重写的onLayout方法中,唯一的目的就是:对当前视图和其所有子View设置它们在父视图中具体位置(确定这个位置就依靠mLeft,mTop,mRight,mBottom这四个值)

之前介绍过,mLeft,mTop,mRight,mBottom这四个值表示的是子view相对于父view的位置。下面我贴出我画的图看一下就明白了。




如图,黄色区域是我们的父view,而中间的深色的区域就是我们的子view。

view.getLeft——mLeft:子View左边界到父view左边界的距离

view.getTop——mTop:子View上边界到父view上边界的距离

view.getRight——mRight:子View右边界到父view左边界的距离

view.getBottom——mBottom:子View下边距到父View上边界的距离

视图宽高:

视图宽度 view.getWidth;子View的右边界 - 子view的左边界。

视图高度 view.getHeight ;子View的下边界 - 子view的上边界。

测量宽高:

view.getMeasuredWidth;measure过程中返回的mMeasuredWidth

view.getMeasuredHeight;measure过程中返回的mMeasuredHeight

最后介绍一下getWidth/Height和getMeasuredWidth/Height的区别:

getWidth,和getLeft等这些函数都是View相对于其父View的位置。而getMeasuredWidth,getMeasuredHeight是测量后该View的实际值(有点绕,下面摘录一段jafsldkfj所写的Blog中的解释).

实际上在当屏幕可以包裹内容的时候,他们的值是相等的,只有当view超出屏幕后,才能看出他们的区别:

getMeasuredHeight是实际View的大小,与屏幕无关,而getHeight的大小此时则是屏幕的大小。

当超出屏幕后,getMeasuredHeight等于getHeight加上屏幕之外没有显示的大小



在计算子View在父View中的位置时,主要就是应用上面这几个函数。下面就来看看如何去重写onLayout。

五、重写onLayout

对于重写onLayout的思路和重写onMeasure相同:

如果只需要测量单个View,则单独测量它自己就行。如果需要测量的View其下还有子View,则需要测量其所有的子View。


就以上面的View为例子,他最外面是一个黄色的父View,中间一个居中的深色子View。


我的思路如下:


如果想画出一个View,就要计算它的l,t,r,b值。并传递到onlayout( l, t, r, b )中;


mRight = view.getWidth + mLeft;


mBottom = view.getHeight + mTop;


所以最后可以用如下形式传入:onlayout( l, t, l+width, t+height );


剩下的任务就只需要知道它的mLeft值,mTop值,加上长、宽值就行了。

长宽值很简单,使用getWidth/Height和getMeasuredWidth/Height都可以。

由于这个View需要居中显示,剩下的问题就是如何计算该View的mLeft值和mTop值。我的思路如下:

r(父View的mRight) = mLeft + width + mLeft(因为左右间距一样)

b(父View的mBottom) = mTop + height + mTop(因为上下间距一样)




六、总结

onMeasure和onLayout的大致总结完了,在自定义View的时候最关键的是onLayout,因为无论你如何Measure这个View的大小,最后的决定权永远在onLayout手中,onLayout会决定具体View的大小和位置。当然onMeasure也很重要,有的情况控件的宽高不确定或者需要自定义,这时候需要我们人工Measure它。而在复杂的自定义View时,很多计算也需要在onMeasure中完成,并且些值会记录下来在onLayout中使用(个人理解,欢迎指正)。