Android自定义View之基本API(二)

时间:2023-02-09 20:45:33

在上一篇博客中介绍了自定义View的几个常用类,在这一篇博客中接着介绍另外的一个常用类,Paint类:

Paint翻译为“画笔”,为绘图定义各种参数:颜色、线条样式、图案样式等。通常的绘图思路是先定义Paint对象,指定绘图参数,再通过Canvas对象进行图形绘制,绘图的结果因Paint的不同而不同。绘图的方法定义在Canvas类中,Paint类用于指定绘图的各种参数。

4.Paint类

Paint类用于定义绘图时的参数,主要包含颜色、文本、图形样式、位图模式、滤镜等几个方面。 通过控制这些参数,我们就可以控制Paint的样式,绘制不同风格的文本、图片等。

颜色是指绘图时使用的颜色,在 Android 中颜色可以指定透明度,使用 16 进制来表示颜色时,格式通常为#AARRGGBB,其中,AA 表示透明度、RR 表示红色、GG 表示绿色、BB 表示蓝色,Color类定义了颜色信息,内置了常用颜色的int型常量,比如Color.RED 红色,Color.BLUE 蓝色,同时在Color类中定义的一个静态方法parseColor(String colorString)将16进制装换为color类型,比如:

int color = Color.parseColor("#FF552E");//将16进制的FF552E转换成Android中直接使用的颜色值
① 创建一个Paint对象

// 创建一个画笔
public Paint()
// 创建一个画笔,并指定一个flags
public Paint(int flags)
// 从已有的画笔对象创建一个画笔
public Paint(Paint paint)

// 创建一个画笔
Paint paint = new Paint();
paint.setFlags(Paint.ANTI_ALIAS_FLAG);// 设置抗锯齿
// 下面这一行代码等价于上面两行代码
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);

② Paint类与颜色相关的方法

// 设置颜色
public native void setColor(intcolor);
// 设置透明度,a的范围取 0~255 之间的整数
public nativevoid setAlpha(int a);
// 指定透明度、 红、 绿、 蓝定义一种颜色
public void setARGB(inta,intr,intg,intb)
// 获取透明度
public native int getAlpha();
// 获取颜色
public native void getColor(intcolor);
③ Paint类与文本相关的方法

// 设置文本大小,单位是 px,这个和我们平时使用的字体单位sp不同,所以最好进行转换
public native void setTextSize(float textSize)
// 设置文本的对齐方式,可选值有Paint.Align.LEFT、Paint.Align.CENTER、Paint.Align.RIGHT等
public void setTextAlign(Paint.Align align)
// 设置文本的倾斜程度,skewx 取值于 -1~1 之间,正负表示倾斜的方向
public native void setTextSkewX(float skewx)
// 给文本添加下载线,underline 为 true 表示添加
public native void setUnderlineText(boolean underline)
// 设置文本的粗体样式,bold 为 true 表示粗体
public native void setFakeBoldText(boolean bold)
// 为文本添加删除线,strike 为 true 时表示添加
public native void setStrikeThruText(boolean strike)
④ Paint类与图形样式相关的方法

// 设置绘制的图形是空心样式还是实心样式,默认为实心样式
public void setStyle(Paint.Style style)
style的可选值有:
public static enum Style {
FILL
FILL_AND_STROKE,
STROKE
}
其中,FILL 表示实心样式,对于闭合图形来说,会用指定的颜色进行填充;STROKE表示空心样式,绘制时只有线条而无填充效果;FILL_AND_STROKE 表示同时使用实心样式和空心样式。

// 当绘图样式为STROKE时,该方法用于指定线条连接处的拐角样式,能使绘制的图形更加平滑
public void setStrokeJoin(Paint.Join join)
可选值如下:
public static enum Join {
BEVEL,
MITER,
ROUND
}
Join三种类型区别可以用下图表示:

Android自定义View之基本API(二)

