Flutter动画与样式定制插件animated_styled_widget的使用

Flutter动画与样式定制插件animated_styled_widget的使用

animated_styled_widget

强大的样式、序列化、动画和自定义组件集于一体。

当前此包的功能包括:

  1. 使用可序列化的样式设计高度可定制的 StyledContainer 小部件。
  2. 使用 AnimatedStyledContainer 小部件进行隐式动画。
  3. 使用 ExplicitAnimatedStyledContainer 小部件进行显式动画(局部/全局,基于时间/滚动)。
  4. 样式组件:StyledButtonStyledToggleButtonsStyledSwitchStyledRadioStyledCheckboxStyledSlider。你的应用不必看起来像Material Design。

此包的许多功能受到CSS或遵循CSS规范的启发。如果你熟悉CSS,那么使用此包应该更加容易。

什么是Style类?

Style 类是一组UI数据类。它目前支持以下属性:

定位和对齐

Dimension width
Dimension height
Alignment alignment
Alignment childAlignment
EdgeInsets margin
EdgeInsets padding

形状和装饰

BoxDecorartion backgroundDecoration
BoxDecorartion foregroundDecoration
List<ShapeShadow> shadows
List<ShapeShadow> insetShadows
MorphableShapeBorder shapeBorder

可见性

bool visible
double opacity

转换

SmoothMatrix4 transform
Alignment transformAlignment

字体

DynamicTextStyle textStyle
TextAlign textAlign

鼠标光标样式

SystemMouseCursor mouseCursor

渐变和图像过滤器

Gradient shaderGradient
ImageFilter imageFilter
ImageFilter backdropFilter

Dimension 类来自 dimension 包。它支持绝对和相对单位。你还可以在 Dimension 上组合/嵌套 min/max/clamp 函数。你可以将其视为一个超级加强版的 SizedBoxFractionallySizedBox 的结合。

ShapeShadowMorphableShapeBorder 来自 morphable_shape 包。ShapeShadow 支持内阴影和渐变填充,除了 BoxShadow 支持的功能之外。MorpableShapeBorder 支持许多常用的形状、形状变形等。请参阅 fluttershape.com 查看互动演示。

DynamicTextStyle 允许你使用绝对/相对值定义字体大小、字间距等。300% 的字体大小意味着默认字体大小的三倍。

SmoothMatrix4 类似于 Matrix4,但确保所有变换都可以平滑地进行动画。它还允许你使用 Dimension 作为平移距离以适应不同的屏幕尺寸。

布局模型和绘制顺序如下所示:

布局模型

绘制顺序

一个响应式的样式示例:

Style style = Style(
  alignment: Alignment.center,
  width: 50.toVWLength,
  height: 50.toPercentLength,
  margin: EdgeInsets.symmetric(vertical: 10),
  backgroundDecoration: BoxDecoration(
    gradient: LinearGradient(
      colors: [Colors.cyanAccent, Colors.purpleAccent],
    ),
  ),
  shapeBorder: RoundedRectangleBorder(
    borderRadius: DynamicBorderRadius.all(DynamicRadius.circular(15.toPXLength)),
  ),
);

定义样式后,使用 StyledContainer 小部件:

var widget = StyledContainer(
  style: style,
  child: ...,
);

隐式动画

几乎 Style 类中的每个属性都可以进行动画处理。以下 GIF 展示了这种效果:

动画示例2 动画示例3 动画示例4 动画示例5 动画示例6 动画示例7 动画示例8

只需将 StyledContainer 替换为 AnimatedStyledContainer 并提供持续时间和曲线。注意动画不仅可以通过提供新的样式/样式映射来触发,还可以通过窗口调整大小/屏幕旋转来触发,只要提供了适当的样式。

样式组件

样式组件是一些带有简单逻辑的UI组件,如按钮、单选按钮、切换按钮、开关等。Material实现这些组件非常棒,但自定义能力有限。有许多其他包提供了更多的自定义能力,但通常仅限于颜色、边框半径、大小等。此包提供了更多功能。

