敞开成长之旅!这是我参与「日新方案 12 月更文应战」的第5天,点击检查活动详情

前沿

上一篇《粒子相册(上)》咱们完成了由粒子构成图片的作用,今天咱们继续来完善后续功用,完成出一个完好的粒子相册

作用演示

先演示下完成后的作用,因为视频转 GIF 后会呈现明显的卡帧,主张咱们把项目 clone 下来自行运行体会。源码

完成

一、完成相册功用

粒子相册自然是离不开相册功用,这儿咱们运用 carousel_slider 插件来完成图片的轮播展现。

新增 CarouselSlider 组件,设置 autoPlay 为 true 用于自动播放,轮播间隔设为 6s

  CarouselSlider(
    options: CarouselOptions(
      aspectRatio: 2.2,
      enlargeCenterPage: true,
      initialPage: 2,
      autoPlay: true,
      autoPlayInterval: const Duration(seconds: 6),
      onPageChanged: _onPageChanged,
    ),
    items: imageSliders,
  )

imageSliders 中界说图片的展现样式,这儿用 ImageText 展现图片和称号

final List<Widget> imageSliders = imgList
    .map((item) => Container(
      margin: const EdgeInsets.all(5.0),
      child: ClipRRect(
          borderRadius: const BorderRadius.all(Radius.circular(5.0)),
          child: Stack(
            children: <Widget>[
              Image.asset(item, fit: BoxFit.cover, width: 1000.0),
              Positioned(
                bottom: 0.0,
                left: 0.0,
                right: 0.0,
                child: Container(
                  decoration: const BoxDecoration(
                    gradient: LinearGradient(
                      colors: [
                        Color.fromARGB(200, 0, 0, 0),
                        Color.fromARGB(0, 0, 0, 0)
                      ],
                      begin: Alignment.bottomCenter,
                      end: Alignment.topCenter,
                    ),
                  ),
                  padding: const EdgeInsets.symmetric(
                      vertical: 10.0, horizontal: 20.0),
                  child: Text(
                    'No. ${imgList.indexOf(item)} image',
                    style: const TextStyle(
                      color: Colors.white,
                      fontSize: 20.0,
                      fontWeight: FontWeight.bold,
                    ),
                  ),
                ),
              ),
            ],
          )),
    )).toList();

最终在 _onPageChanged 中监听图片的切换,在每次切换轮播图片后从头加载新的粒子图片

  void _onPageChanged(int index, CarouselPageChangedReason reason) {
    debugPrint("index=$index, ${reason.toString()}");
    particleManage.reset();
    initImage(index: index);
  }

优化图片的尺寸不一致问题,因为每张图片的尺寸都是不固定的,为了让展现的作用一致,这儿需求对展现的图片进行裁剪缩放处理

  /// 图片转化粒子
  void imageToParticle(){
    if(imagePic == null) return;
    int width = imagePic!.width;
    int height = imagePic!.height;
    double aspect =  width / height;
    int size = min(width, height);
    int left = aspect > 1 ? (width - height) ~/ 2 : 0;
    int top = aspect < 1 ? (height - width) ~/ 2 : 0;
    for(int i = 0; i < 200; i++) {
      for(int j = 0; j < 200; j++) {
        // image库获取的x、y和Flutter相反,需求把j做为x轴
        int x = left + j * size ~/ 200;
        int y = top + i * size ~/ 200;
        ...
      }
    }
  }

完成作用:

假如仅仅只是完成如此功用,自然是没有必要从头写一篇文章,下面让咱们进入今天的正式环节。

二、粒子特点配置

咱们在外面的粒子图片中新增一个进入粒子详情的入口,用于粒子详情页的展现

 Navigator.pushNamed(context, "/detail", arguments: imgList[pageIndex]);

前面粒子图片的展现都只是简略的展现了下作用,还不行完善,咱们在详情页中增加愈加详尽的粒子特点设置,便于用户自界说配置。

  1. 粒子颗粒度

咱们先在粒子管理器 PrticleManage 中新增 granularity 用于界说粒子的颗粒度

  // 粒子颗粒度
  int granularity = 200;

然后运用 Slider 组件来完成颗粒度特点的调节

  Slider(
    value: granularity,
    min: 50,
    max: 400,
    activeColor: Colors.redAccent,
    divisions: 7,
    inactiveColor: Colors.green.withAlpha(99),
    onChanged: (value) => _onSliderChange(value, SliderType.granularity),
    onChangeEnd: (value) =>
        _onSliderChangeEnd(value, SliderType.granularity),
  )

最终在 _onSliderChangeEnd 回调中更新设置的粒子颗粒度

  particleManage.setGranularity(value);
  imageToParticle();
  /// 初始化粒子
  void initParticles() {
    List<Particle> list = [];
    double size = 400 / granularity;
    for (int i = 0; i < granularity; i++) {
      for (int j = 0; j < granularity; j++) {
				...
      }
    }
    setParticleList(list);
  }

完成作用:

  1. 粒子运动速度

同理,咱们新增在 ParticleManage 中新增 speed 特点,用来界说例子的移动速度

  // 粒子移动速度
  double speed = 5.0;

界说 Slider 的特点值设置,最大速度设为 1 最小设为 10

  Slider(
    value: speed,
    min: 1.0,
    max: 10.0,
    activeColor: Colors.redAccent,
    divisions: 9,
    inactiveColor: Colors.green.withAlpha(99),
    onChanged: (value) => _onSliderChange(value, SliderType.speed),
    onChangeEnd: (value) => _onSliderChangeEnd(value, SliderType.speed),
  )

