Banner

Hyper Effects

Hyper Effects 是一个 Flutter 包,其语法灵感来自 SwiftUI,可在 Flutter 中创建精美的效果。只需在小部件后添加几行代码,即可轻松添加任意数量的效果和过渡。

演示: hyper-effects-demo.web.app

功能

  • ? 简洁的语法: 一行代码即可实现一整套复杂的效果。
  • ? 万物皆可动画: 无需动画控制器或缓动。只需一行代码即可为任何小部件添加动画。
  • ? 滚动过渡: 根据小部件在滚动视图中的位置来控制其外观。
  • ? 指针过渡: 无需手势检测器或状态管理。根据指针事件控制小部件的外观。
  • ? 易于集成: 缺少某个效果?API 设计简洁,易于扩展。

示例

滚动过渡

滚动滚轮

滚动模糊

Windows 悬停效果

滚动颜色滤镜

弹簧动画

入门

安装

在 pubspec.yaml 文件中将此包添加到您的依赖项

dependencies:
  hyper_effects: <latest_version>

或在项目目录中运行以下命令

flutter pub add hyper_effects

用法

对于您拥有的任何小部件,您都可以使用类似以下的扩展为其添加任意数量的效果和过渡。

@override
Widget build(BuildContext context) {
  return Container(
    color: Colors.blue,
  ).opacity(myCondition ? 0 : 1);
}

但是,这不会为效果添加动画。它只会用指定的效果包装小部件并使用提供的值。

要为效果添加动画,您需要像这样调用小部件上的 animate 方法。

@override
Widget build(BuildContext context) {
  return Container(
    color: Colors.blue,
  )
      .opacity(myCondition ? 0 : 1)
      .animate(toggle: myCondition);
}

toggle 是一个类型为 Object 的参数。它的灵感来自 SwiftUI 的 animation 修饰符上的 value 参数。每当 toggle 的值发生变化时,效果就会动画到新值。在这种情况下,myCondition 就是切换值。您可以使用任何对象作为切换值,但您很可能会想使用用于控制效果条件的同一对象,因为这是效果应该进行动画处理的点。

HyperEffects 的灵感很大程度上来源于 SwiftUI,它试图为所有内容提供类似 Apple 的默认值。这意味着默认情况下,动画的持续时间为 350 毫秒,并使用 Apple 的自定义 easeInOut 曲线。其结果是流畅自然的动画,非常类似于 Apple 的风格。

尽管具有非常好的默认值,但 API 当然允许您根据自己的喜好自定义动画。您可以通过设置自己的持续时间和曲线来实现。

@override
Widget build(BuildContext context) {
  return Container(
    color: Colors.blue,
  )
      .opacity(myCondition ? 0 : 1)
      .animate(
        toggle: myCondition,
        duration: const Duration(milliseconds: 200),
        curve: Curves.easeOutQuart,
      );
}

您可以像这样组合和链接任意数量的效果。

@override
Widget build(BuildContext context) {
  return Container(
    color: Colors.blue,
  )
      .opacity(myCondition ? 0 : 1)
      .blur(myCondition ? 10 : 0)
      .scale(myCondition ? 0.5 : 1)
      .translateX(myCondition ? 100 : 0)
      .animate(toggle: myCondition);
}

由于这些是方便的扩展,您可以使用 apply 方法将效果用作小部件,如下所示。

@override
Widget build(BuildContext context) {
  return OpacityEffect(
    opacity: myCondition ? 0 : 1,
  ).apply(
    context,
    BlurEffect(
      blur: myCondition ? 10 : 0,
    ).apply(
      context,
      ScaleEffect(
        scale: myCondition ? 0.5 : 1,
      ).apply(
        context,
        TranslateEffect(
          offset: Offset(myCondition ? 100 : 0, 0),
        ).apply(
          context,
          Container(
            color: Colors.blue,
          ),
        ),
      ),
    ),
  );
}

过渡

过渡是一种特殊类型的效果,它允许您根据某个连续值控制小部件的外观。例如,您可以根据小部件在滚动视图中的位置或根据指针位置来控制小部件的不透明度。

