Android绘图阴影渐变和位图运算处理
Posted mChenys
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android绘图阴影渐变和位图运算处理相关的知识,希望对你有一定的参考价值。
目录
本章将向您介绍阴影、渐变和位图运算等技术。阴影只是一个狭义的说法,实际上也包括发光等效果;android 也供了强大的渐变功能,渐变能为物体带来更真实的质感,比如可以用渐变绘制一颗五子棋或一根金属圆棒;位图运算就更有趣了,Android 为 Bitmap 的运算供了多达16 种运算方法,获得的结果也不尽相同。不过,主要还是在于灵活应用。
一、阴影
可以为文字和图形指定阴影(Shader)。在绘图中,有一个叫 layer(层)的概念,默认情况下,我们的文字和图形绘制在主层(main layer)上,其实也可以将内容绘制在新建的 layer 上。实际上阴影就是在 main layer 的下面添加了一个阴影层(shader layer),可以为阴影指定模糊度、偏移量和阴影颜色。
Paint 类定义了一个名为 setShadowLayer 的方法:
/**
* 设置阴影
* @param radius 阴影半径
* @param dx x方向阴影的偏移量
* @param dy y方向阴影的偏移量
* @param shadowColor 阴影的颜色
*/
public void setShadowLayer(float radius, float dx, float dy, int shadowColor);
阴影layer显示阴影时,shader layer 有两种类型:View.LAYER_TYPE_SOFTWARE 和View.LAYER_TYPE_HARDWARE,layer的默认类型为 LAYER_TYPE_HARDWARE,但阴影只能在View.LAYER_TYPE_SOFTWARE 环境下工作, 所以我们需要调用View 类的如下方法为Paint对象指定层的类型为View.LAYER_TYPE_SOFTWARE
public void setLayerType(int layerType, Paint paint)
1.1 案例-为文字添加阴影和发光效果
// 自定义View,后面的案例都是使用此View进行修改
public class MyView extends View
public MyView(Context context)
this(context, null);
public MyView(Context context, AttributeSet attrs)
this(context, attrs, 0);
public MyView(Context context, AttributeSet attrs, int defStyleAttr)
super(context, attrs, defStyleAttr);
init();
private Paint paint;
private void init()
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setAntiAlias(true);
// 启用阴影
this.setLayerType(View.LAYER_TYPE_SOFTWARE, paint); // 参数2可以传null, 否则使用同一个paint的地方都会关联LAYER_TYPE_SOFTWARE
paint.setTextSize(80);
// 指定阴影的半径为10px,x和y的偏移量为1px,阴影颜色为红色
paint.setShadowLayer(10, 1, 1, Color.RED);
@Override
public void onDraw(Canvas canvas)
// 绘制文本
canvas.drawText("Android 开发", 50, 100, paint);
// 修改阴影效果
paint.setShadowLayer(10, 5, 5, Color.BLUE);
// 再次绘制文本
canvas.drawText("Android 绘图技术", 50, 220, paint);
效果图:
上面代码中 , 我们绘制了两行文字, 第一 行 “Android 开 发 ”为红色发光效果,setShadowLayer(10, 1, 1, Color.RED)语句定义了一个模糊半径为 10、x 方向和 y 方向偏移量都为 1的红色阴影,当偏移量足够小时,我们看到的其实是发光效果。
paint.setShadowLayer(10, 5, 5,Color.BLUE)语句定义了一个模糊半径为 10、x 方向和 y 方向偏移量为 5 的蓝色阴影。
注意阴影必须在 LAYER_TYPE_SOFTWARE 模式下才能工作。
最后要强调的是,一旦定义了阴影层,接下来的所有绘制都会带阴影效果了(假设使用了同一个paint),如果想取消阴影,请将 setShadowLayer()方法的 radius 参数设置为 0,并且在调用View的setLayerType方法的第二个参数Paint传入null而不是全局的Paint; 或者调用paint的clearShadowLayer方法也行。
二、 渐变
渐变(Gradient)是绘图过程中颜色或位图以特定规律进行变化,能增强物体的质感和审美情趣。生活中的渐变非常多,例如公路两边的电线杆、树木、建筑物的阳台、铁轨的枕木延伸到远方等等,很多的自然理象都充满了渐变的形式特点。Android 同样对渐变进行了完善支持,通过渐变,可以绘制出更加逼真的效果。
Graphics2D 渐变种类有:
- 线性渐变:LinearGradient
- 径向渐变:RadialGradient
- 扫描渐变:SweepGradient
- 位图渐变:BitmapShader
- 混合渐变:ComposeShader
其中,线性渐变、径向渐变和扫描渐变属于颜色渐变,指定 2 种或 2 种以上的颜色,根据颜色过渡算法自动计算出中间的过渡颜色,从而形成渐变效果,对于开发人员来说,无需关注中间的渐变颜色。位图渐变则不再是简单的颜色渐变,而是以图片做为贴片有规律的变化,类似于壁纸平铺。混合渐变则能将多种渐变进行组合,实现更加复杂的渐变效果。
3种渐变模式
如果 A、B 分别代表 2 种不同的颜色,我们将渐变分为三种:
AABB 型:A、B 两种颜色只出现一次,通过 TileMode 类的 CLAMP 常量来表示, 效果就是:如果超出规定的区域就重复边缘的效果;
ABBA 型:A、B 两种颜色镜像变化,通过 TileMode 类的 MIRROR 常量来表示,效果就是:以镜像的方式显示;
ABAB 型:A、B 两种颜色重复变化,通过 TileMode 类的 REPEAT 常量来表示,效果就是:在竖直和水平方向上重复。
这三种不同的TileMode的渐变模式的效果如下图所示:
定义渐变时,必须指定一个渐变区域,根据定义的渐变内容和渐变模式填满该区域。每一种渐变都被定义成了一个类,他们都继承自同一个父类—Shader。绘图时,调用 Paint 类的setShader(Shader shader)方法指定一种渐变类型,绘制出来的绘图填充区域都将使用指定的渐变颜色或位图进行填充。
2.1 线性渐变(LinearGradient)
线性渐变(LinearGradient)根据指定的角度、颜色和模式使用渐变颜色填充绘图区域。我们必须定义两个点(x0,y0)和(x1,y1),渐变方向就是从起点指向终点,如下图所示:
如何通过坐标设置渐变方向?
通过坐标可以轻松实现,渐变方向的控制:
(0,0)->(0,400) // 从上到下
(0,400)->(0,0) // 从下到上
(0,0)->(getMeasuredWidth(),0) // 表示从左到右
(getMeasuredWidth(),0)->(0,0) // 表示从右到左
(0,0)-> (getMeasuredWidth(),getMeasuredHeight()) // 斜角,从左上角到右下角
(0,getMeasuredHeight())-> (getMeasuredWidth(),0) // 斜角,从左下角到右上角
LinearGradient 的构造方法如下:
/**
* 构造线型渐变
* @param x0 用于决定线性方向的第一个点的坐标(x0,y0)
* @param y0
* @param x1 用于决定线性方向的第二个点的坐标(x1,y1)
* @param y1
* @param color0 第一种颜色
* @param color1 第二种颜色
* @param tile 渐变模式
*/
public LinearGradient(float x0, float y0, float x1, float y1, int color0, int color1, TileMode tile)
假设我们绘制了三个矩形,第一个矩形的渐变区域与矩形恰好一致,第二个矩形的渐变区域大于矩形区域,第三个矩形的渐变区域小于矩形区域,均采用 TileMode 的 CLAMP 模式,渐变方向都是左上角->右下角, 代码如下:
private Paint paint;
private static final int OFFSET = 100;
private Rect rect;
// 三种不同的渐变效果
private LinearGradient lg1;
private LinearGradient lg2;
private LinearGradient lg3;
private void init()
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setAntiAlias(true);
// 定义矩形区域
rect = new Rect(100, 100, 400, 300);
// 定义和矩形区域大小相等的渐变
lg1 = new LinearGradient(rect.left, rect.top, rect.right, rect.bottom,
Color.RED, Color.BLUE, Shader.TileMode.CLAMP);
// 放大渐变矩形
Rect rect2 = new Rect(rect);
// 如果 dx 和 dy 是负的,则两边向外移动,使矩形更宽
rect2.inset(-100, -100);
lg2 = new LinearGradient(
rect2.left, rect2.top, rect2.right, rect2.bottom,
Color.RED, Color.BLUE, Shader.TileMode.CLAMP);
// 缩小渐变矩形
Rect rect3 = new Rect(rect);
// 如果 dx 和 dy 是正的,则两边向内移动,使矩形更窄
rect3.inset(100, 100);
lg3 = new LinearGradient(
rect2.left, rect2.top, rect2.right, rect2.bottom,
Color.RED, Color.BLUE, Shader.TileMode.CLAMP);
@Override
public void onDraw(Canvas canvas)
// 绘制第一个矩型,渐变区域和矩型区域一致
paint.setShader(lg1);
canvas.drawRect(rect, paint);
//坐标往下移动
canvas.translate(0, rect.height() + OFFSET);
paint.setShader(lg2);
// 绘制第二个矩形,渐变区域大于矩形区域
canvas.drawRect(rect, paint);
//坐标往下移动
canvas.translate(0, rect.height() + OFFSET);
paint.setShader(lg3);
// 绘制第三个矩形,渐变区域小于矩形区域
canvas.drawRect(rect, paint);
效果图:
改成边框模式的效果:
如果两种颜色无法满足绘图需求,LinearGradient 支持三种或者三种以上颜色的渐变,对应的构造方法如下:
/**
* 多颜色的线性渐变
* @param x0 起始点的坐标
* @param y0
* @param x1 终止点的坐标
* @param y1
* @param colors 多种颜色
* @param positions 颜色的位置(比例)
* @param tile 渐变模式
*/
public LinearGradient(float x0, float y0, float x1, float y1, int colors[], float positions[], TileMode tile)
参数 colors 和 positions 都是数组,前者用于指定多种颜色,后者用于指定每种颜色的起始比例位置。positions 数组中的元素个数与 colors 要相同,且是 0 至 1 的数值,[0,1]是临界区,如 果小于 0 则当 0 处理,如果大于 1 则当 1 处理。假如在绘图区域和渐变区域大小相同的情况下,colors 包含了三种颜色:red、yellow、green,在渐变区域中这三种颜色的起始比例位置为 0、0.3、 1,则颜色渐变如下图所示:
如果在比例位置为 0.2、0.5、0.8,则颜色渐变如下图所示:
2.1.1 案例- 实现圆角矩形环形渐变
效果图如下:
可以看到渐变颜色有4种, 并且渐变方向是左下角->右上角等分比例, 这里实现圆角矩形环用到了path的图形运算来实现,具体是通过大的圆角矩形减去小的圆角矩形, 代码如下:
class GradientCornerView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
def: Int = 0,
) : View(
context, attrs, def
)
// 外圆角
private var outRadius = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 20f, resources.displayMetrics)
// 内圆角
private var innerRadius = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 18f, resources.displayMetrics)
private var borderWidth = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 4f, resources.displayMetrics)
private val outPath = Path()
// 是否立即显示
private var showBorder = true
set(value)
field = value
invalidate()
// 设置画笔
private val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply
style = Paint.Style.FILL
strokeJoin = Paint.Join.ROUND
strokeCap = Paint.Cap.ROUND
strokeJoin = Paint.Join.ROUND
strokeCap = Paint.Cap.ROUND
private val rect = RectF()
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int)
super.onSizeChanged(w, h, oldw, oldh)
// 设置区域区域
rect.set(0f, 0f, w.toFloat(), h.toFloat())
// 添加渐变从左下角->右上角渐变
paint.shader = LinearGradient(
rect.left, rect.bottom, rect.right, rect.top,
// 支持多个颜色值
intArrayOf(
Color.parseColor("#00A6FF"),
Color.parseColor("#4BC2C2"),
Color.parseColor("#18D19B"),
Color.parseColor("#AFE646")
), floatArrayOf(
// 设置等分比例
0f, 0.25f, 0.75f, 1f
), Shader.TileMode.CLAMP
)
val innerPath = Path()
// 添加外圆角矩形
outPath.addRoundRect(rect, outRadius, outRadius, Path.Direction.CCW)
// 缩小矩形区域
rect.inset(borderWidth, borderWidth)
// 添加内圆角矩形
innerPath.addRoundRect(rect, innerRadius, innerRadius, Path.Direction.CCW)
// 通过path的op操作, 用外圆角矩形区域-内圆角矩形区域 = 圆角矩形环区域
outPath.op(innerPath, Path.Op.DIFFERENCE)
override fun onDraw(canvas: Canvas)
if (showBorder)
canvas.drawPath(outPath, paint)
2.2 径向渐变(RadialGradient)
径向渐变是以指定的点为中心,向四周以渐变颜色进行圆周扩散,和线性渐变一样,支持两种或多种颜色。径向渐变的示意图如图:
径向渐变的主要构造方法如下:
/**
* 径向渐变
* @param x 中心点x坐标
* @param y 中心点y坐标
* @param radius 渐变半径
* @param color0 起始颜色
* @param color1 结束颜色
* @param tile 渐变模式
*/
public RadialGradient(float x, float y, float radius, int color0, int color1, TileMode tile)
支持 3 种或 3 种以上颜色的渐变的构造方法如下:
/**
*
* @param x 中心点x坐标
* @param y 中心点y坐标
* @param radius 渐变半径
* @param colors 多种颜色
* @param positions 颜色的位置(比例)
* @param tile 渐变模式
*/
public RadialGradient(float x, float y, float radius, int colors[], float positions[], TileMode tile)
接下来我们在 View 上绘制相同大小的正方形和圆,使用一致的径向渐变,模式为TileMode.MIRROR,在大部分时候,镜像模式的渐变效果看起来会更舒服更讨人喜欢。
private Paint paint;
private Rect rect;
private RectF oval;
private void init()
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setAntiAlias(true);
// 定义矩形区域
rect = new Rect(100, 100, 500, 500);
// 定义圆的区域
oval = new RectF(rect);
RadialGradient rg = new RadialGradient(300, 300, 200, Color.RED, Color.GREEN, Shader.TileMode.MIRROR);
paint.setShader(rg);
@Override
public void onDraw(Canvas canvas)
// 绘制矩形
canvas.drawRect(rect, paint);
// 平移
canvas.translate(0, 500);
// 绘制圆,虽然用的是drawOval,但是传入的矩形是正方型,所以绘制出来的就是正圆
canvas.drawOval(oval, paint);
上面代码中,我们定义了一个 RadialGradient 对象,中心点坐标为(300, 300),正好是正方形和圆的中心,渐变半径为 200,意味着渐变区域与正方形和圆的大小相同。效果图:
2.2.1 案例-使用径向渐变绘制棋盘的棋子
利用径向渐变,我们可以画出五子棋的棋子,五子棋分为黑色和白色两种不同的棋子,为了画出更逼真的效果,需要考虑棋子的反光效果,光点不能是正中心,而应该向右下角偏移;同时,为棋子加上阴影,棋子似乎跃然纸上。黑色棋子使用黑白两色绘制,白色棋子则使用灰白两色绘制,效果如下图所示。
有了棋子,就应该有棋盘,棋盘是一个 m*n 的矩阵,按照一定的规律画好水平线和垂直线就可以了
private Paint paint;
private float chessSize = 100; // 棋子的大小
private void init()
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setStyle(Paint.Style.FILL);
paint.setColor(Color.GRAY);
enum ChessColor
WHITE, BLACK
@Override
protected void onDraw(Canvas canvas)
int row = (int) (getHeight() / chessSize);
int column = (int) (getWidth() / chessSize);
// 棋盘居中
int offsetX = (int) ((getWidth() - column * chessSize) * 0.5f);
int offsetY = (int) ((getHeight() - row * chessSize) * 0.5f);
canvas.translate(offsetX, offsetY);
// 绘制棋盘
drawChessBox(canvas, row, column);
// 绘制棋子
drawChess(canvas, 3, 5, ChessColor.BLACK);
drawChess(canvas, 4, 6, ChessColor.WHITE);
drawChess(canvas, 5, 5, ChessColor.BLACK);
drawChess(canvas, 5, 6, ChessColor.WHITE);
// 绘制棋盘
private void iOS 2D绘图 (Quartz2D)之阴影和渐变(shadow,Gradient)
一起Talk Android吧(第四百二十一回:绘图中添加阴影)
一起Talk Android吧(第四百二十二回:绘图中添加发光效果)
Android UIPaint Gradient 渐变渲染 ③ ( RadialGradient 环形渐变渲染 | 在给定中心和半径的情况下绘制径向渐变的着色器 | 水波纹效果 )