// 该方法用于设置落笔时的样式,控制我们的画笔在离开画板时留下的最后一点图形,默认BUTT
public void setStrokeCap(Paint.Cap cap)
可选值如下:
public static enum Cap {
BUTT,
ROUND,
SQUARE
}
Cap三种类型区别可以用下图表示:

Android自定义View之基本API(二)

// 设置画笔粗细,参数为float类型,可以小于1
public native void setStrokeWidth(float width)
// 重置画笔
public void reset()
下面的代码是在canvas上面绘制一段文字和两个不同样式的矩形:

@Override
protected void onDraw(Canvas canvas) {
paint.setFlags(Paint.ANTI_ALIAS_FLAG);// 设置画笔去锯齿
paint.setColor(Color.parseColor("#FF0000")); // 设置画笔颜色
paint.setTextSize(50);// 设置字体大小
paint.setTextSkewX(0.2f);// 设置字体倾斜
paint.setFakeBoldText(true); // 设置字体加粗
paint.setUnderlineText(true);// 设置给文字添加下划线
canvas.drawText("Android自定义控件", 100, 100, paint);// 绘制文字

paint.reset(); // 重置画笔
paint.setFlags(Paint.ANTI_ALIAS_FLAG);// 设置画笔去锯齿
paint.setColor(Color.parseColor("#0000FF")); // 设置画笔颜色
paint.setStyle(Paint.Style.STROKE);// 设置内容不填充
paint.setStrokeWidth(30);// 设置边框宽度
paint.setStrokeJoin(Paint.Join.ROUND); // 设置Join
// paint.setStrokeCap(Paint.Cap.ROUND);// 设置Cap
canvas.drawRect(100, 150, 400, 350, paint);// 绘制一个矩形

paint.reset(); // 重置画笔
paint.setFlags(Paint.ANTI_ALIAS_FLAG);// 设置画笔去锯齿
paint.setColor(Color.parseColor("#00FF00")); // 设置画笔颜色
paint.setStyle(Paint.Style.FILL);// 设置内容填充
// paint.setStrokeCap(Paint.Cap.BUTT);// 设置Cap,样式为STROKE时才有效
canvas.drawRect(450,150,850,350,paint);// 绘制一个矩形
}
结果如图:

Android自定义View之基本API(二)

⑤ Paint类高级使用之阴影、渐变和位图

首先说明:在绘图中,有一个叫layer(层)的概念,默认情况下,我们的文字和图形绘制在主层(mainlayer)上,其实也可以将内容绘制在新建的layer上。而上阴影就是在main layer的下面添加了一个阴影层(shaderlayer),可以为阴影指定模糊度、偏移量和阴影颜色。

阴影:

Paint类的setShadowLayer()方法,可以设置阴影效果:
public void setShadowLayer(float radius, float dx, float dy, int shadowColor)
radius:阴影半径
dx:x方向阴影的偏移量
dy:y方向阴影的偏移量
shadowColor:阴影的颜色
阴影layer显示阴影时, shader layer有两种类:View.LAYER_TYPE_SOFTWARE、View.LAYER_TYPE_HARDWARE

layer的默认类型为LAYER_TYPE_HARDWARE,但阴影只能在View.LAYER_TYPE_SOFTWARE环境下工作,所以,我们如果需要显示阴影想过,就要调用View类的public void setLayerType(int layerType, Paint paint)方法为Paint对象指定层的类型:
setLayerType(View.LAYER_TYPE_SOFTWARE, paint);

我在一个View的onDraw()方法中编写了一下代码:

@Override
protected void onDraw(Canvas canvas) {
// 初始化画笔基本属性
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setColor(Color.parseColor("#f0f5f9"));
paint.setStyle(Paint.Style.FILL);
paint.setStrokeWidth(5);
paint.setTextSize(100);

this.setLayerType(LAYER_TYPE_SOFTWARE, paint);// 设置图层类型
paint.setShadowLayer(10, 5, 5, Color.RED);
canvas.drawText("Android", 50, 200, paint);
paint.setShadowLayer(5,3,3,Color.GREEN);
canvas.drawText("自定义组件",50,320,paint);

}
运行的到的结果如图所示:

Android自定义View之基本API(二)

需要注意的是,一旦定义了阴影层,接下来的所有绘制都会带阴影效果了,如果想取消阴影,请将setShadowLayer()方法的radius参数设置为0

渐变:

渐变(Gradient)是绘图过程中颜色或位图以特定规律进行变化,能增强物体的质感和审美情趣。生活中的渐变非常多,例如公路两边的电线杆、树木、建筑物的阳台、铁轨的枕木延伸到远方等等,很多的自然理象都充满了渐变的形式特点。Android同样对渐变进行了完善支持,通过渐变,可以绘制出更加逼真的效果。
Graphics2D渐变种类有:
线性渐变:LinearGradient
径向渐变:RadialGradient
扫描渐变:SweepGradient
位图渐变:BitmapShader
混合渐变:ComposeShader

定义渐变时,必须指定一个渐变区域,根据定义的渐变内容和渐变模式填满该区域。每一种渐变都被定义成了一个类,他们都继承自同一个父类——Shader。绘图时,调用Paint类的setShader(Shader shader)方法指定一种渐变类型,绘制出来的绘图填充区域都将使用指定的渐变颜色或位图进行填充。

线性渐变、径向渐变和扫描渐变属于颜色渐变,指定2种或2种以上的颜色,根据颜色过渡算法自动计算出中间的过渡颜色,从而形成渐变效果,对于开发人员来说,无需关注中间的渐变颜色。


同时,这三种渐变有三种渐变模式可以选择(A、B表示两种颜色):
ABAB型:A、B两种颜色重复变化,通过TileMode类的REPEAT常量来表示;
ABBA型:A、B两种颜色镜像变化,通过TileMode类的MIRROR常量来表示;
AABB型:A、B两种颜色只出现一次,通过TileMode类的CLAMP常量来表示。

Android自定义View之基本API(二)

线性渐变:

线性渐变(LinearGradient)根据指定的角度、颜色和模式使用渐变颜色填充绘图区域。我们必须定义两个点(x0,y0)和(x1,y1),渐变的方向与这两个点的连线垂直。

LinearGradient的构造方法如下:

publicLinearGradient(floatx0,floaty0,floatx1,floaty1,intcolor0,intcolor1,TileModetile):本方法用于两种颜色的渐变,各参数意义如下:
x0、y0:用于决定线性方向的第一个点的坐标(x0,y0);
x1、y1:用于决定线性方向的第二个点的坐标(x1,y1);
color0:第一种颜色;
color1:第二种颜色;
tile:渐变模式

