Android View 之 Canvas,Paint,Matrix,Rect 等介绍

1.Paint画笔介绍

  • Paint即画笔,在绘图过程中起到了极其重要的作用,画笔主要保存了颜色, 样式等绘制信息,指定了如何绘制文本和图形,画笔对象有很多设置方法,大体上可以分为两类,一类与图形绘制相关,一类与文本绘制相关。

1.1 图形绘制

  • 常用的方法有这些

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    * setARGB(int a,int r,int g,int b); 
    设置绘制的颜色,a代表透明度,r,g,b代表颜色值。
    * setAlpha(int a);
    设置绘制图形的透明度。
    * setColor(int color);
    设置绘制的颜色,使用颜色值来表示,该颜色值包括透明度和RGB颜色。
    * setAntiAlias(boolean aa);
    设置是否使用抗锯齿功能,会消耗较大资源,绘制图形速度会变慢。
    * setDither(boolean dither);
    设定是否使用图像抖动处理,会使绘制出来的图片颜色更加平滑和饱满,图像更加清晰
    * setFilterBitmap(boolean filter);
    如果该项设置为true,则图像在动画进行中会滤掉对Bitmap图像的优化操作,加快显示速度,本设置项依赖于dither和xfermode的设置
    * setMaskFilter(MaskFilter maskfilter);
    设置MaskFilter,可以用不同的MaskFilter实现滤镜的效果,如滤化,立体等
    * setColorFilter(ColorFilter colorfilter);
    设置颜色过滤器,可以在绘制颜色时实现不用颜色的变换效果
    * setPathEffect(PathEffect effect);
    设置绘制路径的效果,如点画线等
    * setShader(Shader shader);
    设置图像效果,使用Shader可以绘制出各种渐变效果
    * setShadowLayer(float radius ,float dx,float dy,int color);
    在图形下面设置阴影层,产生阴影效果,radius为阴影的角度,dx和dy为阴影在x轴和y轴上的距离,color为阴影的颜色
    * setStyle(Paint.Style style);
    设置画笔的样式,为FILL,FILL_AND_STROKE,或STROKE
    * setStrokeCap(Paint.Cap cap);
    当画笔样式为STROKE或FILL_AND_STROKE时,设置笔刷的图形样式,如圆形样式 Cap.ROUND,或方形样式Cap.SQUARE
    * setSrokeJoin(Paint.Join join);
    设置绘制时各图形的结合方式,如平滑效果等
    * setStrokeWidth(float width);
    当画笔样式为STROKE或FILL_AND_STROKE时,设置笔刷的粗细度
    * setXfermode(Xfermode xfermode);
    设置图形重叠时的处理方式,如合并,取交集或并集,经常用来制作橡皮的擦除效果

1.2 文本绘制

  • 常用的方法有这些

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    * setFakeBoldText(boolean fakeBoldText); 
    模拟实现粗体文字,设置在小字体上效果会非常差
    * setSubpixelText(boolean subpixelText);
    设置该项为true,将有助于文本在LCD屏幕上的显示效果
    * setTextAlign(Paint.Align align);
    设置绘制文字的对齐方向
    * setTextScaleX(float scaleX);
    设置绘制文字x轴的缩放比例,可以实现文字的拉伸的效果
    * setTextSize(float textSize);
    设置绘制文字的字号大小
    * setTextSkewX(float skewX);
    设置斜体文字,skewX为倾斜弧度
    * setTypeface(Typeface typeface);
    设置Typeface对象,即字体风格,包括粗体,斜体以及衬线体,非衬线体等
    * setUnderlineText(boolean underlineText);
    设置带有下划线的文字效果
    * setStrikeThruText(boolean strikeThruText);
    设置带有删除线的效果

2.Canvas画布介绍

  • 当我们调整好画笔之后,现在需要绘制到画布上,这就得用Canvas类了。在android中既然把Canvas当做画布,那么就可以在画布上绘制我们想要的任何东西。除了在画布上绘制之外,还需要设置一些关于画布的属性,比如,画布的颜色、尺寸等。