Material组件根据其内部状态(如悬停、按下、选择等)进行动画。样式组件也是如此。让我们以 StyledButton 构造函数为例:

StyledButton({
  Key? key,
  this.onPressed,
  required this.style,
  this.hoveredStyle,
  this.pressedStyle,
  this.disabledStyle,
  this.curve = Curves.linear,
  this.duration = const Duration(milliseconds: 100),
  Widget? child
});

StyledButton.builder({
  Key? key,
  this.onPressed,
  required this.style,
  this.hoveredStyle,
  this.pressedStyle,
  this.disabledStyle,
  this.curve = Curves.linear,
  this.duration = const Duration(milliseconds: 100),
  required this.builder
});

按钮可以处于四种状态之一:空闲、悬停、按下、禁用。你需要提供四个相应的样式。请注意,只有默认(空闲)样式是必需的,如果没有提供其他三种状态的样式,则会使用此样式。你还可以指定从不同状态过渡时的持续时间和动画曲线。child 参数是此按钮的子小部件。但你也可以提供一个 builder,它可以访问按钮的内部状态:

builder: (context, state) {
  Widget child;
  switch (state) {
    case StyledState.pressed:
      child = Text("TAPPED", key: UniqueKey());
      break;

    default:
      child = Text("TAP ME", key: UniqueKey());
      break;
  }
  return AnimatedSwitcher(
    duration: Duration(milliseconds: 200),
    child: child,
  );
}

以下是六个不同样式的按钮,它们在有无光标的情况下都能很好地工作:

样式按钮

StyledCheckboxStyledButton 非常相似,只是 pressedStyle 参数被替换为 selectedStyle,因为复选框(和其他可切换组件)关心的是是否被选中(而不是按下)。

样式复选框

然后是 StyledRadio

样式单选按钮

接下来是 StyledToggleButtons

样式切换按钮

注意,所有这些组件除了样式部分外,参数命名方案与内置的一致。

StyledSwitchStyledSlider 稍微复杂一些,因为现在样式涉及两个组件。对于 StyledSwitch,你需要为轨道和拇指提供样式。如果水平方向的开关,拇指会被对齐到轨道的左侧或右侧。

样式开关

StyledSlider 也需要为轨道和拇指提供样式,但你还可以为活动轨道和提示提供样式。

样式滑块

滑块的接口尚未最终确定,将来可能会添加更多样式参数。

我还实现了 StyledNavigationBarStyledPopupMenuButton。你可以在示例应用程序中找到所有这些组件的演示。

下一步可能是为样式组件添加主题功能,以便人们能够更好地自定义样式。

显式动画

隐式动画易于使用,但无法实现我们想要的每一种效果。这时就需要 ExplicitAnimatedStyledContainer 了:

Widget widget = ExplicitAnimatedStyledContainer(
  style: style,
  child: child,
  localAnimations: localAnimations,
  globalAnimationIds: globalAnimationIds,
  id: id,
  ...
);

你仍然需要向该小部件提供初始样式,但之后你可以使用局部/全局动画来改变小部件的样式。首先让我们谈谈 localAnimations

Map<AnimationTrigger, MultiAnimationSequence> localAnimations

这是一个 AnimationTriggerMultiAnimationSequence 之间的映射。当前支持的 AnimationTrigger 如下:

enum AnimationTrigger {
  mouseEnter,
  mouseExit,
  tap,
  visible,
  scroll,
}

当触发事件发生(例如你点击了这个小部件)时,对应的 MultiAnimationSequence 会被触发。MultiAnimationSequence 包含一个 sequences 映射:

Map<AnimationProperty, AnimationSequence> sequences

其中 AnimationProperty 是一个枚举类,对应 Style 类支持的所有可动画属性,而 AnimationSequence 是一个包含通用值、持续时间、延迟和曲线的列表,用于告诉我们某个动画属性是如何演变的。例如:

