zoukankan      html  css  js  c++  java
  • 33、Android自定义控件流程

    自定义控件流程

    View 和 ViewGroup

    View的工作流程主要是指measure、layout和draw三大流程,即测量、布局和绘制。

    View的位置参数

    View的位置参数如下图所示:

    通过上图,我们可以很方便的了解View的位置参数,View和MotionEvent提供的获取坐标的方法如下表所示:

    方法 描述
    View的获取坐标的方法:
    getTop() 获取View自身的定边到其父布局顶边的距离。
    getLeft() 获取View自身的左边到父布局左边的距离。
    getRight() 获取View自身的右边到父布局左边的距离。
    getBottom() 获取View自身的底边到父布局顶边的距离。
    MotionEvent获取坐标的方法:
    getX() 获取点击事件距离控件左边的距离,即视图坐标。
    getY() 获取点击事件距离控件顶边的距离,即视图坐标。
    getRawX() 获取点击事件距离整个屏幕左边的距离,即绝对坐标。
    getRawY() 获取点击事件距离屏幕顶边的距离,即绝对坐标。

    View的测量

    当我们对View和ViewGroup进行测量时,首先是获取它的宽高信息,获取的方式有如下三种:

    方法 描述
    getMeasuredWidth() 对View上的内容进行测量后得到的View内容占据的宽度。
    getWidth() View在设定好布局后整个View的宽度,也就是在onLayout之后。
    getLayoutParams().width 测量后就确定值,getLayoutParams.width比getMeasureWidth多了margin和padding。

    所以,在自定义控件时,有时候会获取到宽高信息为0的情况,就要对照上面的表格进行排查。

    (1)View的测量

    在Measure过程中,系统会将View的LayoutParams根据父容器所施加的规则转换成对应的MeasureSpec,然后再根据这个MeasureSpec来测量View的宽高。

    MeasureSpec代表一个32位的int值,高2位代表SpecMode(测量模式),低30位代表SpecSize(规格大小),可以通过getSize()和getMode()来获取相应的值。

    测量的模式可以分为以下三种:

    模式 描述
    EXACTLY 即精确模式,当控件的layout_width或layout_height为具体数值时,系统使用的是EXACTLY模式。
    AT_MOST 最大值模式,当控件的layout_width或layout_height为wrap_content或match_parent时,控件的尺寸不能超过父控件允许的最大尺寸即可。
    UNSPECIFIED 未指定模式,它不指定其大小测量模式,View想多大就多大。

    系统最终会调用setMeasuredDimension(int measuredWidth,int measuredHeight)方法将测量后的宽高传递进去,以完成测量操作。

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(measureWidth(widthMeasureSpec), measureHeight(heightMeasureSpec));
    }
    /**测量宽度的模板代码*/
    private int measureWidth(int measureSpec){
        int result = 0;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);
        if(specMode == MeasureSpec.EXACTLY){    // 精确数值
            result = specSize;
        }else{                                    // 非精确数值
            result = 200;
            if(specMode == MeasureSpec.AT_MOST){// 自动包含
                result = Math.min(result, specSize);// 取出指定大小与specSize中最小一个作为最后测量值。
            }
        }
        return result;
    }
    /**测量高度的模板代码*/
    private int measureHeight(int measureSpec){
        int result = 0;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);
        
        if(specMode == MeasureSpec.EXACTLY){
            result = specSize;
        }else{
            result = 200;
            if(specMode == MeasureSpec.AT_MOST){
                result = Math.min(result, specSize);
            }
        }
        return result;
    } 
    

    a) 布局文件中指定精确的宽高值是400px时,View会根据指定的宽高进行设定。

    b) 当指定宽高属性为match_parent时,View会填充整个父布局。

    c) 当指定宽高属性为wrap_content时,如果不重写onMeasure()方法则会填充整个父布局,重写的话则会根据内容自动包含。

    (2)ViewGroup的测量

    当ViewGroup大小为wrap_content时,需要对子View进行遍历,以便根据所有子View的大小,来确定自身的大小。

    ViewGroup调用子View的measure()方法遍历测量后,获取到子View的测量结果,然后打包成MeasureSpec传递给子View。

    // measureChild(View, int, int)为子组件添加Padding   
    measureChild(child, parentWidthMeasureSpec, parentHeightMeasureSpec);
    // measureChildren(int, int)根据指定的高和宽来测量所有子View中显示参数非GONE的组件。  
    measureChildren(widthMeasureSpec, heightMeasureSpec);
    // measureChildWithMargins(View, int, int, int, int)测量指定的子组件,为子组件添加Padding和Margin。
    measureChildWithMargins(child, parentWidthMeasureSpec, widthUsed, parentHeightMeasureSpec, heightUsed);  
    

    View的绘制

    装载画布

    Android中,创建画布有两种方式:

    Canvas canvas = new Canvas();   或    Canvas canvas = new Canvas(bitmap);
    

    当在创建画布传入bitmap对象时,bitmap和画布是紧紧相连的,这个过程我们称之为装载画布。

    这个bitmap用来存储所有绘制在Canvas上的像素信息。且Canvas调用所有的Canvas.drawXXX方法都发生在该bitmap上。

    装载画布时,当Canvas将绘制效果作用在bitmap时,刷新view就会改变bitmap,如果非装载画布模式下,改变的是bitmap对象,并让view重绘。

    绘制解析

    Android系统中要自定义view,首先需要了解Android的view加载机制。主要有三个方法:

    1、onMeasure() //计算出view自身大小
    2、onLayout() //仅在ViewGroup中,用来为子view指定位置(left,top)
    3、onDraw() //view绘制内容

    下面根据源码中的相关说明,进一步分析控件的绘制操作及顺序:

    /*
      * Draw traversal performs several drawing steps which must be executed
      * in the appropriate order:
      *  1. Draw the background                                             绘制控件设置的背景
      *  2. If necessary, save the canvas' layers to prepare for fading     保存画布的图层和阴影信息
      *  3. Draw view's content                                             重写onDraw(canvas)进行绘制
      *  4. Draw children                                                   绘制子控件,对应方法dispatchDraw(canvas)
      *  5. If necessary, draw the fading edges and restore layers          绘制控件阴影渐变效果
      *  6. Draw decorations (scrollbars for instance)                      绘制滚动条,对应方法onDrawScrollBars(canvas)
      */  
    

    在第四步时,如果当前需要绘制的控件是ViewGroup,则需要通过dispatchDraw()方法绘制子控件,如果是View则不需要。

    通常情况下ViewGroup不需要进行绘制,因为其本身没有需要绘制的东西,如果不是指定背景色,那么ViewGroup的onDraw方法不会被调用。

    但是,ViewGroup会通过dispatchDraw()方法来绘制其子View。

    下面我们看看onDraw()和dispatchDraw()的区别:

    • 绘制View本身内容时,可以调用View.onDraw(Canvas canvas)方法。
    • 绘制View的子View的内容时,可以调用diapatchDraw方法。
  • 相关阅读:
    JavaSE基础(七)--Java流程控制语句之switch case 语句
    JavaSE基础(六)--Java流程控制语句之条件语句
    JavaSE基础(五)--Java运算符
    搭建seafile文档系统
    centos7 DHCP搭建双机热备 集群
    centos7安装DHCP后启动不了的问题解决方法
    思科ASA防火墙精华配置总结
    思科常用命令大全
    浅谈集线器、路由器、交换机、网关的作用与区别
    交换机端口镜像及其工作原理
  • 原文地址:https://www.cnblogs.com/pengjingya/p/14952704.html
Copyright © 2011-2022 走看看