2.1 设置属性

  • 一般属性有:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    * Canvas(Bitmap bitmap): 以bitmap对象创建一个画布,则将内容都绘制在bitmap上,因此bitmap不得为null。
    * Canvas(GL gl): 在绘制3D效果时使用,与OpenGL相关。
    * isOpaque(boolean isOpaque):检测是否支持透明。
    * setViewport(int left, int top, int right, int bottom, int clipflag): 设置画布中显示窗口。
    * drawColor(int color): 设置Canvas的背景颜色。
    * setBitmap(Bitmap mBitmap): 设置具体画布,画的内容,保存为一个Bitmap。
    * clipRect(float left, float top, float right, float bottom): 设置显示区域,即设置裁剪区。
    * translate(float x, float y): 平移画布。
    * rotate(float degree, float px, float py): 旋转画布 。
    * skew(float sx, float sy): 设置偏移量。
    * save(): 将Canvas当前状态保存在堆栈,save之后可以调用Canvas的平移、旋转、错切、剪裁等操作。
    * restore(): 恢复为之前堆栈保存的Canvas状态,防止save后对Canvas执行的操作对后续的绘制有影响。restore和save要配对使用,restore可以比save少,但不能比save多,否则会引发error。save和restore之间,往往夹杂的是对Canvas的特殊操作。
    * save(int num):将Canvas当前状态保存在堆栈,并予以编号int
    * restoreToCount(int num):恢复为之前堆栈保存的编号为int的Canvas状态
    * concat(Matrix matrix):画布关联矩阵,画出来的内容按矩阵改变,而不是画布改变。
    * Drawable.draw(Canvas canvas):将Drawable画到Canvas中
    注:这种方式画Drawable怎么设置透明度呢?((BitmapDrawable)Drawable).getPaint().setAlpha(mBgAlpha);

2.2 画图【重点】

  • 画图部分

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    *  canvas.drawPaint(Paint paint)
    将画笔设置的颜色和透明度铺满画布
    * drawRect(RectF rect, Paint paint)
    绘制矩形,参数一为RectF一个区域
    * drawRect(float left, float top, float right, float bottom, Paint paint)
    绘制矩形,left:矩形left的x坐标,top:矩形top的y坐标,right:矩形right的x坐标,bottom:矩形bottom的y坐标
    * drawRoundRect(RectF rect, float rx, float ry, Paint paint)
    绘制圆角矩形, rx:x方向的圆角半径,ry:y方向的圆角半径
    * drawRoundRect(float left, float top, float right, float bottom, float rx, float ry, Paint paint)
    * drawPath(Path path, Paint paint)
    绘制一个路径,参数一为Path路径对象
    * drawBitmap(Bitmap bitmap, Rect src, Rect dst, Paint paint)
    贴图,参数一就是我们常规的Bitmap对象,参数二是源区域(这里是bitmap),参数三是目标区域(应该在canvas的位置和大小),参数四是Paint画刷对象,因为用到了缩放和拉伸的可能,当原始Rect不等于目标Rect时性能将会有大幅损失。
    * drawBitmap (Bitmap bitmap, float left, float top, Paint paint)
    * drawLine(float startX, float startY, float stopX, float stopY, Paintpaint)
    画线,参数一起始点的x轴位置,参数二起始点的y轴位置,参数三终点的x轴水平位置,参数四y轴垂直位置,最后一个参数为Paint 画刷对象。
    * drawPoint(float x, float y, Paint paint)
    画点,参数一水平x轴,参数二垂直y轴,第三个参数为Paint对象。
    * drawText(String text, float x, floaty, Paint paint)
    渲染文本,Canvas类除了上面的还可以描绘文字,参数一是String类型的文本,参数二文字左侧到x轴距离,参数三文字BaseLine到y轴距离,参数四是Paint对象。
    * drawOval(RectF oval, Paint paint)
    绘制椭圆,参数一是扫描区域,参数二为paint对象
    * drawOval(float left, float top, float right, float bottom, Paint paint)
    * drawCircle(float cx, float cy, float radius,Paint paint)
    绘制圆,参数一是中心点的x轴,参数二是中心点的y轴,参数三是半径,参数四是paint对象;
    * drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter, Paint paint)
    画弧,参数一是RectF对象,指定圆弧的外轮廓矩形区域,参数二是起始角(度)在电弧的开始,参数三扫描角(度)开始顺时针测量的,参数四是如果这是真的话,包括椭圆中心的电弧,并关闭它,如果它是假这将是一个弧线,参数五是Paint对象;

