安卓自定义控件(一)Canvas、Paint、Shader、Xfermode

关于自定义控件,之前就写过一篇自定义控件,上图下字的Button,图片任意指定大小,但是使用效果还是让人感觉不幸福,这次索性彻彻底底地对自定义控件做一次彻彻底底的总结。

我会花4篇博客来介绍自定义控件,由潜入深慢慢学:
工具类:ViewHelper(View处理常用方法封装)
安卓自定义控件(一)Canvas、Paint、Shader、Xfermode
安卓自定义控件(二)BitmapShader、ShapeDrawable、Shape
安卓自定义控件(三)实现自定义View
4、onLayout(实现自定义ViewGroup)。
这一篇博客就先介绍一下onDraw方法。

概述

自定义控件有三个比较重要的方法

  • onDraw 就是在绘制View的样式。
  • onMeasure 算出自己需要占用多大的面积。
  • onLayout 按照我们想要的规则自定义子View排列。

因为View设计出来肯定是给别人看的,首先我们考虑绘制View,而绘制View就必须先学会Canvas(画布)和Paint(画笔);然后就是设置View的大小,让我们的控件更加强大,更加通用,就像Button,它可以指定大小,也可以不指定;如果你设计的是ViewGroup,那可能还得考虑每一个子View的位置,那就得考虑重写onLayout方法。

最简单的自定义View

源码:

写一个最简单的自定义控件,鼓励一下自己吧。

/**
* 矩形
* Created by ChenSS on 2016/11/24.
*/
public class RectView extends View{
public RectView(Context context) {
super(context);
} @Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Paint paint = new Paint();
paint.setColor(Color.RED);
canvas.drawRect(60, 60, 180, 180, paint);
//或者
//canvas.drawRect(new Rect(200,200,300,300),paint);
}
}

在Activity使用我们的自定义View:

public class RectActivity extends AppCompatActivity {

    @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(new RectView(this));
}
}

安卓自定义控件(一)Canvas、Paint、Shader、Xfermode
这样就写完了,是不是感觉自己棒棒哒?我们实现了一个最简单的自定义View,不过它非常弱小,只是一个矩形,甚至连长宽都被我们写死了。

小结:

这个自定义控件虽然有点Low,但是我们也有一些收获:

  1. onDraw的参数是一个Canvas(画布),画布决定了绘制的是一个什么样的图形;
  2. 画布的使用需要有Paint(画笔)的配合,画笔决定了绘制的颜色;
  3. 此外,还会涉及到Color(颜色),Bitmap(位图),Shader(着色),Shape(形状)等等。

以上是一个粗略总结,查看API会发现,上面提到的类都属于 android.graphics这个包,接下去就去瞅一瞅他们到底有什么本事。

认识graphics中常用的类

