如何创建高级枚举的混合并在通用小部件中使用它?

huangapple go评论73阅读模式
英文:

How to create a mixin for advanced enum and use it in a generic widget?

问题

以下是你要翻译的代码部分:

class WheelPickerWidget<T extends Enum> extends StatelessWidget {
  /// 初始值
  final T? value;

  /// onChanged 回调
  final void Function(T)? onChanged;

  /// 返回滚轮枚举选择器
  const WheelPickerWidget({Key? 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会生成以下错误:

方法'fromValue'未定义于类型'Type'。
请更正名称为现有方法的名称,或定义一个名为'fromValue'的方法。

获取器'values'未定义于类型'Type'。
尝试导入定义'values'的库,更正名称为现有获取器的名称,或定义一个名为'values'的获取器或字段。

我通常将我的枚举定义如下:

/// 应用程序使用的主题
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("未知的值 $v"));

  /// 返回与枚举对应的名称
  @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 的特定形式,但我没有成功,其中一个原因是 mixin 不支持工厂方法。

总之,我的问题总结如下:

  1. 如何使我的滚轮选择器类适用于我的枚举?;
  2. 如何创建一种通用方式(可能是 mixin),以使所有我的枚举都符合可以由我的通用滚轮选择器支持的方式?
英文:

My goal is to write a generic Widget that, in this case, enables the user for selecting an enum value among all the values from the enum.

So I'd like to write something like so:


class WheelPickerWidget&lt;T extends Enum&gt; 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) =&gt; onChanged?.call(T.fromValue(value)),
        children: T.values.map((c) =&gt; Text(&quot;$c&quot;)).toList());
  }
}

But I see T.fromValues() and T.values are generating errors as follows:

> The method 'fromValue' isn't defined for the type 'Type'.
Try correcting the name to the name of an existing method, or defining a method named 'fromValue'.

> The getter 'values' isn't defined for the type 'Type'.
Try importing the library that defines 'values', correcting the name to the name of an existing getter, or defining a getter or field named 'values'.

I usually write my enums as follows:

/// 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) =&gt; values.firstWhere((x) =&gt; x.value == v,
      orElse: () =&gt; throw Exception(&quot;Unknown value $v&quot;));

  /// 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;
    }
  }
}

Where I make fromValue() readily available.

And I guess I could use mixin to create a specific form of enum that complies to the requirements.

/// Advanced enum
mixin EnumMixin {
}

But I didn't manage to do it: one reason is the factory cannot be supported by the mixin.

So to sum up, my questions are:

  1. How to make my wheel picker class works with my enum?;
  2. How to create a generic way (possibly being a mixin) to conform all my enums to a way it can be supported by my generic wheel picker?

答案1

得分: 2

以下是翻译好的部分,代码部分不翻译:

"You cannot make T.someConstructor() or T.someStaticMethod() work for some generic type T. Dart does not consider constructors and static methods to be part of the class interface, and they are not inherited."

"在某些泛型类型 T 上无法使 T.someConstructor()T.someStaticMethod() 工作。Dart 不认为构造函数和 static 方法属于类接口的一部分,它们不会被继承。"

"In general, whenever you want to use something like T.someConstructor() or T.someStaticMethod(), you're probably better off using a callback instead. Similarly, instead of using T.values, you can accept a List<T> argument."

"一般来说,每当你想要使用类似 T.someConstructor()T.someStaticMethod() 的东西时,最好使用回调函数。类似地,不要使用 T.values,可以接受一个 List<T> 参数。"

"For example:"

"例如:"

"and then callers would use:"

"然后调用者将使用:"

"Note that fromValue is a bit redundant in principle if you already have values; you could just iterate over values to find the Enum you want. For example, you could do:"

"请注意,原则上,如果你已经有了 valuesfromValue 有点多余;你可以直接迭代 values 来找到你想要的 Enum。例如,你可以这样做:"

"Unfortunately, findValue is slightly awkward because there doesn't seem to be a good way to enforce that T derives from both Enum and HasValue, so it must perform runtime type-checking. Additionally, Dart will not perform automatic type promotion between unrelated types (in this case, Enum and HasValue), so findValue upcasts to Object first as a workaround."

