编写自定义View必然会在
onDraw(Canvas canvas)
方法里面实现,Canvas是图层,用来显示图像,而图像的形成是由一个画笔勾勒出来的,这里所说的画笔就是Paint
。
(1)构造方法和flag标志
//创建默认设置的画笔
public Paint()
//创建指定标记的画笔,类似于给画笔取了个名字
public Paint(int flags)
//创建和现有画笔属性相同的画笔
public Paint(Paint paint)
其中第二个构造方法的参数flags代表Paint标志,下面列出Paint的标志(这个一般只要了解就行了,开发中Paint标志用到的很少,大部分)
//抗锯齿标志,对应的方法是mPaint.setAntiAlias(true),一些图画出来之后可以很明显的看出锯齿的存在,加上这句话可以基本消除锯齿
public static final int ANTI_ALIAS_FLAG = 1;
//使位图过滤的位掩码标志(对位图进行滤波),对应的方法是mPaint.setFilterBitmap(true),可以消除bitmap的滤波
public static final int FILTER_BITMAP_FLAG = 2;
//使位图进行有利的抖动的位掩码标志(抖动处理),对应的方法是mPaint.setDither(true),可以让图像看起来更加柔和
public static final int DITHER_FLAG = 4;
//给文字添加下划线,对应的方法是mPaint.setUnderlineText(true)
public static final int UNDERLINE_TEXT_FLAG = 8;
//给文字添加删除线,对应的方法是mPaint.setStrikeThruText(true)
public static final int STRIKE_THRU_TEXT_FLAG = 16;
//文字加粗,对应的方法是mPaint1.setFakeBoldText(true)
public static final int FAKE_BOLD_TEXT_FLAG = 32;
//使文本平滑线性扩展的油漆标志
public static final int LINEAR_TEXT_FLAG = 64;
//使文本的亚像素定位的绘图标志
public static final int SUBPIXEL_TEXT_FLAG = 128;
//遗弃的画笔标志,不用管
public static final int DEV_KERN_TEXT_FLAG = 256;
//绘制文本时允许使用位图字体的绘图标志
public static final int EMBEDDED_BITMAP_TEXT_FLAG = 1024;
//画笔隐藏模式关闭
public static final int HINTING_OFF = 0;
//画笔隐藏模式打开
public static final int HINTING_ON = 1;
(2)设置画笔的样式
setStyle(Paint.Style style)
设置画笔样式,取值有
Paint.Style.FILL :填充内部
Paint.Style.FILL_AND_STROKE :填充内部和描边
Paint.Style.STROKE :仅描边、
(3)设置画笔的宽度
setStrokeWidth(float width)
(4)设置画笔是否抗锯齿
setAntiAlias(boolean aa)
(5)设置线冒样式
setStrokeCap(Paint.Cap cap)
设置线冒样式,取值有
Cap.ROUND(圆形线冒)
Cap.SQUARE(方形线冒)
Paint.Cap.BUTT(无线冒)
注意:冒多出来的那块区域就是线帽!就相当于给原来的直线加上一个帽子一样,所以叫线帽
如图所示:
第一个是无线帽,第二个是圆形线帽,第三个是方形线帽。
(6)设置线段连接处样式
setStrokeJoin(Paint.Join join)
如果不设置setStrokeJoin,那么线段的连接处默认是尖角
只有当角度太小时,Android为了防止尖角的延长线过长,自己做了处理,尖角变成了平角,如图:
下面我来说明一下setStrokeJoin的三种样式:
Join.MITER(结合处为尖角)
注意:
虽然设置了拐角处为尖角,但是如果角度太小,Android依然会将尖角变成平角。
Join.Round(结合处为圆弧)
Join.BEVEL(结合处为平角)
(7)设置 MITER 型拐角的延长线的最大值
这个方法是对于 setStrokeJoin() 的一个补充,它用于设置 MITER 型拐角的延长线的最大值。(如果没有设置setStrokeJoin,则拐角默认是尖角)
setStrokeMiter(float miter)
miter的默认值是4。我们来看下图:
miter >=b / a;
也就是说,当 b/a<=miter时,依然显示尖角,如果b/a > miter,尖角就会变成平角,这时a和b也随之变化。
如果一但从尖角变成平角,那么 miter = b / a。
当我们设置miter时,比如:从默认值4改成2
mPaint.setStrokeMiter(2);
那么miter =2, b / a的最大值就是2,如果 b / a超过2,那么尖角就会变成平角。
(8)清空画笔复位
void reset()
重置 Paint 的所有属性为默认值。相当于重新 new 一个。
(9)设置一个外来Paint画笔
void set(Paint src)
(10)获取与设置alpha值、颜色、ARGB
setARGB(int a, int r, int g, int b)
getAlpha()
setAlpha(int a)
getColor()
setColor(int color)
(11)获取与设置是否使用抗锯齿功能
isAntiAlias()
setAntiAlias(boolean aa)
会消耗较大资源,绘制图形速度会变慢,一般会开启。设置后会平滑一些
final boolean isDither()
(12)获取与设定是否使用图像抖动处理
setDither(boolean dither)
会使绘制出来的图片颜色更加平滑和饱满、图像更加清晰。
(13)setPathEffect(PathEffect effect)
设置绘制路径的效果
基本代码如下:
setLayerType(View.LAYER_TYPE_SOFTWARE, null);//关闭硬件加速
mPaint.setColor(Color.BLACK);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(5);
canvas.drawLine(0, 200, canvas.getWidth(), 200, mPaint);
canvas.drawLine(200, 0, 200, canvas.getHeight(), mPaint);
canvas.translate(200, 200);//将画布移动到(100,100)位置
mPaint.setPathEffect(new PathEffect());
mPaint.setColor(Color.BLUE);
Path path = new Path();
path.moveTo(-180, 50);
path.lineTo(-150, -50);
path.lineTo(-120, -10);
path.lineTo(-100, -70);
path.lineTo(-80, -40);
path.lineTo(-40, 10);
path.lineTo(-20, -10);
path.lineTo(0, 50);
path.lineTo(20, -20);
path.lineTo(50, 70);
path.lineTo(100, 10);
path.lineTo(120, -10);
path.lineTo(150, 60);
path.lineTo(200, -70);
path.lineTo(220, -10);
path.lineTo(250, 0);
path.lineTo(280, -70);
path.lineTo(300, 70);
path.lineTo(330, 10);
path.lineTo(360, -100);
path.lineTo(380, 10);
path.lineTo(400, -70);
canvas.drawPath(path, mPaint);
mPaint.reset();
此时Path是没有任何效果的,演示图如下
下面分别介绍Path效果
-
CornerPathEffect——圆形拐角效果
mPaint.setPathEffect(new CornerPathEffect(50));
利用半径R=50的圆来代替原来两条直线间的夹角。
效果如下:
- DashPathEffect——虚线效果
//画同一条线段,偏移值为phase
mPaint.setPathEffect(new DashPathEffect(new float[]{10,5,6,15},phase));
intervals[]:表示组成虚线的各个线段的长度;整条虚线就是由intervals[]中这些基本线段循环组成的。比如,我们定义new float[] {20,10};那这个虚线段就是由两段线段组成的,第一个可见的线段长为20,每二个线段不可见,长度为10;
phase:开始绘制的偏移值。
效果图如下
如果在onDraw
处添加以下两句代码会产生动画效果
phase ++;
invalidate();
- PathDashPathEffect——虚线效果
和DashPathEffect
类似,也是虚线效果,不同的是PathDashPathEffec
t可以让我们自己定义路径虚线的样式。
PathDashPathEffect(Path shape, float advance, float phase, Style style)
shape:虚线点的填充图形
advance:shape和shape之间的间隔
phase:开始绘制的偏移量,结合invalidate()
同样有动画效果
style:虚线连接处的样式,现有三种样式
PathDashPathEffect.Style.MORPH:
图形会以发生拉伸或压缩等变形的情况与下一段相连接
PathDashPathEffect.Style.ROTATE:
线段连接处的图形转换以旋转到与下一段移动方向相一致的角度进行连接
PathDashPathEffect.Style.TRANSLATE:
图形会以位置平移的方式与下一段相连接
DashPathEffect
可以设置虚线不同线段的长度,但是PathDashPathEffect
不行,PathDashPathEffect
的优势在于可以设置虚线的图形。
Path shape = new Path();
RectF rectF = new RectF();
rectF.left = 0;
rectF.top = 0;
rectF.right = 10;
rectF.bottom = 10;
shape.addCircle(0, 0, 5, Path.Direction.CW);
mPaint.setPathEffect(new PathDashPathEffect(shape, 20, phase, PathDashPathEffect.Style.TRANSLATE));
添加如上代码的效果图如下:
添加一下代码可以实现动画效果:
phase ++;
invalidate();
- DiscretePathEffect——离散路径效果
DiscretePathEffect(float segmentLength, float deviation)
其构造方法有两个参数:
segmentLength:
每隔segmentLength都会有一个突出点;
deviation:
突出点的偏移量;
mPaint.setPathEffect(new DiscretePathEffect(3, 10));
每隔距离为3的地方都会出现偏移量为10的突出点。
它同样也能做出动画效果
if(deviation > 10){
deviation = 0;
}
deviation = deviation + 0.02f;
invalidate();
- ComposePathEffect——组合路径效果
ComposePathEffect(PathEffect outerpe, PathEffect innerpe)
组合方式是:先将路径变成innerpe的效果,再去复合outerpe的路径效果,即:outerpe(innerpe(Path))
PathEffect dashPathEffect= new DashPathEffect(new float[]{10,5,6,15},5);
PathEffect discretePathEffect = new DiscretePathEffect(3, 10);
mPaint.setPathEffect(new ComposePathEffect(dashPathEffect, discretePathEffect));
效果如下:
- SumPathEffect——叠加路径效果
SumPathEffect(PathEffect first, PathEffect second)
组合方式是:将两种合二为一
PathEffect dashPathEffect= new DashPathEffect(new float[]{10,5,6,15},5);
PathEffect discretePathEffect = new DiscretePathEffect(3, 10);
mPaint.setPathEffect(new SumPathEffect(dashPathEffect, discretePathEffect));
(14)setXfermode(Xfermode xfermode)
设置图形重叠时的处理方式,如合并,取交集或并集,经常用来制作橡皮的擦除效果。
(15)setMaskFilter(MaskFilter maskfilter)
设置MaskFilter,可以用不同的MaskFilter实现滤镜的效果,如滤化,立体等。
如图所示,有两种滤镜需要我们去实验
- BlurMaskFilter-----模糊遮罩滤镜 (改变图像的透明度值来实现的)
BlurMaskFilter(float radius, Blur style)
radius:
半径从原始遮罩延伸模糊的半径;
style:
模糊样式;
模糊样式可分为以下四种:
NORMAL:
在原始边界内外模糊。
SOLID:
在边界内绘制实体,在边界外模糊。
OUTER:
在边界内不绘制任何内容,在边界外模糊。
INNER:
在边界内模糊,在边界外不绘制任何内容。
现在绘制一张图片,图片原始效果如下:
//使用NORMAL样式
mPaint.setMaskFilter(new BlurMaskFilter(20, BlurMaskFilter.Blur.NORMAL));
使用NORMAL样式的效果图如下:
//使用SOLID样式
mPaint.setMaskFilter(new BlurMaskFilter(20, BlurMaskFilter.Blur.SOLID));
使用SOLID样式的效果图如下:
//使用OUTER样式
mPaint.setMaskFilter(new BlurMaskFilter(20, BlurMaskFilter.Blur.OUTER));
使用OUTER样式的效果图如下:
//使用INNER样式
mPaint.setMaskFilter(new BlurMaskFilter(20, BlurMaskFilter.Blur.INNER));
使用INNER样式的效果图如下:
-
EmbossMaskFilter----浮雕遮罩滤镜(类似于灯光照射效果)
//指定光源的位置,长度为xxx的数组标量[x,y,z] float[] direction = new float[] { 1, 1, 1 }; // 环境光的因子 (0~1),越接近0,环境光越暗 float light = .03f; // 镜面反射系数 越接近0,镜面反射越强 float specular = 60; //模糊半径 值越大,模糊效果越明显 float blur = 80f; EmbossMaskFilter emboss = new EmbossMaskFilter(direction, light, specular, blur); mPaint.setMaskFilter(emboss); canvas.drawBitmap(BitmapFactory.decodeResource(getResources(), R.mipmap.pic_shi), 0, 0, mPaint);
(16)setColorFilter(ColorFilter colorfilter)(颜色过滤器)
设置颜色过滤器,可以在绘制颜色时实现不用颜色的变换效果。
ColorFilter有三个子类,分别是LightingColorFilter
、PorterDuffColorFilter
、ColorMatrixColorFilter
。
在讲解这三种颜色过滤器之前,首先对RGB和ARGB做个普及。
颜色有三个通道,分别是R(红色)、G(绿色)、B(蓝色),在Android中,我们通常使用16进制表示一个RBG,比如:
红色:#ff0000
绿色:#00ff00
蓝色:#0000ff
黄色:#ffff00
该16进制有6为,前两位代表红色通道,中间两位代表绿色通道,后面两位代表蓝色通道。
如果加上透明度的话,就是所谓的ARGB,ARGB中的A是alpha(透明度)
的意思,用8位16进制表示,比如:
100%透明度的红色:#ffff0000
0%透明度的红色:#00ff0000
其中前两位代表透明度,#ff
代表不透明,#00
代表完全透明。
关于透明度和对应的颜色值,请看下表
透明度 | 颜色(16进制) |
---|---|
100% | FF |
95% | F2 |
90% | E6 |
85% | D9 |
80% | CC |
75% | BF |
70% | B3 |
65% | A6 |
60% | 99 |
55% | 8C |
50% | 80 |
45% | 73 |
40% | 66 |
35% | 59 |
30% | 4D |
25% | 40 |
20% | 33 |
15% | 26 |
10% | 1A |
5% | 0D |
0% | 00 |
下面开始讲解三种颜色过滤器。
LightingColorFilter
这个 LightingColorFilter 是用来模拟简单的光照效果的。其构造方法如下:
LightingColorFilter(@ColorInt int mul, @ColorInt int add)
创建一个颜色过滤器,将RGB通道乘以一种颜色,然后添加第二种颜色。mul和add的alpha分量参数被忽略。
【解析开始】:
假如有一张图,这张图的色彩由R、G、B三种通道组成,这张图展示如下:
-
现在我想去除红色
mPaint.setColorFilter(new LightingColorFilter(Color.parseColor("#00ffff"), Color.parseColor("#000000")));
其计算方式如下:
R' = R * 0x00 / 0xff + 0x0 = R
G' = G * 0xff / 0xff + 0x0 = G
B' = B * 0xff / 0xff + 0x0 = B
效果如下:
-
去除绿色
mPaint.setColorFilter(new LightingColorFilter(Color.parseColor("#ff00ff"), Color.parseColor("#000000")));
其计算方式如下:
R' = R * 0xff / 0xff + 0x0 = R
G' = G * 0x00 / 0xff + 0x0 = G
B' = B * 0xff / 0xff + 0x0 = B
效果图如下:
-
去除蓝色
mPaint.setColorFilter(new LightingColorFilter(Color.parseColor("#ffff00"), Color.parseColor("#000000")));
其计算方式如下:
R' = R * 0xff / 0xff + 0x0 = R
G' = G * 0xff / 0xff + 0x0 = G
B' = B * 0x00 / 0xff + 0x0 = B
效果图如下
- 让红色更亮一些
mPaint.setColorFilter(new LightingColorFilter(Color.parseColor("#ffffff"), Color.parseColor("#300000")));
其计算方式如下:
R' = R * 0xff / 0xff + 0x30 = R
G' = G * 0xff / 0xff + 0x0 = G
B' = B * 0xff / 0xff + 0x0 = B
效果图如下
- 让绿色更亮一些
mPaint.setColorFilter(new LightingColorFilter(Color.parseColor("#ffffff"), Color.parseColor("#003000")));
其计算方式如下:
R' = R * 0xff / 0xff + 0x0 = R
G' = G * 0xff / 0xff + 0x30 = G
B' = B * 0xff / 0xff + 0x0 = B
效果图如下
- 让蓝色更亮一些
mPaint.setColorFilter(new LightingColorFilter(Color.parseColor("#ffffff"), Color.parseColor("#000030")));
其计算方式如下:
R' = R * 0xff / 0xff + 0x0 = R
G' = G * 0xff / 0xff + 0x0 = G
B' = B * 0xff / 0xff + 0x30 = B
效果图如下
PorterDuffColorFilter
mPaint.setColorFilter(new PorterDuffColorFilter(Color.parseColor("#ff0000"), PorterDuff.Mode.ADD));
canvas.drawBitmap(BitmapFactory.decodeResource(getResources(), R.mipmap.pic_shi), 0, 0, mPaint);
这个 PorterDuffColorFilter 的作用是使用一个指定的颜色和一种指定的 PorterDuff.Mode 来与绘制对象进行合成。它的构造方法是 PorterDuffColorFilter(int color, PorterDuff.Mode mode) 其中的 color 参数是指定的颜色, mode 参数是指定的 Mode。同样也是 PorterDuff.Mode ,不过和 ComposeShader 不同的是,PorterDuffColorFilter 作为一个 ColorFilter,只能指定一种颜色作为源,而不是一个 Bitmap。
有关PorterDuff.Mode的使用可以参考:高级UI<第十六篇>:Xfermode 详解
ColorMatrixColorFilter
ColorMatrixColorFilter 使用一个 ColorMatrix 来对颜色进行处理。 ColorMatrix 这个类,内部是一个 4x5 的矩阵:
[ a, b, c, d, e,
f, g, h, i, j,
k, l, m, n, o,
p, q, r, s, t ]
通过计算, ColorMatrix 可以把要绘制的像素进行转换。对于颜色 [R, G, B, A] ,转换算法是这样的:
R’ = a*R + b*G + c*B + d*A + e;
G’ = f*R + g*G + h*B + i*A + j;
B’ = k*R + l*G + m*B + n*A + o;
A’ = p*R + q*G + r*B + s*A + t;
具体可以参考这篇博客高级UI<第十八篇>:图像处理之颜色矩阵
(17)setShader(Shader shader)(着色器、渲染器)
有关设置Android渲染器,请参考我的另外几篇博客
高级UI<第十九篇>:Android之高级渲染Shader
高级UI<第二十篇>:使用LinearGradient实现文字轮播
高级UI<第二十一篇>:使用SweepGradient实现雷达扫描效果
(18)setShadowLayer(float radius ,float dx,float dy,int color);
在图形下面设置阴影层,产生阴影效果,radius为阴影的模糊范围,dx和dy为阴影的偏移量,color为阴影的颜色 。
mPaint.setShadowLayer(50, 0, 0, Color.RED);
效果如下:
如果要清除阴影层,使用
mPaint.clearShadowLayer();
需要特别注意的是:
- 在硬件加速开启的情况下, setShadowLayer() 只支持文字的绘制,文字之外的绘制必须关闭硬件加速才能正常绘制阴影。
- 如果 shadowColor 是半透明的,阴影的透明度就使用 shadowColor 自己的透明度;而如果 shadowColor 是不透明的,阴影的透明度就使用 paint 的透明度。
(19)文字相关
文字相关的比较简单,这里就不一一介绍了。
float getFontSpacing()
获取字符行间距。
float getLetterSpacing()
void setLetterSpacing(float letterSpacing)
设置和获取字符间距
final boolean isUnderlineText()
void setUnderlineText(boolean underlineText)
是否有下划线和设置下划线。
final boolean isStrikeThruText()
void setStrikeThruText(boolean strikeThruText)
获取与设置是否有文本删除线。
float getTextSize()
void setTextSize(float textSize)
获取与设置文字大小,注意:Paint.setTextSize传入的单位是px,TextView.setTextSize传入的单位是sp,注意使用时不同分辨率处理问题。
Typeface getTypeface()
Typeface setTypeface(Typeface typeface)
获取与设置字体类型。Android默认有四种字体样式:BOLD(加粗)、BOLD_ITALIC(加粗并倾斜)、ITALIC(倾斜)、NORMAL(正常),我们也可以通过Typeface类来自定义个性化字体。
float getTextSkewX()
void setTextSkewX(float skewX)
获取与设置文字倾斜,参数没有具体范围,官方推荐值为-0.25,值为负则右倾,为正则左倾,默认值为0。
Paint.Align getTextAlign()
void setTextAlign(Paint.Align align)
获取与设置文本对齐方式,取值为CENTER、LEFT、RIGHT,也就是文字绘制是左边对齐、右边还是局中的。
setSubpixelText(boolean subpixelText)
设置自像素。如果该项为true,将有助于文本在LCD屏幕上的显示效果。
固定的几个范围:320*480,480*800,720*1280,1080*1920等等;那么如何在同样的分辨率的显示器中增强显示清晰度呢?
亚像素的概念就油然而生了,亚像素就是把两个相邻的两个像素之间的距离再细分,再插入一些像素,这些通过程序加入的像素就是亚像素。在两个像素间插入的像素个数是通过程序计算出来的,一般是插入两个、三个或四个。
所以打开亚像素显示,是可以在增强文本显示清晰度的,但由于插入亚像素是通过程序计算而来的,所以会耗费一定的计算机性能。
int breakText(String text, boolean measureForwards, float maxWidth, float[] measuredWidth)
比如文本阅读器的翻页效果,我们需要在翻页的时候动态折断或生成一行字符串,这就派上用场了~
[本章完...]