publicLinearGradient(floatx0,floaty0,floatx1,floaty1,intcolors[],floatpositions[],TileMode tile),这是一个功能更加强大的构造方法,我们来看看该构造方法参数的作用:
x0、y0:起始点的坐标
x1、y1:终止点的坐标
colors:多种颜色
positions:颜色的位置(比例)
TileMode:渐变模式
以下代码都是以Shader.TileMode.CLAMP模式,并且都是2种渐变颜色,只是改变了渐变矩形的大小,能得到不同的效果
private void linerGradientTest(Canvas canvas) {        canvas.save();//保存画布        Rect rect = new Rect(50, 50, 200, 200); // 创建矩形        // 以Shader.TileMode.CLAMP模式创建线性渐变        LinearGradient lg = new LinearGradient(rect.left, rect.top, rect.right, rect.bottom, Color.RED, Color.BLUE, Shader.TileMode.CLAMP);        paint.setShader(lg);//设置渐变        canvas.drawRect(rect, paint); // 画矩形        Rect rect1 = new Rect(rect);// 根据rect来创建一个新的矩形        rect1.inset(70, 70);// 缩小渐变矩形        canvas.translate(rect.width() + 20, 0);// 将画布水平平移20像素        LinearGradient lg1 = new LinearGradient(rect1.left, rect1.top, rect1.right, rect1.bottom, Color.RED, Color.BLUE, Shader.TileMode.CLAMP);        paint.setShader(lg1);        canvas.drawRect(rect, paint);        Rect rect2 = new Rect(rect); // 根据rect来创建一个新的矩形        rect1.inset(-70, -70); // 放大渐变矩形        canvas.translate(rect.width() + 20, 0);        LinearGradient lg2 = new LinearGradient(rect2.left, rect2.top, rect2.right, rect2.bottom, Color.RED, Color.BLUE, Shader.TileMode.CLAMP);        paint.setShader(lg2);        canvas.drawRect(rect, paint);        canvas.restore();// 还原画布    }

Android自定义View之基本API(二)

下面的代码分别使用了两种渐变颜色和三种渐变颜色:

private void linerGradient(Canvas canvas) {
canvas.save();//保存画布
Rect rect = new Rect(30, 30, 450, 450);
// 使用2种渐变颜色
LinearGradient lg = new LinearGradient(rect.left, rect.top + rect.height() / 2, rect.right, rect.top + rect.height() / 2, Color.RED, Color.BLUE, Shader.TileMode.CLAMP);
paint.setShader(lg);
canvas.drawRect(rect, paint);

canvas.translate(rect.width() + 20, 0);
// 使用3种渐变颜色
LinearGradient lg1 = new LinearGradient(rect.left, rect.top + rect.height() / 2, rect.right, rect.top + rect.height() / 2, new int[]{Color.RED, Color.BLUE,Color.YELLOW},new float[]{0.3f,0.6f,0.8f}, Shader.TileMode.CLAMP);
paint.setShader(lg1);
canvas.drawRect(rect, paint);

canvas.restore();// 还原画布
}
Android自定义View之基本API(二)
径向渐变

径向渐变是以指定的点为中心,向四周以渐变颜色进行圆周扩散,和线性渐变一样,支持两种或多种颜色。
径向渐变的主要构造方法如下:

public RadialGradient(float x, float y, float radius, int color0, int color1, TileMode tile),该构造方法支持两种颜色,下面是参数的作用:
x、y:中心点坐标
radius:渐变半径
color0:起始颜色
color1:结束颜色
TileMode:渐变模式
public RadialGradient(float x, float y, float radius, int colors[], float positions[], TileMode tile),该构造方法支持3种或3种以上颜色的渐变,各参数的作用如下:
x、y:中心点坐标
radius:渐变半径
colors:多种颜色
positions:颜色的位置(比例)
TileMode:渐变模式

以下代码定义了径向渐变:

private void radiaGradientTest(Canvas canvas) {
canvas.save();
// 2种颜色圆形渐变
RadialGradient rg = new RadialGradient(200,200,180,Color.RED,Color.GREEN, Shader.TileMode.CLAMP);
paint.setShader(rg);
canvas.drawCircle(200, 200, 180, paint);

canvas.translate(420,0);
// 2种颜色矩形渐变
RadialGradient rg1 = new RadialGradient(200,200,180,Color.RED,Color.GREEN, Shader.TileMode.CLAMP);
paint.setShader(rg1);
canvas.drawRect(20, 0, 380, 360, paint);

canvas.translate(-420, 450);
// 3种颜色圆形渐变
RadialGradient rg2 = new RadialGradient(200,200,220,new int[]{Color.RED, Color.GREEN,Color.YELLOW},new float[]{0.3f,0.6f,0.8f}, Shader.TileMode.CLAMP);
paint.setShader(rg2);
canvas.drawCircle(200, 200, 180, paint);

canvas.translate(420,0);
// 3种颜色矩形渐变
RadialGradient rg3 = new RadialGradient(200,200,220,new int[]{Color.RED, Color.GREEN,Color.YELLOW},new float[]{0.3f,0.6f,0.8f}, Shader.TileMode.CLAMP);
paint.setShader(rg3);
canvas.drawRect(20,0,380,360,paint);
canvas.restore();
}
运行结果如图:

Android自定义View之基本API(二)

扫描渐变

SweepGradient类似于军事题材电影中的雷达扫描效果,固定圆心,将半径假想为有形并旋转一周而绘制的渐变颜色。
SweepGradient定义了两个主要的构造方法:

publicSweepGradient(floatcx,floatcy,intcolor0,intcolor1)
支持两种颜色的扫描渐变,参数的作用如下:
cx、cy:圆点坐标;
color0:起始颜色;
color1:结束颜色。
publicSweepGradient(floatcx,floatcy,intcolors[],floatpositions[])
支持多种颜色的扫描渐变,参数的作用如下:
cx、cy:圆点坐标;
colors:多种颜色;
positions:颜色的位置(比例)
以下代码定义了扫描渐变:

private void sweepGradientTest(Canvas canvas) {
canvas.save();
SweepGradient sg = new SweepGradient(300,300,new int[]{Color.GREEN,Color.YELLOW,Color.BLUE,Color.GREEN}, null);
paint.setShader(sg);
canvas.drawCircle(300, 300, 280, paint);

SweepGradient sg1 = new SweepGradient(300,300,new int[]{Color.GREEN,Color.YELLOW,Color.BLUE,Color.GREEN}, new float[]{0.2f,0.5f,0.8f,0.9f});
paint.setShader(sg1);
canvas.drawCircle(300, 300, 280, paint);
canvas.restore();
}
运行结果如图:

Android自定义View之基本API(二)

位图渐变

位图渐变其实就是在绘制的图形中将指定的位图作为背景,如果图形比位图小,则通过渐变模式进行平铺,TileMode.CLAMP模式不平铺,TileMode.REPEAT模式表示平铺,TileMode.MIRROR模式也表示平铺,但是交错的位图是彼此的镜像,方向相反。可以同时指定水平和垂直两个方向
的渐变模式。
BitmapShader只有一个构造方法:
public BitmapShader(Bitmap bitmap, TileMode tileX, TileMode tileY),参数如下:
bitmap:位图;
tileX:x方向的重复模式;
tileY:y方向的重复模式。

以下代码定义了一个水平和竖直方向都平铺的位图渐变:

private void bitmaGradientTest(Canvas canvas) {
Bitmap bmp = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher);
BitmapShader bs = new BitmapShader(bmp,Shader.TileMode.REPEAT, Shader.TileMode.MIRROR);
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setShader(bs);
canvas.drawRect(new Rect(0, 0,1000, 1000), paint);
}