2.3 Canvas对象的获取方式

  • 2.3.1 Canvas对象的获取方式有两种:

    • 第一种通过重写View.onDraw方法,View中的Canvas对象会被当做参数传递过来,操作这个Canvas,效果会直接反应在View中。
    • 第二种通过new创建一个Canvas对象
    • 代码如下所示
    1
    2
    3
    4
    5
    6
    @Override
    protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    }

    Canvas canvas = new Canvas();

2.4 Canvas的作用

  • Canvas可以绘制的对象有:弧线(arcs)、填充颜色(argb和color)、Bitmap、圆(circle和oval)、点(point)、线(line)、矩形(Rect)、图片(Picture)、圆角矩形(RoundRect)、文本(text)、顶点(Vertices)、路径(path)。

2.5 Canvas绘制圆和椭圆

  • 绘制圆

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    private Paint paint = new Paint();

    @Override
    protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    paint.setAntiAlias(true);
    paint.setColor(Color.BLUE);
    paint.setStyle(Paint.Style.FILL);
    canvas.drawCircle(200,200,100 , paint);
    }

2.6 Canvas绘制矩形、圆角矩形

  • 如下所示

  • image

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    private Paint paint = new Paint();

    @Override
    protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    paint.setAntiAlias(true);
    paint.setColor(Color.RED);
    paint.setStyle(Paint.Style.FILL);
    canvas.drawRect(100, 100, 200, 200, paint);
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
    canvas.drawRoundRect(400, 100, 600, 300, 30, 30, paint);
    }
    paint.setStyle(Paint.Style.STROKE);
    paint.setStrokeWidth(20);
    canvas.drawRect(100, 400, 300, 600, paint);
    }

2.7 Canvas绘制文字

  • Canvas绘制文字

  • image

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    private Paint paint = new Paint();

    @SuppressLint("DrawAllocation")
    @Override
    protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    paint.setAntiAlias(true);
    paint.setColor(Color.RED);
    paint.setTextSize(100);
    canvas.drawText("潇湘剑雨", 100, 100, paint);
    }

2.8 Canvas绘制弧形、封闭弧形

  • 绘制弧形、封闭弧形

  • image

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    private Paint paint = new Paint();

    @SuppressLint("DrawAllocation")
    @Override
    protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    paint.setAntiAlias(true);
    paint.setColor(Color.RED);
    RectF rel = new RectF(50, 50, 150, 150);
    //实心圆弧
    canvas.drawArc(rel, 0, 135, false, paint);
    //实心圆弧 将圆心包含在内
    RectF rel2 = new RectF(50, 200, 150, 300);
    canvas.drawArc(rel2, 0, 135, true, paint);
    //设置空心Style
    paint.setStyle(Paint.Style.STROKE);
    paint.setStrokeWidth(20);
    RectF rel3 = new RectF(50, 350, 150, 450);
    canvas.drawArc(rel3, 0, 270, false, paint);
    RectF rel4 = new RectF(50, 250, 150, 600);
    canvas.drawArc(rel4, 0, 270, true, paint);
    }