1.Color(颜色)类

  Android系统中颜色的常用表示方法有以下4种:
  (1)int color = Color.BLUE;
  (2)int color = Color.argb(150,200,0,100);
  (3)layout.setBackgroundColor(R.color.white),引用颜色资源;
  (4)Color.parseColor(“#365663”)。

  在实际应用中,我们常用的颜色有以下几种,其颜色常量及其表示的颜色如下所示:
  

  • Color.BLACK 黑色
  • Color.GREEN 绿色
  • Color.BLUE 蓝色
  • Color.LTGRAY浅灰色
  • Color.CYAN 青绿色
  • Color.MAGENTA 红紫色
  • Color.DKGRAY 灰黑色
  • Color.RED 红色
  • Color.YELLOW 黄色
  • Color.TRANSPARENT 透明
  • Color.GRAY 灰色
  • Color.WHITE 白色

2.Paint(画笔)类

  要绘制图形,首先得调整画笔,按照自己的开发需要,设置画笔的相关属性。Pain类的常用属性设置方法如下:

  • setAntiAlias(); //设置画笔的锯齿效果
  • setColor(); //设置画笔的颜色
  • setARGB(); //设置画笔的A、R、G、B值
  • setAlpha(); //设置画笔的Alpha值
  • setStyle(); //设置画笔的风格(空心或实心)
  • setStrokeWidth(); //设置空心边框的宽度
  • getColor(); //获取画笔的颜色
  • setTextScaleX(float scaleX) // 设置文本缩放倍数,1.0f为原始
  • setTextSize(float textSize) // 设置字体大小
  • setUnderlineText(booleanunderlineText) // 设置下划线

3.Canvas(画布)类

  画笔属性设置好之后,还需要将图像绘制到画布上。Canvas绘制常用图形的常用方法如下:  

  • 绘制直线:canvas.drawLine(float startX, float startY, float stopX, float stopY, Paint paint);
  • 绘制矩形:canvas.drawRect(float left, float top, float right, float bottom, Paint paint);
  • 绘制圆形:canvas.drawCircle(float cx, float cy, float radius, Paint paint);
  • 绘制字符:canvas.drawText(String text, float x, float y, Paint paint);
  • 绘制图形:canvas.drawBitmap(Bitmap bitmap, float left, float top, Paint paint);
  • 绘制扇形(在一个矩形区域的两个角之间绘制一个弧。):drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter, Paint paint)
  • 绘制圆角矩形:drawRoundRect(RectF rect, float rx, float ry, Paint paint)
  • 绘制椭圆:drawOval(float left, float top, float right, float bottom, Paint paint)

补充:绘制任意多边形

其实画笔还有其它功能,这里就不再展开,这里再补充一个绘制多边形的案例。

/**
* 绘制任意多边形(简单地绘制了一个三角形,我比较懒)
* Created by ChenSS on 2016/11/24.
*/
public class RectView extends View{
public RectView(Context context) {
super(context);
} @Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Paint paint = new Paint();
//设置空心
paint.setStyle(Paint.Style.STROKE);
Path path1=new Path();
path1.moveTo(200, 800);
path1.lineTo(800, 800);
path1.lineTo(800, 200);
path1.close();//封闭
canvas.drawPath(path1, paint);
}
}

安卓自定义控件(一)Canvas、Paint、Shader、Xfermode

Shader(着色器、渲染)

绘制完图形,接下来就要考虑颜色的问题了,虽然Color很强大,但是也并不能满足我们全部的问题,就比如:颜色渐变,这时候就要依靠Shader了,Shader直译是着色器,我就称呼它着色器吧。

Shader它主要实现颜色的渲染效果,此外还提供了颜色渐变、图片拉伸、平铺的功能,它包含以下5个子类:BitmapShader, ComposeShader, LinearGradient, RadialGradient, SweepGradient。

LinearGradient线性渲染

Create a shader that draws a linear gradient along a line.
创建一个着色器,颜色沿一条直线进行渐变。

        //创建线性渲染对象
int mColorLinear[] = {Color.RED, Color.GREEN, Color.BLUE, Color.WHITE};
Shader mShader = new LinearGradient(0, 0, 100, 100, mColorLinear, null, Shader.TileMode.REPEAT);

构造函数:

LinearGradient(float x0, float y0, float x1, float y1, int[] colors, float[] positions, Shader.TileMode tile)

  • float x0: 渐变起始点x坐标
  • float y0:渐变起始点y坐标
  • float x1:渐变结束点x坐标
  • float y1:渐变结束点y坐标
  • int[] colors:颜色 的int 数组
  • float[] positions: 相对位置的颜色数组,可为null, 若为null,可为null,颜色沿渐变线均匀分布
  • Shader.TileMode tile: 渲染器平铺模式

LinearGradient(float x0, float y0, float x1, float y1, int color0, int color1, Shader.TileMode tile)

  • float x0: 渐变起始点x坐标
  • float y0:渐变起始点y坐标
  • float x1:渐变结束点x坐标
  • float y1:渐变结束点y坐标
  • int color0: 起始渐变色
  • int color1: 结束渐变色
  • Shader.TileMode tile: 渲染器平铺模式

SweepGradient梯度渲染

A subclass of Shader that draws a sweep gradient around a center point.
创建一个着色器,颜色沿着某个点的旋转方向进行渐变。

        //创建线性渲染对象
