dart 如何为高级枚举创建一个mixin并将其用在一个普通的小部件中?

f87krz0w  于 2023-02-20  发布在  其他
关注(0)|答案(1)|浏览(89)

我的目标是编写一个泛型Widget,在本例中,它使用户能够从枚举的所有值中选择一个enum值。
所以我想这样写:

class WheelPickerWidget<T extends Enum> extends StatelessWidget {
  /// The initial value
  final T? value;

  /// The onChanged callback
  final void Function(T)? onChanged;

  /// Retuns the wheel enum picker
  const WheelPickerWidget(
      {super.key, required this.value, required this.onChanged});

  @override
  Widget build(BuildContext context) {
    return ListWheelScrollView(
        physics: const BouncingScrollPhysics(),
        itemExtent: 50,
        diameterRatio: 0.6,
        //offAxisFraction: -0.4,
        squeeze: 1.8,
        //useMagnifier: true,
        //overAndUnderCenterOpacity: 0.8,
        clipBehavior: Clip.antiAlias,
        onSelectedItemChanged: (value) => onChanged?.call(T.fromValue(value)),
        children: T.values.map((c) => Text("$c")).toList());
  }
}

但我看到T.fromValues()T.values生成如下错误:
没有为类型“Type”定义方法“fromValue”。请尝试将名称更正为现有方法的名称,或定义名为“fromValue”的方法。
没有为类型“Type”定义getter“values”。请尝试导入定义“values”的库,将名称更正为现有getter的名称,或者定义名为“values”的getter或字段。
我通常将我的enum写为:

/// Theme to use for the app
enum AppTheme {
  green(0),
  yellow(1),
  nightBlue(2);

  const AppTheme(this.value);
  final int value;

  factory AppTheme.fromValue(int v) => values.firstWhere((x) => x.value == v,
      orElse: () => throw Exception("Unknown value $v"));

  /// Returns the name corresponding to the enum
  @override
  String toString() {
    switch (this) {
      case green:
        return i18n_Green.i18n;
      case yellow:
        return i18n_Yellow.i18n;
      case nightBlue:
        return i18n_Night_blue.i18n;
    }
  }
}

在这里我使fromValue()随时可用。
我想我可以使用mixin来创建符合要求的enum的特定形式。

/// Advanced enum
mixin EnumMixin {
}

但我没能做到:一个原因是mixin不能支持工厂。
所以总结一下,我的问题是:
1.如何使轮式选择器类与枚举一起工作?
1.如何创建一个通用的方法(可能是一个mixin)来使我所有的枚举符合我的通用滚轮选择器所支持的方式?

b1uwtaje

b1uwtaje1#

不能使T.someConstructor()T.someStaticMethod()为某个泛型类型T工作。Dart不认为构造函数和static方法是类接口的一部分,并且它们不是继承的。
一般来说,当你想使用T.someConstructor()T.someStaticMethod()时,你最好使用回调函数,类似地,你可以接受一个List<T>参数来代替T.values
例如:

class WheelPickerWidget<T extends Enum> extends StatelessWidget {
  WheelPickerWidget({required this.values, required this.fromValue});

  final List<T> values;
  final T Function(int) fromValue;

  ...

  @override
  Widget build(BuildContext context) {
    return ListWheelScrollView(
        ...
        onSelectedItemChanged: (value) => onChanged?.call(fromValue(value)),
        children: values.map((c) => Text("$c")).toList());
  }
}

然后呼叫者将使用:

WheelPickerWidget(values: AppTheme.values, fromValue: AppTheme.fromValue);

注意,如果您已经有了values,那么fromValue原则上是有点冗余的;你可以遍历values来找到你想要的Enum。例如,你可以这样做:

abstract class HasValue<T> {
  T get value;
}

enum AppTheme implements HasValue<int> {
  green(0),
  yellow(1),
  nightBlue(2);

  const AppTheme(this.value);

  @override
  final int value;

  ...
}

class WheelPickerWidget<T extends Enum> extends StatelessWidget {
  WheelPickerWidget({required this.values})

  final List<T> values;

  ...

  @override
  Widget build(BuildContext context) {
    return ListWheelScrollView(
        ...
        onSelectedItemChanged: (value) => onChanged?.call(values.findValue(value)),
        children: values.map((c) => Text("$c")).toList());
  }
}

extension<T extends Enum> on List<T> {
  T findValue<U>(U value) {
    for (Object e in this) {
      if (e is HasValue<U> && e.value == value) {
        return e as T;
      }
    }
    throw Exception("Unknown value $value");
  }
}

不幸的是,findValue有点笨拙,因为似乎没有一种好的方法来强制T派生自EnumHasValue,因此它必须执行运行时类型检查。(在本例中为EnumHasValue),因此findValue首先向上转换为Object作为解决方法。
如果你不想调用者传递额外的参数,一个替代方法是将这些参数存储在一个全局查找表中,并将泛型类型作为键。这不是一个很好的通用方法,因为Map<Type, ...>依赖于精确的Type匹配,所以在Map中查找子类型不会匹配超类型。但是,Enum不允许扩展也不允许实现,所以这不是问题。不过,我认为它的健壮性较低,因为初始化这样的Map需要额外的工作,并且不可能在编译时用你所关心的所有类型初始化它,举个例子来说明这可能是什么样子:

final _fromValueMap = <Type, Enum Function(int)>{
  AppTheme: AppTheme.fromValue,
};

final _lookupValuesMap = <Type, List<Enum>>{
  AppTheme: AppTheme.values,
};

T fromValue<T>(int value) => _fromValueMap[T]!(value) as T;

List<T> lookupValues<T>() => _lookupValuesMap[T]! as List<T>;

class WheelPickerWidget<T extends Enum> extends StatelessWidget {
  ...

  @override
  Widget build(BuildContext context) {
    return ListWheelScrollView(
        ...
        onSelectedItemChanged: (value) => onChanged?.call(fromValue<T>(value)),
        children: lookupValues<T>().map((c) => Text("$c")).toList());
  }
}

相关问题