『Flutter-绘制篇』实现炫酷的雨雪特效

class RainSnowPainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {

}

@override
bool shouldRepaint(CustomPainter oldDelegate) {
return true;
}
}
复制代码

有没有很熟悉,看到了 Canvas 的类,自然也有 Paint 类,有了画笔和画板,剩下就好办了。

构造雨雪对象

对需要实现的效果进行分析,首先雨雪效果是由一张图片不同属性拼接而成,每个雨滴和雪花落实在屏幕上,必须有 x,y 的坐标属性。为了营造远近的效果,需要加上 scale 值,由于更加还原真实的视觉效果,雨滴的远近,必然速度上和清晰度上会有差异,因此加上 speed 和 alpha 属性,再加上其他计算用的属性,最后类的声明如下:

class RainSnowParams {
double x;
double y;
double speed;
double scale;
double width;
double height;
double alpha;
WeatherType weatherType;

RainSnowParams(this.width, this.height, this.weatherType);
}
复制代码

属性初始化

有了属性后,接下来就是对属性进行赋值,为了保证效果更加的还原,所有属性既要有规则,又要随机。怎么解释规则性和随机性都要同时拥有,就拿雨速而言,小雨相对于大雨,雨的速度稍慢,但是不能很慢,并且每滴雨滴的速度不一样,这就致使小雨的速度必然在一个区间下随机,同样雪也一样。

初始化又分成两步,第一次的初始化和雨滴下落结束后的数据重置,实际上两者的区别只在于 y。第一次初始化 y 在屏幕高度中随机放置,而雨滴下落结束后,y 值置为0。那么就可以把重置逻辑封装统一的方法。

void reset() {
double initScale = 0.1;
double gapScale = 0.2;
double initSpeed = 40;
double gapSpeed = 40;
if (weatherType == WeatherType.lightRainy) {
initScale = 1.05;
gapScale = 0.1;
initSpeed = 15;
gapSpeed = 10;
} else if(){
…// 其他雨雪情况
}
double random = Random().nextDouble();
this.scale = initScale + gapScale * random;
this.speed = initSpeed + gapSpeed * (1 - random);
this.alpha = 0.1 + 0.9 * random;
x = Random().nextInt(width * 1.2 ~/ scale).toDouble() - width * 0.1 ~/ scale;
}
复制代码

其中 init 代表这初始值,gap 代表浮动值,这两个根据雨雪量大小而做不同区分。通过 Random().nextDouble() 获取随机 [0.0, 1.0] 的值,random * gap + init 就是最终的值。

x 的属性控制在 [-0.1*width, 1.1 width] 的区间内随机,y 值上面已提到。

雪相对有雨有个不同,雨是垂直下落,而雪是随风摇摆,那为了营造这种感觉,此时就需要借助 sin 函数。

if (WeatherUtil.isSnow(_state.widget.weatherType)) {
double offsetX = sin(params.y / (300 + 50 * params.alpha)) * (1 + 0.5 * params.alpha);
params.x += offsetX;
}
复制代码

开始绘制

终于到了最重要的步骤 绘制,但他并不是最难的,有了前面创建好的属性和对其初始化,剩下就只是调用 api 进行绘制即可。不过再此之前好像漏了什么没说,没错,就是 动画,一个无限循环的动画。

Flutter 中创建动画也很简单,需要在动画监听中,判断如果动画结束则重新继续执行即可。

  1. 在 initState 函数中初始化 controller, animation 和 listener
    _controller =
    AnimationController(duration: Duration(minutes: 1), vsync: this);
    CurvedAnimation(parent: _controller, curve: Curves.linear);
    _controller.addListener(() {
    setState(() {});
    });
    _controller.addStatusListener((status) {
    if (status == AnimationStatus.completed) {
    _controller.repeat();
    }
    });
    _controller.forward();
  2. 在 dispose 函数中释放掉动画资源
    @override
    void dispose() {
    _controller.dispose();
    super.dispose();
    }
    复制代码

在初始化是便让他执行并一直执行知道页面销毁,有了动画后,开始进行绘制,雨雪的绘制逻辑基本相似,只不过图片源不一样。

别看屏幕上有很多雨滴,其实只用了一直图片,通过控制 alpha、speed和scale 的属性来随机展现不同的形态。还有,根据气象大中小雨类型的区分,会直接落实到雨滴数量和雨滴形态上的变化,营造出多样的差异。

void drawRain(Canvas canvas, Size size) {
weatherPrint(
“开始绘制雨层 image: s t a t e . i m a g e s ? . l e n g t h , r a i n s : {_state._images?.length}, rains: state.images?.length,rains:{_state._rainSnows?.length}”);
if (_state._images != null && _state._images.length > 1) {
ui.Image image = _state._images[0];
if (_state._rainSnows != null && _state._rainSnows.isNotEmpty) {
_state._rainSnows.forEach((element) {
move(element);
ui.Offset offset = ui.Offset(element.x, element.y);
canvas.save();
canvas.scale(element.scale, element.scale);
var identity = ColorFilter.matrix([
1, 0, 0, 0, 0,
0, 1, 0, 0, 0,
0, 0, 1, 0, 0,
0, 0, 0, element.alpha, 0,
]);
_paint.colorFilter = identity;
canvas.drawImage(image, offset, _paint);
canvas.restore();
});
}
}
}
复制代码

这里绘制逻辑只用到了 drawImage 的方法,参数分别为图片、位置和画笔,不像 Android 提供了 paint.setAlpha() 的方法控制图片的透明值,这里需要通过 colorFilter 修改矩阵中对应的值来控制 alpha。

move() 函数用于控制雨滴在运动过程中 x和y 值的不断变化。

void move(RainSnowParams params) {
params.y = params.y + params.speed;

题外话

我在一线互联网企业工作十余年里,指导过不少同行后辈。帮助很多人得到了学习和成长。

我意识到有很多经验和知识值得分享给大家,也可以通过我们的能力和经验解答大家在IT学习中的很多困惑,所以在工作繁忙的情况下还是坚持各种整理和分享。但苦于知识传播途径有限,很多程序员朋友无法获得正确的资料得到学习提升,故此将并将重要的Android进阶资料包括自定义view、性能优化、MVC与MVP与MVVM三大框架的区别、NDK技术、阿里面试题精编汇总、常见源码分析等学习资料。

【Android思维脑图(技能树)】

知识不体系?这里还有整理出来的Android进阶学习的思维脑图,给大家参考一个方向。

希望我能够用我的力量帮助更多迷茫、困惑的朋友们,帮助大家在IT道路上学习和发展~
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》点击传送门,即可获取!
0Mkf8f-1715154734699)]

希望我能够用我的力量帮助更多迷茫、困惑的朋友们,帮助大家在IT道路上学习和发展~
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》点击传送门,即可获取!

  • 8
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值