int mColorLinear[] = {Color.RED, Color.GREEN, Color.BLUE, Color.WHITE};
mSweepGradient = new SweepGradient(240, 360,mColorLinear , null);

构造函数:

SweepGradient(float cx, float cy, int[] colors, float[] positions)

  • cx 渲染中心点x 坐标
  • cy 渲染中心y 点坐标
  • colors 围绕中心渲染的颜色数组,至少要有两种颜色值
  • positions 相对位置的颜色数组,可为null, 若为null,可为null,颜色沿渐变线均匀分布

SweepGradient(float cx, float cy, int color0, int color1)

  • cx 渲染中心点x 坐标
  • cy 渲染中心点y 坐标
  • color0 起始渲染颜色
  • color1 结束渲染颜色

RadialGradient环形渲染

Create a shader that draws a radial gradient given the center and radius.
创建一个着色器,颜色沿着半径方向进行渐变。

        //颜色渐变效果为一圈一圈的圆环
int mColorRadial[] = {Color.GREEN, Color.RED, Color.BLUE, Color.WHITE};
Shader mShader = new RadialGradient(350, 325, 75, mColorRadial, null, Shader.TileMode.REPEAT);

构造函数:

RadialGradient(float centerX, float centerY, float radius, int[] colors, float[] stops, Shader.TileMode tileMode)

  • float x: 圆心X坐标
  • float y: 圆心Y坐标
  • float radius: 半径
  • int[] colors: 渲染颜色数组
  • floate[] positions: 相对位置数组,可为null, 若为null,可为null,颜色沿渐变线均匀分布
  • Shader.TileMode tile:渲染器平铺模式

RadialGradient(float centerX, float centerY, float radius, int centerColor, int edgeColor, Shader.TileMode tileMode)

  • float x: 圆心X坐标
  • float y: 圆心Y坐标
  • float radius: 半径
  • int color0: 圆心颜色
  • int color1: 圆边缘颜色
  • Shader.TileMode tile:渲染器平铺模式

ComposeShader组合渲染

看它的构造方法,显然这个类的作用是Shader组合使用的方法,不过后面两个参数很陌生,后面我们再做介绍
ComposeShader(Shader shaderA, Shader shaderB, Xfermode mode)
ComposeShader(Shader shaderA, Shader shaderB, PorterDuff.Mode mode)

BitmapShader位图渲染

可以设置图片的CLAMP(拉伸)、REPEAT(重复)、MIRROR( 镜像),关于Shader.TileMode后面也会具体介绍
BitmapShader(Bitmap bitmap, Shader.TileMode tileX, Shader.TileMode tileY)

在自定义控件中使用着色器

/**
* 颜色渲染
* Created by ChenSS on 2016/11/24.
*/
public class RectView extends View {
public RectView(Context context) {
super(context);
} @Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//创建线性渲染对象
int mColorLinear[] = {Color.RED, Color.GREEN, Color.BLUE, Color.WHITE};
Shader mShader = new LinearGradient(0, 0, 100, 100, mColorLinear, null, Shader.TileMode.REPEAT);
Paint paint = new Paint();
paint.setShader(mShader);
paint.setColor(Color.RED);
canvas.drawRect(60, 60, 800, 800, paint);
}
}

安卓自定义控件(一)Canvas、Paint、Shader、Xfermode

Shader.TileMode(平铺模式)

emun Shader.TileMode这是一个枚举类,决定了图形的平铺方式,就像我们电脑上更换桌面图片,可以选择拉伸、平铺等等。

3种模式:
static final Shader.TileMode CLAMP: 边缘拉伸。
replicate the edge color if the shader draws outside of its original bounds

static final Shader.TileMode MIRROR:横向不断翻转重复,纵向不断翻转重复,不断对称变化铺满。
repeat the shader’s image horizontally and vertically, alternating mirror images so that adjacent images always seam

Static final Shader.TillMode REPETA:在水平方向和垂直方向重复摆放,两个相邻图像间有缝隙缝隙。
repeat the shader’s image horizontally and vertically