"不幸的是,findValue 有点尴尬,因为似乎没有很好的方法来强制要求 T 派生自 EnumHasValue,因此必须执行运行时类型检查。此外,Dart 不会在不相关的类型之间执行自动类型提升(在这种情况下是 EnumHasValue),因此 findValue 首先将其提升为 Object 作为一种解决方法。"

"If you don't want callers to pass extra arguments, one alternative would be to store those arguments in a global lookup table with the generic type as the key. This isn't a great general approach since a Map<Type, ...> depends on exact Type matches, so looking up a subtype wouldn't match a supertype in the Map. However, Enums are not allowed to be extended nor implemented, so that is not a concern. I would consider it to be less robust, however, since it would require extra work to initialize such a Map, and there's no way at compile-time that it's been initialized with all of the types you care about. As an example of how this could look:"

"如果你不希望调用者传递额外的参数,一个替代方法是将这些参数存储在全局查找表中,以泛型类型作为键。这不是一个很好的通用方法,因为 Map<Type, ...> 依赖于精确的 Type 匹配,因此查找子类型不会匹配 Map 中的超类型。但是,Enum 不允许被扩展或实现,因此这不是一个问题。然而,我认为这不太健壮,因为它需要额外的工作来初始化这样的 Map,并且在编译时没有办法确定它是否已经初始化为你关心的所有类型。以下是一个示例,展示了这种方法的外观:"

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

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

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

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

英文:

You cannot make T.someConstructor() or T.someStaticMethod() work for some generic type T. Dart does not consider constructors and static methods to be part of the class interface, and they are not inherited.

In general, whenever you want to use something like T.someConstructor() or T.someStaticMethod(), you're probably better off using a callback instead. Similarly, instead of using T.values, you can accept a List&lt;T&gt; argument.

For example:

class WheelPickerWidget&lt;T extends Enum&gt; extends StatelessWidget {
  WheelPickerWidget({required this.values, required this.fromValue});

  final List&lt;T&gt; values;
  final T Function(int) fromValue;

  ...

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

and then callers would use:

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

Note that fromValue is a bit redundant in principle if you already have values; you could just iterate over values to find the Enum you want. For example, you could do:

abstract class HasValue&lt;T&gt; {
  T get value;
}

enum AppTheme implements HasValue&lt;int&gt; {
  green(0),
  yellow(1),
  nightBlue(2);

  const AppTheme(this.value);

  @override
  final int value;

  ...
}

class WheelPickerWidget&lt;T extends Enum&gt; extends StatelessWidget {
  WheelPickerWidget({required this.values})

  final List&lt;T&gt; values;

  ...

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

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

Unfortunately, findValue is slightly awkward because there doesn't seem to be a good way to enforce that T derives from both Enum and HasValue, so it must perform runtime type-checking. Additionally, Dart will not perform automatic type promotion between unrelated types (in this case, Enum and HasValue), so findValue upcasts to Object first as a workaround.

If you don't want callers to pass extra arguments, one alternative would be to store those arguments in a global lookup table with the generic type as the key. This isn't a great general approach since a Map&lt;Type, ...&gt; depends on exact Type matches, so looking up a subtype wouldn't match a supertype in the Map. However, Enums are not allowed to be extended nor implemented, so that is not a concern. I would consider it to be less robust, however, since it would require extra work to initialize such a Map, and there's no way at compile-time that it's been initialized with all of the types you care about. As an example of how this could look:

final _fromValueMap = &lt;Type, Enum Function(int)&gt;{
  AppTheme: AppTheme.fromValue,
};

final _lookupValuesMap = &lt;Type, List&lt;Enum&gt;&gt;{
  AppTheme: AppTheme.values,
};

T fromValue&lt;T&gt;(int value) =&gt; _fromValueMap[T]!(value) as T;

List&lt;T&gt; lookupValues&lt;T&gt;() =&gt; _lookupValuesMap[T]! as List&lt;T&gt;;

class WheelPickerWidget&lt;T extends Enum&gt; extends StatelessWidget {
  ...

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

huangapple
  • 本文由 发表于 2023年2月19日 00:20:58
  • 转载请务必保留本文链接:https://go.coder-hub.com/75494684.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定