2.9 Canvas绘制Path路径

  • Canvas绘制Path路径

  • image

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    private Paint paint = new Paint();
    @SuppressLint("DrawAllocation")
    @Override
    protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    Path angle = new Path();
    angle.moveTo(250, 0);
    angle.lineTo(0, 500);
    angle.lineTo(100, 300);
    angle.lineTo(200, 350);
    angle.lineTo(500, 500);
    angle.close();
    canvas.drawPath(angle, paint);
    }

3.Matrix变换矩阵介绍【Canvas位置转换】

  • 思考:如果要画一个仪表盘(数字围绕显示在一个圆圈中),或者类似钟表指针样的控件,如何实现?
  • Android还提供了一些对Canvas位置转换的方法:rorate、scale、translate、skew(扭曲)等,而且它允许你通过获得它的转换矩阵对象(getMatrix方法)直接操作它。这些操作就像是虽然你的笔还是原来的地方画,但是画纸旋转或者移动了,所以你画的东西的方位就产生变化。为了方便一些转换操作,Canvas还提供了保存和回滚属性的方法(save和restore),比如你可以先保存目前画纸的位置(save),然后旋转90度,向下移动100像素后画一些图形,画完后调用restore方法返回到刚才保存的位置。

3.1 translate平移

3.2 rorate旋转

  • rorate旋转

  • image

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    private Paint mPaint = new Paint();

    @SuppressLint("DrawAllocation")
    @Override
    protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    canvas.drawColor(Color.BLUE);
    mPaint.setColor(Color.RED);
    canvas.drawRect(new Rect(0, 0, 800, 800), mPaint);
    canvas.save();
    mPaint.setColor(Color.GREEN);
    canvas.rotate(45,400,400);
    canvas.drawRect(new Rect(0, 0, 800, 800), mPaint);
    canvas.restore();
    }
  • 源代码有两个可以使用的方法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    /**  
    * Preconcat the current matrix with the specified rotation.
    * @param degrees The amount to rotate, in degrees
    */
    public native void rotate(float degrees);
    /**
    * Preconcat the current matrix with the specified rotation.
    * @param degrees The amount to rotate, in degrees
    * @param px The x-coord for the pivot point (unchanged by the rotation)
    * @param py The y-coord for the pivot point (unchanged by the rotation)
    */
    public final void rotate(float degrees, float px, float py) {
    translate(px, py);
    rotate(degrees);
    translate(-px, -py);
    }

3.3 scale缩放

  • scale缩放

  • image

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    private Paint mPaint = new Paint();

    @SuppressLint("DrawAllocation")
    @Override
    protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    canvas.drawColor(Color.YELLOW);
    mPaint.setColor(Color.RED);
    canvas.drawRect(new Rect(0, 0, 800, 800), mPaint);
    // 保存画布状态
    canvas.save();
    canvas.scale(0.5f, 0.5f);
    mPaint.setColor(Color.GREEN);
    canvas.drawRect(new Rect(0, 0, 800, 800), mPaint);
    // 画布状态回滚
    canvas.restore();
    canvas.scale(0.5f, 0.5f, 400, 400);
    mPaint.setColor(Color.BLUE);
    canvas.drawRect(new Rect(0, 0, 800, 800), mPaint);
    }
  • 源码如下所示

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    /**  
    * Preconcat the current matrix with the specified scale.
    * @param sx The amount to scale in X
    * @param sy The amount to scale in Y
    */
    public native void scale(float sx, float sy);

    /**
    * Preconcat the current matrix with the specified scale.
    * @param sx The amount to scale in X
    * @param sy The amount to scale in Y
    * @param px The x-coord for the pivot point (unchanged by the scale)
    * @param py The y-coord for the pivot point (unchanged by the scale)
    */
    public final void scale(float sx, float sy, float px, float py) {
    translate(px, py);
    scale(sx, sy);
    translate(-px, -py);
    }

3.4 skew扭曲

4.RectF介绍

