安卓自定义View基础06-View的onMeasure(),onDraw()方法详解以及Padding的处理

来源:互联网 发布:windows php 定时任务 编辑:程序博客网 时间:2024/05/21 05:07

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窗口的组成图吧,以便于大家更加直观地理解:



下面进入正题:


  1. 为什麼要实现onMeasure()。

  2. 什么情况下需要实现onMeasure()

       举个栗子:小时候我们都玩过这样一个游戏,一个人蒙着眼睛,拿着粉笔在黑板上画出指定图形,另一个人通过说话来指导他如何去画。比如你会知道他,在距离黑板边缘一掌宽的地方画一个边长为20厘米的正方形,那他就可以准确的画出来,但是如果他告诉你,画一个矩形,那么你就无法准确的画出这个图形,因为你不知道在哪个位置化画和矩形应该画多大的。Android就是那个被蒙着双眼的美女画家,你必须给他准确的描述出来如何去画她才会绘制出你想要的图形。
      再举个栗子:就好像客户说,你给我做一个关于公司考勤的APP,这个时候的你就好像是Android系统,如果客户不告诉你需求,你怎么知道做成什么样,这就蛋疼了,你只有了解了具体的需求,才能做成客户满意的产品。(可能不太恰当)
    在现实生活中,如果我们要去画一个图形,就必须知道他的大小和位置。同理,Android系统在绘制View前,也必须对view进行测量,即告诉系统画一个多大的View,这个过程必须在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最后显示的大小。

   一、不重写OnMeasure()f方法或者仅仅是调用父类的onMeasure()方法时。
    
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时,默认最大的宽高都是500dp



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);//    }@Overrideprotected 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在以后的课程找那个进行详细讲解


阅读全文
0 0
原创粉丝点击