本文主要是记录自定义控件时常用到的几个类和相关方法

1,Paint类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
private void init() {
paint = new Paint();
paint.setAntiAlias(true);//反锯齿
paint.setColor(Color.RED);
/*
* 设置画笔样式为描边,圆环嘛……当然不能填充不然就么意思了
*
* 画笔样式分三种:
* 1.Paint.Style.STROKE:描边
* 2.Paint.Style.FILL_AND_STROKE:描边并填充
* 3.Paint.Style.FILL:填充
*/
paint.setStyle(Paint.Style.FILL);
/*
* 设置描边的粗细,单位:像素px
* 注意:当setStrokeWidth(0)的时候描边宽度并不为0而是只占一个像素
*/
paint.setStrokeWidth(paintStrokeWidth);
}

Paint的其他方法:

  • setStrokeCap(Paint.Cap cap)
    方法,该方法用来设置我们画笔的笔触风格,上面的例子中我使用的是ROUND,表示是圆角的笔触,那么什么叫笔触呢,其实很简单,就像我们现实世界中的笔,如果你用圆珠笔在纸上戳一点,那么这个点一定是个圆,即便很小,它代表了笔的笔触形状,如果我们把一支铅笔笔尖削成方形的,那么画出来的线条会是一条弯曲的“矩形”,这就是笔触的意思。除了ROUND,Paint.Cap还提供了另外两种类型:SQUARE和BUTT

  • setStrokeJoin(Paint.Join join)
    这个方法用于设置结合处的形态,就像上面的代码中我们虽说是花了一条心电线,但是这条线其实是由无数条小线拼接成的,拼接处的形状就由该方法指定。

  • setShadowLayer(float radius, float dx, float dy, int shadowColor)
    该方法为我们绘制的图形添加一个阴影层效果:


TextPaint专门用户绘制文字的画笔

1
2
3
4
5
6
7
/* 
* 初始化文字画笔
*/
textPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG | Paint.SUBPIXEL_TEXT_FLAG);
textPaint.setColor(Color.WHITE);
textPaint.setTextSize(30);
textPaint.setTextAlign(Paint.Align.CENTER);

与画笔颜色相关的类

详情见博客:2,http://blog.csdn.net/aigestudio/article/details/41316141

PorterDuffXfermode类,图形混合模式的意思

2,View类的invalidate()方法,和postInvalidate()方法

在Android中提供了一个叫invalidate()的方法来让我们重绘我们的View。poistInvalidate()方法和invaliadate方法相同,只是它可以在子线程中运行.
nvalidate()方法只能在主线程中调用,而postInvalidate()方法可以在子线程中调用。

3,Shader类(着色器)

/15-23-34.jpg)
Shader类呢也是个灰常灰常简单的类,它有五个子类,像PathEffect一样每个子类都实现了一种Shader,Shader在三维软件中我们称之为着色器,其作用嘛就像它的名字一样是来给图像着色的或者更通俗的说法是上色!这么说该懂了吧!再不懂去厕所哭去!这五个Shader里最异类的是BitmapShader,因为只有它是允许我们载入一张图片来给图像着色,那我们还是先来看看这个怪胎吧BitmapShader只有一个含参的构造方法BitmapShader (Bitmap bitmap, Shader.TileMode tileX, Shader.TileMode tileY)

BitmapShader (Bitmap bitmap, Shader.TileMode tileX, Shader.TileMode tileY)的第一个参数是位图这个很显然,而后两个参数则分别表示XY方向上的着色模式。
Shader.TileMode里有三种模式:CLAMP、MIRROR和REPETA。MIRROR镜像效果,REPETA重复,CLAMP的意思就是边缘拉伸的意思

4,Canvas 画布

  • canvas.save();锁定画布
  • canvas.restore(); 释放画布,即将画布释放到之前锁定的位置。先调用sava()方法,才能调用restore()方法进行释放。
  • canvas.translate(x, y);平移画布,就相当于将画布的原点移到x,y的位置 ,即相当于坐标系移动了。

/15-24-19.jpg)

  • canvas.rotate(degrees);旋转画布,将画布按照一定角度进行旋转,即相当于坐标系进行了旋转。
    /15-24-36.jpg)
    注意:画布的平移旋转同样也会影响画布的自身坐标

    5,Canvas类(画布)