MultiAnimationSequence(sequences: {
  AnimationProperty.width: AnimationSequence()
    ..add(
      delay: Duration(seconds: 1),
      duration: Duration(milliseconds: 200),
      curve: Curves.linear,
      value: 100.toPXLength
    )
    ..add(
      duration: Duration(milliseconds: 200),
      curve: Curves.easeIn,
      value: 50.toVWLength
    )
});

这将在 1 秒后延迟,然后在 200 毫秒内将宽度从当前值动画到 100 像素,然后在 200 毫秒内将宽度动画到 50% 屏幕宽度。你可以使用相同的语法来动画其他属性。

鼠标悬停效果

上述鼠标悬停效果通过编写以下代码实现:

Widget widget = ExplicitAnimatedStyledContainer(
  style: style,
  child: child,
  localAnimations: {
    AnimationTrigger.mouseEnter: enterSequence,
    AnimationTrigger.mouseExit: exitSequence,
  }
);

你可以为鼠标进入和退出设置不同的持续时间和曲线,也可以为不同的样式属性设置不同的动画。

现在让我们谈谈其他动画触发器。AnimationTrigger.tap 很容易理解。AnimationTrigger.visible 在小部件在视口内变得可见时触发(通过使用 visibility_detector 包)。AnimationTrigger.scroll 当小部件位于滚动容器(如 ListView)内时触发。然后小部件将根据其沿滚动方向的位置进行动画:

滚动进度

动画进度默认按上图所示的方式计算(如果水平滚动)。但你也可以使用两个百分比偏移量使动画提前开始或延迟结束。

动画示例10 动画示例10 动画示例11 动画示例12

预设动画

那些 MultiAnimationSequence 看起来很强大,但也相当复杂。我为常见用途准备了一些预定义动画。它们分为进入、吸引注意力和退出类别。例如,一个常见的进入动画称为 SlideInAnimation,定义如下:

class SlideInAnimation extends PresetAnimation {
  final AxisDirection direction;
  final Dimension distance;
  const SlideInAnimation({
    this.distance = const Length(100, unit: LengthUnit.vmax),
    this.direction = AxisDirection.up,
    Duration duration = const Duration(seconds: 1),
    Duration delay = Duration.zero,
    Curve curve = Curves.linear,
    CustomAnimationControl control = CustomAnimationControl.play
  }) : super(duration: duration, delay: delay, curve: curve, control: control);
  ...
}

你可以配置滑动距离和方向,以及持续时间、延迟、曲线和控制(动画是否应播放一次或无限次)。其他预定义动画如下:

FadeInAnimation
ZoomInAnimation
FadeOutAnimation
SlideOutAnimation
ZoomOutAnimation
FlipAnimation
FlashAnimation
PulseAnimation
SwingAnimation
WobbleAnimation
RainbowAnimation
ElevateAnimation
...

你可以这样使用它们:

Widget widget = ExplicitAnimatedStyledContainer(
  style: style,
  child: child,
  localAnimations: {
    AnimationTrigger.visible: FadeInAnimation().getAnimationSequences()
  }
);

然后每次小部件移动到屏幕上时,它都会淡入(透明度从 0 到 1)。MultiAnimationSequence 的另一个特性是可以合并或扩展其他 MultiAnimationSequence。因此你可以这样做:

Widget widget = ExplicitAnimatedStyledContainer(
  style: style,
  child: child,
  localAnimations: {
    AnimationTrigger.visible: FadeInAnimation().getAnimationSequences()..merge(SlideInAnimation().getAnimationSequences())
  }
);

然后小部件在出现在视口中时会同时淡入和滑入。如果你使用 extend,动画将依次播放。预定义动画使得动画更容易使用,同时仍为你提供了很大的灵活性。

动画示例13

全局显式动画

如果我们想跨不同小部件交错动画,可以通过提供全局动画来实现。全局动画包含一个字符串和 MultiAnimationSequence 之间的映射,其中字符串是小部件的标识符。你可以在动画池中提供所有要使用的全局动画。然后你可以像这样触发全局动画:

var animationPool = {
  "animation1": GlobalAnimation(sequences: {
    "container1" : sequence1,
    "container2": sequence2,
    ...
  })
};

ChangeNotifierProvider<GlobalAnimationNotifier>(
  create: (_) => GlobalAnimationNotifier(animationPool: animationPool), child: child
);

Widget widget = ExplicitAnimatedStyledContainer(
  id: "container1",
  style: style,
  child: child,
  globalAnimationIds: {
    AnimationTrigger.visible: "animation1"
  }
);

id 不必唯一。你可以有多个具有相同 id 的小部件,因此它们将在同一个全局动画下进行动画。请注意,如果小部件根本不使用全局动画,则不需要 id

动画示例14

其他需要注意的事项

显式动画使用了 providersimple_animation 包。

你可以通过调用以下方法在 ExplicitAnimatedStyledContainer 的子组件中编程更改动画状态:

Provider.of<LocalAnimationNotifier>(context, listen: false)
  .updateAnimationStatus(animationSequence, status);

Provider.of<GlobalAnimationNotifier>(context, listen: false)
  .updateAnimationStatus(animationId, status);

这将更新动画及其状态(如播放、停止、循环等)。

你还可以在动画触发事件中提供回调:

Widget widget = ExplicitAnimatedStyledContainer(
  style: style,
  child: child,
  localAnimations: {
    AnimationTrigger.visible: animationSequence
  },
  onVisible: onVisible,
);

序列化

Style 类可以轻松地进行序列化/反序列化:

String styleJson = json.encode(style.toJson());
Style newStyle = Style.fromJson(json.decode(styleJson));

更多关于Flutter动画与样式定制插件animated_styled_widget的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter动画与样式定制插件animated_styled_widget的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,下面是一个关于如何使用 animated_styled_widget 插件来实现 Flutter 动画与样式定制的示例代码。animated_styled_widget 是一个强大的 Flutter 插件,它允许开发者以声明式的方式创建动画和样式。

首先,确保你已经在 pubspec.yaml 文件中添加了 animated_styled_widget 依赖:

dependencies:
  flutter:
    sdk: flutter
  animated_styled_widget: ^x.y.z  # 请替换为最新版本号

然后运行 flutter pub get 来安装依赖。

接下来是一个简单的示例代码,展示如何使用 animated_styled_widget 来创建一个带有动画和样式定制的按钮:

import 'package:flutter/material.dart';
import 'package:animated_styled_widget/animated_styled_widget.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Animated Styled Widget Example'),
        ),
        body: Center(
          child: AnimatedStyledContainer(
            child: AnimatedStyledButton(
              onPressed: () {
                print("Button pressed!");
              },
              text: 'Click Me',
              textStyle: TextStyle(color: Colors.white),
              decoration: BoxDecoration(
                color: Colors.blue,
                borderRadius: BorderRadius.circular(20),
              ),
              animationDuration: Duration(milliseconds: 300),
              animationCurve: Curves.easeInOut,
              hoverEffect: HoverEffect(
                enabled: true,
                color: Colors.lightBlue,
                scale: 1.05,
              ),
            ),
          ),
        ),
      ),
    );
  }
}

// 你可以根据需求扩展这个示例,添加更多的动画和样式定制

在这个示例中,我们使用了 AnimatedStyledContainerAnimatedStyledButton 来创建一个带有动画效果的按钮。AnimatedStyledButton 接受多个参数来定制按钮的样式和动画行为,例如:

  • onPressed: 按钮点击事件回调。
  • text: 按钮上的文本。
  • textStyle: 按钮文本的样式。
  • decoration: 按钮的装饰(如背景色、边框半径等)。
  • animationDuration: 动画持续时间。
  • animationCurve: 动画曲线。
  • hoverEffect: 悬停效果,包括颜色变化和缩放比例。

这个示例代码展示了如何使用 animated_styled_widget 插件来快速创建带有动画和样式定制的 Flutter 组件。你可以根据需求进一步扩展和定制这些组件。

回到顶部