Android中的每个控件都会在界面中占得一块矩形的区域,而android中,控件大致被分为两类ViewGroup 和 View空间
ViewGroup空间为父控件可以保含多个View控件,并管理其包含的View控件。上层控件负责下层子控件的测量与绘制并传递交互事件。
通常在Activity中使用findViewById()方法,就是在控件树中以树的深度优先遍历来查找对应元素。每颗控件树的顶部,都有一个ViewParent对象,这就是整棵树的控制核心,所有的交互管理事件都由他来统一调度和分配,从而可以对整个视图进行整体控制。
通常情况下,在Activity
中使用setContent()
方法设置一个布局在调用本方法后,布局内容才会真正显示出来。看下图:
Activity整体架构图
每个Activity都包含一个window对象,在Android中的Window对象通常由PhoneWindow来实现。PhoneWindow将一个DecorView设置为整个应用窗口的根View。DecorView作为窗口界面的顶层视图,封装了一些窗口操作的通用方法。可以说DecorView将要显示的具体内容呈现在了PhoneWindow上,这里面的所有View的监听事件,都是通过WindowManagerService来进行接收,并通过Activity对象来回调相应的onClickListener。在显示上,它将屏幕分为两个部分,一个是TiTleView,另一个是ContentView。
在代码中,当程序在onCreate()方法中调用setContentView()方法后,ActivityManagerService会回调onResume()方法,此时系统才会把整个DecorView添加到PhoneWindow中,并让其显示出来,从而最终完成界面的绘制
虽然setContentView()方法大家都会用,但实际上Android界面显示的原理要比我们所看到的东西复杂得多。任何一个Activity中显示的界面其实主要都由两部分组成,标题栏和内容布局。标题栏就是在很多界面顶部显示的那部分内容,比如刚刚我们的那个例子当中就有标题栏,可以在代码中控制让它是否显示。而内容布局就是一个FrameLayout,这个布局的id叫作content,我们调用setContentView()方法时所传入的布局其实就是放到这个FrameLayout中的,这也是为什么这个方法名叫作setContentView(),而不是叫setView()。
最后再附上一张Activity窗口的组成图吧,以便于大家更加直观地理解:
-
为什麼要实现onMeasure()。
-
什么情况下需要实现onMeasure()
android系统给我们提供了一个强大的类MeasureSpec,通过这个类,可以帮助我们测量测量View,MeasureSpec是一个32位的int值,其中高2位代表测量的模式,低30位代表测量的大小。
所以说MeasureSpec类中包含View测量的模式和大小。
那么什么事测量的模式,什么是测量的大小呢?
测量有三种模式
-
EXACTLY (精确值模式): 当我们测量的控件的layout_width属性或者layout_height属性指定为具体的数值,比如
android:layout_width="200dp"
,或者指定为match_parent,比如android:layout_height="match_parent"
时,系统使用EXACTLY模式。 -
AT_MOST(最大值模式):当我们测量的控件的layout_width属性或者layout_height属性指定为wrap_content时,即控件的大小一般随着其子空间或者内容的内容的变化而变化,此时控件的大小只要不超过父控件所允许的最大尺寸即可。
-
UNSPECIFIED (未指定模式):它未指定控件的大小测量模式,View想多大就多大,通常像ScrollView中这种类型控件使用这种模式。
onMeasure方法默认只支持EXACTLY模式,所以当我们自定义控件的时候,如果不去重写OnMeasure方法,就只能使用EXACTLY模式,控件可以响应你指定的具体的宽高值,或者match_parent属性。 如果你想要你的自定义View支持wrap_content属性,就必须要重写onMeasure()方法来制定wrap_content时的大小。
通过Meassure类,我们就获取了View的测量模式和View想要绘制的大小,有了这些信息,我们就可以控制View最后显示的大小。
public class CoustomView extends View {
public CoustomView (Context context) {
super(context);
}
public CoustomView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
acitvity_main.xml中j
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<gb.com.coustomview02.coustomview
android:background="@android:color/holo_blue_dark"
android:layout_margin="10dp"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
修改layout文件中的CoustomView layout_width以及layout_height属性修改成wrap_content,结果仍然是一样的,就不贴图了。因为虽然指定了warp_content 但是系统并不知道具体是多大所以,系统默认还是以全屏效果 处理。效果如下:
修改layout文件中CoustomView layout_width以及layout_height属性修改成固定的值比如200dp,产生效果如下,发现CoustomView 的大小为设定的值200dpx200dp
二、重写onMeasure()方法以及如何重写
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(measureWidth(widthMeasureSpec),measuredHeight(heightMeasureSpec));
}
/**
* 测量宽
* @param widthMeasureSpec
*/
private int measureWidth(int widthMeasureSpec) {
int result ;
int specMode = MeasureSpec.getMode(widthMeasureSpec);
int specSize = MeasureSpec.getSize(widthMeasureSpec);
if (specMode == MeasureSpec.EXACTLY){
result = specSize;
}else {
result = 500;
if (specMode == MeasureSpec.AT_MOST){
result = Math.min(result,specSize);
}
}
return result;
}
/**
* 测量高
* @param heightMeasureSpec
*/
private int measuredHeight(int heightMeasureSpec) {
int result ;
int specMode = MeasureSpec.getMode(heightMeasureSpec);
int specSize = MeasureSpec.getSize(heightMeasureSpec);
if (specMode == MeasureSpec.EXACTLY){
result = specSize;
}else{
result = 500;
if(specMode == MeasureSpec.AT_MOST){
result = Math.min(result,specSize);
}
}
return result;
}
}
加入了利用
MeasureSpec
来判断模式。根据不同模式,进行对宽高赋值。在
AT_MOST
也就是
wrap_content
时,默认最大的宽高都是5
00dp
package gb.com.coustomview02.coustomview;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.util.AttributeSet;
import android.view.View;
/**
* =====================================================================================
* <p/>
* 版权所有(c)2017
* <p/>
* 作者:Administrator on 2017/5/8
* <p/>
* 邮箱:
* <p/>
* 创建日期:2017/5/8 22:54
* <p/>
* 描述:
* =====================================================================================
*/
public class CoustomView extends View {
public CoustomView(Context context) {
super(context);
}
public CoustomView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CoustomView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
// @Override
// protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// }
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(measureWidth(widthMeasureSpec),measuredHeight(heightMeasureSpec));
}
/**
* 测量宽
* @param widthMeasureSpec
*/
private int measureWidth(int widthMeasureSpec) {
int result ;
int specMode = MeasureSpec.getMode(widthMeasureSpec);
int specSize = MeasureSpec.getSize(widthMeasureSpec);
if (specMode == MeasureSpec.EXACTLY){
result = specSize;
}else {
result = 500;
if (specMode == MeasureSpec.AT_MOST){
result = Math.min(result,specSize);
}
}
return result;
}
/**
* 测量高
* @param heightMeasureSpec
*/
private int measuredHeight(int heightMeasureSpec) {
int result ;
int specMode = MeasureSpec.getMode(heightMeasureSpec);
int specSize = MeasureSpec.getSize(heightMeasureSpec);
if (specMode == MeasureSpec.EXACTLY){
result = specSize;
}else{
result = 500;
if(specMode == MeasureSpec.AT_MOST){
result = Math.min(result,specSize);
}
}
return result;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawColor(Color.parseColor("#00aad6"));
}
}
因此:重写onMeasure()方法的目的就是为了给View 一个wrap_content属性下默认的大小。这个方法内,主要是对于MeasureSpec.AT_MOST
这个模式,也就是针对在布局xml
文件控件的宽高写wrap_content
时的处理。无论MeasureSpec.EXACTLY(match_parent)
还是MeasureSpec.AT_MOST
,这两种模式都是根据当前控件以及所在的父控件大小共同来确定的。
三、Padding的处理
四、onDraw()方法讲解
onDraw()方法,看到这个方法,大家就知道它跟绘画有关。
onDraw()方法中有一个Canvas类,也就是画布的意思。Canvas类有许多绘画的方法,比如
画圆调用canvas的
drawCircle(left, top, radius, paint);
方法中的属性分别对应要绘制的这个圆最左侧的横坐标,最上侧的纵坐标,半径,和画笔。
画直线调用canvas的
drawLine(left, top, right, bottom, paint)
方法中的属性也就是这条直线左上右下的四个坐标点,因为宽度已经被paint的setStrokeWidth()方法给固定了。
画虚线稍微比他们复杂一些,要用到PathEffect类
最关键的是 invalidata()会导致onDraw()方法重新被执行。关于view的渲染是在Activity的onReusme()被调用之后才会开始的,因此在自定义控件的时候要注意清楚流程。
关于viewGroup的测量和摆放子view在以后的课程找那个进行详细讲解