Canvas所提供的各种方法根据功能来看大致可以分为几类,第一是以drawXXX为主的绘制方法,第二是以clipXXX为主的裁剪方法,第三是以scale、skew、translate和rotate组成的Canvas变换方法,最后一类则是以saveXXX和restoreXXX构成的画布锁定和还原。

  • drawBitmapMesh (Bitmap bitmap, int meshWidth, int meshHeight, float[] verts, int vertOffset, int[] colors, int colorOffset, Paint paint)
    很吊毛的方法,但是计算复杂度很高,所有被沉默了。(一般不推荐使用,因为计算难度很大)
    详细原理http://blog.csdn.net/aigestudio/article/details/41960507
    drawBitmapMesh是个很屌毛的方法,为什么这样说呢?因为它可以对Bitmap做几乎任何改变,是的,你没听错,是任何,几乎无所不能,这个屌毛方法我曾一度怀疑谷歌那些逗比为何将它屈尊在Canvas下,因为它对Bitmap的处理实在在强大了。上一节我们在讲到Matrix的时候说过Matrix可以对我们的图像做多种变换,实际上drawBitmapMesh也可以,只不过需要一点计算,比如我们可以使用drawBitmapMesh来模拟错切skew的效果。
  • Canvas(Bitmap bitmap);含参数的构造。
    可以实现在一张Bitmap的基础上进行绘制。- canvas.setBitmap(Bitmap bitmap); 作用同含参数的构造.
    例如:
    1
    2
    3
    4
    5
    ivMain = (ImageView) findViewById(R.id.main_iv);
    Bitmap bitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
    Canvas canvas = new Canvas(bitmap);
    canvas.drawColor(Color.RED);
    ivMain.setImageBitmap(bitmap);
  • canvas.translate()和rotate()方法:
    此类方法是对画布进行平移旋转等操作。其实可以理解为对坐标轴的旋转平移等操作。
    -canvas.clipXXX()方法:对画布的一种剪切方式,剪切后只能在剪切的部分显示需要绘制的内容。当前画布被“裁剪”了,只有剪裁的部分能够出现我们绘制的内容,如果我们所绘制的东西在该区域外部,即便绘制了你也看不到
    1
    2
    3
    4
    5
    clipPath(Path path, Region.Op op)
    clipRect(Rect rect, Region.Op op)
    clipRect(RectF rect, Region.Op op)
    clipRect(float left, float top, float right, float bottom, Region.Op op)
    clipRegion(Region region, Region.Op op)

Canvas中有关裁剪的方法,你会发现有一大堆带有Region.Op参数的重载方法.要明白这些方法的Region.Op参数那么首先要了解Region为何物。Region的意思是“区域”,在Android里呢它同样表示的是一块封闭的区域。
详情请看 一下8中有关Region的简介
-canvas.drawPath(Path path, Paint paint) 绘制路径。
-canvas.drawTextOnPath (String text, Path path, float hOffset, float vOffset, Paint paint) ; 绘制文字在Path路径上。

6 Rect和RectF类

Rect和RectF是类似的,只不过RectF中涉及计算的时候数值类型均为float型,两者均表示一块规则矩形。重要的方法:

  • intersect(RectF rectF) / intersect (float left, float top, float right, float bottom) :此方法表示两个矩形相交的部分。取两个区域的相交区域作为最终区域。intersect方法的计算方式是相当有趣的,它不是单纯地计算相交而是去计算相交区域最近的左上端点和最近的右下端点
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class CanvasView extends View {  
private Rect mRect;

public CanvasView(Context context, AttributeSet attrs) {
super(context, attrs);
mRect = new Rect(0, 0, 500, 500);

mRect.intersect(250, 250, 750, 750);
}

@Override
protected void onDraw(Canvas canvas) {
canvas.drawColor(Color.BLUE);

canvas.clipRect(mRect);

canvas.drawColor(Color.RED);
}

/15-26-50.jpg)
注意:黄色线框为后期加上的辅助线非程序生成

  • union(RectF rect) / union(float left, float top, float right, float bottom) 方法:union方法与intersect相反,取的是相交区域最远的左上端点作为新区域的左上端点,而取最远的右下端点作为新区域的右下端点
  • mRect.union(250, 250, 750, 750);