4.1 Rect简单属性

  • 这是一个我们常用的一个“绘画相关的工具类”,常用语描述长方形/正方形,他只有4个属性

    1
    2
    3
    4
    public int left;
    public int top;
    public int right;
    public int bottom;
  • 其中常用的构造方法如下所示

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    public Rect(int left, int top, int right, int bottom) {
    this.left = left;
    this.top = top;
    this.right = right;
    this.bottom = bottom;
    }

    public Rect(Rect r) {
    if (r == null) {
    left = top = right = bottom = 0;
    } else {
    left = r.left;
    top = r.top;
    right = r.right;
    bottom = r.bottom;
    }
    }
  • 这4个属性描述着这一个“方块”,但是这有一个知识点需要理清楚,先看这张图

    • image

4.2 Rect父类的实现

  • 实现了Parcelable 所以需要实现一堆Object的方法,诸如equals,toString等等,来简单看一看

    • 对于equals方法,首先先对传来的对象进行判空,类型判断,再强转成Rect对象,最后还是一个个去比对那4个属性。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    @Override
    public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;

    Rect r = (Rect) o;
    return left == r.left && top == r.top && right == r.right && bottom == r.bottom;
    }

    @Override
    public int hashCode() {
    int result = left;
    result = 31 * result + top;
    result = 31 * result + right;
    result = 31 * result + bottom;
    return result;
    }

    @Override
    public String toString() {
    StringBuilder sb = new StringBuilder(32);
    sb.append("Rect("); sb.append(left); sb.append(", ");
    sb.append(top); sb.append(" - "); sb.append(right);
    sb.append(", "); sb.append(bottom); sb.append(")");
    return sb.toString();
    }

4.3 Rect常用的一些方法

  • 获取“宽”

    1
    2
    3
    4
    //文章开头说的公式在这里得到了应验
    public final int width() {
    return right - left;
    }
  • 获取“高”

    1
    2
    3
    public final int height() {
    return bottom - top;
    }
  • 有效性的判断

    1
    2
    3
    4
    //因为left是最左侧,right比left还小不就不成形了么?宽高同是如此
    public final boolean isEmpty() {
    return left >= right || top >= bottom;
    }
  • 全部置0操作

    1
    2
    3
    public void setEmpty() {
    left = right = top = bottom = 0;
    }
  • 设置参数方法,和构造函数的区别仅在于不会创建新对象

    1
    2
    3
    4
    5
    6
    public void set(int left, int top, int right, int bottom) {
    this.left = left;
    this.top = top;
    this.right = right;
    this.bottom = bottom;
    }

5.关于使用到这几个属性的自定义View

  • 上面比较详细介绍了Canvas,Paint,Matrix,RectF等等的属性,作用,常用方法,接下来就需要结合具体业务需求练手写一下小案例自定义控件呢