具体的效果,这里不好展开说明,具体案例我放到了下一篇博客。自定义控件(二)

Xfermode(遮罩图层)

前面介绍了ComposeShader组合渲染,不过只列出了两个构造方法,现在开始介绍这几个陌生的家伙:PorterDuff、Xfermode、PorterDuff.Mode。

查看API你会发现Xfermode有3个子类,不过,有两个类已经过时了,而且,在Android Studio上也只能使用其中一个:PorterDuffXfermode,我就只介绍PorterDuffXfermode的使用。

Xfermode能做什么?从下面的效果图,我们可以看出,Xfermode大概就是做一些运算,给他两张图片,然后做出一个组合之后的效果。

效果图

安卓自定义控件(一)Canvas、Paint、Shader、Xfermode

API截图:

安卓自定义控件(一)Canvas、Paint、Shader、Xfermode

常量介绍:

1.PorterDuff.Mode.CLEAR
所绘制不会提交到画布上。
2.PorterDuff.Mode.SRC
显示上层绘制图片
3.PorterDuff.Mode.DST
显示下层绘制图片
4.PorterDuff.Mode.SRC_OVER
正常绘制显示,上下层绘制叠盖。
5.PorterDuff.Mode.DST_OVER
上下层都显示。下层居上显示。
6.PorterDuff.Mode.SRC_IN
取两层绘制交集。显示上层。
7.PorterDuff.Mode.DST_IN
取两层绘制交集。显示下层。
8.PorterDuff.Mode.SRC_OUT
取上层绘制非交集部分。
9.PorterDuff.Mode.DST_OUT
取下层绘制非交集部分。
10.PorterDuff.Mode.SRC_ATOP
取下层非交集部分与上层交集部分
11.PorterDuff.Mode.DST_ATOP
取上层非交集部分与下层交集部分
12.PorterDuff.Mode.XOR
异或:去除两图层交集部分
13.PorterDuff.Mode.DARKEN
取两图层全部区域,交集部分颜色加深
14.PorterDuff.Mode.LIGHTEN
取两图层全部,点亮交集部分颜色
15.PorterDuff.Mode.MULTIPLY
取两图层交集部分叠加后颜色
16.PorterDuff.Mode.SCREEN
取两图层全部区域,交集部分变为透明色
17.PorterDuff.Mode.ADD Saturate(S + D)
18.PorterDuff.Mode.OVERLAY

Xfermode测试Demo

/**
* Xfermode的使用
* Created by ChenSS on 2016/11/24.
*/
public class RectView extends View {
private int screenW, screenH;
private int width = 200;
private int height = 200;
private Paint mPaint;
private Bitmap srcBitmap, dstBitmap;
private PorterDuffXfermode xfermode; public RectView(Context context) {
this(context, null);
} public RectView(Context context, AttributeSet attrs) {
super(context, attrs);
//ViewHelper是我写的工具类,顶部已经给出链接
screenW = ViewHelper.getScreenW(context);
screenH = ViewHelper.getScreenH(context);
srcBitmap = makeSrc(width, height);
dstBitmap = makeDst(width, height);
//修改Mode常量,查看不同的效果
xfermode = new PorterDuffXfermode(PorterDuff.Mode.DST_IN);
mPaint = new Paint();
mPaint.setFilterBitmap(false);
mPaint.setStyle(Paint.Style.FILL);
} @Override
protected void onDraw(Canvas canvas) {
//左边的图片
canvas.drawBitmap(srcBitmap, (screenW / 3 - width) / 2, (screenH / 2 - height) / 2, mPaint);
//中间的图片
canvas.drawBitmap(dstBitmap, (screenW / 3 - width) / 2 + screenW / 3, (screenH / 2 - height) / 2, mPaint); //创建一个图层,关于图层的使用,结尾附上其他人的博客链接
int sc = canvas.saveLayer(0, 0, screenW, screenH, null, Canvas.MATRIX_SAVE_FLAG |
Canvas.CLIP_SAVE_FLAG |
Canvas.HAS_ALPHA_LAYER_SAVE_FLAG |
Canvas.FULL_COLOR_LAYER_SAVE_FLAG |
Canvas.CLIP_TO_LAYER_SAVE_FLAG); //合并两个图片
canvas.drawBitmap(dstBitmap, (screenW / 3 - width) / 2 + screenW / 3 * 2, (screenH / 2 - height) / 2, mPaint);
//设置Paint的Xfermode
mPaint.setXfermode(xfermode);
canvas.drawBitmap(srcBitmap, (screenW / 3 - width) / 2 + screenW / 3 * 2, (screenH / 2 - height) / 2, mPaint);
mPaint.setXfermode(null); // 还原画布
canvas.restoreToCount(sc);
} /**
* 绘制一个圆形的Bitmap
*/
private Bitmap makeDst(int w, int h) {
Bitmap bm = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
Canvas c = new Canvas(bm);
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setColor(Color.RED);
c.drawOval(new RectF(0, 0, w * 3 / 4, h * 3 / 4), paint);
return bm;
} /**
* 绘制一个矩形的Bitmap
*/
private Bitmap makeSrc(int w, int h) {
Bitmap bm = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
Canvas c = new Canvas(bm);
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setColor(Color.GREEN);
c.drawRect(w / 3, h / 3, w * 19 / 20, h * 19 / 20, paint);
return bm;
}
}

