View的三大流程之View的测量

本文深入解析Android中的View系统,包括View和ViewGroup的基本概念、View的位置参数及其三大流程(measure, layout, draw)。通过实例演示如何自定义View以支持wrap_content属性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1、
java.lang.Object
   ↳android.view.View
 

Class Overview

This class represents the basic building block for user interface components. A View occupies a rectangular area on the screen and is responsible for drawing and event handling. View is the base class forwidgets, which are used to create interactive UI components (buttons, text fields, etc.).

public abstract class
 
View是Android中所有控件的基类,Button/TextView/RelativeLayout/ListView等,其共同基类都是View。View是界面层的控件的一种抽象,待变一个控件。

ViewGroup

extends View
implements ViewManager ViewParent

java.lang.Object
   ↳android.view.View
    ↳android.view.ViewGroup

 

Class Overview

A ViewGroup is a special view that can contain other views (called children.) The view group is the base class for layouts and views containers.

ViewGroup内部包含一组View,ViewGroup也继承自View,这就说明View本身就可以使单个控件或多个控件组成的一组控件。

UI界面架构图:

每个Activity都包含一个Window对象,Window对象通常由PhoneWindow来实现。PhoneWindow将一个DecorWindow设置为整个窗口的根View。DecView里有所有View的监听事件,通过WindowManagerService进行接收,并通过Activity对象回调相应的onClickListener。显示界面上,将屏幕分为两部分,分别为TitleView和ContentView。ContentView是一个ID为content的FrameLayout,布局文件activity_main.xml就是设置在这样一个FrameLayout中。

2、

View的位置参数:

View根据上图得到View的宽高和坐标(相对于ViewGroup,而不是原点)的关系:

width = right - left;

height = bottom - top;

获取这几个参数的方法如下:

mLeft = getLeft();
mRight = getRight();
mTop = getTop();
mBottom = getBottpm();

Android3.0开始View增加了几个参数:x,y,translationX,translationY,其中x,y是View左上角的坐标,translationX,translationY是View左上角相对于父容器的偏移量:

x = left + translationX;

y = top + translationY;

需要注意:View在平移过程中,top/left表示的是原始左上角的位置信息,其值在移动期间不会发生改变,变化的是x,y,translationX,translationY.


3、View的三大流程(measure,layout,draw)之View的测量

View的绘制流程从ViewRoot的performTraversals方法开始,ViewRoot对应于ViewRootImpl类,是连接WindowManager和DecorView的纽带,View的三大流程均是通过ViewRoot来完成。在ActivityThread中,当Activity对象被创建完毕后,会将DecorView添加到Window中,同时会创建ViewRootImpl对象,并将ViewRootImpl对象与DecorView相关联:

root = new ViewRootImpl(view.getContext(),display);
root.setView(view,wparams,panelParentView);

View的绘制流程从ViewRoot的performTraversals方法开始,经过measure,layout,draw三个过程将一个View绘制出来。

performTraversals会依次调用performMeasure,performLayout,performDraw三个方法,分别完成顶级View的measure,layout,draw三大流程,其中performMeasure中会调用measure方法,在measure方法中又调用onMeasure方法,在inMeasure方法中会对所有子元素进行measure过程,此时measure流程就从父元素传到子元素了,这样完成了一次measure过程。接着子元素会重复父容器的measure过程,如此反复完成整个View树的遍历。performLayout与performDraw与之类似,不同的是performDraw的传递过程是在draw方法中通过dispatchDraw来实现。

测量过程在onMeasure()方法中进行。

Android提供了一个功能强大的类--MeasureSpec类。

public static class

View.MeasureSpec

extends Object
java.lang.Object
   ↳android.view.View.MeasureSpec

public static class

View.MeasureSpec

extends Object
java.lang.Object
   ↳android.view.View.MeasureSpec

Class Overview

A MeasureSpec encapsulates the layout requirements passed from parent to child. Each MeasureSpec represents a requirement for either the width or the height. A MeasureSpec is comprised of a size and a mode. There are three possible modes:

UNSPECIFIED
The parent has not imposed any constraint on the child. It can be whatever size it wants.
EXACTLY
The parent has determined an exact size for the child. The child is going to be given those bounds regardless of how big it wants to be.
AT_MOST
The child can be as large as it wants up to the specified size.

MeasureSpec是一个32位的int值,高2位为测量模式(SpecMode),低30位是测量大小(SpecSize).

View默认的onMeasure()方法只支持EXACTLY模式,因此如果让自定义View支持wrap_content属性,就必须重写onMeasure()方法来指定wrap_content大小(进一步说明,由源码得出,wrap_content下的SpecMode是AT_MOST,此时的specSize是parentSize,即如果不指定wrap_content,在布局中使用wrap_content就相当于使用match_parent).

解决方法:

protected void onMeasure(int widthMeasureSpec,int heightMeasureSpec){
	super.onMeasure(widthMeasureSpec, heightMeasureSpec);
	int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
	int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
	int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
	int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
	
	if(widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST){
		setMeasureDimension(mWidth,mHeight);
	}else if(widthSpecMode == MeasureSpec.AT_MOST){
		setMeasureDimension(mWidth,heightSpecSize);
	}else if(heightSpecMode == MeasureSpec.AT_MOST){
		setMeasureDimension(widthSoecSize,mHeight)
	}
}

在上面代码中指定View的默认宽高(mWidth/mHeight),并在wrap_content时设置此宽高。
Demo:

MeasureView.java


 

package sunny.example.ahthreeviewmeasure;

import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

public class MeasureView extends View{

	public MeasureView(Context context) {
		super(context);
		// TODO Auto-generated constructor stub
		 
	}
	
	public MeasureView(Context context, AttributeSet attrs) {
		super(context, attrs);
		// TODO Auto-generated constructor stub
	}
	
	public MeasureView(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
		// TODO Auto-generated constructor stub
	}

	@Override
	protected void onMeasure(int widthMeasureSpec,int heightMeasureSpec){
		super.onMeasure(widthMeasureSpec, heightMeasureSpec);
		int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
		int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
		int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
		int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
		
		if(widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST){
			setMeasuredDimension(400,400);//指定宽高
		}else if(widthSpecMode == MeasureSpec.AT_MOST){
			setMeasuredDimension(400,heightSpecSize);
		}else if(heightSpecMode == MeasureSpec.AT_MOST){
			setMeasuredDimension(widthSpecSize,400);
		}
	}

	

}

 

activity_main.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="sunny.example.ahthreeviewmeasure.MainActivity" >

    <sunny.example.ahthreeviewmeasure.MeasureView
        android:id="@+id/measureView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="#09acda" />

</RelativeLayout>
 


MainActivity.java

 

package sunny.example.ahthreeviewmeasure;

import android.support.v7.app.ActionBarActivity;
import android.os.Bundle;

public class MainActivity extends ActionBarActivity {

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		
		//MeasureView mView = (MeasureView)findViewById(R.id.measureView);
	}

	
}

 

 

 

 
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值