在 Flutter 中,什么时候你应该更喜欢“Widget”继承而不是组合?

Tay*_*own 5 dart flutter flutter-layout

我一直在读像这篇文章这样的东西解释 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
            ),
        );
    }
}
Run Code Online (Sandbox Code Playgroud)

目标MyFadingAnimation是同时执行平移和不透明动画Widget。伟大的!

现在,假设我想为此小部件创建一些“快捷方式”或“别名”,例如MyHorizontalAnimation水平淡入或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,
        );
    }
}
Run Code Online (Sandbox Code Playgroud)

这对我来说似乎非常冗长。所以我最初的想法是“好吧,也许我应该尝试延长课程……”

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),
    );
}
Run Code Online (Sandbox Code Playgroud)

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

不过,我使用继承的主要问题(这可能真的很小)是,const除了我的基本小部件MyFadingAnimation. 再加上继承的强烈阻碍,让我觉得有更好的方法。

因此,总而言之,这是我的两个问题:

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

hac*_*024 2

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

关于组合与继承的主题,还有另一种适合您的用例的解决方案:基类中的辅助构造函数。这种模式在整个框架中使用 -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();
}
Run Code Online (Sandbox Code Playgroud)