运行后我们会看到如下结果:
/15-27-13.jpg)
是不是觉得不是我们想象中的那样单纯地两个区域相加。此方法是指的左上角的点和右下角的点的最远距离。

7 Path 类

此类表示一个路径

  • lineTo(float x, float y): 将路径连接至某个点坐标。(不适用moveTo方法时默认起点坐标为原点)。我们可以考虑多次调用lineTo方法来绘制更复杂的图形.
  • moveTo(float x, float y):将Path的起始点移动到某个特定的点。(Path默认起始点为原点)
  • close() 此方法含义师闭合曲线。
1
2
3
4
5
6
7
8
9
10
// 实例化路径  
mPath = new Path();
// 移动点至[300,300]
mPath.moveTo(100, 100);
// 连接路径到点
mPath.lineTo(300, 100);
mPath.lineTo(400, 200);
mPath.lineTo(200, 200);
// 闭合曲线
mPath.close();

/15-28-08.jpg)

  • XXXTo() 贝塞尔曲线相关方法这些方法帮助我们绘制各类直线、曲线
    – quadTo(float x1, float y1, float x2, float y2) 绘制二阶贝塞尔曲线。 quadTo的前两个参数为控制点的坐标,后两个参数为终点坐标, 当然起点坐标就是path的起点坐标
    – cubicTo(float x1, float y1, float x2, float y2, float x3, float y3) 绘制三阶贝塞尔曲线。与quadTo类似,前四个参数表示两个控制点,最后两个参数表示终点:
1
2
3
4
5
6
// 实例化路径  
mPath = new Path();
// 移动点至[100,100]
mPath.moveTo(100, 100);
// 连接路径到点
mPath.cubicTo(200, 200, 300, 0, 400, 100);

/15-28-37.jpg)

  • arcTo (RectF oval, float startAngle, float sweepAngle) 用来生成弧线的方法。说白了就是从圆或者椭圆上截取一部分而已。
1
2
3
4
5
6
7
// 实例化路径  
mPath = new Path();
// 移动点至[100,100]
mPath.moveTo(100, 100);
// 连接路径到点
RectF oval = new RectF(100, 100, 200, 200);
mPath.arcTo(oval, 0, 90);

/15-29-02.jpg)
运行后效果跟我们想想的有点不一样,这是因为使用Path生成的路径必定都是连贯的,虽然我们使用arcTo绘制的时一段狐,但是其最终都会与我们Path的起点链接起来。如果你不想要链接,那么path也提供了一种方法,而已设置是否强制链接起点。

  • arcTo (RectF oval, float startAngle, float sweepAngle, boolean forceMoveTo) 该方法只是多了一个布尔值,值为true时将会把弧的起点作为Path的起点

  • Path中除了上面介绍的几个XXXTo方法外还有一套rXXXTo方法:

    1
    2
    3
    4
    rCubicTo(float x1, float y1, float x2, float y2, float x3, float y3)  
    rLineTo(float dx, float dy)
    rMoveTo(float dx, float dy)
    rQuadTo(float dx1, float dy1, float dx2, float dy2)

这一系列rXXXTo方法其实跟上面的那些XXXTo差不多的,唯一的不同是rXXXTo方法的参考坐标是相对的而XXXTo方法的参考坐标始终是参照画布原点坐标。相对的指的是,终点的坐标是相对于起点的坐标的,例如rMoveTo(100,100) ; rLineTo(200,200) ,表示这个线段的长度是200而不是100.

  • addXXX()方法,Path的这一系列的add方法允许我们直接往Path中添加一些曲线。
    比如:addArc(RectF oval, float startAngle, float sweepAngle) 方法允许我们将一段弧形添加至Path,注意这里我用到了“添加”这个词汇,也就是说,通过addXXX方法添加到Path中的曲线是不会和上一次的曲线进行连接的。
    – 除了addArc(RectF oval, float startAngle, float sweepAngle)方法之外还有这一系列的add方法。
    1
    2
    3
    4
    addCircle(float x, float y, float radius, Path.Direction dir)  
    addOval(float left, float top, float right, float bottom, Path.Direction dir)
    addRect(float left, float top, float right, float bottom, Path.Direction dir)
    addRoundRect(float left, float top, float right, float bottom, float rx, float ry, Path.Direction dir)

