自定义 View基础(三)

Paint 详解

Paint 的 API 大致可以分为 4 类:

  • 颜色
  • 效果
  • drawText() 相关
  • 初始化

1 颜色

Canvas 绘制的内容,有三层对颜色的处理:


canvas_flow.png

1.1 基本颜色

  • 像素的基本颜色,根据绘制内容的不同而有不同的控制方式: Canvas 的颜色填充类方法 drawColor/RGB/ARGB() 的颜色,是直接写在方法的参数里,通过参数来设置的;
  • drawBitmap() 的颜色,是直接由 Bitmap 对象来提供的;
  • 图形和文字的绘制,它们的颜色就需要使用 paint 参数来额外设置(drawCircle() / drawPath() / drawText() ...)。

Paint 设置颜色的方法有两种:

  • 一种是直接用 Paint.setColor/ARGB() 来设置颜色;
  • 另一种是使用 Shader 来指定着色方案。
1.1.1 直接设置颜色
1.1.1.1 setColor(int color)
paint.setColor(Color.parseColor("#000000"))
canvas.drawRect(left, top, right, bottom, paint)

paint.setColor(Color.parseColor("#90909009"))
canvas.drawLine(left, top, right, bottom, paint)

paint.setColor(Color.parseColor("#567898"))
canvas.drawText("HenCoder", 100, 100, paint)
1.1.1.2 setARGB(int a, int r, int g, int b)

参数用的是更直接的三原色与透明度的值

paint.setARGB(a, r, g, b)
canvas.drawRect(left, top, right, bottom, paint)
paint.setARGB(a, r, g, b)
canvas.drawLine(left, top, right, bottom, paint)
1.1.2 setShader(Shader shader) 设置 Shader

Shader 着色器,也是用于设置绘制颜色的。着色器设置的是一个颜色方案,或者说是一套着色规则。当设置了 Shader 之后,Paint 在绘制图形和文字时就不使用 setColor/ARGB() 设置的颜色了,而是使用 Shader 的方案中的颜色。

在 Android 的绘制里使用 Shader ,并不直接用 Shader 这个类,而是用它的几个子类。具体来讲有 LinearGradient RadialGradient SweepGradient BitmapShader ComposeShader :

1.1.2.1 LinearGradient 线性渐变
    val shader = LinearGradient(x0, y0, x1, y1, Color.parseColor("#E91E63"),Color.parseColor("#123456"), Shader.TileMode.CLAMP)
    paint.setShader(shader)
    canvas.drawCircle(cx, cy, radius, paint)

置两个点和两种颜色,以这两个点作为端点,使用两种颜色的渐变来绘制颜色。

构造方法:
  • LinearGradient(float x0, float y0, float x1, float y1, int color0, int color1, Shader.TileMode tile) 。
参数:
  • x0 y0 x1 y1:渐变的两个端点的位置
  • color0 color1 是端点的颜色tile:端点范围之外的着色规则,类型是 TileMode。
TileMode 一共有 3 个值可选: CLAMP, MIRROR 和 REPEAT。
  • CLAMP 会在端点之外延续端点处的颜色;
  • MIRROR 是镜像模式;
  • REPEAT 是重复模式。
1.1.2.2 RadialGradient 辐射渐变

辐射渐变就是从中心向周围辐射状的渐变。

    val shader = RadialGradient(cx, cy, radius, Color.parseColor("#111111"), Color.parseColor("#2222222"), Shader.TileMode.CLAMP)
    paint.setShader(shader)
    canvas.drawCircle(cx, cy, radius, paint)
构造方法:
  • RadialGradient(float centerX, float centerY, float radius, int centerColor, int edgeColor, TileMode tileMode)。
参数:
  • centerX centerY:辐射中心的坐标
  • radius:辐射半径
  • centerColor:辐射中心的颜色
  • edgeColor:辐射边缘的颜色
  • tileMode:辐射范围之外的着色模式。
1.1.2.3 SweepGradient 扫描渐变
    val shader = SweepGradient(cx, cy, Color.parseColor("#33333"),Color.parseColor("#444444"))
    paint.setShader(shader)
    canvas.drawCircle(cx, cy, radius, paint)
构造方法:
  • SweepGradient(float cx, float cy, int color0, int color1)
参数:
  • cx cy :扫描的中心
  • color0:扫描的起始颜色
  • color1:扫描的终止颜色
1.1.2.4 BitmapShader

用 Bitmap 的像素来作为图形或文字的填充

    val bitmap = BitmapFactory.decodeResource(resources, R.drawable.batman)
    val shader = BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)
    paint.setShader(shader)
    canvas.drawCircle(cx, cy, radius, paint)

