第三章 Android控件架构与自定义控件详解

这章的重要性不言而喻,按照以下几个方面进行介绍:

  1. Android控件架构
  2. View的测量与绘制
  3. 自定义控件的三种方式
  4. 事件的拦截机制

3.1   Android控件架构

3.2 View的测量

  Android系统提供了一个设计短小精悍的类---MeasureSpec类,通过它帮助我们测量。MeasureSpec是一个32位的int值,其中高2位是测量的模式,低30位是测量的大小。

注意:View类默认的onMeasure()方法只支持EXACTLY模式。所以在自定义控件的时候不重写onMeasure的话,就只能使用EXACTLY模式。控件可以响应你指定的具体的宽高或match_parent属性。而如果要让自定义view支持wrap_content属性,那么就要重写onMeasure。

package com.fightzhao.gesturedetectordemo.ui;

import android.content.Context;
import android.util.AttributeSet;
import android.view.View;

/**
 * Created by fightzhao on 16-3-7.
 */
public class TestView extends View {
    public TestView(Context context) {
        super(context);
    }

    public TestView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public TestView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(measureWidth(widthMeasureSpec), measureHight(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);
            }
        }
        return result;
    }

    private int measureHight(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;
    }
}

3.3 View的绘制
  这个书中写的不是太明白,难点在于创建Canvas时候为何在其构造函数中传入的参数是bitmap。
Canvas相当于是一个画板,使用Paint在上面作画了。通常是用过继承view,重写其中的onDraw()方法来完成绘图。
那么什么是Canvas呢?一般情况下,可以使用重写的View类中的onDraw方法来绘图。onDraw中有一个参数就是Canvas canvas对象。使用这个对象就可以进行绘图了,而在其他地方,通常需要代码创建一个Canvas对象:
    Canvas canvas = new Canvas(bitmap);
当创建一个Canvas对象的时候,为什么要传进去一个bitmap对象呢?如果不传入一个bitmap的话,编译器虽然不会报错,,但是一般我是不会这么做的。原因如下:
  1. 传进去的bitmap和与这个bitmap创建的canvas是紧密联系在一起的,这个过程我们称之为装载画布。这个bitmap用来存储所有绘制在Canvas上的像素信息。所以通过这种方式创建的canvas对象后,后面调用所有的Canvas.drawXXX方法都发生在这个bitmap上。

  2. 如果在view类的onDraw()方法,通过如下代码:可以了解canvas和bitmap的关系:

      canvas.drawBitmap(bitmap1,0,0,null);

      canvas.drawBitmap(bitmap2,0,0,null);

  而对于bitmap2,我们将它装载到另一个Canvas对象中: Canvas mCanvas = new Canvas(bitmap2);

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

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

3.6 自定义View

  1. 可以分为以下三类:
    1. 对现有的控件进行拓展,需要重写onDraw()比如TextView。那么什么时候使用呢?这种效果不方便通过布局的组合方式展现的时候,往往需要动态或者静态的展现一些不规则图形的时候,即需要重写onDraw().。采用这种方式需要自己手动的自己支持wrap_content,并且可以padding。
    2. 通过组合来实现新的控件:
      1. 继承ViewGroup派生特殊的Layout:这种方法用在需要自定义布局的时候,即除了LinearLayout、RelativeLayout、FrameLayout这几种系统的布局外,我们重新定义一种新的布局的时候,当某种效果看起来很像几种View组合在一起的时候,可以采用这种方法实现。采用这种方法要麻烦一点,需要合适的处理ViewGroup的测量、布局这两个过程,并同时处理子元素的测量和布局。
      2. 继承特定的ViewGroup(比如LinearLayout):这种方法比较常见,当效果看起来像是几个View组合在一起的效果的时候,可以用这种方法实现。不需要自己处理ViewGroup的测量和布局,注意和上面的区别,一般来说上面能实现的,这个也一定可以实现,两者差别在于上面的更加接近View的底层。、
    3. 重写View,实现全新的效果。