过渡使用构建器模式,为您提供一个 event 属性,该属性提供有关小部件过渡状态的活动信息。您可以使用此信息来控制小部件的外观,它会根据您在其中使用的效果自动进行动画。

滚动过渡

滚动过渡是基于小部件在滚动视图中位置的过渡。您可以将 scrollTransition 扩展应用于任何小部件以为其添加滚动过渡。

  @override
  Widget build(BuildContext context) {
    return ListView.builder(
      itemBuilder: (context, index) {
        return Container(
          width: 350,
          height: 100,
          margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
          alignment: Alignment.center,
          decoration: BoxDecoration(
            color: randomColor(index),
            borderRadius: BorderRadius.circular(16),
          ),
          child: Text(
            'Item $index',
            style: const TextStyle(
              fontSize: 24,
              color: Colors.white,
            ),
          ),
        ).scrollTransition(
          (context, widget, event) => widget
              .blur(
                switch (event.phase) {
                  ScrollPhase.identity => 0,
                  ScrollPhase.topLeading => 10,
                  ScrollPhase.bottomTrailing => 10,
                },
              )
              .scale(
                switch (event.phase) {
                  ScrollPhase.identity => 1,
                  ScrollPhase.topLeading => 0.9,
                  ScrollPhase.bottomTrailing => 0.9,
                },
              ),
        );
      },
    );
  }

在滚动过渡中,phase 是一个 ScrollPhase 枚举,它确定小部件在滚动视图中的视图状态。

  • ScrollPhase.identity 表示小部件在滚动视图中完全可见。
  • ScrollPhase.topLeading 表示小部件从顶部/左侧部分可见,即:它正在离开滚动视图的顶部或左侧。
  • ScrollPhase.bottomTrailing 表示小部件从底部/右侧部分可见,即:它正在离开滚动视图的底部或右侧。

除了 ScrollPhase 之外,event 还提供了 screenOffsetFraction,这是一个介于 -1、0 和 1 之间的双精度值,表示滚动视图从滚动视图中心移开的进度。

  • 如果小部件靠近滚动视图的中心,该值趋向于 0。
  • 当小部件移向滚动视图的顶部时,该值趋向于 1。当该项完全超出滚动视图时,它会限制在 1。
  • 当该项移向滚动视图的底部时,该值趋向于 -1。当该项完全超出滚动视图时,它会限制在 -1。

注意

使用 screenOffsetFraction 时要小心,因为它可能有点棘手。过渡的内部工作方式是它们在特定内部值之间进行插值。滚动过渡的值是 ScrollPhase。

考虑到这一点,尽管您可以使用 phase 通过扩展轻松地为小部件设置动画,但对于 screenOffsetFraction 等其他属性,您需要使用手动 Effects API 来为它们设置动画。

  @override
  Widget build(BuildContext context) {
    return ListView.builder(
      itemBuilder: (context, index) {
        return Container(
          width: 350,
          height: 100,
          margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
          alignment: Alignment.center,
          decoration: BoxDecoration(
            color: randomColor(index),
            borderRadius: BorderRadius.circular(16),
          ),
          child: Text(
            'Item $index',
            style: const TextStyle(
              fontSize: 24,
              color: Colors.white,
            ),
          ),
        ).scrollTransition((context, widget, event) => TransformEffect(
              rotateX: -90 * event.screenOffsetFraction * pi / 180,
              translateY: (event.screenOffsetFraction * -1) * 200,
              translateZ: event.screenOffsetFraction.abs() * 100,
              scaleX: 1 - (event.screenOffsetFraction.abs() / 2),
              depth: 0.002,
            ).apply(context, widget));
      },
    );
  }

当直接使用手动 Effects API 时,您可以像上面示例一样使用任何参数并任意修改它们的值;而使用方便的扩展 API 时,您可能只能使用内部动画值。在滚动过渡中,内部动画值是 phase 属性。

指针过渡

指针过渡是提供事件的过渡,这些事件基于指针的位置(取决于配置),可以是本地的或全局的。此过渡有许多可配置的参数,可让您控制过渡在其构建器中输出其值的方式。