运行结果如图:

Android自定义View之基本API(二)

混合渐变

混合渐变ComposeShader是将两种不同的渐变通过位图运算后得到的一种更加复杂的渐变。
ComposeShader有两个构造方法:

publicComposeShader(ShadershaderA,ShadershaderB,Xfermodemode)
publicComposeShader(ShadershaderA,ShadershaderB,Modemode)
shaderA和shaderB是两个渐变对象,mode为位图运算类型,16种运算模式如图(图片来源网络):

Android自定义View之基本API(二)

以下代码定义了位图渐变和扫描渐变相结合:

private void composeGradientTest(Canvas canvas) {
Bitmap bmp = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher);
BitmapShader bs = new BitmapShader(bmp,Shader.TileMode.REPEAT, Shader.TileMode.MIRROR);//定义位图渐变
// 定义扫描渐变
SweepGradient sg = new SweepGradient(500, 525, new int[]{Color.GREEN, Color.YELLOW, Color.BLUE, Color.GREEN}, null);
ComposeShader sc = new ComposeShader(bs,sg, PorterDuff.Mode.SRC_IN);
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setShader(sc);
canvas.drawRect(new Rect(0, 0,1000, 1000), paint);
}
运行结果如下图:

Android自定义View之基本API(二)

位图运算PorterDuffXfermode

