我一直在读《this post》这样的书,它解释了Ffltter在很大程度上更喜欢构图,而不是继承.虽然我部分理解其中的原因,但我质疑在这种做法变得冗长的情况下该怎么做.此外,在Ffltter的内部代码中,到处都是内置组件的继承.因此,从哲学上讲,在可以的情况下,肯定会有一些场景.

考虑这个例子(基于我做的一个真实的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上同时执行平移和不透明动画.太棒了!

现在,假设我想为这个小部件创建一些"快捷方式"或"别名",比如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,
        );
    }
}

那看起来...对我来说很冗长.所以我最初的 idea 是"嗯,也许我应该试着扩展一下这个课程……"

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),
    );
}

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

我使用继承的主要问题(这可能真的很小)是,除了我的基本小部件MyFadingAnimation之外,我不能为任何东西使用const构造函数.这一点,再加上strong对继承的阻碍,让我觉得有更好的方法.

所以,总而言之,我有两个问题:

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

推荐答案

我不会担心重定向构造函数中缺少const--毕竟,组合示例的内部MyFadingAnimation构造中也缺少const.不可能用未知的整数参数生成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相关问答推荐

飘动的文本用/字符绕转

如何在应用程序启动时解析和加载JSON文件?

build_runner显示错误没有为类ClassDeclaration定义getter宏关键字'

如何在扩展其他小部件时访问Ref

Flutter 图标记移动

Flutter 应用程序中的Firebase实时数据库中的orderByChild()不适用于我

如何从DART中的事件列表中获取即将到来的日期

脚本具有不受支持的MIME类型(';Text/html';).(messaging/failed-service-worker-registration)

将列表<;Events>;从Flutter 应用程序中的函数传回EventLoader属性时出错

2023 年如何使用 youtube_explode_dart 包下载 URL?

尽管设置了对齐方式,如何对齐列中的行?

如何将取消图标放置在右上角

在 Flutter 中,SpinEdit 叫什么?

如何在 Flutter 中的 BackdropFilter.blur 中制作一个 100x100 的清晰孔

如何让小部件根据小部件的大小构建不同的布局?

Flutter/Dart - 从外部类更新状态

flutter 创建一个带有 2 个图标的按钮

Listview 如何转到下一行/新行?

运行Flutter 测试时出现 FirebaseAppPlatform.verifyExtends 错误

为什么这个参数类型不能分配给参数类型?