flutter - 在 Flutter 中,什么时候你更喜欢 `Widget` 继承而不是组合?

标签 flutter dart flutter-layout

我一直在阅读类似 this post 的内容解释 Flutter 如何更喜欢组合而不是继承。虽然我部分理解原因,但我质疑在这种做法变得冗长的情况下该怎么做。另外,在 Flutter 的内部代码中,内置组件到处都是继承。所以从哲学上讲,一定有合适的场景。

考虑这个例子(基于我制作的一个真实的Widget):

class MyFadingAnimation extends StatefulWidget {
    final bool activated;
    final Duration duration;
    final Curve curve;
    final Offset transformOffsetStart;
    final Offset transformOffsetEnd;
    final void Function()? onEnd;
    final Widget? child;

    const MyFadingAnimation({
        super.key,
        required this.activated,
        this.duration = const Duration(milliseconds: 500),
        this.curve = Curves.easeOut,
        required this.transformOffsetStart,
        this.transformOffsetEnd = const Offset(0, 0),
        this.onEnd,
        this.child,
    });

    @override
    State<MyFadingAnimation> createState() => _MyFadingAnimationBuilder();
}

class _MyFadingAnimationBuilder extends State<MyFadingAnimation> {
    @override
    Widget build(BuildContext context) {
        return AnimatedContainer(
            duration: widget.duration,
            curve: widget.curve,
            transform: Transform.translate(
                offset: widget.activated ?
                    widget.transformOffsetStart : widget.transformOffsetEnd,
            ).transform,
            onEnd: widget.onEnd,
            child: AnimatedOpacity(
                duration: widget.duration,
                curve: widget.curve,
                opacity: widget.activated ? 1 : 0,
                child: widget.child
            ),
        );
    }
}

MyFadingAnimation 的目标是同时在 Widget 上执行平移动画和不透明动画。太棒了!

现在,假设我想为这个小部件创建一些“快捷方式”或“别名”,例如用于水平淡入的 MyHorizo​​ntalAnimation 或用于垂直淡入的 MyVerticalAnimation .使用组合,您必须创建如下内容:

class MyHorizontalAnimation extends StatelessWidget {
    final bool activated;
    final Duration duration;
    final Curve curve;
    final double offsetStart;
    final void Function()? onEnd;
    final Widget? child;

    const MyHorizontalAnimation({
        super.key,
        required this.activated,
        this.duration = const Duration(milliseconds: 500),
        this.curve = Curves.easeOut,
        required this.offsetStart,
        this.onEnd,
        this.child,
    });

    @override
    Widget build(BuildContext context) {
        return MyFadingAnimation(
            activated: activated,
            duration: duration,
            curve: curve,
            transformOffsetStart: Offset(offsetStart, 0),
            onEnd: onEnd,
            child: child,
        );
    }
}

这似乎......对我来说非常冗长。所以我最初的想法是“好吧,也许我应该尝试扩展类(class)......”

class MyHorizontalAnimation extends MyFadingAnimation {
    final double offsetStart;

    MyHorizontalAnimation({
        super.key,
        required super.activated,
        super.duration,
        super.curve,
        this.offsetStart,
        super.onEnd,
        super.child,
    }) : super(
        transformOffsetStart: Offset(offsetStart, 0),
    );
}

对我来说,这看起来更干净。此外,它还有一个额外的好处,即如果我向 MyFadingAnimation 添加功能/ Prop ,它几乎会自动集成到 MyHorizo​​ntalAnimation 中(除了必须添加 super.newProp)。使用组合方法,我必须添加一个新属性,可能会复制/维护默认值,将其添加到构造函数中,当我完成时,感觉就像一件苦差事。

虽然我使用继承的主要问题(这可能真的很琐碎)是除了我的基本小部件 MyFadingAnimation 之外,我不能为任何东西提供 const 构造函数。加上继承强烈的阻碍,让我觉得有更好的方法。

所以,总而言之,这是我的两个问题:

  1. 我应该如何组织上面的代码,使 const Widget 重定向到其他“基础”Widget
  2. 什么时候可以使用继承而不是组合?这有什么好的经验法则吗?

最佳答案

我不会担心您的重定向构造函数中缺少 const - 毕竟,组合示例在内部 MyFadingAnimation< 中也缺少 const/ build 。不可能用未知整数参数创建 const Offset,因此这是一个不可避免的语言限制。

关于组合与继承的主题,您的用例还有另一种解决方案:基类中的辅助构造函数。这种模式在整个框架中使用 - 查看 SizedBox ,例如。

请注意,当涉及到默认参数值时,这种风格确实引入了一些重复性。

class MyFadingAnimation extends StatefulWidget {
    final bool activated;
    final Duration duration;
    final Curve curve;
    final Offset transformOffsetStart;
    final Offset transformOffsetEnd;
    final void Function()? onEnd;
    final Widget? child;

    const MyFadingAnimation({
        super.key,
        required this.activated,
        this.duration = const Duration(milliseconds: 500),
        this.curve = Curves.easeOut,
        required this.transformOffsetStart,
        this.transformOffsetEnd = const Offset(0, 0),
        this.onEnd,
        this.child,
    });

    MyFadingAnimation.horizontal({
      super.key,
      required this.activated,
      this.duration = const Duration(milliseconds: 500),
      this.curve = Curves.easeOut,
      required double offsetStart,
      this.onEnd,
      this.child,
    })  : transformOffsetStart = Offset(offsetStart, 0),
          transformOffsetEnd = const Offset(0, 0);

    @override
    State<MyFadingAnimation> createState() => _MyFadingAnimationBuilder();
}

关于flutter - 在 Flutter 中,什么时候你更喜欢 `Widget` 继承而不是组合?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/74540642/

相关文章:

firebase - 如何将 Flutter Web 应用程序部署到 Firebase 上托管的现有网站?

linux - 在 Dart 中解码 ANSI 转义序列

dart - 在 Dart 中将整数位模式解析为 IEEE 754 float

flutter - 如何在Flutter中向TabBar动态添加标签?

android - 如何设置 flutter togglebuttons 小部件的宽度

flutter - 如何显示编号用户可以输入的字符数以及用户在 TextFormField Flutter 中输入的字符数

android - 使用Visual Studio在Flutter上运行Gradle时出错

flutter - 将 Wrap 小部件与文本一起使用

Flutter 后退按钮隐藏在导航中

dart - Flutter:如何通过 appbar 操作显示 snackbar