依次介绍这几种重写view的方法:

  1. 第一种是对现有View进行拓展:

      

package com.fightzhao.gesturedetectordemo.ui;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.widget.TextView;

/**
 * Created by fightzhao on 16-3-8.
 */
public class MyTextView extends TextView {
    private Paint mPaint1;
    private Paint mPaint2;

    public MyTextView(Context context) {
        super(context);
        init();
    }

    public MyTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public MyTextView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }


    private void init() {
        mPaint1 = new Paint();
        mPaint1.setColor(Color.BLUE);
        mPaint1.setStyle(Paint.Style.FILL);
        mPaint1.setStrokeWidth(2);

        mPaint2 = new Paint();
        mPaint2.setColor(Color.YELLOW);
        mPaint2.setStyle(Paint.Style.FILL);
    }

    @Override
    protected void onDraw(Canvas canvas) {
//        在回调父类方法前,实现自己的逻辑,对TextView来说就是绘制文本之前
//        绘制外层矩形
        int paddingLeft = getPaddingLeft();
        int paddingRight = getPaddingLeft();
        int paddingTop = getPaddingLeft();
        int paddingBoom = getPaddingLeft();

        int width = getWidth() - paddingLeft - paddingRight;
        int height = getHeight() - paddingTop - paddingBoom;
        canvas.drawRect(0, 0, getMeasuredWidth(), getHeight(), mPaint1);

//        绘制内层圆
        int radius = Math.min(width, height) / 2;
        canvas.drawCircle(paddingLeft + width / 2, paddingTop + height / 2, radius, mPaint2);
        canvas.save();

        canvas.translate(10, 0);

        super.onDraw(canvas);
//        在回调父类方法后,实现自己的逻辑,对TextView来说就是绘制文本内容之后
        canvas.restore();
    }
}

  

  再来实现一个稍微复杂的:预览图如下:

具体代码:

package com.fightzhao.gesturedetectordemo.ui;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.LinearGradient;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Shader;
import android.util.AttributeSet;
import android.widget.TextView;

/**
 * Created by fightzhao on 16-3-7.
 */
public class TestView extends TextView {
    private int mViewWidth,mTranslate;
    private Paint mPaint;
    private LinearGradient mLinearGradient;
    private Matrix mMatrix;
    public TestView(Context context) {
        super(context);
    }

    public TestView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public TestView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mPaint = new Paint();
        mPaint.setColor(Color.YELLOW);
        mPaint.setStyle(Paint.Style.FILL);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(measureWidth(widthMeasureSpec), measureHight(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);
            }
        }
        return result;
    }

    private int measureHight(int measureSpec) {
        int result = 0;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);

        if (specMode == MeasureSpec.EXACTLY) {
            result = specSize;
        } else {
            result = 150;
            if (specMode == MeasureSpec.AT_MOST) {
                result = Math.min(result, specSize);
            }
        }
        return result;
    }


    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        if (mViewWidth==0){
            mViewWidth=getMeasuredWidth();
            if(mViewWidth>0){
                mPaint=getPaint();
                mLinearGradient = new LinearGradient(0,0,mViewWidth,0,new int[]{
                        Color.BLUE,0xfffffff,Color.BLUE
                },null, Shader.TileMode.CLAMP);
                mPaint.setShader(mLinearGradient);
                 mMatrix = new Matrix();
            }
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if(mMatrix!=null){
            mTranslate += mViewWidth / 5;
            if (mTranslate > 2 * mViewWidth) {
                mTranslate = -mViewWidth;
            }
            mMatrix.setTranslate(mTranslate, 0);
            mLinearGradient.setLocalMatrix(mMatrix);
            postInvalidateDelayed(100);
        }
    }
}

  这里有两个要点:一个是设置的wrap_content的实现。需要重写onMeasure()方法。

第二个:设置的效果直接看书。

      

原文地址:https://www.cnblogs.com/fightzhao/p/5251954.html