5.1 自定义轮播图圆点

  • 5.1.1 需求介绍

    • 绘制圆环,一个实心中心圆,还有一个外圆环
    • 此控件可以设置宽度和高度,可以设置颜色
  • 5.1.2 思路介绍

    • 3.2.1 既然是绘制圆形,可以写一个继承View的自定义view
    • 3.2.2 重写onDraw方法,获取控件宽高,然后比较宽高值,取小值的一半作为圆的半径
    • 3.2.3 然后分别绘制选中状态和未选中状态的圆
    • 3.2.4 创建画笔Paint,并且设置相关属性,比如画笔颜色,类型等
    • 3.2.5 利用canvas绘制圆,然后再又用相同方法绘制外边缘
    • 3.2.6 自定义一个是否选中状态的方法,传入布尔值是否选中,然后调用view中invalidate方法
  • 5.1.3 代码介绍

    • 具体代码如下所示:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    /**
    * 红点自定义控件, 建议设置红点宽高一样,否则是椭圆
    */
    public class DotView extends View {

    private boolean isInit = false;
    private boolean isSelected = false;
    private float mViewHeight;
    private float mViewWidth;
    private float mRadius;
    private Paint mPaintBg = new Paint();
    private int mBgUnselectedColor = Color.parseColor("#1A000000");
    private int mBgSelectedColor = Color.parseColor("#FDE26E");
    private static final float mArcWidth = 2.0f;

    public DotView(Context context) {
    super(context);
    }

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

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

    @Override
    protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    if (!isInit) {
    isInit = true;
    mViewHeight = getHeight();
    mViewWidth = getWidth();
    if (mViewHeight >= mViewWidth) {
    mRadius = mViewWidth / 2.f;
    } else {
    mRadius = mViewHeight / 2.f;
    }
    }

    //是否选中
    if (isSelected){
    drawSelectedDot(canvas);
    } else{
    drawUnSelectedDot(canvas);
    }
    }

    /**
    * 绘制选中指示器红点
    * @param canvas canvas
    */
    private void drawSelectedDot(Canvas canvas) {
    //设置paint相关属性
    mPaintBg.setAntiAlias(true);
    mPaintBg.setColor(mBgSelectedColor);
    mPaintBg.setStyle(Style.FILL);

    //绘制圆
    canvas.drawCircle(mViewWidth / 2.f, mViewHeight / 2.f, mRadius - 8.f, mPaintBg);

    mPaintBg.setStyle(Style.STROKE);
    float offset = 1.f + mArcWidth;
    RectF oval = new RectF(mViewWidth / 2.f - mRadius + offset, mViewHeight / 2.f - mRadius + offset,
    mViewWidth / 2.f + mRadius - offset, mViewHeight / 2.f + mRadius - offset);

    //绘制指定的弧线,该弧线将被缩放以适应指定的椭圆形。
    canvas.drawArc(oval, 0.f, 360.f, false, mPaintBg);
    }

    /**
    * 绘制未选中指示器红点
    * @param canvas canvas
    */
    private void drawUnSelectedDot(Canvas canvas) {
    mPaintBg.setAntiAlias(true);
    mPaintBg.setColor(mBgUnselectedColor);
    mPaintBg.setStyle(Style.FILL);
    canvas.drawCircle(mViewWidth / 2.f, mViewHeight / 2.f, mRadius - 8.f, mPaintBg);
    }


    /**
    * 设置是否选中
    * @param isSelected isSelected
    */
    public void setIsSelected(boolean isSelected) {
    this.isSelected = isSelected;
    //使整个视图无效。如果视图是可见的,则{@link#onDraw(android.Graphics.Canvas)}将在将来的某个时候被调用。
    //调用该方法,会进行重新绘制,也就是调用onDraw方法
    this.invalidate();
    }
    }