安卓自定义控件(一)Canvas、Paint、Shader、Xfermode

代码中,我将PorterDuff.Mode设置为DST_IN,他是取第二个图层(DST)的交集部分,大家可以修改Mode值,查看每个常量的效果。

ComposeShader组合渲染Demo

ComposeShader的放到这里来介绍,因为它的构造函数我们还不理解,了解了PorterDuff.Mode每一个常量的作用之后,ComposeShader的功能也比较好理解一些。
它的作用就是根据PorterDuff.Mode模式,组合使用两个Shader。

/**
* ComposeShader对象的使用
* Created by ChenSS on 2016/11/24.
*/
public class RectView extends View {
private LinearGradient mLinearGradient;
private BitmapShader mBitmapShader;
private ComposeShader mComposeShader;
private Paint mPaint;
private Bitmap mBitmap;
private int mWidth, mHeight; public RectView(Context context) {
super(context);
//得到图像
mBitmap = ViewHelper.findBitmapById(context, R.mipmap.view_robot);
mWidth = mBitmap.getWidth();
mHeight = mBitmap.getHeight(); //创建LinearGradient对象
int mColorLinear[] = {Color.WHITE, Color.BLUE, Color.WHITE};
mLinearGradient = new LinearGradient(0, 0, mWidth, mHeight, mColorLinear, null, Shader.TileMode.REPEAT); // 创建BitmapShader对象
mBitmapShader = new BitmapShader(mBitmap, Shader.TileMode.MIRROR, Shader.TileMode.MIRROR); // 混合渲染 将两个效果叠加,Mode取值DST_ATOP
mComposeShader = new ComposeShader(mBitmapShader, mLinearGradient, PorterDuff.Mode.DST_ATOP); mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setColor(Color.GRAY);
} @Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 绘制混合渲染效果
mPaint.setShader(mComposeShader);
canvas.drawRect(0, 0, mWidth, mHeight, mPaint);
}
}

安卓自定义控件(一)Canvas、Paint、Shader、Xfermode

Xfermode实战的初步设想:

我们先画一个圆,然后再给他一张Bitmap,然后通过Xfermode,把Bitmap圆形的部分显示出来,实现一个圆形的ImageView。

安卓自定义控件(一)就到此结束,简单地介绍了一下图形绘制、颜色的使用,下一篇进一步介绍BitmapShader位图渲染和Xfermode的实际使用,然后再想办法做出各种形状的图片。

Canvas图层也是十分重要的,这里就不具体展开,这里给出其他人博客的链接。
android canvas layer (图层)详解与进阶

上一篇:在Winform界面中实现对多文档窗体的参数传值


下一篇:Chapter 1(数据结构绪论)