利用Android实现光影流动特效的方法详解
作者:岛上码农 发布时间:2023-09-03 01:22:31
前言
Flutter 的画笔类 Paint
提供了很多图形绘制的配置属性,来供我们绘制更丰富多彩的图形。前面几篇我们介绍了 shader
属性来绘制全屏渐变的聊天气泡背景、渐变流动的边框和毛玻璃效果的背景图片,具体可以参考下面几篇文章。
让你的聊天气泡丰富多彩!
手把手教你实现一个流动的渐变色边框
利用光影变化构建立体旋转效果
Flutter 实现背景图片毛玻璃效果
本篇我们引入一个 Paint
类新的属性:maskFilter
,再结合之前的 shader
和动画,看看能玩出什么花样。
MaskFilter 类简介
MaskFilter 类,顾名思义是遮罩过滤器,也就是在绘制过程中给图像加一层遮罩效果,这个遮罩效果是通过某种变换函数实现的。Flutter 官方文档的说明如下,可以看出其实就是对位图的颜色处理。
A mask filter to apply to shapes as they are painted. A mask filter is a function that takes a bitmap of color pixels, and returns another bitmap of color pixels. 遮罩过滤器应用于要绘制的形状,其实就是一个函数,接收一个彩色像素位图,然后返回另一个彩色像素位图。
在 Flutter 里面,目前只提供了一个 MaskFilter 的命名构造函数方式实例化,就是模糊效果。这个模糊效果和我们的图片模糊有点类似,也是使用高斯模糊,只是多了一个模糊样式参数。定义如下:
const MaskFilter.blur(
this._style,
this._sigma,
)
关于这个方法的说明,为了便于理解,将官方的文档翻译如下:
Creates a mask filter that takes the shape being drawn and blurs it. This is commonly used to approximate shadows. The
style
argument controls the kind of effect to draw; Thesigma
argument controls the size of the effect. It is the standard deviation of the Gaussian blur to apply. The value must be greater than zero. The sigma corresponds to very roughly half the radius of the effect in pixels. A blur is an expensive operation and should therefore be used sparingly. The arguments must not be null. 创建一个遮罩过滤器将要绘制的形状进行模糊处理。通常用于实现近似阴影的效果。style
参数控制绘制的效果类型 (BlurStyle
枚举);sigma
参数控制效果的尺寸,实际就是使用的高斯模糊的标准差。sigma
的值必须大于0,这个值在像素上,大致是效果范围的半径值。模糊处理比较耗性能,因此要有节制地使用。
因为效果有点类似阴影,而且比较耗性能,因此如果仅仅是要绘制阴影的话,官方推荐是使用 Canvas 的 drawShadow
方法替代这个效果。
MaskFilter 的几种效果对比
看文档使用起来比较简单,我们来看看 MaskFilter
的几种不同的模糊样式的区别。模糊样式通过 style 参数控制,BlurStyle
枚举取值有以下四种。
normal
:形状内外都会做模糊处理,可以用于绘制图形表面投影的阴影效果。solid
:内部不模糊,外侧模糊,会让图形看起来更明亮,类似荧光的效果。outer
:外侧模糊,内部没有东西,适用于绘制半透明图形的阴影。inner
:外侧不处理,内部模糊,看起来有种内发光的效果。
看文档说明我们是体会不到具体的呈现效果的,我们绘制同一个图形的不同效果对比看看。
outer
类型有点奇怪,中间掏空了,不过看上去还挺酷的。我画了一个不模糊的叠加上去,发现组合后的效果就和 solid
模式一样。
上面绘制的代码如下。
@override
void paint(Canvas canvas, Size size) {
var paint = Paint();
paint.style = PaintingStyle.fill;
var center = Offset(size.width / 2, size.height / 2);
var radius = 80.0;
paint.color = Colors.blue[400]!;
paint.maskFilter = MaskFilter.blur(BlurStyle.outer, 20.0);
canvas.drawCircle(center, radius, paint);
}
光影流动
通过绘制的效果发现,其实 MaskFilter 实现的效果有点像设计师给的各种发光效果,比如内发光、外发光。这时候,配合动画可以玩点有趣的东西了。
光影流动效果1
我们利用之前实现的全屏渐变色聊天气泡中的效果,通过动画控制一个圆形上下移动看看会有什么效果,感觉是一个彩色的光球在上升和下降。
上面效果的实现代码如下,基本的逻辑如下:
整个绘图范围通过
shader
预填充,使得圆形移动过程的填充色渐变变化,和我们聊天气泡中的效果一样;通过
transform
动画控制颜色旋转,使得绘制的圆形的填充颜色旋转,看起来会有立体感;通过 maskFilter,设置为 solid 模式让圆形有荧光的效果;
通过动画控制圆形上升和下降。
void _drawMovingCircle(Canvas canvas, Size size, Paint paint) {
var radius = 80.0;
paint.shader = LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Colors.black87,
Colors.purple,
Colors.blue,
Colors.green,
Colors.yellow[500]!,
Colors.orange,
Colors.red[400]!
].reversed.toList(),
tileMode: TileMode.clamp,
transform: GradientRotation(
animationValue * 2 * pi,
),
).createShader(Offset(0, 0) & size);
paint.maskFilter = MaskFilter.blur(BlurStyle.solid, 20.0);
canvas.drawCircle(
Offset(size.width / 2, size.height * animationValue), radius, paint);
}
光影流动效果2
上面的效果是一个圆形的,我们换成多个并排的矩形来看看,这种效果感觉整个填充区域的颜色在不停流动变幻,就好像有霓虹灯照耀的感觉。
上面效果的实现代码如下,其实就是通过循环绘制了一排矩形,然后通过动画控制上下移动位置。
void _drawMultiMovingRect(Canvas canvas, Size size, Paint paint) {
paint.shader = LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Colors.black87,
Colors.purple,
Colors.blue,
Colors.green,
Colors.yellow[500]!,
Colors.orange,
Colors.red[400]!
].reversed.toList(),
tileMode: TileMode.clamp,
transform: GradientRotation(
animationValue * 2 * pi,
),
).createShader(Offset(0, 0) & size);
paint.maskFilter = MaskFilter.blur(BlurStyle.solid, 20.0);
var count = 10;
for (var i = 0; i < count + 1; ++i) {
canvas.drawRect(
Offset(size.width / count * i, size.height * animationValue) &
Size(size.width / count, size.width / count * 2),
paint,
);
}
}
光影流动效果3
这一次我们使用 outer
类型的模糊效果,然后让一串圆形沿屏幕对角线从收起到展开,再从展开到收起,效果如下所示,光束球发出来的时候,感觉就像是从左上角发了一个大招。
上面的实现代码如下所示,就是通过控制圆形的中心位置实现对角线移动的,间距则是随着动画值的增加而拉大,因此会有发射的效果。
void _drawMultiMovingCircle(Canvas canvas, Size size, Paint paint) {
paint.shader = LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Colors.black87,
Colors.purple,
Colors.blue,
Colors.green,
Colors.yellow[500]!,
Colors.orange,
Colors.red[400]!
].reversed.toList(),
tileMode: TileMode.clamp,
transform: GradientRotation(
animationValue * 2 * pi,
),
).createShader(Offset(0, 0) & size);
paint.maskFilter = MaskFilter.blur(BlurStyle.outer, 20.0);
var count = 10;
for (var i = 0; i < count + 1; ++i) {
canvas.drawCircle(
Offset(size.width * i / count * animationValue,
size.height * i / count * animationValue),
size.width / count,
paint,
);
}
}
光影流动效果4:光影沿贝塞尔曲线流动
我们来把图形通过贝塞尔曲线控制绘制位置,来画一组图形,就能够实现光影沿着贝塞尔曲线流动的效果了。这里我们沿着两条首尾相接的贝塞尔曲线,绘制了一组正方形,看起来就像光影在一个闭合的图形中来回穿梭一样。
实现代码和之前的类似,只是矩形的位置通过贝塞尔曲线生成,如下所示。
void _drawRectsUsingBezier(Canvas canvas, Size size, Paint paint) {
paint.shader = LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Colors.black87,
Colors.purple,
Colors.blue,
Colors.green,
Colors.yellow[500]!,
Colors.orange,
Colors.red[400]!
].reversed.toList(),
tileMode: TileMode.clamp,
transform: GradientRotation(
animationValue * 2 * pi,
),
).createShader(Offset(0, 0) & size);
paint.maskFilter = MaskFilter.blur(BlurStyle.outer, 2);
final height = 120.0;
var p0 = Offset(0, size.height / 2 + height);
var p1 = Offset(size.width / 4, size.height / 2 - height);
var p2 = Offset(size.width * 3 / 4, size.height / 2 + height);
var p3 = Offset(size.width, size.height / 2 - height);
var count = 150;
var squareSize = 20.0;
for (var t = 1; t <= count; t += 1) {
var curvePoint =
BezierUtil.get3OrderBezierPoint(p0, p1, p2, p3, t / count);
canvas.drawRect(
curvePoint & Size(squareSize, squareSize),
paint,
);
}
for (var t = 1; t <= count; t += 1) {
var curvePoint =
BezierUtil.get3OrderBezierPoint(p3, p1, p2, p0, t / count);
canvas.drawRect(
curvePoint & Size(squareSize, squareSize),
paint,
);
}
}
来源:https://juejin.cn/post/7115990446311997448


