- setStyle
- setStrokeCap - 笔头的样式
- setStrokeJoin - 线交角的样式
- setShader(Shader shader) - 用来处理颜色渐变
- setColorFilter(ColorFilter filter) - 用来基于颜色进行过滤处理
- setXfermode(Xfermode xfermode) - 用来处理源图像和 View 已有内容的关系
- Paint对文字的处理
- setShadowLayer(模糊半径,x偏移,y偏移,颜色)
- setAntiAlias (boolean aa)
- setStrokeWidth(float width) - 设置线条粗细
- setStrokeMiter(float miter) - 设置 MITER 型拐角的延长线的最大值
- setDither(boolean dither)
- setFilterBitmap(boolean filter)
- setPathEffect(PathEffect effect)
- CornerPathEffect(float radius)
- DiscretePathEffect(float segmentLength, float deviation) -离散路径(小段长,偏移量)
- DashPathEffect(float[] intervals, float phase)
- PathDashPathEffect(Path shape, float advance, float phase, PathDashPathEffect.Style style)
- SumPathEffect(PathEffect first, PathEffect second)
- ComposePathEffect(PathEffect outerpe, PathEffect innerpe)
- setShadowLayer(float radius, float dx, float dy, int shadowColor)
- setMaskFilter(MaskFilter maskfilter)
- 下节看点:Canvas:drawLine/drawPoint
对于画笔我们想到的就是画笔对颜色的设置,还能写文字.
下面就是大概的一些常见的paint设置API,
总的来说分成以下几种
- 设置颜色,透明度,反锯齿等
- 设置粗细,PathEffect(CornerPathEffect:将线段的锐角变成顺滑的圆角)
- 设置ColorFilter,MaskFilter
- 设置Shader
- 设置文字的各种相关效果(下划线,放大,对齐等)
- 其他
下面就是着重找几个常用的使用一下:
setStyle
笔的样式分三种FILL|STROKE|FILL_AND_STROKE
private val mRedPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
style = Paint.Style.FILL
color = Color.RED
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
testStyle(canvas)
}
private fun testStyle(canvas: Canvas) {
val rect = Rect(0, 0, 100, 100)
mRedPaint.strokeWidth = 15f
canvas.save()
mRedPaint.style = Paint.Style.FILL
canvas.translate(0f, 100F)
canvas.drawRect(rect, mRedPaint)
canvas.translate(150F, 0F)
mRedPaint.style = Paint.Style.STROKE
canvas.drawRect(rect, mRedPaint)
canvas.translate(150F, 0F)
mRedPaint.style = Paint.Style.FILL_AND_STROKE
canvas.drawRect(rect, mRedPaint)
canvas.restore()
mRedPaint.strokeWidth = 40F
}
表现上看就是
- FILL是填充内部,但是不算paint的线的宽度
- STROKE是只进行描边
- FILL_AND_STROKE就是前两个相加
setStrokeCap - 笔头的样式
private fun testOfCap(canvas: Canvas) {
mRedPaint.strokeWidth = 20F
canvas.save()
canvas.translate(150F, 200F)
//线帽测试:
mRedPaint.strokeCap = Paint.Cap.BUTT//无头(默认)
canvas.drawLine(0F, 0F, 0F, 200F, mRedPaint)
canvas.translate(50F, 0F)
mRedPaint.strokeCap = Paint.Cap.ROUND//圆头
canvas.drawLine(0F, 0F, 0F, 200F, mRedPaint)
canvas.translate(50F, 0F)
mRedPaint.strokeCap = Paint.Cap.SQUARE//方头
canvas.drawLine(0F, 0F, 0F, 200F, mRedPaint)
canvas.restore()
}
setStrokeJoin - 线交角的样式
/**
* 角型测试:Paint.Join.BEVEL、Paint.Join.ROUND、Paint.Join.MITER
*
* @param canvas
*/
private fun testOfJoin(canvas: Canvas) {
mRedPaint.setStyle(Paint.Style.STROKE)
mRedPaint.setStrokeWidth(40F)
val path = Path()
path.moveTo(30F, 0F)
path.lineTo(0F, 100F)
path.lineTo(100F, 100F)
path.lineTo(100F, 200F)
canvas.save()
canvas.translate(100F, 100F)
mRedPaint.strokeJoin = Paint.Join.BEVEL//直线(默认)
canvas.drawPath(path, mRedPaint)
canvas.translate(150F, 0F)
mRedPaint.strokeJoin = Paint.Join.ROUND//圆角
canvas.drawPath(path, mRedPaint)
canvas.translate(150F, 0F)
mRedPaint.strokeJoin = Paint.Join.MITER//锐角
canvas.drawPath(path, mRedPaint)
canvas.restore()
}
setShader(Shader shader) - 用来处理颜色渐变
Shader的模式
- LinearGradient - 线性渐变
- SweepGradient - 创建360度颜色旋转渐变效果
- RadialGradient - 中心向四周发散的辐射渐变效果
- BitmapShader - 位图着色,可以利用该类做各种各样的图片裁剪。
- ComposeShader - 组合Shader,可以将连个Shader组合在一起
LinearGradient - 线性渐变
LinearGradient(float x0, float y0, float x1, float y1, int color0, int color1, Shader.TileMode tile)
LinearGradient(float x0, float y0, float x1, float y1, int[] colors, float[] positions, Shader.TileMode tile)
LinearGradient是用来创建线性渐变效果的,它是沿着某条直线的方向渐变的,坐标(x0,y0)就是这条渐变直线的起点,坐标(x1,y1)就是这条渐变直线的终点。需要说明的是,坐标(x0,y0)和坐标(x1,y1)都是Canvas绘图坐标系中的坐标。color0和color1分别表示了渐变的起始颜色和终止颜色。与BitmapShader类似,LinearGradient也支持TileMode,有以下三个取值:CLAMP 、REPEAT 和 MIRROR。
@RequiresApi(Build.VERSION_CODES.S)
private fun setShader(canvas: Canvas) {
val colorStart = Color.parseColor("#FF0000")
val colorEnd = Color.parseColor("#00FF00")
canvas.save();
canvas.translate(0f, 0F);
mRedPaint.setStyle(Paint.Style.FILL);
shaderEffects.add(
LinearGradient(
0F, 0F, 400F, 100F, colorStart, colorEnd, Shader.TileMode.MIRROR
)
)
shaderEffects.add(
LinearGradient(
0F, 0F, 400F, 100F, colorStart, colorEnd, Shader.TileMode.CLAMP
)
)
shaderEffects.add(
LinearGradient(
0F, 0F, 400F, 100F, colorStart, colorEnd, Shader.TileMode.REPEAT
)
)
shaderEffects.add(
LinearGradient(
0F, 0F, 400F, 100F, colorStart, colorEnd, Shader.TileMode.DECAL
)
)
for (i in shaderEffects.indices) {
mRedPaint.setShader(shaderEffects[i])
canvas.translate(0f, 200f)
canvas.drawRect(0F, 0F, 800F, 100F, mRedPaint);
}
}
从上往下分别是
Shader.TileMode.MIRROR,如果没有颜色选定了,那就镜像设置一遍
Shader.TileMode.CLAMP,如果没有颜色选定了,那就复制边缘的颜色
Shader.TileMode.REPEAT,如果没有颜色选定了,那就是重复开始一遍
Shader.TileMode.DECAL,如果没有颜色选定了,那就没有了,后面透明(target version = 31)
然后我们再看一下LinearGTradient 的(x,y)的区别
@RequiresApi(Build.VERSION_CODES.S)
private fun setShader(canvas: Canvas) {
val colorStart = Color.parseColor("#FF0000")
val colorEnd = Color.parseColor("#00FF00")
canvas.save();
canvas.translate(0f, 0F);
mRedPaint.setStyle(Paint.Style.FILL);
shaderEffects.add(
LinearGradient(
0F, 50F, 400F, 50F, colorStart, colorEnd, Shader.TileMode.MIRROR
)
)
shaderEffects.add(
LinearGradient(
0F, 0F, 400F, 100F, colorStart, colorEnd, Shader.TileMode.MIRROR
)
)
shaderEffects.add(
LinearGradient(
0F, 0F, 800F, 100F, colorStart, colorEnd, Shader.TileMode.MIRROR
)
)
shaderEffects.add(
LinearGradient(
0F, 0F, 400F, 100F, colorStart, colorEnd, Shader.TileMode.CLAMP
)
)
shaderEffects.add(
LinearGradient(
0F, 0F, 400F, 100F, colorStart, colorEnd, Shader.TileMode.REPEAT
)
)
shaderEffects.add(
LinearGradient(
0F, 0F, 400F, 100F, colorStart, colorEnd, Shader.TileMode.DECAL
)
)
for (i in shaderEffects.indices) {
mRedPaint.setShader(shaderEffects[i])
canvas.translate(0f, 200f)
canvas.drawRect(0F, 0F, 800F, 100F, mRedPaint);
}
}
图中一个长方形的坐标是(0,0,800,100),
所以LinearGradient的坐标如果是(0F, 50F, 400F, 50F),说明start的光照点在左中的位置,end的光照点在右中的位置,如红点所示;
(0F, 50F, 400F, 100F)的start光照点在左中,end的光照点在正中间
(0F, 0F, 800F, 100F)的start的光照点在左上,end的光照点在右下
跟着代码看图就能理解.
另外:
在LinearGradient的第二个构造函数中可以通过参数colors传入多个颜色值进去,这样就会用colors数组中指定的颜色值一起进行颜色线性插值。还可以指定positions数组,该数组中每一个position对应colors数组中每个颜色在线段中的相对位置,position取值范围为[0,1],0表示起始位置,1表示终止位置。如果positions数组为null,那么Android会自动为colors设置等间距的位置。
int[] colors = new int[]{
Color.parseColor("#F60C0C"),//红
Color.parseColor("#F3B913"),//橙
Color.parseColor("#E7F716"),//黄
Color.parseColor("#3DF30B"),//绿
Color.parseColor("#0DF6EF"),//青
Color.parseColor("#0829FB"),//蓝
Color.parseColor("#B709F4"),//紫
};
float[] pos = new float[]{
1.f / 7, 2.f / 7, 3.f / 7, 4.f / 7, 5.f / 7, 6.f / 7, 1
};
canvas.translate(0, 150);
mRedPaint.setShader(
new LinearGradient(
-300, 0, 300, 0,
colors, pos,
Shader.TileMode.CLAMP
));
canvas.drawRect(-400, -200, 400, -100, mRedPaint);
效果如下
SweepGradient - 圆形扫面渐变
SweepGradient不支持TileMode参数
坐标(cx,cy)决定了中心点的位置,会绕着该中心点进行360度旋转。color0表示的是起点的颜色位置,color1表示的是终点的颜色位置。
private fun setSweepShader(canvas: Canvas) {
val colorStart = Color.parseColor("#FF0000")
val colorEnd = Color.parseColor("#00FF00")
canvas.save();
canvas.translate(200f, 200F);
mRedPaint.setStyle(Paint.Style.FILL);
shaderEffects.add(
SweepGradient(
0F, 0F, colorStart, colorEnd
)
)
for (i in shaderEffects.indices) {
mRedPaint.setShader(shaderEffects[i])
canvas.drawCircle(0F, 0F, 200F, mRedPaint);//200f是半径
}
}
在SweepGradient的第二个构造函数中,我们可以传入一个colors颜色数组,这样Android就会根据传入的颜色数组一起进行颜色插值。还可以指定positions数组,该数组中每一个position对应colors数组中每个颜色在360度中的相对位置,position取值范围为[0,1],0和1都表示3点钟位置,0.25表示6点钟位置,0.5表示9点钟位置,0.75表示12点钟位置,诸如此类。如果positions数组为null,那么Android会自动为colors设置等间距的位置。
private fun setSweepShader(canvas: Canvas) {
val colorStart = Color.parseColor("#FF0000")
val colorEnd = Color.parseColor("#00FF00")
canvas.save();
canvas.translate(200f, 200F)
mRedPaint.setStyle(Paint.Style.FILL)
val colors = intArrayOf(Color.RED, Color.GREEN, Color.BLUE,Color.WHITE)
val positions = floatArrayOf(0f, 0.5f, 0.75f,1f)
shaderEffects.add(
SweepGradient(
0F, 0F, colors, positions
)
)
for (i in shaderEffects.indices) {
mRedPaint.setShader(shaderEffects[i])
canvas.drawCircle(0F, 0F, 200F, mRedPaint);
}
}
RadialGradient - 从中心向四周发散的辐射
我们可以用RadialGradient创建从中心向四周发散的辐射渐变效果,其有两个构造函数:
RadialGradient(float centerX, float centerY, float radius, int centerColor, int edgeColor, Shader.TileMode tileMode)
RadialGradient(float centerX, float centerY, float radius, int[] colors, float[] stops, Shader.TileMode tileMode)
这两个构造函数和LinearGradient的两个构造函数很类似,我们此处还是重点讲解第一个构造函数,在此基础上理解第二个构造函数就很简单了。
RadialGradient是用来创建从中心向四周发散的辐射渐变效果的,所以我们需要在其构造函数中传入一些圆的参数,坐标(centerX,centerY)是圆心,即起始的中心颜色的位置,radius确定了圆的半径,在圆的半径处的颜色是edgeColor,这样就确定了当位置从圆心移向圆的轮廓时,颜色逐渐从centerColor渐变到edgeColor。RadialGradient也支持TileMode参数,有以下三个取值:CLAMP 、REPEAT 和 MIRROR。
private fun setRadialShader(canvas: Canvas) {
val colorStart = Color.parseColor("#FF0000")
val colorEnd = Color.parseColor("#00FF00")
canvas.save();
canvas.translate(0f, 0F);
mRedPaint.setStyle(Paint.Style.FILL);
shaderEffects.add(
RadialGradient(
100F, 100F, 50F, colorStart, colorEnd, Shader.TileMode.MIRROR
)
)
shaderEffects.add(
RadialGradient(
100F, 100F, 50F, colorStart, colorEnd, Shader.TileMode.REPEAT
)
)
shaderEffects.add(
RadialGradient(
100F, 100F, 50F, colorStart, colorEnd, Shader.TileMode.CLAMP
)
)
for (i in shaderEffects.indices) {
mRedPaint.setShader(shaderEffects[i])
canvas.translate(0f, 250f)
canvas.drawRect(0F, 0F,200f, 200F, mRedPaint);
}
}
没什么好说的,跟LinearGradient差不太多
在RadialGradient的第二个构造函数中可以通过参数colors传入多个颜色值进去,这样就会用colors数组中指定的颜色值一起进行颜色线性插值。还可以指定stops数组,该数组中每一个stop对应colors数组中每个颜色在半径中的相对位置,stop取值范围为[0,1],0表示圆心位置,1表示圆周位置。如果stops数组为null,那么Android会自动为colors设置等间距的位置。
BitmapShader - 位图着色,可以利用该类做各种各样的图片裁剪。
BitmapShader(Bitmap bitmap, Shader.TileMode tileX, Shader.TileMode tileY)
第一个参数是Bitmap对象,该Bitmap决定了用什么图片对绘制的图形进行贴图。
第二个参数和第三个参数都是Shader.TileMode类型的枚举值,表示的是对x轴上怎么排列,和对y轴上怎么排列
用Bitmap对绘制的图形进行渲染着色,其实就是用图片对图形进行贴图。
一样分成三个:CLAMP 、REPEAT 和 MIRROR。
CLAMP表示,当所画图形的尺寸大于Bitmap的尺寸的时候,会用Bitmap四边的颜色填充剩余空间。
我们有一个Bitmap,如下所示:
CLAMP如下:
REPEAT表示,当我们绘制的图形尺寸大于Bitmap尺寸时,会用Bitmap重复平铺整个绘制的区域。REPEAT如下:
与REPEAT类似,当绘制的图形尺寸大于Bitmap尺寸时,MIRROR也会用Bitmap重复平铺整个绘图区域,与REPEAT不同的是,两个相邻的Bitmap互为镜像。
MIRROR如下:
使用BitmapShader给文字的图片上底色
private fun setBitmapShader(canvas: Canvas) {
//加载图片,生成图片着色器
val bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.rank_banner);
val bs = BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)
mRedPaint.setShader(bs);
mRedPaint.setTextSize(150F);
mRedPaint.setStrokeWidth(10F);
mRedPaint.setStyle(Paint.Style.FILL_AND_STROKE);
canvas.drawText("周杰伦龍龍龍", 0F, 500F, mRedPaint);
}
这个没什么好说的,就是对绘制的图形上图片的底色,这里就是用了drawText,其实如果最后一句改成 canvas.drawCircle( 100F, 100F, 50f, mRedPaint)
就是下面比较常见的情况
ComposeShader - 组合Shader,可以将连个Shader组合在一起
//todo
ComposeShader,顾名思义,就是混合Shader的意思,它可以将两个Shader按照一定的Xfermode组合起来。
ComposeShader有两个构造函数,如下所示:
ComposeShader(Shader shaderA, Shader shaderB, Xfermode mode)
ComposeShader(Shader shaderA, Shader shaderB, PorterDuff.Mode mode)
类android.graphics.PorterDuffXfermode继承自android.graphics.Xfermode,所以上面的第二个构造函数可以看做是第一个构造函数的特例。我们主要讲解第二个,二者大同小异。
有一点需要说明,ComposeShader这个类不是必须的,也就是我们不用这个类也能创造对应的效果,它类似于一个助手类,为我们实现某种效果提供了方便,下面举例说明。构造函数中的shaderA对应着目标像素,shaderB对应着源像素。所以mode应用时将把从shaderA(ComposeShader 第一个参数)中获取的结果作为目标图像,shaderB(ComposeShader第二个参数)中获取的结果作为源图像,进行混合叠加。如果相交的两个图像大小不同,就会存在不想交的部分,不想交的部分是不会相互影响的
我们的图片中间的❤形区域是纯白色,该区域的像素颜色值ARGB分量是(255,255,255,255)。❤形区域以外的区域是纯透明的,该区域的像素颜色值ARGB分量是(0,0,0,0)。
为了使用Xfermode,我们将绘图的代码放到了canvas.saveLayer()和canvas.restore()之间,对此有疑问的同学可以参见我上述提到的博文。canvas.saveLayer()会创建一个新的绘图图层,而且该图层是全透明的,我们后面的代码都是绘制到这个图层上,而不是直接绘制到Canvas上。
我们用上述Bitmap创建了一个BitmapShader,并将其绑定到画笔Paint中。当我们用canvas.drawRect()绘制矩形时,就会用该BitmapShader填充,此时的效果应该是在新创建的layer上绘制了一个白色的心形。
然后我们创建了一个PorterDuffXfermode的实例,并通过paint.setXfermode()将其绑定到画笔paint上。其中PorterDuffXfermode的mode类型为MULTIPLY。MULTIPLY的意思是将源像素的ARGB四个分量分别与目标像素对应的ARGB四个分量相乘,将相乘的结果作为混合后的像素。此处进行相乘时,ARGB四个分量都已经从[0, 255]的区间归一化到[0.0, 1.0]的区间。
然后我们创建了一个LinearGradient,用以实现颜色线性渐变效果。颜色从左上角的绿色渐变到右下角的蓝色。然后我们通过paint.setShader()方法将其绑定到画笔paint的shader上。
后面我们再次调用canvas.drawRect()绘制同样大小的一个矩形。在绘制时,我们的画笔已经同时绑定了Xfermode和Shader。首先canvas会用LinearGradient绘制一个具有渐变色的矩形区域。然后根据画笔设置的PorterDuff.Mode.MULTIPLY类型,将那些由渐变色填充的矩形区域中的像素与我们在第3步中绘制的心形图片中的像素颜色进行相乘混合。渐变色填充的矩形区域中的像素是源像素,第3步中绘制的心形图片中的像素是目标像素。目标像素中❤形区域是纯白色的,其像素颜色是(255,255,255,255),归一化后的颜色是(1,1,1,1),对应位置的源像素中的ARGB颜色分量与其相乘,最终的颜色还是源像素的颜色,即心形区域被源像素着上了渐变色。目标像素中❤形区域以外的颜色是纯透明的,颜色是(0,0,0,0),对应位置的源像素中的ARGB颜色分量与其相乘,最终的颜色还是目标像素中的(0,0,0,0),即心形区域以外没有被着色,依旧呈现透明色。
最后通过调用canvas.restore()方法将新创建的layer绘制到Canvas上去,这样我们就看到最终的效果了。
下面我们看看如和用ComposeShader实现上述效果,代码如下所示:
int bitmapWidth = bitmap.getWidth();
int bitmapHeight = bitmap.getHeight();
//创建BitmapShader,用以绘制❤形
BitmapShader bitmapShader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
//创建LinearGradient,用以产生从左上角到右下角的颜色渐变效果
LinearGradient linearGradient = new LinearGradient(0, 0, bitmapWidth, bitmapHeight, Color.GREEN, Color.BLUE, Shader.TileMode.CLAMP);
//bitmapShader对应目标像素,linearGradient对应源像素,像素颜色混合采用MULTIPLY模式
ComposeShader composeShader = new ComposeShader(bitmapShader, linearGradient, PorterDuff.Mode.MULTIPLY);
//将组合的composeShader作为画笔paint绘图所使用的shader
paint.setShader(composeShader);
//用composeShader绘制矩形区域
canvas.drawRect(0, 0, bitmapWidth, bitmapHeight, paint);
用ComposeShader实现的效果与上图相同,我就不再贴图了。我们可以看到,使用ComposeShader之后,实现相同的效果时,代码量明显减少了,而且我们也不需要将绘图代码放到canvas.saveLayer()和canvas.restore()之间了。
根据上面的示例,我们可以得出如下结论:
假设我们定义了两个Shader的变量,shaderA和shaderB,并分别对这两个Shader进行了实例化。
可以使用ComposeShader将二者组合使用,基本代码如下所示:
ComposeShader composeShader = new ComposeShader(shaderA, shaderB, porterDuffMode);
paint.setShader(composeShader);
canvas.drawXXX(..., paint);
上述代码等价于下面的代码片段:
canvas.saveLayer(left, top, right, bottom, null, Canvas.ALL_SAVE_FLAG);
paint.setShader(shaderA);
canvas.drawXXX(..., paint);
paint.setXfermode(new PorterDuffXfermode(mode));
paint.setShader(shaderB);
canvas.drawXXX(..., paint);
paint.setXfermode(null);
canvas.restore();
此处所说的以上两个代码片段等价的前提是,两个代码片段中的canvas.drawXXX(…, paint)方法中调用的drawXXX方法相同,并且里面传入的参数都相同,例如我们之前两段心形代码示例中都调用drawRect()方法且绘制的矩形的位置及尺寸都相同。
setColorFilter(ColorFilter filter) - 用来基于颜色进行过滤处理
ColorFilter的使用
颜色过滤器可以将颜色按照一定的规则输出,常见于各种滤镜效果
LightingColorFilter - 模拟光照效果
LightingColorFilter(int mul, int add)
mul 用来乘以原图的 RPG 值
add 添加到前面得出的结果上
计算方法:(RGB 值 * mul + Add) % 255
private fun setLightingColorFilter(canvas: Canvas) {
val mainBitmap =
BitmapFactory.decodeResource(getResources(), R.drawable.rank_banner_avatar_7);
mRedPaint.setStyle(Paint.Style.FILL);
canvas.save()
mRedPaint.setColorFilter(
LightingColorFilter(
Color.parseColor("#FF0000"),//红
Color.parseColor("#0000ff")//蓝
)
);
canvas.drawBitmap(mainBitmap, 0f, 0f, mRedPaint);
canvas.translate(350f, 0f);
mRedPaint.setColorFilter(
LightingColorFilter(
Color.parseColor("#FF0000"),//红
Color.parseColor("#00ff00")//绿
)
);
canvas.drawBitmap(mainBitmap, 0f, 0F, mRedPaint);
canvas.translate(350f, 0f);
mRedPaint.setColorFilter(
LightingColorFilter(
Color.parseColor("#FF0000"),//红
Color.parseColor("#000000")//黑
)
);
canvas.drawBitmap(mainBitmap, 0F, 0F, mRedPaint);
canvas.restore();
}
LightingColorFilter(0xFFFFFFFF, 0x00000000)的时候原图是不会有任何改变的
如果我们想增加红色的值,那么LightingColorFilter(0xFFFFFFFF, 0x00XX0000)就好,其中XX取值为00至FF
如下:
mRedPaint.setColorFilter(
LightingColorFilter(
Color.parseColor("#FFFFFF"),//红
Color.parseColor("#00550000")//蓝
)
)
PorterDuffColorFilter - 模拟颜色混合效果
private fun setPorterDuffColorFilter(canvas: Canvas) {
val mainBitmap =
BitmapFactory.decodeResource(getResources(), R.drawable.rank_banner_avatar_7);
mRedPaint.setStyle(Paint.Style.FILL);
canvas.save()
mRedPaint.setColorFilter(
PorterDuffColorFilter(
Color.parseColor("#FF0000"),//红
PorterDuff.Mode.DARKEN
)
)
canvas.drawBitmap(mainBitmap, 0f, 0f, mRedPaint);
canvas.translate(350f, 0f);
mRedPaint.setColorFilter(
PorterDuffColorFilter(
Color.parseColor("#FF0000"),//红
PorterDuff.Mode.LIGHTEN,
)
);
canvas.drawBitmap(mainBitmap, 0f, 0F, mRedPaint);
canvas.translate(350f, 0f);
mRedPaint.setColorFilter(
PorterDuffColorFilter(
Color.parseColor("#FF0000"),//红
PorterDuff.Mode.OVERLAY,
)
);
canvas.drawBitmap(mainBitmap, 0F, 0F, mRedPaint);
canvas.restore();
}
ColorMatrixColorFilter - 颜色矩阵过滤
很多应用是有滤镜效果的,这里的MatrixColor就能完成滤镜的效果,通常我们见过的常见的滤镜就是预设的矩阵值
{
// 黑白
val colormatrix_heibai = floatArrayOf(
0.8f, 1.6f, 0.2f, 0f,
-163.9f, 0.8f, 1.6f, 0.2f, 0f, -163.9f, 0.8f, 1.6f, 0.2f, 0f,
-163.9f, 0f, 0f, 0f, 1.0f, 0f
)
// 怀旧
val colormatrix_huaijiu = floatArrayOf(
0.2f, 0.5f, 0.1f, 0f,
40.8f, 0.2f, 0.5f, 0.1f, 0f, 40.8f, 0.2f, 0.5f, 0.1f, 0f, 40.8f, 0f,
0f, 0f, 1f, 0f
)
// 哥特
val colormatrix_gete = floatArrayOf(
1.9f, -0.3f, -0.2f, 0f,
-87.0f, -0.2f, 1.7f, -0.1f, 0f, -87.0f, -0.1f, -0.6f, 2.0f, 0f,
-87.0f, 0f, 0f, 0f, 1.0f, 0f
)
// 淡雅
val colormatrix_danya = floatArrayOf(
0.6f, 0.3f, 0.1f, 0f,
73.3f, 0.2f, 0.7f, 0.1f, 0f, 73.3f, 0.2f, 0.3f, 0.4f, 0f, 73.3f, 0f,
0f, 0f, 1.0f, 0f
)
// 蓝调
val colormatrix_landiao = floatArrayOf(
2.1f, -1.4f, 0.6f,
0.0f, -71.0f, -0.3f, 2.0f, -0.3f, 0.0f, -71.0f, -1.1f, -0.2f, 2.6f,
0.0f, -71.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f
)
// 光晕
val colormatrix_guangyun = floatArrayOf(
0.9f, 0f, 0f, 0f, 64.9f,
0f, 0.9f, 0f, 0f, 64.9f, 0f, 0f, 0.9f, 0f, 64.9f, 0f, 0f, 0f, 1.0f, 0f
)
// 梦幻
val colormatrix_menghuan = floatArrayOf(
0.8f, 0.3f, 0.1f,
0.0f, 46.5f, 0.1f, 0.9f, 0.0f, 0.0f, 46.5f, 0.1f, 0.3f, 0.7f, 0.0f,
46.5f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f
)
// 酒红
val colormatrix_jiuhong = floatArrayOf(
1.2f, 0.0f, 0.0f, 0.0f,
0.0f, 0.0f, 0.9f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.8f, 0.0f, 0.0f,
0f, 0f, 0f, 1.0f, 0f
)
// 胶片
val colormatrix_fanse = floatArrayOf(
-1.0f, 0.0f, 0.0f, 0.0f,
255.0f, 0.0f, -1.0f, 0.0f, 0.0f, 255.0f, 0.0f, 0.0f, -1.0f, 0.0f,
255.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f
)
// 湖光掠影
val colormatrix_huguang = floatArrayOf(
0.8f, 0.0f, 0.0f, 0.0f,
0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.9f, 0.0f, 0.0f,
0f, 0f, 0f, 1.0f, 0f
)
// 褐片
val colormatrix_hepian = floatArrayOf(
1.0f, 0.0f, 0.0f, 0.0f,
0.0f, 0.0f, 0.8f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.8f, 0.0f, 0.0f,
0f, 0f, 0f, 1.0f, 0f
)
// 复古
val colormatrix_fugu = floatArrayOf(
0.9f, 0.0f, 0.0f, 0.0f,
0.0f, 0.0f, 0.8f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.5f, 0.0f, 0.0f,
0f, 0f, 0f, 1.0f, 0f
)
// 泛黄
val colormatrix_huan_huang = floatArrayOf(
1.0f, 0.0f, 0.0f,
0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.5f, 0.0f,
0.0f, 0f, 0f, 0f, 1.0f, 0f
)
// 传统
val colormatrix_chuan_tong = floatArrayOf(
1.0f, 0.0f, 0.0f, 0f,
-10f, 0.0f, 1.0f, 0.0f, 0f, -10f, 0.0f, 0.0f, 1.0f, 0f, -10f, 0f, 0f,
0f, 1f, 0f
)
// 胶片2
val colormatrix_jiao_pian = floatArrayOf(
0.71f, 0.2f, 0.0f,
0.0f, 60.0f, 0.0f, 0.94f, 0.0f, 0.0f, 60.0f, 0.0f, 0.0f, 0.62f,
0.0f, 60.0f, 0f, 0f, 0f, 1.0f, 0f
)
// 锐色
val colormatrix_ruise = floatArrayOf(
4.8f, -1.0f, -0.1f, 0f,
-388.4f, -0.5f, 4.4f, -0.1f, 0f, -388.4f, -0.5f, -1.0f, 5.2f, 0f,
-388.4f, 0f, 0f, 0f, 1.0f, 0f
)
// 清宁
val colormatrix_qingning = floatArrayOf(
0.9f, 0f, 0f, 0f, 0f, 0f,
1.1f, 0f, 0f, 0f, 0f, 0f, 0.9f, 0f, 0f, 0f, 0f, 0f, 1.0f, 0f
)
// 浪漫
val colormatrix_langman = floatArrayOf(
0.9f, 0f, 0f, 0f, 63.0f,
0f, 0.9f, 0f, 0f, 63.0f, 0f, 0f, 0.9f, 0f, 63.0f, 0f, 0f, 0f, 1.0f, 0f
)
// 夜色
val colormatrix_yese = floatArrayOf(
1.0f, 0.0f, 0.0f, 0.0f,
-66.6f, 0.0f, 1.1f, 0.0f, 0.0f, -66.6f, 0.0f, 0.0f, 1.0f, 0.0f,
-66.6f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f
)
}
private fun setColorMatrixColorFilter(canvas: Canvas) {
canvas.save();
canvas.translate(0f, 0F);
mRedPaint.setStyle(Paint.Style.FILL);
val mainBitmap =
BitmapFactory.decodeResource(getResources(), R.drawable.rank_banner_avatar_5);
mRedPaint.setStyle(Paint.Style.FILL);
canvas.save()
for (floats in FloatMarixImg.getList()) {
val colorMatrix = ColorMatrix(floats)
mRedPaint.colorFilter = ColorMatrixColorFilter(colorMatrix)
canvas.drawBitmap(mainBitmap, 0f, 0f, mRedPaint);
canvas.translate(0f, 250f)
canvas.drawRect(0F, 0F, 200f, 200F, mRedPaint);
}
}
产生的图像就如下:
关于矩阵Color因为平时没有用到,所以没有更多的研究
setXfermode(Xfermode xfermode) - 用来处理源图像和 View 已有内容的关系
此处对Xfermode做一下简单介绍,Xfermode可以用于实现新绘制的像素与Canvas上对应位置已有的像素按照混合规则进行颜色混合。Xfermode有三个子类:AvoidXfermode, PixelXorXfermode和PorterDuffXfermode,其中前两个类现在被Android废弃了,现在主要用的是PorterDuffXfermode。PorterDuffXfermode的构造函数需要指定PorterDuff.Mode的类型。
在用Android中的Canvas进行绘图时,可以通过使用PorterDuffXfermode将所绘制的图形的像素与Canvas中对应位置的像素按照一定规则进行混合,形成新的像素值,从而更新Canvas中最终的像素颜色值,这样会创建很多有趣的效果。当使用PorterDuffXfermode时,需要将将其作为参数传给Paint.setXfermode(Xfermode xfermode)
方法,这样在用该画笔paint进行绘图时,Android就会使用传入的PorterDuffXfermode,如果不想再使用Xfermode,那么可以执行Paint.setXfermode(null)
.
我们知道,在使用Xfermode的时候,存在目标像素DST和源像素SRC之说。源像素指的是将要向Canvas上绘制的像素,目标像素指的是源像素在Canvas上对应位置已经存在的像素。
Android 中PorterDuff.Mode模式包括:
Mode.CLEAR;
Mode.SRC;
Mode.DST;
Mode.SRC_OVER;
Mode.DST_OVER;
Mode.SRC_IN;
Mode.DST_IN;
Mode.SRC_OUT;
Mode.DST_OUT;
Mode.SRC_ATOP;
Mode.DST_ATOP;
Mode.XOR;
Mode.DARKEN;
Mode.LIGHTEN;
Mode.MULTIPLY;
Mode.SCREEN;
Mode.ADD;
Mode.OVERLAY;
Xfermode的使用
//todo
Paint对文字的处理
Paint里有大量方法来设置文字的绘制属性,事实上文字在Android底层是被当做图片来处理的。
setTextSize(float textSize):设置文字大小
setTypeface(Typeface typeface):设置文字字体
setFakeBoldText(boolean fakeBoldText):是否使用伪粗体(并不是提到size,而是在运行时描粗的)
setTextAlign(Paint.Align align):设置文字对齐方式
private fun setTextStyle(canvas: Canvas) {
canvas.save()
val tempPaint = Paint(Paint.ANTI_ALIAS_FLAG)
tempPaint.strokeWidth = 4f
tempPaint.color = Color.BLUE
canvas.translate(500F, 200F)
mRedPaint.setTypeface(Typeface.SANS_SERIF)
mRedPaint.setTextAlign(Paint.Align.LEFT)
mRedPaint.setTypeface(Typeface.DEFAULT) //字体
mRedPaint.setTextSize(100F)
canvas.drawText("SANS_SERIF", 0F, 0F, mRedPaint)
canvas.drawRect(0F, 0F, 10f, 50F, tempPaint)
canvas.translate(0F, 150F)
mRedPaint.setTextAlign(Paint.Align.RIGHT)
mRedPaint.setTypeface(Typeface.SERIF)
canvas.drawText("SERIF", 0F, 0F, mRedPaint)
canvas.drawRect(0F, 0F, 10f, 50F, tempPaint)
canvas.translate(0F, 150F)
mRedPaint.setTextAlign(Paint.Align.CENTER)
mRedPaint.setTypeface(Typeface.MONOSPACE)
canvas.drawText("MONOSPACE", 0F, 0F, mRedPaint)
canvas.drawRect(0F, 0F, 10f, 50F, tempPaint)
canvas.restore()
}
这三个点就是起始点,这里主要要看Paint.Align对Text的影响
setStrikeThruText(boolean strikeThruText):是否添加删除线
setUnderlineText(boolean underlineText):是否添加下划线
setTextSkewX(float skewX):设置文字倾斜度
setTextScaleX(float scaleX):设置文字横向缩放
setLetterSpacing(float letterSpacing):设置文字间距 Added in API level 21,设置文本字母间距,默认0,负值收紧文本
setFontFeatureSettings(String settings):使用CSS的font-feature-settings的方式来设置文字。
setTextLocale(Locale locale):设置文字Local
setHinting(int mode):设置字体Hinting(微调),过向字体中加入 hinting 信息,让矢量字体在尺寸过小的时候得到针对性的修正,从而提高显示效果。
setSubpixelText(boolean subpixelText):设置次像素级抗锯齿,根据程序所运行的设备的屏幕类型,来进行针对性的次像素级的抗锯齿计算,从而达到更好的抗锯齿效果。
setLinearText 设置线性文本
setShadowLayer(模糊半径,x偏移,y偏移,颜色)
private fun setTextShadowLayer(canvas: Canvas) {
canvas.save()
val tempPaint = Paint(Paint.ANTI_ALIAS_FLAG)
tempPaint.strokeWidth = 4f
tempPaint.color = Color.BLUE
mRedPaint.setShadowLayer(20F, 4F, 4F, Color.parseColor("#000000"));
canvas.translate(500F, 200F)
mRedPaint.setTypeface(Typeface.SANS_SERIF)
mRedPaint.setTypeface(Typeface.DEFAULT) //字体
mRedPaint.setTextSize(100F)
canvas.drawText("SANS_SERIF", 0F, 0F, mRedPaint)
canvas.drawRect(0F, 0F, 10f, 50F, tempPaint)
}
setAntiAlias (boolean aa)
设置抗锯齿,默认关闭,用来是图像的绘制更加圆润。我们还可以在初始化的时候设置Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);。
setStrokeWidth(float width) - 设置线条粗细
setStrokeMiter(float miter) - 设置 MITER 型拐角的延长线的最大值
setDither(boolean dither)
设置图像的抖动。抖动是指把图像从较高色彩深度(即可用的颜色数)向较低色彩深度的区域绘制时,在图像中有意地插入噪点,通过有规律地扰乱图像来让图像对于肉眼更加真实的做法。当然这个效果旨在低位色的时候比较有用,例如,ARGB_4444 或者 RGB_565,不过现在Android默认的色彩深度都是32位的ARGB_8888,这个方法的效果没有那么明显。
setFilterBitmap(boolean filter)
设置是否使用双线性过滤来绘制 Bitmap .图像在放大绘制的时候,默认使用的是最近邻插值过滤,这种算法简单,但会出现马赛克现象;而如果开启了双线性过滤,就可以让结果图像显得更加平滑。
private fun setFilterBitmap(canvas: Canvas) {
val mainBitmap =
BitmapFactory.decodeResource(getResources(), R.drawable.rank_banner_avatar_5);
val matrix = Matrix()
matrix.setScale(5F, 5F);//放大矩阵
mRedPaint.setFilterBitmap(false);
canvas.drawBitmap(mainBitmap, matrix, mRedPaint);
canvas.translate(-500F, -300F);
mRedPaint.setFilterBitmap(true);
canvas.drawBitmap(mainBitmap, matrix, mRedPaint);
}
setPathEffect(PathEffect effect)
设置图形的轮廓效果。Android有六种PathEffect:
- CornerPathEffect:将拐角绘制成圆角
- DiscretePathEffect:将线条进行随机偏离
- DashPathEffect:绘制虚线
- PathDashPathEffect:使用指定的Path来绘制虚线
- SumPathEffect:组合两个PathEffect,叠加应用
- ComposePathEffect:组合两个PathEffect,叠加应用。
简单的直接看这个demo
private void initPath() {
mPath = new Path();
mPath.moveTo(10, 50);
for (int i = 0; i < 20; i++) {
mPath.lineTo(i * 35, (float) (Math.random() * 100));
}
pathEffects[0] = null;
pathEffects[1] = new CornerPathEffect(20);
pathEffects[2] = new DashPathEffect(new float[]{20, 10}, mPhase);
pathEffects[3] = new DiscretePathEffect(5.0f, 10.0f);
Path path = new Path();
path.addRect(0, 0, 8, 8, Path.Direction.CCW);
pathEffects[4] = new PathDashPathEffect(path, 20, mPhase, PathDashPathEffect.Style.ROTATE);
pathEffects[5] = new ComposePathEffect(pathEffects[2], pathEffects[1]);
pathEffects[6] = new SumPathEffect(pathEffects[4], pathEffects[3]);
}
对应的7个就是以下的效果
CornerPathEffect(float radius)
radius 圆角半径
/**
* 圆角折线
*
* @param canvas
*/
private fun cornerEffect(canvas: Canvas) {
// 不断改变mEffectCorner圆角大小可以看到效果
val mEffectCorner = 20f
mEffectPaint.setPathEffect(CornerPathEffect(mEffectCorner))
val path = Path()
path.moveTo(550F, 550F);
path.lineTo(900F, 300F);
path.lineTo(1000F, 550F);
canvas.drawPath(path, mEffectPaint);
//蓝色辅助线
val tempPaint = Paint();
tempPaint.setStyle(Paint.Style.STROKE);
tempPaint.setColor(Color.BLUE);
tempPaint.setStrokeWidth(4F);
tempPaint.setPathEffect(DashPathEffect(floatArrayOf(20F, 20F), 0F));
val helpPath = Path();
helpPath.moveTo(550F, 550F);
helpPath.lineTo(900F, 300F);
helpPath.lineTo(1000F, 550F);
canvas.drawPath(helpPath, tempPaint);
}
上面的是radius =200f,下面的radius = 20f
DiscretePathEffect(float segmentLength, float deviation) -离散路径(小段长,偏移量)
float segmentLength:用来拼接每个线段的长度,float deviation:偏离量
第一个参数:将原来的路径切成多长的线段,越小,所切成的小线段越多
第二参数:被切成的每个小线段的可偏移距离。越大,每个线段的可偏移距离就越大。
/**
* 离散路径
*
* @param canvas
*/
private fun discreteEffect(canvas: Canvas) {
canvas.save();//保存画布状态
canvas.translate(0F, 100F);
val path = Path();
// 定义路径的起点
path.moveTo(100F, 0F);
path.lineTo(600F, 100F);
path.lineTo(1000F, 0F);
//第一个参数:将原来的路径切成多长的线段,越小,所切成的小线段越多
//第二参数:被切成的每个小线段的可偏移距离。越大,每个线段的可偏移距离就越大。
mEffectPaint.setPathEffect(DiscretePathEffect (2F, 5F));
mEffectPaint.setStrokeWidth(2F);
canvas.drawPath(path, mEffectPaint);
canvas.translate(0F, 100F);
mEffectPaint.setPathEffect( DiscretePathEffect (2F, 20F));
canvas.drawPath(path, mEffectPaint);
canvas.translate(0F, 100F);
mEffectPaint.setPathEffect( DiscretePathEffect (20F, 20F));
canvas.drawPath(path, mEffectPaint);
canvas.restore();//重新储存画布状态
}
从上到下分别参数是(2,5),(2,20),(20,20)
DashPathEffect(float[] intervals, float phase)
float[] intervals:指定了虚线的格式,数组中元素必须为偶数(最少是 2 个),按照「画线长度、空白长度、画线长度、空白长度」……的顺序排列,float phase:虚线的偏移量
下面会动的线是不断改变偏移量:mDashOffSet的结果
/**
* 虚线测试
*
* @param canvas
*/
private fun dashEffect(canvas: Canvas) {
val mEffectPaint = Paint(mRedPaint)
mEffectPaint.style = Paint.Style.STROKE
mEffectPaint.strokeWidth = 40f
// 显示100,隐藏50,显示50,隐藏50,的循环
mEffectPaint.pathEffect = DashPathEffect(floatArrayOf(100F, 50F, 50F, 50F), 0F)
val path = Path()
path.moveTo(100F, 650F)
path.lineTo(1000F, 650F)
canvas.drawPath(path, mEffectPaint)
//显示100,隐藏50,显示60,隐藏50,的循环,偏移:mDashOffSet
val mDashOffSet = 50f // 100f的一半
mEffectPaint.pathEffect = DashPathEffect(floatArrayOf(100F, 50F, 50F, 50F), mDashOffSet)
val pathOffset50 = Path()
pathOffset50.moveTo(100F, 750F)
pathOffset50.lineTo(1000F, 750F)
canvas.drawPath(pathOffset50, mEffectPaint)
}
PathDashPathEffect(Path shape, float advance, float phase, PathDashPathEffect.Style style)
Path shape:用来绘制的Path,float advance:两个相邻Path段起点间的间隔,float phase:虚线的偏移量, PathDashPathEffect.Style style:指定拐弯改变的时候 shape 的转换方式:TRANSLATE:位移、ROTATE:旋转、MORPH:变体
//Path shape:表示[路径点样],这里是一个五角星
//float advance:表示两个[路径点样]间的距离
//float phase:路径绘制偏移距离
//Style style:表示在遇到转角时的过渡样式
// ----Style.ROTATE表示通过旋转[路径点样]来过渡转角;
// ----Style.MORPH表示通过变形[路径点样]来过渡转角;
// ----Style.TRANSLATE表示通过位移[路径点样]来过渡转角。
//一个正四方形,为了更好的看到pathdash变形的样子
public static Path commonPath() {
Path path = new Path();
path.moveTo(50, 50);
path.lineTo(100,50);
path.lineTo(100,100);
path.lineTo(50,100);
path.lineTo(50,50);//最后这个不用写path也会自动闭合
path.close();
return path;
}
/**
* 路径点样路径样式
*
* @param canvas
*/
private fun PathDashEffect(canvas: Canvas) {
canvas.save();
val path = Path();
// 定义路径的起点
path.moveTo(100F, 100F);
path.lineTo(300F, 400F);
path.lineTo(1000F, 300F);
//变形过渡
// val shape = CommonPath.nStarPath(5, 16F, 4F)
val shape = CommonPath.commonPath()
val mDashOffSet = 0F
//Path shape:表示[路径点样],这里是一个五角星
//float advance:表示两个[路径点样]间的距离
//float phase:路径绘制偏移距离
//Style style:表示在遇到转角时的过渡样式
// ----Style.ROTATE表示通过旋转[路径点样]来过渡转角;
// ----Style.MORPH表示通过变形[路径点样]来过渡转角;
// ----Style.TRANSLATE表示通过位移[路径点样]来过渡转角。
mEffectPaint.pathEffect = PathDashPathEffect(
shape, 60F, mDashOffSet, PathDashPathEffect.Style.ROTATE
)
canvas.drawPath(path, mEffectPaint);
canvas.restore();
//旋转过渡
canvas.save();
canvas.translate(0F, 200F);
mEffectPaint.setPathEffect(
PathDashPathEffect(
shape, 60F, mDashOffSet, PathDashPathEffect.Style.MORPH
)
);
canvas.drawPath(path, mEffectPaint);
canvas.restore();
//移动过渡
canvas.save();
canvas.translate(0F, 500F);
mEffectPaint.setPathEffect(
PathDashPathEffect(
shape, 60F, mDashOffSet, PathDashPathEffect.Style.TRANSLATE
)
);
canvas.drawPath(path, mEffectPaint);
canvas.restore();
}
从上到下分别是
- Style.ROTATE表示通过旋转[路径点样]来过渡转角,随着路径的旋转而旋转
- Style.MORPH表示通过变形[路径点样]来过渡转角,MORPH和ROTATE基本一样,会随着路径的旋转而旋转,但是在拐点的地方,会增加平滑度。
- Style.TRANSLATE表示通过位移[路径点样]来过渡转角,不会随着路径的旋转而旋转
注意看第一个的第一个正方形,实际上他旋转了,如果是Style.TRANSLATE正方形就是正方形,因为是Style.ROTATE 所以这个正方形旋转对向了拐角
SumPathEffect(PathEffect first, PathEffect second)
PathEffect first:同时应用的PathEffect,PathEffect second:同时应用的PathEffect
ComposePathEffect是将两个子类进行组合,让你看到的是组合后的样子,而且SumPathEffect则是将这两个子类全的效果全显示出来,然后叠加在一起
/**
* 叠加样式
*
* @param canvas
*/
private fun sumEffect(canvas: Canvas) {
mBluePaint.setStyle(Paint.Style.STROKE)
mBluePaint.setStrokeWidth(4F)
canvas.save()
canvas.translate(0F, 100F)
val path = Path()
// 定义路径的起点
path.moveTo(100F, 80F);
path.lineTo(600F, 300F);
path.lineTo(1000F, 80F);
val dPathEffect = DashPathEffect(floatArrayOf(10F, 10F), 0F)
val cPathEffect = CornerPathEffect(100F)
mBluePaint.pathEffect = SumPathEffect(dPathEffect, cPathEffect)
canvas.drawPath(path, mBluePaint)
canvas.restore()
}
ComposePathEffect(PathEffect outerpe, PathEffect innerpe)
PathEffect outerpe:后应用的PathEffect,PathEffect innerpe:先应用的PathEffect
/**
* 叠加样式
*
* @param canvas
*/
private fun composeEffect(canvas: Canvas) {
mBluePaint.setStyle(Paint.Style.STROKE)
mBluePaint.setStrokeWidth(4F)
canvas.save()
canvas.translate(0F, 100F)
val path = Path()
val mDashOffSet = 0f
// 定义路径的起点
path.moveTo(100F, 80F);
path.lineTo(600F, 300F);
path.lineTo(1000F, 80F);
val shape = CommonPath.commonPath()
val effect1 = PathDashPathEffect(shape, 40F, mDashOffSet, PathDashPathEffect.Style.ROTATE)
val effect2 = DiscretePathEffect(20F, 20F);
mBluePaint.setPathEffect(ComposePathEffect(effect1, effect2))//离散效果+样点效果
mGreenPaint.setPathEffect(effect2)
canvas.drawPath(path, mBluePaint)
canvas.drawPath(path, mGreenPaint)
canvas.restore()
}
就是在两个effect的基础上叠加,绿色的线是辅助用,可以看到只用了DiscretePathEffect离散效果,
蓝色的的是使用了ComposePathEffect,在离散的线上使用了DiscretePathEffect
setShadowLayer(float radius, float dx, float dy, int shadowColor)
设置阴影图层,处于目标下层图层。float radius:阴影半径,float dx:阴影偏移量,float dy:阴影偏移量,int shadowColor:阴影颜色.注:在硬件加速开启的情况下, setShadowLayer() 只支持文字的绘制,文字之外的绘制必须关闭硬件加速才能正常绘制阴影。如果 shadowColor 是半透明的,阴影的透明度就使用 shadowColor 自己的透明度;而如果 shadowColor 是不透明的,阴影的透明度就使用 paint 的透明度。
setMaskFilter(MaskFilter maskfilter)
设置图层遮罩层,处于目标上层图层。MaskFilter有两个子类:
- BlurMaskFilter:模糊效果
- EmbossMaskFilter:浮雕效果
注:在硬件加速开启的情况下, setMaskFilter(MaskFilter maskfilter)只支持文字的绘制,文字之外的绘制必须关闭硬件加速才能正常绘制阴影。关闭硬件加速可以调用
View.setLayerType(View.LAYER_TYPE_SOFTWARE, null)
或者在Activity标签里设置android:hardwareAccelerated="false"
。
BlurMaskFilter设置画笔模糊阴影效果
mPaint.setMaskFilter(new BlurMaskFilter(20f, BlurMaskFilter.Blur.SOLID));
复制代码
参数1:模糊延伸半径,必须>0;
参数2:有四种枚举
NORMAL,同时绘制图形本身内容+内阴影+外阴影,正常阴影效果
INNER,绘制图形内容本身+内阴影,不绘制外阴影
OUTER,不绘制图形内容以及内阴影,只绘制外阴影
SOLID,只绘制外阴影和图形内容本身,不绘制内阴影
BlurMaskFilter绘制的Bitmap基本完全不受影响
下节看点:Canvas:drawLine/drawPoint
Canvas实现了Android 2D图形的绘制,底层基于Skia实现,Canvas提供了丰富的对象绘制方法,一般都以drawXXX()打头,绘制的对象包括:
弧线(Arcs)
颜色(Argb、Color)
位图(Bitmap)
圆(Circle)
点(Point)
线(Line)
矩形(Rect)
图片(Picture)
圆角矩形(RoundRect)
文本(Text)
顶点(Vertices)
路径(Path)
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!