Android控件架构与自定义控件(二)

View的测量:

在现实生活中,如果我们要去画一个图形,就必须知道他的大小和位置。同样Android系统在绘制View之前,也必须对View进行测量,即告诉系统该画一个多大的View,这个国政在onMeasure(0方法中进行。
Android系统给我们提供了一个设计短小精悍却功能强大的类——MeasureSpec类,通过他来帮助我们测量View,MeasureSpec是一个32位的int值,其中高2位为测量的模式,低30位为测量的大小,在计算中使用位运算的原因是为了提高并优化效率。

测试的模式可以分为以下三种:
EXACTLY:
即精确值模式,当我们将控件的layout_width属性或layout_height属性指定为具体数值时,比如android:layout_width=”100dp”,或者指定为match_parent属性时(占据父View的大小)。系统使用的是EXACTLY模式。

AT_MOST:
即最大值模式,当控件的layout_width属性或layout_height属性指定为wrap_content时,控件大小一般随着控件的子空间或内容的变化而变化,此时控件的尺寸只要不超过父控件允许的最大尺寸即可。

UNSPECIFIED:
这个属性比较奇怪,,他不指定其大小测量模式,View像多大就多大。通常情况下在绘制自定义View时才会使用。

View默认的onMeasure(0方法只支持EXACTLY模式,所以如果在自定义控件的时候不重写onMeasure()方法,就只能使用EXACTLY模式。控件可以响应你指定的具体宽高值或者是match_warp属性,而如果要让自定义View支持wrap_content属性,那么就必须重写onMeasure(0方法来指定wrap_content时的大小。

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        // TODO Auto-generated method stub
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

通过查看源码,可以发现其最终调用的是:

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }

setMeasuredDimension这个方法。该方法试将测量后的宽高设置进去。从而完成测量工作。所以我们就可以重写onMeasure()方法

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        // TODO Auto-generated method stub
        setMeasuredDimension(measureWidth(widthMeasureSpec),
                measureHeight(heightMeasureSpec));
    }

我们调用自定义的measureWidth()和measureHeight()方法分别对宽高进行重新定义。

private int measureWidth(int measureSpec) {
        // TODO Auto-generated method stub
        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;
    }

上面的方法是通过判断测量的模式,给出不同的测量值,当specMode为EXACTLY时,直接使用指定的specSize即可,当specMode为其他两种模式石,需要给他一个默认的大小。特别的如果制定wrap_content属性,即AT_MOST模式,则需要取出我们指定的大小与soecSize中的最小的一个来作为最后的测量值。上面的代码基本上可以作为模板代码。

运行上面代码,当指定宽高为400PX的时候就会显示具体的宽高View。如果制定为match_parent属性时,就会match父view,当指定宽高属性为wrap_content的时候,如果不重写onMeasure()方法,那么系统就不知道该使用默认多大的尺寸,因此,他就会默认填充整个父布局,所以重写,onMeasure()方法的目的,就是为了能够给View一个wrap_conteng属性下的默认大小。(感觉说了这么多还是这句最明白。擦。。。。)

View的绘制:

onDraw(),Canvas ,Paint这三个就是绘制所必需的具体的我就不多说了。

如果是在onDraw方法里面就不用创建Canvas对象,因为参数里面就有,那么如果在别的地方就需要new出来一个:

Canvas camvas=new Canvas(bitmap);

为什么要穿进去一个bitmap对象?如果不传入一个bitmap,IDE编译虽然不报错,但是一般我们不会这样做。这是因为穿进去的bitmap与通过这个bitmap穿件的Canvas画布是仅仅联系在一起的。这个过程我们称之为装载画布。这个bitmap是用来存储所有绘制在Canvas上的像素信息。所以当你通过这种方式创建了Canvas对戏那个后,门后面调用所有的Canvas.drawXXX方法都是发生在这个bittmap上。如果在View类的onDraw方法中,通过下面这段代码,我们可以了解到bitmap与canvas直接的关系。首先在onDraw方法中绘制两个bitmap:

@Override
    protected void onDraw(Canvas canvas) {
        // TODO Auto-generated method stub
        super.onDraw(canvas);
        canvas.drawBitmap(bitmap1, 0, 0,null);
        canvas.drawBitmap(bitmap2, 0, 0,null);
    }

而对于bitmap2我们将他装载到另一个Canvas对象中:

Canvas mCanvas=new Canvas(bitmap2);

在其他地方使用Canvas对象的绘图方法在装载bitmap2的Canvas对象上进行绘图:

mCamvas.drawXXX

通过mCanvas将绘制效果作用在了bittmap2上,再刷新View的时候就会发现通过onDraw方法画出来的bitmap2已经发生了改变,这就是因为bitmap2承载了在mCanvas上所进行的绘图操作。虽然我们也使用了Canvas的绘制API。但其实并没有经图形直接绘制在onDraw()方法指定的那块画布上,而是通过改变bitmap,然后让View重回,从而显示改变之后的bittmap。

ViewGroup的测量:

ViewGroup会去管理其子View,其中的一个管理项目就是负责子View的显示大小,当Vie我Group的大小为wrap_content时,ViewGroup就需要对子View进行遍历,以便获得所有子view的大学哎奥,从而来决定自己的大小,而在其他模式下则会通过具体的指定值来设置自身的大小。

ViewGroup在测量时通过遍历所有子View从而调用子View 的Measure方法获得每一个子View的测量结果前面所说的对View的测量就是在这里进行的。
当子View测量完毕后,就需要将子View放到合适的位置,这个过程就是View的Layout过程,ViewGroup在执行Layout过程时,同样是使用遍历来调用子View的Layout方法,并指定其具体的显示位置,从而来决定其布局的位置。

在自定义ViewGroup时,通常会去重写onLayout()方法来控制其子View显示位置的逻辑。同样,如果需要支持wrap_content属性,那么他还必须重写onMeasure()方法,这点与VIEW是相同的。

ViewGroup的绘制:

ViewGroup通常情况下不需要绘,因为他本身就没有需要绘制的东西,如果不是指定了ViewGroup的北京颜色,那么ViewGroup的onDraw()方法都不会被调用。但是,ViewGRoup会使用dispatchDraw()方法来绘制其子View,其过程同样是遍历所有的子View,并调用子View的绘制方法来完成绘制工作的。

自定义View:

在View中通常有以下一些比较重要的回调方法:

onFinishInflate():从XML加载组件后回调。

onSizeChanged():组件大小改变时回调。

onMeasure():回调给方法来进行测量。

onLayout():回调该方法来确定显示的位置。

onTouchEvent():监听到触摸事件时的回调。

通常情况下,由以下三种方法来实现自定义控件:

对现有控件进行拓展

通过组合来实现新的控件

重写View来实现全新的控件

对现有控件进行拓展:

以一个TextView为例,比如,让一个TextView的背景更加丰富,给其多绘制几层背景。

我们先来分析一下如何实现,原生的TextView使用onDraw()方法绘制要显示的文字,当继承了系统deTextView之后,如果不重写其onDraw()方法,则不会修改TextView的任何效果,可以认为在自定义的TextView中调用TextView类的onDraw()方法来绘制了显示的文字,代码如下:

@Override
    protected void onDraw(Canvas canvas) {
        // TODO Auto-generated method stub
        super.onDraw(canvas);
    }

程序调用super.onDraw(canvas)方法来实现原生控件的功能,但是在调用super.onDraw()方法之前和之后,我们都可以实现自己的逻辑,分别在系统绘制文字前后,完成自己的操作,即如下:

@Override
    protected void onDraw(Canvas canvas) {
        // TODO Auto-generated method stub
        //在回调父类方法前,实现自己的逻辑,对TextView来说即是在绘制文本内容前
        super.onDraw(canvas);
        //在回调父类方法后,实现自己的逻辑。对TextVeiw来说既是在绘制文本内容后
    }

这样就可以改变原生系统绘制的方式。

    原文作者:空白_c46e
    原文地址: https://www.jianshu.com/p/147ccbfa8ef6
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