开启成长之旅!这是我参与「日新方案 12 月更文应战」的第4天,点击查看活动详情
前沿
世间万物,由简入繁,皆由粒子构成。就像“物质构成的最小单位是夸克”相同,图片构成的最小单位便是像素。今天就让咱们经过粒子来构建出相册,我把它称之为粒子相册。
作用展现
按照国际惯例,咱们先演示下完成的最终作用:
思路
在开端完成功用之前,咱们先捋清一下完成思路。
在上述思想导图中咱们能够把完成内容分为 4个部分:
粒子。分为粒子和粒子办理器两部分。
提取图片像素。需求把图片转为对应的像素,便于粒子制作图片。
粒子复原图片。根据图片的像素用粒子进行复原。
粒子动画。给粒子生成图片的进程增加动画。
功用完成
一、粒子和粒子办理器
咱们先创立 Particle
实体类,增加对应特点:Github源码
Particle({
this.x = 0,
this.y = 0,
this.size = 0,
this.color = Colors.white,
});
然后创立 ParticleManage
办理类,用于粒子的办理。因为咱们是经过 ParticleManage
来更新粒子的,这儿 extends ChangeNotifier
用于粒子的更新:
class ParticleManage extends ChangeNotifier {}
ParticleManage
新增 addParticle
和 setParticleList
方法:
/// 设置粒子列表
void setParticleList(List<Particle> list) {
particleList = list;
notifyListeners();
}
/// 增加粒子
void addParticle(Particle particle) {
particleList.add(particle);
notifyListeners();
}
自定义 ParticlePainter
经过 CustomPaint
来完成粒子的制作:
CustomPaint(
size: const Size(200, 200),
painter: ParticlePainter(manage: particleManage),
);
class ParticlePainter extends CustomPainter {
...
@override
void paint(Canvas canvas, Size size) {
for (Particle particle in manage.particleList) {
_drawParticle(canvas, particle);
}
}
/// 制作粒子
void _drawParticle(Canvas canvas, Particle particle) {
canvas.drawCircle(Offset(particle.cx, particle.cy), particle.size,
particlePaint..color = particle.color);
}
}
制作单个粒子:
_manage.addParticle(Particle(x: 200, y: 250, size: 25, color: Colors.red)
作用:
生成粒子背景:
double size = 4;
for (int i = 0; i < 50; i++) {
for (int j = 0; j < 50; j++) {
_manage.addParticle(Particle(
x: size + 2 * size * j,
y: size + 2 * size * i,
size: size,
));
}
}
作用:
生成回形粒子图形,代码:
/// 回形图形
void toPaperClip() {
List<int> scales = [0, 1, 2, 3, 4];
for (int i = 0; i < 50; i++) {
for (int j = 0; j < 50; j++) {
if (((i == 24 - scales[0] * 5 || i == 24 + scales[0] * 5) && j >= 24 - scales[0] * 5 && j <= 24 + scales[0] * 5) ||
((j == 24 - scales[0] * 5 || j == 24 + scales[0] * 5) && i >= 24 - scales[0] * 5 && i <= 24 + scales[0] * 5)) {
particleManage.particleList[i * 50 + j].color = Colors.blue;
}
...
if (((i == 24 - scales[4] * 5 || i == 24 + scales[4] * 5) && j >= 24 - scales[4] * 5 && j <= 24 + scales[4] * 5) ||
((j == 24 - scales[4] * 5 || j == 24 + scales[4] * 5) && i >= 24 - scales[4] * 5 && i <= 24 + scales[4] * 5)) {
particleManage.particleList[i * 50 + j].color = Colors.blue;
}
}
}
}
作用:
二、提取图片像素
图片像素的提取,咱们这儿经过 image 库来完成。
Future<void> initImage() async {
ByteData data = await rootBundle.load("assets/images/hanzi.png");
List<int> bytes = data.buffer.asUint8List(data.offsetInBytes, data.lengthInBytes);
imagePic = image.decodeImage(bytes)!;
}
要注意,经过 image
获取到的图片 x轴 和 y轴 与 Flutter 相反,其颜色的 blue 和 red 也相反,需求进行转化。
// image库获取的x、y和Flutter相反,需求把j做为x轴
int x = width * j ~/ 200;
int y = height * i ~/ 200;
// 颜色进行转化
Color.fromARGB(color.alpha, color.blue, color.green, color.red)
三、粒子复原图片
根据获取到的图片像素用粒子进行复原。
/// 图片转化粒子
void imageToParticle(){
if(imagePic == null) return;
int width = imagePic!.width;
int height = imagePic!.height;
for(int i = 0; i < 50; i++) {
for(int j = 0; j < 50; j++) {
// image库获取的x、y和Flutter相反,需求把j做为x轴
int x = j;
int y = i;
var pixel = imagePic!.getPixel(x, y);
if(pixel != Colors.white.value) {
particleManage.particleList[i * 200 + j].color = Colors.blue;
}
}
}
}
原图:
作用:
调整下粒子的颗粒度:
四、粒子动画
假如仅仅只是经过粒子来展现图片,未免也太 “大材小用” 了。这儿咱们经过增加粒子动画让其生成图片的进程动起来。
- 打印机作用
咱们经过模拟打印机打印文件的动画来让粒子动起来。
(1)给粒子 particle
增加当时方位坐标,用于粒子的运动。
// 当时x坐标
double cx;
// 当时x坐标
double cy;
(2)粒子办理器 ParticleManage
新增粒子更新 onUpdate
,办理粒子方位更新。
void onUpdate() {
for (Particle particle in particleList) {
updateParticle(particle);
}
notifyListeners();
}
/// 更新粒子方位
void updateParticle(Particle particle) {
if (particle.cx < particle.x) {
particle.cx = min(particle.x, particle.cx + 5);
}
if (particle.cy < particle.y) {
particle.cy = min(particle.y, particle.cy + 5);
}
}
(3)增加 AnimationController
执行动画。
_controller = AnimationController(duration: const Duration(seconds: 6), vsync: this);
_controller.addListener(() {
particleManage.onUpdate();
});
作用:
- 粒子生成作用
咱们还能够经过粒子的随机运动来完成从无到有的生成作用。
(1)Particle
新增 ax
和 ay
特点来为粒子的运动增加加快度
// 加快度ax
double ax;
// 加快度 ay
double ay;
(2)初始化 Particle
增加随机加快度 ax
和 ay
。
double size = 1;
var random = Random();
for (int i = 0; i < 200; i++) {
for (int j = 0; j < 200; j++) {
double x = size + 2 * size * j;
double y = size + 2 * size * i;
list.add(Particle(
x: x,
y: y,
cx: x - (random.nextDouble() * 200 - 100),
cy: y - (random.nextDouble() * 200 - 100),
size: size,
ax: 2 + random.nextDouble() * 10,
ay: 2 + random.nextDouble() * 10,
));
}
}
particleManage.setParticleList(list);
(3)粒子办理器 ParticleManage
调整粒子更新 onUpdate
的逻辑。
if (particle.cx > particle.x) {
if (particle.cx > particle.x) {
particle.cx = max(particle.x, particle.cx - particle.ax);
} else if (particle.cx < particle.x) {
particle.cx = min(particle.x, particle.cx + particle.ax);
}
if (particle.cy > particle.y) {
particle.cy = max(particle.y, particle.cy - particle.ay);
} else if (particle.cy < particle.y) {
particle.cy = min(particle.y, particle.cy + particle.ay);
}
作用:
五、优化
前面的粒子动画存在2个问题。
- 粒子无法完好生成比屏幕尺度还大的图片
因为屏幕的粒子是有限的,关于尺度特别大的图片,咱们应该做缩放处理。 缩放逻辑也非常简略,经过换算图片宽高和粒子宽高的份额即可:
/// 图片转化粒子
void imageToParticle(){
if(imagePic == null) return;
int width = imagePic!.width;
int height = imagePic!.height;
for(int i = 0; i < 200; i++) {
for(int j = 0; j < 200; j++) {
// image库获取的x、y和Flutter相反,需求把j做为x轴
int x = width * j ~/ 200;
int y = height * i ~/ 200;
...
}
}
}
- 粒子运动结束的时刻不可控
因为咱们初始化的加快的是随机的,所以无法准确的计算出所有粒子完成运动的最终时刻,导致咱们设置的动画时长为理论最大时长,会存在有时粒子早已运动结束了,但动画还剩下一半时长的状况。
为了优化这种状况,咱们这儿不运用 AnimationController
改为 Ticker
。
Ticker
类用于在每一帧之间发送通知。Ticker
目标经过回调函数向它的侦听器发送 tick
事件。咱们能够运用 Ticker
目标来完成动画或守时使命。
_ticker = createTicker(_updateTicker);
@override
void dispose() {
// _controller.dispose();
_ticker.stop(canceled: true);
super.dispose();
}
在 ParticleManage
办理器中增加粒子 completed
逻辑判断。
/// 更新粒子
void onUpdate() {
bool completed = true;
for (Particle particle in particleList) {
updateParticle(particle);
completed = completed && isParticleCompleted(particle);
}
isCompleted = completed;
notifyListeners();
}
/// 粒子是否已移动到指定方位
bool isParticleCompleted(Particle particle) {
return particle.cx == particle.x && particle.cy == particle.y;
}
若粒子已完成运动,则 Ticker 停止更新。
void _updateTicker(Duration elapsed) {
particleManage.onUpdate();
// 获取粒子已完成运动,则停止ticker监听
if(particleManage.isCompleted) {
_ticker.stop();
}
}
OK,至此咱们完成了粒子相册的前半部分。
总结
本篇文章作为粒子相册的上篇,主要完成了由粒子构成图片、并增加粒子动画的功用。下一篇文章将丰富更多的粒子动画、完成粒子相册的完好功用。假如感兴趣的同学能够重视下,你的重视是我持续更新的动力!(#^.^#)
Github源码