@override
Widget build(BuildContext context) {
  return Container(
    decoration: const BoxDecoration(
      color: Colors.blue,
    ),
  ).pointerTransition(
        (context, child, event) => child.translateXY(
      event.valueOffset.dx / 2,
      event.valueOffset.dy / 2,
      fractional: true,
    ),
  );
}

在上面的示例中,容器将在 x 和 y 轴上移动指针移动距离的一半。fractional 参数设置为 true,这意味着小部件将通过乘以提供的值来移动其大小。如果 fractional 设置为 false,小部件将直接以像素为单位移动。

value 是一个 double 参数,表示指针设备与 origin 的距离。值为零表示指针设备位于 origin。值为 1 或 -1 表示指针设备位于距离 origin 最远的点。此值是 valueOffset 的 x 和 y 值的平均值。

valueOffset 是一个 Offset 参数,与 value 非常相似,但提供了关于单个 x 和 y 轴的更多信息。它是指针设备与 origin 的距离(作为偏移量)。值为偏移量 (0, 0) 表示指针设备位于 origin。值为偏移量 (1, 1) 表示指针设备位于距离 origin 最远的点。

isInsideBounds 是一个 boolean 参数,用于确定指针是否在小部件的边界内。

position 是指针的原始位置。它是指针设备相对于目标左上角的距离。目标取决于下面的配置。

指针过渡属性

使用指针过渡时,您可以提供多个参数来控制其行为,这些参数会反映在提供给构建器的 event 中。

  Widget pointerTransition(
    PointerTransitionBuilder builder, {
    Alignment origin = Alignment.center,
    bool useGlobalPointer = false,
    bool transitionBetweenBounds = true,
    bool resetOnExitBounds = true,
    Curve curve = appleEaseInOut,
    Duration duration = const Duration(milliseconds: 125),
  }) {
    return PointerTransition(
      builder: builder,
      origin: origin,
      useGlobalPointer: useGlobalPointer,
      transitionBetweenBounds: transitionBetweenBounds,
      resetOnExitBounds: resetOnExitBounds,
      curve: curve,
      duration: duration,
      child: this,
    );
  }

origin 是一个 Alignment 参数,用于确定用于转换指针位置的原点。

  • 如果 origin 设置为 Alignment.center,当指针移离屏幕中心时,value 会增加。
  • 如果 origin 设置为 Alignment.topLeft,当指针移离屏幕左上角时,value 会增加。

useGlobalPointer 是一个 boolean 参数,用于确定指针位置是全局计算还是局部计算。如果设置为 true,指针位置将从屏幕左上角计算。如果设置为 false,指针位置将从小部件左上角计算。

transitionBetweenBounds 是一个 boolean 参数,用于确定过渡是否应在小部件边界之间进行动画。如果设置为 true,当指针移入和移出小部件边界时,过渡将触发内部动画重置。如果设置为 false,当指针移入和移出小部件边界时,过渡将立即跳转到新值。

resetOnExitBounds 是一个 boolean 参数,用于确定当指针设备位于小部件边界之外时,此过渡是否应将子项的位置重置为 value 为零。transitionBetweenBounds 决定过渡是否应进行动画。

curve 是一个 Curve 参数,用于确定边界之间过渡的内部动画曲线。

duration 是一个 Duration 参数,用于确定边界之间过渡的内部持续时间。

注意

与滚动过渡一样,使用 valuevalueOffset 时要小心,因为它可能有点棘手。指针过渡在内部使用 value 属性进行内部插值。这意味着如果您使用其他属性并期望它们完美过渡,有时可能会得到意外的结果。

演示: hyper-effects-demo.web.app

欢迎您为该软件包做出贡献。有关详细信息,请参阅 CONTRIBUTING.md

作者

Birju Vachhani Saad Ardati

欢迎加入我们的 Discord 服务器以获取任何咨询或支持: https://discord.gg/yrahEhCqTJ

许可证

BSD 3-Clause License

Copyright (c) 2023, Hyperdesigned

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this
   list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice,
   this list of conditions and the following disclaimer in the documentation
   and/or other materials provided with the distribution.

3. Neither the name of the copyright holder nor the names of its
   contributors may be used to endorse or promote products derived from
   this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

GitHub

查看 Github