![1NI1{IN{FDBB[]M2{E4_WM.png

构造方法:
  • BitmapShader(Bitmap bitmap, Shader.TileMode tileX, Shader.TileMode tileY)
参数:
  • bitmap:用来做模板的 Bitmap 对象
  • tileX:横向的 TileMode
  • tileY:纵向的 TileMode。
1.1.2.5 ComposeShader 混合着色器

所谓混合,就是把两个 Shader 一起使用。

    // 第一个 Shader:头像的 Bitmap
    val bitmap1 = BitmapFactory.decodeResource(resources, R.drawable.batman)
    val shader1 = BitmapShader(bitmap1, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)

// 第二个 Shader:从上到下的线性渐变(由透明到黑色)
    val bitmap2 = BitmapFactory.decodeResource(resources, R.drawable.batman_logo)
    val shader2 = BitmapShader(bitmap2, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)

// ComposeShader:结合两个 Shader
    val shader = ComposeShader(shader1, shader2, PorterDuff.Mode.SRC_OVER)
    paint.setShader(shader)

    canvas.drawCircle(cx, cy, radius, paint)

上面这段代码中两个 BitmapShader 来作为 ComposeShader() 的参数,而 ComposeShader() 在硬件加速下是不支持两个相同类型的 Shader 的,所以这里也需要关闭硬件加速才能看到效果。

构造方法:
  • ComposeShader(Shader shaderA, Shader shaderB, PorterDuff.Mode mode)
参数:
  • shaderA, shaderB:两个相继使用的
  • Shadermode: 两个 Shader 的叠加模式,即 shaderA 和 shaderB 应该怎样共同绘制。它的类型是 PorterDuff.Mode 。
PorterDuff.Mode

PorterDuff.Mode 一共有 17 个,可以分为两类:

  • Alpha 合成 (Alpha Compositing)
  • 混合 (Blending)

第一类,Alpha 合成,其实就是 「PorterDuff」 这个词所指代的算法。 「PorterDuff」 并不是一个具有实际意义的词组,而是两个人的名字(准确讲是姓)。这两个人当年共同发表了一篇论文,描述了 12 种将两个图像共同绘制的操作(即算法)。而这篇论文所论述的操作,都是关于 Alpha 通道(也就是我们通俗理解的「透明度」)的计算的,后来人们就把这类计算称为Alpha 合成 ( Alpha Compositing ) 。

看下效果吧。效果直接盗 Google 的官方文档吧。

源图像和目标图像:
![RIE{0$QUU]EJ~]3F5OE8CE.png

Alpha 合成:

![WBOFB_3R)MM~M78{LQLDFO.png

9%JC85WB4VTO93Y2)QXJEHA.png

第二类,混合,也就是 Photoshop 等制图软件里都有的那些混合模式(multiply darken lighten 之类的)。这一类操作的是颜色本身而不是 Alpha 通道,并不属于 Alpha 合成,所以和 Porter 与 Duff 这两个人也没什么关系,不过为了使用的方便,它们同样也被 Google 加进了 PorterDuff.Mode 里。

官方文档![C35C}H87(IA@E~L8M@ZCH8.png

结论

从效果图可以看出,Alpha 合成类的效果都比较直观,基本上可以使用简单的口头表达来描述它们的算法(起码对于不透明的源图像和目标图像来说是可以的),例如 SRC_OVER 表示「二者都绘制,但要源图像放在目标图像的上面」,DST_IN 表示「只绘制目标图像,并且只绘制它和源图像重合的区域」。
而混合类的效果就相对抽象一些,只从效果图不太能看得出它们的着色算法,更看不出来它们有什么用。
所以对于这些 Mode,正确的做法是:对于 Alpha 合成类的操作,掌握他们,并在实际开发中灵活运用。

好了,这些就是几个 Shader 的具体介绍。

setShadowLayer(float radius, float dx, float dy, int shadowColor)

在之后的绘制内容下面加一层阴影。

paint.setShadowLayer(10, 0, 0, Color.RED)
canvas.drawText(text, 80, 300, paint);

方法的参数里, radius 是阴影的模糊范围; dx dy 是阴影的偏移量; shadowColor 是阴影的颜色。
如果要清除阴影层,使用 clearShadowLayer() 。

注意:

在硬件加速开启的情况下, setShadowLayer() 只支持文字的绘制,文字之外的绘制必须关闭硬件加速才能正常绘制阴影。

如果 shadowColor 是半透明的,阴影的透明度就使用 shadowColor 自己的透明度;而如果 shadowColor 是不透明的,阴影的透明度就使用 paint 的透明度。

setMaskFilter(MaskFilter maskfilter)

为之后的绘制设置 MaskFilter。上一个方法 setShadowLayer() 是设置的在绘制层下方的附加效果;而这个 MaskFilter 和它相反,设置的是在绘制层上方的附加效果。

到现在已经有两个 setXxxFilter(filter) 了。前面有一个 setColorFilter(filter) ,是对每个像素的颜色进行过滤;而这里的 setMaskFilter(filter) 则是基于整个画面来进行过滤。

MaskFilter 有两种:
  • BlurMaskFilter
  • EmbossMaskFilter。
BlurMaskFilter

模糊效果的 MaskFilter。

    paint.setMaskFilter(new BlurMaskFilter(50, BlurMaskFilter.Blur.NORMAL))

    canvas.drawBitmap(bitmap, 100, 100, paint)

它的构造方法 BlurMaskFilter(float radius, BlurMaskFilter.Blur style) 中, radius 参数是模糊的范围, style 是模糊的类型。一共有四种:

  • NORMAL: 内外都模糊绘制
  • SOLID: 内部正常绘制,外部模糊
  • INNER: 内部模糊,外部不绘制
  • OUTER: 内部不绘制,外部模糊(什么鬼?)
EmbossMaskFilter

雕效果的 MaskFilter。

    paint.setMaskFilter( EmbossMaskFilter(new float[]{0, 1, 1}, 0.2f, 8, 10))

    canvas.drawBitmap(bitmap, 100, 100, paint)

它的构造方法 EmbossMaskFilter(float[] direction, float ambient, float specular, float blurRadius) 的参数里, direction 是一个 3 个元素的数组,指定了光源的方向; ambient 是环境光的强度,数值范围是 0 到 1; specular 是炫光的系数; blurRadius 是应用光线的范围。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 196,165评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,503评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,295评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,589评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,439评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,342评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,749评论 3 387
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,397评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,700评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,740评论 2 313
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,523评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,364评论 3 314
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,755评论 3 300
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,024评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,297评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,721评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,918评论 2 336

推荐阅读更多精彩内容