猜你喜欢
- 前言在我们日常的开发过程中通过打印详细的日志信息能够帮助我们很好地去发现开发过程中可能出现的Bug,特别是在开发Controller层的接口
- 背景数据库在保存数据时,对于某些敏感数据需要脱敏或者加密处理,如果一个一个的去加显然工作量大而且容易出错,这个时候可以考虑使用 * ,本文针
- 强调一下阅读系统源码,起码要对进程间通信要了解,对binder机制非常非常清楚,binder就是指南针,要不然你会晕头转向;强行阅读,就容易
- 今天弄了一个多小时,写了一个GPS获取地理位置代码的小例子,包括参考了网上的一些代码,并且对代码进行了一些修改,希望对大家的帮助。具体代码如
- 本文实例为大家分享了java实现文件夹解压和压缩的具体代码,供大家参考,具体内容如下效果实现多个文件以及文件夹的压缩和解压代码分析impor
- 最近由于工作原因,没时间更新,开始吧~~关于json的返回需要用到一个工具包来将书转换为json格式,在此用到的jar包为: im
- 本文实例为大家分享了Android实现简单banner轮播图的具体代码,供大家参考,具体内容如下说明:想玩一个简单的轮播图效果
- 在实际项目开发中,业务逻辑层的处理速度往往很快,特别是在开发Socket通信服务的时候,网络传输很快,但是一旦加上数据库操作,性能一落千丈,
- java.util.Scanner类是一个简单的文本扫描类,它可以解析基本数据类型和字符串。它本质上是使用正则表达式去读取不同的数据类型。J
- 一,JDK环境变量;下载地址:HTTP://pan.baidu.com/s/1bpG3KYz1,新建变量名:JAVA_HOME,变量值:C:
- 前言👉本文中所有的代码和运行结果都是在amazon corretto openjdk 1.8环境中的,如果你不是使用该环境,可能会略有偏差。
- 本文实例讲述了Java Socket实现传输压缩对象的方法。分享给大家供大家参考,具体如下:前面文章《Java Socket实现的传输对象功
- 前言最近使用QT中的QTextEdit控件,作为实时数据显示的UI,在一次写入超过多少k的时候循环写入则会卡顿,网上也没有什么好的解决方案,
- 本文实例为大家分享了Android使用GridView实现横向滚动效果的具体代码,供大家参考,具体内容如下第一次做横向滑动,看了一些列子,基
- 一:简介方法引用分为三种,方法引用通过一对双冒号:: 来表示,方法引用是一种函数式接口的另一种书写方式静态方法引用,通过类名::静态方法名,
- 前言目前有两套RocketMQ集群,集群A包含topic名称为cluster_A_topic,集群B包含topic名称为cluster_B_
- 这是一次阿里面试里被问到的题目,在我的印象中,final修饰的方法是不能被子类重写的。如果在子类中重写final修饰的方法,在编译阶段就会提
- 其中包含两个jsp文件,分别为login.jsp和index.jsp代码如下:login.jsp<%@ page language=&
- 目录引言SqlSessionFactory不使用 XML 构建 SqlSessionFactorySqlSessionFactoryBuil
- 一、Intent的用途Intent主要有以下几种重要用途: 1. 启动Activity:可以将Intent对象传递给startActivit