Flutter文本输入效果插件typer的使用

发布于 1周前 作者 bupafengyu 来自 Flutter

Flutter文本输入效果插件typer的使用

Typer

提供 <code>Typer&lt;X&gt;</code>,一个用户编写但功能更强大的版本的 <code>Type</code>

内置的 <code>Type</code>

Dart语言内置了通过评估相应的类型字面量作为表达式来获取给定类型 <code>T</code> 的实例化表示的功能:

typedef TypeOf&lt;X&gt; = X;

void main() {
  // `type` 是表示类型 `int` 的对象。
  Type type = int;
  
  // 有些类型不是表达式,但我们可以通过类型别名如 `TypeOf` 获取其 `<code>Type`
  Type functionType = TypeOf&lt;void Function([String])&gt;;
  
  // `<code>Type` 实例不能用于太多事情,但它们确实具有相等性。
  print(type == functionType); // 'false'.
  
  // 例如,我们不能在类型测试或子类型检查中使用它们。
  1 is type; // 编译错误。
  1 as type; // 编译错误。
  type &lt;= functionType; // 编译错误。
}

内置 <code>Type</code> 类的最大用途是我们可以使用 <code>runtimeType</code> 获取任何给定对象的运行时类型:

void main() {
  Object? o1 = ..., o2 = ...; // 任意对象都可以。
  print(o1.runtimeType == o2.runtimeType);
}

Typer

对于获取现有对象的运行时类型以外的所有其他操作,我们可以使用 <code>Typer&lt;T&gt;</code> 的实例,其中 <code>T</code> 是我们可以表示的任何类型,这将比 <code>Type</code> 能做更多事情。

比较类型

特别是,<code>Typer</code> 支持关系运算符 <code>&lt;</code><code>&lt;=</code><code>&gt;</code><code>&gt;=</code>。它们会确定一个 <code>Typer</code> 表示的类型是否是另一个类型的子类型/超类型:

void main() {
  const t1 = Typer&lt;int&gt;();
  const t2 = Typer&lt;num&gt;();
  print(t1 &lt;= t2); // 'true'.
  print(t2 &lt;= t1); // 'false'.
}

类型测试和类型转换

如果 <code>typer</code><code>Typer&lt;T&gt;</code> 的实例,则我们可以使用 <code>o.isA(typer)</code> 测试给定对象 <code>o</code> 是否是 <code>T</code> 的实例,并使用 <code>o.asA(typer)</code> 进行 <code>T</code> 类型的转换。注意这些测试将使用 <code>typer</code> 的实际类型值,而不是静态已知的类型值(可能是实际值的任何超类型)。

void main() {
  // 假设我们忘记了 `<code>typer</code>` 表示 `<code>int</code>`,我们只知道它是 `<code>num</code>` 的子类型。
  Typer&lt;num&gt; typer = Typer&lt;int&gt;();
  
  // 但是 `<code>isA</code>` (和 `<code>asA</code>`) 方法将使用实际类型。
  print(2.isA(typer)); // 'true'.
  print(1.5.isA(typer)); // 'false'.
  2.asA(typer); // OK.
  1.5.asA(typer); // 抛出异常。
}

<code>isA</code><code>isNotA</code><code>asA</code> 是扩展方法。它们基于实例成员。我们可能希望使用扩展方法,因为它们具有更常规的语法,或者我们可能(或需要)使用实例成员,例如因为我们不想导入扩展,或者因为调用必须是动态的。

// 同样使用实例成员。

void main() {
  Typer&lt;num&gt; typer = Typer&lt;int&gt;();

  print(typer.containsInstance(2)); // 'true'.
  print(typer.containsInstance(1.5)); // 'false'.
  typer.cast(2); // OK.
  typer.cast(1.5); // 抛出异常。
}

我们可以使用 getter <code>type</code> 访问底层类型作为一个对象(<code>Type</code> 的实例):

void main() {
  Typer&lt;num&gt; typer = Typer&lt;int&gt;();
  print(typer.type); // 'int'.
}

使用 <code>Typer</code> 表示的类型

我们可以使用方法 <code>callWith</code> 以静态安全的方式访问底层类型(这本质上是一个“存在性打开”操作):

List&lt;X&gt; createList&lt;X&gt;(Typer&lt;X&gt; typer) =&gt;
    typer.callWith(&lt;Y&gt;() =&gt; &lt;Y&gt;[] as List&lt;X&gt;);

void main() {
  // 再次,我们并不完全了解类型。
  Typer&lt;num&gt; typer = Typer&lt;int&gt;();

  List&lt;num&gt; xs = createList(typer);
  print(xs is List&lt;int&gt;); // 'true'.
}

要理解为什么这是一个非平凡的操作,请尝试完成以下示例,该示例使用内置的 <code>Type</code> 对象执行相同的操作:

List&lt;X&gt; createList&lt;X&gt;(Type type) =&gt; ...; // 注意!

void main() {
  Type type = int;

  List&lt;num&gt; xs = createList(type);
  print(xs is List&lt;int&gt;); // 'true'.
}

请注意 <code>X</code> 是无约束的,并且没有任何保证它与给定的 <code>Type</code> 有任何关系(<code>X</code> 可能的值为 <code>String</code>,而 <code>type</code> 可能是 <code>int</code> 的具体表示,我们不会知道有问题)。

实际上,除了使用 'dart:mirrors' 之外,不可能从给定的 <code>type</code> 中提取任何信息(除了相等性,比如可以用来测试 <code>type != X</code>,但这不是很有用)。因此我们基本上无法编写代码(再次,除非使用镜子)来创建一个 <code>List&lt;int&gt;</code>,基于 <code>type</code><code>int</code> 的具体表示。使用相等性我们可以迈出一些小步,但它不会扩展:

List&lt;X&gt; createList&lt;X&gt;(Type type) =&gt; switch (type) {
    int =&gt; &lt;int&gt;[],
    String =&gt; &lt;String&gt;[],
    _ =&gt; throw "Surely we don't need more cases! ;-)",
  };

最后我们有两个与提升相关的函数:

void main() {
  Typer&lt;num&gt; typer = Typer&lt;int&gt;();
  num n = Random().nextBool() ? 2 : 2.5;

  print('Promoting:');
  List&lt;num&gt;? xs = typer.promoteOrNull(n, &lt;X extends num&gt;(X promoted) {
    print('  The promotion to `typer` succeeded!');
    return &lt;X&gt;[promoted];
  });
  print('Type of `xs`: ${xs.runtimeType}'); // `List&lt;int&gt;` 或 `Null`.

  print('Promoting with `orElse` fallback:');
  Object o = n; // 从一个相当通用的类型进行提升。
  num n2 = typer.promote(o, &lt;X extends num&gt;(X promoted) {
      print('  The promotion to `typer` succeeded!');
      // `typer` 具有静态类型 `Typer&lt;num&gt;`,所以我们可以在 `num` 成员上使用。
      promoted.floor();
      return promoted;
    },
    orElse: () =&gt; 14,
  );
  print('n2: $n2'); // '2' 或 '14'。
}

我们不能直接使用 <code>is</code><code>as</code> 来获得提升,因为我们不能直接测试给定 <code>Typer&lt;T&gt;</code> 的底层类型 <code>T</code>。我们可以调用 <code>isA</code><code>asA</code>,但这些方法不会导致接收者的提升,因为类型系统不知道 <code>isA</code> 实际返回 <code>true</code><code>false</code>,就像 <code>is</code> 一样,而且 <code>asA</code> 将抛出异常,尽管是基于类型 <code>T</code> 而不是本地可描述的类型。

然而,我们可以传递一个泛型回调,该回调将接收到 <code>Typer&lt;T&gt;</code> 的底层类型 <code>T</code> 作为其实际参数,并且它也将接收到正在类型测试的对象(在示例中为 <code>promoted</code>)。

在该回调的主体中,我们可以使用提升后的值,且静态已知它具有一个子类型于该类型参数的类型(在示例中:我们知道实际参数的类型为 <code>X</code>)。请注意 <code>X</code> 受到 <code>typer</code> 静态已知类型参数的限制,这意味着我们可以在提升后的对象上使用 <code>num</code> 接口。

不过,需要注意的是,我们实际上是将提升后的对象提升到 <code>typer</code> 表示的类型(这里是 <code>int</code>),而不仅仅是提升到静态已知的边界(这里是 <code>num</code>)。所以当 <code>n</code> 的值为 <code>2.5</code> 时,我们将使用 <code>orElse()</code> 的值,而不运行 <code>callback</code>

示例设计

这是一个设计示例,可以启用类似的存在性打开操作,这是一种相当通用的方法。也就是说,它允许我们从外部使用给定对象的类型参数。

基本思想是,一个具有类型参数 <code>X1 .. Xk</code> 的泛型类具有每个类型变量 <code>Xj</code> 的getter,返回 <code>Typer&lt;Xj&gt;</code>

这些getter可以用于编写使用类的每个类型变量的实际值的代码,这也是Dart不支持客户端代码的一个特性。事实上,只有类体内的代码才能访问类型变量,因为这些类型变量仅在该代码范围内有效。外部客户端只知道一个大致的值,这是基于给定对象的静态已知类型的一个超类型。