5.2 自定义圆环百分比进度条

  • 5.2.1 需求分析

    • 1.业务需求:可以设置圆角,可以设置圆形,如果是圆角则必须设置半径,默认圆角半径为10dp
    • 2.如果设置了圆形,则即使设置圆角也无效;如果设置非圆形,则圆角生效,同时需要判断圆角半径是否大于控件宽高,处理边界逻辑
    • 3.当设置圆形的时候,即使设置宽高不一样,那么取宽高中的最小值的一半为圆形半径
  • 5.2.2 代码介绍

    • 代码如下所示
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    public class ARoundImageView extends AppCompatImageView {

    /*
    * Paint:画笔
    * Canvas:画布
    * Matrix:变换矩阵
    *
    * 业务需求:可以设置圆角,可以设置圆形,如果是圆角则必须设置半径,默认圆角半径为10dp
    */
    /**
    * 圆形模式
    */
    private static final int MODE_CIRCLE = 1;
    /**
    * 普通模式
    */
    private static final int MODE_NONE = 0;
    /**
    * 圆角模式
    */
    private static final int MODE_ROUND = 2;
    /**
    * 圆角半径
    */
    private int currRound = dp2px(10);
    /**
    * 画笔
    */
    private Paint mPaint;
    /**
    * 默认是普通模式
    */
    private int currMode = 0;

    public ARoundImageView(Context context) {
    this(context,null);
    }

    public ARoundImageView(Context context, AttributeSet attrs) {
    this(context, attrs, 0);
    }

    public ARoundImageView(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    obtainStyledAttrs(context, attrs, defStyleAttr);
    initViews();
    }

    private void obtainStyledAttrs(Context context, AttributeSet attrs, int defStyleAttr) {
    TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ARoundImageView, defStyleAttr, 0);
    currMode = a.hasValue(R.styleable.ARoundImageView_type) ? a.getInt(R.styleable.ARoundImageView_type, MODE_NONE) : MODE_NONE;
    currRound = a.hasValue(R.styleable.ARoundImageView_radius) ? a.getDimensionPixelSize(R.styleable.ARoundImageView_radius, currRound) : currRound;
    a.recycle();
    }

    private void initViews() {
    //ANTI_ALIAS_FLAG 用于绘制时抗锯齿
    mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
    }


    /**
    * 当模式为圆形模式的时候,我们强制让宽高一致
    */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    if (currMode == MODE_CIRCLE) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    int result = Math.min(getMeasuredHeight(), getMeasuredWidth());
    // 此方法必须由{@link#onMeasure(int,int)}调用,以存储已测量的宽度和测量的高度。
    // 如果不这样做,将在测量时触发异常。
    setMeasuredDimension(result, result);
    } else {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }
    }

    @SuppressLint("DrawAllocation")
    @Override
    protected void onDraw(Canvas canvas) {
    //获取ImageView图片资源
    Drawable mDrawable = getDrawable();
    //获取Matrix对象
    Matrix mDrawMatrix = getImageMatrix();
    if (mDrawable == null) {
    return;
    }
    if (mDrawable.getIntrinsicWidth() == 0 || mDrawable.getIntrinsicHeight() == 0) {
    return;
    }
    if (mDrawMatrix == null && getPaddingTop() == 0 && getPaddingLeft() == 0) {
    mDrawable.draw(canvas);
    } else {
    final int saveCount = canvas.getSaveCount();
    canvas.save();
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
    if (getCropToPadding()) {
    final int scrollX = getScrollX();
    final int scrollY = getScrollY();
    canvas.clipRect(scrollX + getPaddingLeft(), scrollY + getPaddingTop(),
    scrollX + getRight() - getLeft() - getPaddingRight(),
    scrollY + getBottom() - getTop() - getPaddingBottom());
    }
    }
    canvas.translate(getPaddingLeft(), getPaddingTop());
    switch (currMode){
    case MODE_CIRCLE:
    Bitmap bitmap1 = drawable2Bitmap(mDrawable);
    mPaint.setShader(new BitmapShader(bitmap1, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP));
    canvas.drawCircle(getWidth() / 2, getHeight() / 2, getWidth() / 2, mPaint);
    break;
    case MODE_ROUND:
    Bitmap bitmap2 = drawable2Bitmap(mDrawable);
    mPaint.setShader(new BitmapShader(bitmap2, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP));
    canvas.drawRoundRect(new RectF(getPaddingLeft(), getPaddingTop(),
    getWidth() - getPaddingRight(), getHeight() - getPaddingBottom()),
    currRound, currRound, mPaint);
    break;
    case MODE_NONE:
    default:
    if (mDrawMatrix != null) {
    canvas.concat(mDrawMatrix);
    }
    mDrawable.draw(canvas);
    break;
    }
    canvas.restoreToCount(saveCount);
    }
    }

    /**
    * drawable转换成bitmap
    */
    private Bitmap drawable2Bitmap(Drawable drawable) {
    if (drawable == null) {
    return null;
    }
    Bitmap bitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
    Canvas canvas = new Canvas(bitmap);
    //根据传递的scaleType获取matrix对象,设置给bitmap
    Matrix matrix = getImageMatrix();
    if (matrix != null) {
    canvas.concat(matrix);
    }
    drawable.draw(canvas);
    return bitmap;
    }

    private int dp2px(float value) {
    return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
    value, getResources().getDisplayMetrics());
    }
    }