位图运算为位图的功能繁衍ᨀ供了强大的技术基础,增强了位图的可塑性和延伸性,使很多看起来非常复杂的效果和功能都能轻易实现,比如圆形头像、不规则图片、橡皮擦、稀奇古怪的自定义进度条等等。

位图运算模式定义在PorterDuff类的内部枚举类型Mode中,对应了16个不同的枚举值

publicenumMode{
/**[0,0]*/
CLEAR(0),
/**[Sa,Sc]*/
SRC(1),
/**[Da,Dc]*/
DST(2),
/**[Sa+(1-Sa)*Da,Rc=Sc+(1-Sa)*Dc]*/
SRC_OVER(3),
/**[Sa+(1-Sa)*Da,Rc=Dc+(1-Da)*Sc]*/
DST_OVER(4),
/**[Sa*Da,Sc*Da]*/
SRC_IN(5),
/**[Sa*Da,Sa*Dc]*/
DST_IN(6),
/**[Sa*(1-Da),Sc*(1-Da)]*/
SRC_OUT(7),
/**[Da*(1-Sa),Dc*(1-Sa)]*/
DST_OUT(8),
/**[Da,Sc*Da+(1-Sa)*Dc]*/
SRC_ATOP(9),
/**[Sa,Sa*Dc+Sc*(1-Da)]*/
DST_ATOP(10),
/**[Sa+Da-2*Sa*Da,Sc*(1-Da)+(1-Sa)*Dc]*/
XOR(11),
/**[Sa+Da-Sa*Da,
Sc*(1-Da)+Dc*(1-Sa)+min(Sc,Dc)]*/
DARKEN(16),
/**[Sa+Da-Sa*Da,
Sc*(1-Da)+Dc*(1-Sa)+max(Sc,Dc)]*/
LIGHTEN(17),
/**[Sa*Da,Sc*Dc]*/
MULTIPLY(13),
/**[Sa+Da-Sa*Da,Sc+Dc-Sc*Dc]*/
SCREEN(14),
/**Saturate(S+D)*/
ADD(12),
OVERLAY(15);

Mode(intnativeInt){
this.nativeInt=nativeInt;
}

/**
*@hide
*/
publicfinalintnativeInt;
}
具体使用哪一种模式才能达到效果,可以参考下图(图片来源网络):

Android自定义View之基本API(二)
以下代码通过位图运算简单实现了圆形图片的制作:

public class CircleImage extends View {
private Paint paint;
private Bitmap bitmap;
private int width,height,centerX,centerY,radius;

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

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

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

private void init() {
paint = new Paint(Paint.ANTI_ALIAS_FLAG);

bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.a); // 从资源文件中获取图片并变为Bitmap对象
// 获取图片的宽和高,并计算圆心位置
width = bitmap.getWidth();
height = bitmap.getHeight();
centerX = width / 2;
centerY = height / 2;
radius = Math.min(width,height) / 2;
}

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
/**
* Canvas类中的saveLayer()方法,表示出创建一个图层入栈并返回该图层所在栈中的位置,
* 而restoreToCount()方法表示将指定栈中位置的图层绘制到canvas上面显示。
*/
int saveLayer = canvas.saveLayer(0, 0, width, height, paint, Canvas.ALL_SAVE_FLAG);
// 画一个圆,也就是需要显示出来的圆
canvas.drawCircle(centerX,centerY,radius,paint);
//设置位图模式为PorterDuff.Mode.SRC_IN
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
canvas.drawBitmap(bitmap, 0, 0, paint);
// 清空位图模式
paint.setXfermode(null);
// 给圆形图片画一个3像素的边框
paint.setStrokeWidth(3);
paint.setStyle(Paint.Style.STROKE);
paint.setColor(Color.parseColor("#65f692"));
canvas.drawCircle(centerX,centerY,radius,paint);

canvas.restoreToCount(saveLayer);
}
}
运行结果如图所示:

Android自定义View之基本API(二)