例如,在 <code>List&lt;E&gt;</code> 类体内的类型参数 <code>E</code> 在范围内,我们可以像 <code>return &lt;E&gt;{};</code> 这样做。但在 <code>List</code> 外部的代码中,我们可能只知道列表是一个 <code>List&lt;T&gt;</code>,其中 <code>T</code> 可能是实际值的任何超类型,例如,我们可能只知道它是一个 <code>List&lt;Object?&gt;</code>

在这个示例中,我们使用 <code>Typer</code> 从给定的列表创建一个集合,保留实际的类型参数。

这是一个非平凡的壮举,如果你曾经尝试过创建一个具有与现有对象相同类型参数的新对象,或者类似的事情,你可能会有所体会。

该示例使用模拟类 <code>MyIterable</code><code>MyList</code><code>MySet</code>。这只是因为我们不能轻易地向真实的 <code>List</code><code>Set</code> 类添加必要的 <code>Typer</code> getter。尽管如此,如果添加了这些getter,这些技术也可以应用于真实的 <code>List</code><code>Set</code> 对象,就像在这里使用的一样。

这适用于任何类,当然:如果你想为其中一个类启用这种类型的存在性打开,你只需要添加那些 <code>Typer</code> getter,然后就可以使用这些技术。

以下是示例代码:

abstract class MyIterable&lt;E&gt; {
  const MyIterable();

  E get first;

  Typer&lt;E&gt; get typerOfE =&gt; Typer();
}

class MyList&lt;E&gt; extends MyIterable&lt;E&gt; {
  final E e;

  const MyList(this.e);

  [@override](/user/override)
  E get first =&gt; e;
}

class MySet&lt;E&gt; extends MyIterable&lt;E&gt; {
  final E e;

  const MySet(this.e);

  [@override](/user/override)
  E get first =&gt; e;
}

MySet&lt;X&gt; iterableToSet&lt;X&gt;(MyIterable&lt;X&gt; iterable) =&gt;
    iterable.typerOfE.callWith&lt;Y&gt;() {
      return MySet&lt;Y&gt;(iterable.first as Y) as MySet&lt;X&gt;;
    };

void main() {
  // 假设我们不知道 `iterable` 的确切类型。
  MyIterable&lt;Object?&gt; iterable = MyList&lt;int&gt;(42);

  // 现在我们想创建一个具有相同元素类型的集合。
  var set = iterableToSet(iterable);
  print(set.runtimeType); // 'MySet&lt;int&gt;'。
}

更多关于Flutter文本输入效果插件typer的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter文本输入效果插件typer的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,下面是一个关于如何在Flutter中使用typer插件来实现文本输入效果的代码示例。typer插件允许你以类型机(打字机)的效果显示文本,非常适合在应用中创建有趣的文本显示动画。

首先,确保你已经在你的pubspec.yaml文件中添加了typer依赖:

dependencies:
  flutter:
    sdk: flutter
  typer: ^x.y.z  # 替换为最新的版本号

然后,运行flutter pub get来安装依赖。

下面是一个完整的Flutter应用示例,展示如何使用typer插件:

import 'package:flutter/material.dart';
import 'package:typer/typer.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Typer Example',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  final TyperController _typerController = TyperController();

  @override
  void initState() {
    super.initState();
    // 初始化 TyperController 并设置打字文本
    _startTyping();
  }

  void _startTyping() {
    // 文本内容
    String textToType = "欢迎使用Flutter Typer插件!";
    
    // 开始打字效果
    _typerController.startTyping(
      text: textToType,
      duration: Duration(seconds: 5),  // 打字总时长
      onFinish: () {
        // 打字完成后的操作
        print("打字完成!");
      },
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Flutter Typer 示例'),
      ),
      body: Center(
        child: TyperAnimatedText(
          text: _typerController.text,  // 绑定控制器中的文本
          style: TextStyle(fontSize: 24, color: Colors.black),
          speedFactor: _typerController.speedFactor,  // 使用控制器中的速度因子
          cursorColor: Colors.blue,
          cursorBlink: true,
          cursorWidth: 2.0,
          showCursor: true,
          onTap: () {
            // 点击重新开始打字效果
            _startTyping();
          },
        ),
      ),
    );
  }
}

在这个示例中:

  1. 我们创建了一个Flutter应用,并在pubspec.yaml中添加了typer依赖。
  2. MyHomePage是一个有状态的Widget,包含一个TyperController实例。
  3. initState方法中,我们调用_startTyping方法来初始化打字效果。
  4. _startTyping方法设置了要显示的文本、打字总时长以及打字完成后的回调。
  5. build方法中,我们使用TyperAnimatedText来显示打字效果,并绑定到TyperController的文本和速度因子。
  6. 用户可以通过点击文本重新开始打字效果。

这个示例展示了如何使用typer插件来创建一个简单的打字机效果。你可以根据需要调整文本、速度、样式等参数,以符合你的应用需求。

回到顶部