Flutter进阶—实现动画效果(七)

119 篇文章 9 订阅
82 篇文章 1417 订阅

我们假设一种情况,如果应用程序使用条形图显示给定年份的产品类别的销售额,用户可以选择另一年,然后该应用程序将动画到该年的条形图。如果产品类别在两年内是相同的,或者恰好是相同的,除了在其中一个图表中右侧显示的其他类别,我们可以使用我们现有的代码。但如果公司在2016年有A,B,C和X类产品,但是在2017年中断了B并推出了D?

这里写图片描述

动画效果可以做得非常好看,但仍然会让用户感到困惑。为什么?因为它不保留语义。它将表示产品类别B的图形元素转换为代表类别C的一个图形元素,而将C的图形元素转换到其他地方。正因为2016 B恰好是在2017 C后来出现的同一位置,并不意味着前者应该变成后者。相反,2016年B应该消失,2016年C应该向左移动到2017年,而2017年D应该出现在右边。我们可以使用传统的合并排序列表实现这种混合。

我们可以分别给每一个条形以不同的颜色,然后使用颜色来区分销售的产品,一种颜色代表一个产品,当一种颜色消失时,说明该产品已经下架,反之,则说明新产品已经上架。

通过语义对应的组件进行复合值之间的线性插值(lerp),当组件形成排序列表时,合并算法可以将这些组件放在侧面上,使用不可见组件来处理单面合并。我们所需要的是使Bar实例以线性顺序相互比较。然后我们可以将它们合并。

// ...
class BarChart {
  // ...
  factory BarChart.random(Size size, Random random) {
    const barWidthFraction = 0.75;
    final ranks = selectRanks(random, ColorPalette.primary.length);
    final barCount = ranks.length;
    final barDistance = size.width / (1+barCount);
    final barWidth = barDistance * barWidthFraction;
    final startX = barDistance - barWidth/2;
    final bars = new List.generate(
      barCount,
      (i)=> new Bar(
        ranks[i],
        startX + i * barDistance,
        barWidth,
        random.nextDouble() * size.height,
        ColorPalette.primary[ranks[i]],
      ),
    );
    return new BarChart(bars);
  }

  static List<int> selectRanks(Random random, int cap) {
    final ranks = <int>[];
    var rank = 0;
    while(true) {
      // 模拟产品的上架下架
      if(random.nextDouble() < 0.2) rank++;
      if(cap <= rank) break;
      ranks.add(rank);
      rank++;
    }
    return ranks;
  }

  static BarChart lerp(BarChart begin, BarChart end, double t) {
    final bars = <Bar>[];
    final bMax = begin.bars.length;
    final eMax = end.bars.length;
    var b = 0;
    var e = 0;
    while (b + e < bMax + eMax) {
      /*
      这里的条件判断中包含两种情况
      b < bMax && e == eMax:
        当新图表条形数减少时
      b < bMax && begin.bars[b] < end.bars[e]:
        当新图表不包含旧图表的颜色条形时
      满足一种情况,处理旧图表中多余的条形,即向左侧方清除旧条形
       */
      if(b < bMax && (e == eMax || begin.bars[b] < end.bars[e])) {
        bars.add(Bar.lerp(begin.bars[b], begin.bars[b].collapsed, t));
        b++;
      /*
      这里的条件判断中包含两种情况
      e < eMax && b == bMax:
        当新图表条形数增加时
      e < eMax && end.bars[e] < begin.bars[b]:
        当新图表包含旧图表没有的颜色条形时
      满足一种情况,处理旧图表中没有的条形,即向右侧方绘制新条形
       */
      } else if(e < eMax && (b == bMax || end.bars[e] < begin.bars[b])) {
        bars.add(Bar.lerp(end.bars[e].collapsed, end.bars[e], t));
        e++;
      // 当新图表与旧图表有同一个颜色条形时,原地变形
      } else {
        bars.add(Bar.lerp(begin.bars[b], end.bars[e], t));
        b++;
        e++;
      }
    }
    return new BarChart(bars);
  }
}

class BarChartTween extends Tween<BarChart> {
  BarChartTween(BarChart begin, BarChart end) : super(begin: begin, end: end);

  @override
  BarChart lerp(double t) => BarChart.lerp(begin, end, t);
}

class Bar {
  Bar(this.rank, this.x, this.width, this.height, this.color);
  final int rank;
  final double x;
  final double width;
  final double height;
  final Color color;

  Bar get collapsed => new Bar(rank, x, 0.0, 0.0, color);

  /*
  bool operator <(
    Duration other
  )
  如果此Duration的值小于other值,则返回true
  bool operator <(Duration other) => this._duration < other._duration;
   */
  bool operator <(Bar other) => rank < other.rank;

  static Bar lerp(Bar begin, Bar end, double t) {
    assert(begin.rank == end.rank);
    return new Bar(
      begin.rank,
      lerpDouble(begin.x, end.x, t),
      lerpDouble(begin.width, end.width, t),
      lerpDouble(begin.height, end.height, t),
      Color.lerp(begin.color, end.color, t)
    );
  }
}

class BarTween extends Tween<Bar> {
  BarTween(Bar begin, Bar end) : super(begin: begin, end: end) {
    assert(begin.rank == end.rank);
  }

  @override
  Bar lerp(double t) => Bar.lerp(begin, end, t);
}
// ...

具体来说,我们将以整数rank属性的形式为每个条形分配一个排序键。然后可以方便地使用rank来从调色板中分配每个条形的颜色,从而使我们能够跟踪动画演示中各个条形图的移动。

随机条形图现在将基于随机选择的行列。

这里写图片描述

但这不是最有效的解决方案,我们正在BarChart.lerp中重复执行合并算法,对于t的每个值都执行一次。为了解决这个问题,我们将实现前面一篇文章中提到的想法,以便在BarChartTween中存储可重用的信息。

// ...
class BarChartTween extends Tween<BarChart> {
  final _tweens = <BarTween>[];

  BarChartTween(BarChart begin, BarChart end) : super(begin: begin, end: end) {
    final bMax = begin.bars.length;
    final eMax = end.bars.length;
    var b = 0;
    var e = 0;
    while (b + e < bMax + eMax) {
      if(b < bMax && (e == eMax || begin.bars[b] < end.bars[e])) {
        _tweens.add(new BarTween(begin.bars[b], begin.bars[b].collapsed));
        b++;
      } else if(e < eMax && (b == bMax || end.bars[e] < begin.bars[b])) {
        _tweens.add(new BarTween(end.bars[e].collapsed, end.bars[e]));
        e++;
      } else {
        _tweens.add(new BarTween(begin.bars[b], end.bars[e]));
        b++;
        e++;
      }
    }
  }

  @override
  BarChart lerp(double t) => new BarChart(
    new List.generate(
      _tweens.length,
      (i) => _tweens[i].lerp(t))
  );
}
// ...

然后我们就可以删除静态的BarChart.lerp方法了。

未完待续~~~

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

何小有

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值