这些方法和addArc有很明显的区别,就是多了一个Path.Direction参数Path.Direction只有两个常量值CCW和CW分别表示逆时针方向闭合和顺时针方向闭合

Path.Direction.CW); ```顺时针
1
2
3
4
![](自定义控件相关类汇总(一)/15-33-28.jpg)

``` mPath.addOval(oval, Path.Direction.CCW); ``` 逆时针
![](自定义控件相关类汇总(一)/15-34-39.jpg)

public class PathView extends View {
private Path mPath;// 路径对象
private Paint mPaint;// 路径画笔对象

public PathView(Context context, AttributeSet attrs) {  
    super(context, attrs);    
    /* 
     * 实例化画笔并设置属性 
     */  
    mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);  
    mPaint.setStyle(Paint.Style.STROKE);  
    mPaint.setColor(Color.CYAN);  
    mPaint.setStrokeWidth(5);    
    // 实例化路径  
    mPath = new Path();    
    // 移动点至[100,100]  
    mPath.moveTo(100, 100);    
    // 连接路径到点  
    mPath.lineTo(200, 200);    
    // 添加一条弧线到Path中  
    RectF oval = new RectF(100, 100, 300, 400);  
    mPath.addArc(oval, 0, 90);  
}    
@Override  
protected void onDraw(Canvas canvas) {  
    // 绘制路径  
    canvas.drawPath(mPath, mPaint);  
}  

}

1
2
3
4
5
6
7
8
9
10
11
12
![](自定义控件相关类汇总(一)/15-29-55.jpg)
### 8 Region
回顾Canvas中有关裁剪的方法,你会发现有一大堆带有Region.Op参数的重载方法:
`clipPath(Path path, Region.Op op)
clipRect(Rect rect, Region.Op op)
clipRect(RectF rect, Region.Op op)
clipRect(float left, float top, float right, float bottom, Region.Op op)
clipRegion(Region region, Region.Op op) `
要明白这些方法的Region.Op参数那么首先要了解Region为何物。Region的意思是“区域”,在Android里呢它同样表示的是一块封闭的区域,Region中的方法都非常的简单,我们重点来瞧瞧Region.Op,Op是Region的一个枚举类,里面呢有六个枚举常量:
![](自定义控件相关类汇总(一)/15-30-13.jpg)

那么Region.Op究竟有什么用呢?其实它就是个组合模式,我们曾学过一个叫图形混合模式的,而在本节开头我们也曾讲过Rect也有类似的组合方法,Region.Op灰常简单,如果你看过图形混合模式的话。这里我就给出一段测试代码,大家可以尝试去改变不同的组合模式看看效果

public class CanvasView extends View {
private Region mRegionA, mRegionB;// 区域A和区域B对象
private Paint mPaint;// 绘制边框的Paint

public CanvasView(Context context, AttributeSet attrs) {  
    super(context, attrs);    
    // 实例化画笔并设置属性  
    mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);  
    mPaint.setStyle(Paint.Style.STROKE);  
    mPaint.setColor(Color.WHITE);  
    mPaint.setStrokeWidth(2);    
    // 实例化区域A和区域B  
    mRegionA = new Region(100, 100, 300, 300);  
    mRegionB = new Region(200, 200, 400, 400);  
}    
@Override  
protected void onDraw(Canvas canvas) {  
    // 填充颜色  
    canvas.drawColor(Color.BLUE);    
    canvas.save();    
    // 裁剪区域A  
    canvas.clipRegion(mRegionA);    
    // 再通过组合方式裁剪区域B  
    canvas.clipRegion(mRegionB, Region.Op.DIFFERENCE);    
    // 填充颜色  
    canvas.drawColor(Color.RED);    
    canvas.restore();    
    // 绘制框框帮助我们观察  
    canvas.drawRect(100, 100, 300, 300, mPaint);  
    canvas.drawRect(200, 200, 400, 400, mPaint);  
}  

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
以下是各种组合模式的效果
DIFFERENCE
![](http://img.blog.csdn.net/20141216162450404?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvYWlnZXN0dWRpbw==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
最终区域为第一个区域与第二个区域不同的区域。
INTERSECT
![](http://img.blog.csdn.net/20141216162521994?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvYWlnZXN0dWRpbw==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
最终区域为第一个区域与第二个区域相交的区域。
REPLACE
![](http://img.blog.csdn.net/20141216162548498?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvYWlnZXN0dWRpbw==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
最终区域为第二个区域。
REVERSE_DIFFERENCE
![](http://img.blog.csdn.net/20141216162616531?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvYWlnZXN0dWRpbw==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
最终区域为第二个区域与第一个区域不同的区域。
UNION
![](http://img.blog.csdn.net/20141216162620343?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvYWlnZXN0dWRpbw==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
最终区域为第一个区域加第二个区域。
XOR
![](http://img.blog.csdn.net/20141216162642968?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvYWlnZXN0dWRpbw==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
最终区域为第一个区域加第二个区域并减去两者相交的区域。
Region.Op就是这样,它和我们之前讲到的图形混合模式几乎一模一样换汤不换药……我在做示例的时候仅仅是使用了一个Region,实际上Rect、Cricle、Ovel等封闭的曲线都可以使用Region.Op,介于篇幅,而且也不难以理解就不多说了。
`有些童鞋会问那么Region和Rect有什么区别呢?`
`首先最重要的一点,Region表示的是一个区域,而Rect表示的是一个矩形,这是最根本的区别之一,其次,Region有个很特别的地方是它不受Canvas的变换影响,Canvas的local不会直接影响到Region自身,`
什么意思呢?我们来看一个simple你就会明白:

public class CanvasView extends View {
private Region mRegion;// 区域对象
private Rect mRect;// 矩形对象
private Paint mPaint;// 绘制边框的Paint

public CanvasView(Context context, AttributeSet attrs) {  
    super(context, attrs);  

    // 实例化画笔并设置属性  
    mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);  
    mPaint.setStyle(Paint.Style.STROKE);  
    mPaint.setColor(Color.DKGRAY);  
    mPaint.setStrokeWidth(2);  

    // 实例化矩形对象  
    mRect = new Rect(0, 0, 200, 200);  

    // 实例化区域对象  
    mRegion = new Region(200, 200, 400, 400);  
}  

@Override  
protected void onDraw(Canvas canvas) {  
    canvas.save();  

    // 裁剪矩形  
    canvas.clipRect(mRect);  
    canvas.drawColor(Color.RED);  

    canvas.restore();  

    canvas.save();  

    // 裁剪区域  
    canvas.clipRegion(mRegion);  
    canvas.drawColor(Color.RED);  

    canvas.restore();  

    // 为画布绘制一个边框便于观察  
    canvas.drawRect(0, 0, canvas.getWidth(), canvas.getHeight(), mPaint);  
}  

}

1
2
3
大家看到,我在[0, 0, 200, 200]和[200, 200, 400, 400]的位置分别绘制了Rect和Region,它们两个所占大小是一样的:
![](http://img.blog.csdn.net/20141216162814452?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvYWlnZXN0dWRpbw==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
画布因为和屏幕一样大,so~~我们看不出描边的效果,这时,我们将Canvas缩放至75%大小,看看会发生什么:

@Override
protected void onDraw(Canvas canvas) {
// 缩放画布
canvas.scale(0.75F, 0.75F);

canvas.save();  

// 裁剪矩形  
canvas.clipRect(mRect);  
canvas.drawColor(Color.RED);  

canvas.restore();  

canvas.save();  

// 裁剪区域  
canvas.clipRegion(mRegion);  
canvas.drawColor(Color.RED);  

canvas.restore();  

// 为画布绘制一个边框便于观察  
canvas.drawRect(0, 0, canvas.getWidth(), canvas.getHeight(), mPaint);  

}
`
这时我们会看到,Rect随着Canvas的缩放一起缩放了,但是Region依旧泰山不动地淡定:

本文参考了以下blog
1,http://blog.csdn.net/aigestudio/article/details/41212583
2,http://blog.csdn.net/aigestudio/article/details/41316141
3,http://blog.csdn.net/aigestudio/article/details/41447349
4,http://blog.csdn.net/aigestudio/article/details/41960507