当一个Activity取得焦点后就会向Android系统请求绘制它的布局。Android框架会处理这个绘制的过程,一个View的显示,需要先后调用onMeasure,onLayout和onDraw方法。从字面意思理解onMeasure,为计算,测量。上图所示,A,B,C分别表示为三个View,其中,A View包含B View,B View 又包含C View。这三个View在屏幕上显示出来, 会调用每一个View中的onMeasure方法,其调用的顺序是怎样的呢?A ----> B---->C 还是神马呢???先上代码吧。分别为A,B,C三个类源码码。
- <span style="color:#330033;">public class LayoutA extends LinearLayout{
- private String TAG = LayoutA.class.getSimpleName();
- public LayoutA(Context context) {
- super(context);
- // TODO Auto-generated constructor stub
- }
- public LayoutA(Context context, AttributeSet attrs) {
- super(context, attrs);
- // TODO Auto-generated constructor stub
- }
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- // TODO Auto-generated method stub
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- int widthMode = MeasureSpec.getMode(widthMeasureSpec);
- int heightMode = MeasureSpec.getMode(heightMeasureSpec);
- int widthSize = MeasureSpec.getSize(widthMeasureSpec);
- int heightSize = MeasureSpec.getSize(heightMeasureSpec);
- if(widthMode == MeasureSpec.AT_MOST){
- Log.e(TAG,"LayoutA onMeasure() called,widthMode = MeasureSpec.AT_MOST,widthSize = " + widthSize + " ,heightSize = " + heightSize);
- }
- if(widthMode == MeasureSpec.EXACTLY){
- Log.e(TAG,"LayoutA onMeasure() called,widthMode = MeasureSpec.EXACTLY,widthSize = " + widthSize + " ,heightSize = " + heightSize);
- }
- if(widthMode == MeasureSpec.UNSPECIFIED){
- Log.e(TAG,"LayoutA onMeasure() called,widthMode = MeasureSpec.UNSPECIFIED,widthSize = " + widthSize + " ,heightSize = " + heightSize);
- }
- if(heightMode == MeasureSpec.AT_MOST){
- }
- }
- @Override
- protected void onLayout(boolean changed, int l, int t, int r, int b) {
- // TODO Auto-generated method stub
- super.onLayout(changed, l, t, r, b);
- // Log.e(TAG," LayoutA onLayout() called...");
- }
- @Override
- protected void onDraw(Canvas canvas) {
- // TODO Auto-generated method stub
- super.onDraw(canvas);
- // Log.e(TAG," LayoutA onDraw() called...");
- }
- }</span>
... ... ... ...
my_main.xml文件为:
- <com.example.measuretest.LayoutA xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- android:orientation="vertical"
- android:gravity="center"
- android:background="#ffff0000">
- <com.example.measuretest.LayoutB
- android:layout_width="300dip"
- android:layout_height="300dip"
- android:gravity="center"
- android:layout_marginTop="40dip"
- android:background="#ff00ff00">
- <com.example.measuretest.LayoutC
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
- android:text="@string/hello_world">
- </com.example.measuretest.LayoutC>
- </com.example.measuretest.LayoutB>
- </com.example.measuretest.LayoutA>
类LayoutB与LayoutA相似,LayoutC的代码为:
- public class LayoutC extends Button{
- private String TAG = LayoutC.class.getSimpleName();
- private float mRadius = 4;
- public LayoutC(Context context) {
- super(context);
- // TODO Auto-generated constructor stub
- }
- public LayoutC(Context context, AttributeSet attrs) {
- super(context, attrs);
- // TODO Auto-generated constructor stub
- }
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- ... ... ... ... ...
运行后,log信息为:
- 08-20 21:15:53.338: E/LayoutC(2377): LayoutC onMeasure() called,widthMode = MeasureSpec.AT_MOST,widthSize = 450 ,heightSize = 450
- 08-20 21:15:53.338: E/LayoutB(2377): LayoutB onMeasure() called,widthMode = MeasureSpec.EXACTLY,widthSize = 450 ,heightSize = 450
- 08-20 21:15:53.338: E/LayoutA(2377): LayoutA onMeasure() called,widthMode = MeasureSpec.EXACTLY,widthSize = 480 ,heightSize = 778
- 08-20 21:15:53.370: E/LayoutC(2377): LayoutC onMeasure() called,widthMode = MeasureSpec.AT_MOST,widthSize = 450 ,heightSize = 450
- 08-20 21:15:53.370: E/LayoutB(2377): LayoutB onMeasure() called,widthMode = MeasureSpec.EXACTLY,widthSize = 450 ,heightSize = 450
- 08-20 21:15:53.370: E/LayoutA(2377): LayoutA onMeasure() called,widthMode = MeasureSpec.EXACTLY,widthSize = 480 ,heightSize = 778
- 08-20 21:15:53.424: E/LayoutC(2377): LayoutC onMeasure() called,widthMode = MeasureSpec.AT_MOST,widthSize = 450 ,heightSize = 450
- 08-20 21:15:53.424: E/LayoutB(2377): LayoutB onMeasure() called,widthMode = MeasureSpec.EXACTLY,widthSize = 450 ,heightSize = 450
- 08-20 21:15:53.424: E/LayoutA(2377): LayoutA onMeasure() called,widthMode = MeasureSpec.EXACTLY,widthSize = 480 ,heightSize = 778
从log信息显示为:先调用LayoutC 的onMeasure 方法,而后是LayoutB,最后才是调用LayoutA方法的onMeasure方法。
绘制View有两个过程,一个messure过程和一个layout过程。 messure过程只通过measure(int, int)方法中实现的,messure过程会自顶向下遍历Viewtree。在递归过程中,每个View都设置自己的尺寸规格。在measure过程的最后,每个View都已经存储了它自身的大小了。
由于是递归调用,所以当然是从最里层的view开始调用其onMeasure方法,计算其自身大小,然后调用次外层View 的onMeasure方法。也就是说,上图中A,B,C的调用顺序为:C------>B------->A
(关于measure的调用,请参看Google文档:http://developer.android.com/guide/topics/ui/how-android-draws.html)
接着对上篇文章进行谈论。Android中onMesure研究(1) 。上一篇文章介绍,每一个View在显示的时候,对onMeasure方法的调用,是从最里层的View开始measure。从数据结构上讲述,是对二叉树最外层的儿子(View)开始measure。上篇博文提到是因为递归调用。
在一个Activity中,调用SetContentView后,最终就会触发ViewRoot中的scheduleTraversals这个函数。
- public void scheduleTraversals() {
- if (!mTraversalScheduled) {
- mTraversalScheduled = true;
- sendEmptyMessage(DO_TRAVERSAL);
- }
- }
该函数会发送一个DO_TRAVERSAL消息,在handleMessage中处理,调用到performTraversals函数,这个函数涉及到measure和onLayout方法。该函数有600行,并且是一个递归函数。
- private void performTraversals() {
- // cache mView since it is used so much below...
- final View host = mView;
- if (DBG) {
- System.out.println("======================================");
- System.out.println("performTraversals");
- host.debug();
- }
- if (host == null || !mAdded)
- return;
- mTraversalScheduled = false;
- mWillDrawSoon = true;
- ... ... ...
- host.measure(childWidthMeasureSpec, childHeightMeasureSpec);
- ... ... ...
- host.layout(0, 0, host.mMeasuredWidth, host.mMeasuredHeight);
- } else {
- // We were supposed to report when we are done drawing. Since we canceled the
- // draw, remember it here.
- if ((relayoutResult&WindowManagerImpl.RELAYOUT_FIRST_TIME) != 0) {
- mReportNextDraw = true;
- }
- if (fullRedrawNeeded) {
- mFullRedrawNeeded = true;
- }
- // Try again
- scheduleTraversals();
- }
- }