_onSliderChangeEnd 中更新粒子速度

  particleManage.setSpeed(value);
   if (this.speed == speed) return;
    this.speed = speed;
    for (Particle particle in particleList) {
      particle.ax = speed + random.nextDouble() * 10;
      particle.ay = speed + random.nextDouble() * 10;
    }
  1. 粒子离散规模

粒子离散规模的设置也是一样,界说 Slider 最小为 50 最大 300 。相同经过 _onSliderChangeEndParticleManage 中更新粒子参数


  Slider(
    value: range,
    min: 50.0,
    max: 300.0,
    activeColor: Colors.redAccent,
    divisions: 5,
    inactiveColor: Colors.green.withAlpha(99),
    onChanged: (value) => _onSliderChange(value, SliderType.range),
    onChangeEnd: (value) => _onSliderChangeEnd(value, SliderType.range),
  )
 particleManage.setRange(value);
  if (this.range == range) return;
    this.range = range;
    for (Particle particle in particleList) {
      particle.cx = particle.x - (random.nextDouble() * range - range ~/ 2);
      particle.cy = particle.y - (random.nextDouble() * range - range ~/ 2);
    }

完成作用:

三、粒子动画

上一篇文章咱们介绍了打印机动画、和粒子动画,咱们先把它们整合进来。在 actions 中增加 PopupMenuButton 用于多种动画的切换

  PopupMenuButton(
    itemBuilder: (context) {
      return [
        const PopupMenuItem<int>(
          value: 0,
          child: Text("打印动画"),
        ),
        const PopupMenuItem<int>(
          value: 1,
          child: Text("粒子运动动画"),
        ),
      ];
    },
    onSelected: _onAnimChanged,
  )

ParticleManage 中新增 anim 特点,并新增 Anim 枚举用于粒子动画的类型界说

// 粒子动画类型
Anim anim = Anim.particleMotion;
enum Anim {
  printer,
  particleMotion,
}

最终经过在 ParticleManagereset 办法中从头界说粒子的当时方位和加速度来完成不同的动画作用

  case Anim.printer: // 打印机动画
    particle.cx = particle.x;
    particle.cy = particle.y - 400;
    particle.ax = speed;
    particle.ay = speed + 2;
  break;
  case Anim.particleMotion: // 粒子运动
    particle.cx = particle.x - (random.nextDouble() * range - range ~/ 2);
    particle.cy = particle.y - (random.nextDouble() * range - range ~/ 2);
    particle.ax = speed + random.nextDouble() * 10;
    particle.ay = speed + random.nextDouble() * 10;
  break;
  1. 原点动画

咱们新增第 3 种动画原点动画,动画是以中心点为原点开端从小到大展现整张图片

粒子的 cxcyaxay 的特点设置如下:

  int index = granularity ~/ 2;
  particle.cx = index * particle.size - particle.size * 0.5;
  particle.cy = index * particle.size - particle.size * 0.5;
  particle.ax = speed;
  particle.ay = speed;
  1. 印刷动画

前面咱们的打印机动画是整张图片从上到下慢慢平移出来,其实还有另外一种更好的印刷动画作用。

ParticleManage 中新增 my特点 ,用于动画履行进展的衡量,即 动画履行进展 = my / 组件高度(400)

  // 粒子动画履行间隔,总间隔是400
  double my = 0;

这一次不再让图片从 -400 的方位开端移动,而是直接在当时方位显现

      case Anim.printer2:
        particle.cx = particle.x;
        particle.cy = particle.y;
        particle.ax = speed;
        particle.ay = speed + 2;
        break;

然后在粒子的更新 updateParticle 中,新增例子颜色透明度的判别。当粒子当时的 y 坐标 cy < my 时,粒子透明度不为空。

  if (anim == Anim.printer2) {
    particle.color =
        particle.color.withAlpha(particle.cy <= my ? 255 : 0);
  }

最终咱们需求完善下粒子运动 completed 的逻辑判别,保证动画能够完全履行。

  /// 粒子是否已移动到指定方位
  bool isParticleCompleted(Particle particle) {
    if (my > 0) {
      return my >= particle.y &&
          particle.cx == particle.x &&
          particle.cy == particle.y;
    } else {
      return particle.cx == particle.x && particle.cy == particle.y;
    }
  }

完成作用:

粒子相册(下)

  1. 粒子动画2

因为咱们在 ParticleManage 中新增了 my特点,用于动画履行进展的衡量,因此咱们能够在本来的粒子动画中做出更详尽的动画作用。 咱们首先在 setParticleAnim 中新增 particleMotion2 动画的粒子特点设置

      case Anim.particleMotion2: // 粒子运动2
        particle.cx = particle.x - (random.nextDouble() * range - range ~/ 2);
        particle.cy = particle.y + 100;
        particle.ax = speed + random.nextDouble() * 10;
        particle.ay = speed + random.nextDouble() * 5;
        my = 50;
        break;

然后在 updateParticle 中新增判别,只要在 my - 10 规模内的粒子才开端移动,一起新增粒子 alpha 判别,只要当粒子方位小于 my 时,才显现粒子

    if (anim == Anim.particleMotion2) {
      particle.color =
          particle.color.withAlpha(particle.cy <= my ? 255 : 0);
      if (particle.cy - my < 10) {
        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);
        }
      }
    }

完成作用:

总结

今天首要完成粒子相册的相册功用,并且新增粒子颗粒度、运动速度、离散规模等具体特点配置,最终对原有的粒子动画进行整理概括,经过新增my特点来操控粒子的动画履行进展然后做出更详尽的粒子动画作用。

源码 particle_album

写下这篇文章的时分是我了的第四天,尽管我症状比较轻,但这个病毒依旧让我前3天十分难受。并且身边了的朋友根本都有症状,有的乃至一周还没缓过来。期望咱们不要小看这个病毒,能晚尽量晚点。