Flutter简单实现滑块验证

news2024/12/26 20:13:45

现在实现一个 Flutter 滑动验证组件,类似于许多网站和应用程序中常见的“滑动以验证”功能。它通过滑动一个滑块来完成验证操作,用户需要将滑块拖动到指定位置以完成验证。

前置知识点整理

StatefulWidget

在 Flutter 中,`StatefulWidget` 是一种可以拥有状态的组件,状态的变化会导致 UI 的重建。这与 `StatelessWidget` 不同,后者是无状态的,通常用于不需要维护状态的简单 UI 组件。

`StatefulWidget` 的基本结构

一个完整的 `StatefulWidget` 由两个类组成:

1.`StatefulWidget` 类:
  • 这个类本身是不可变的。
  • 它负责创建一个 `State` 对象,该对象持有所有与这个组件相关的状态。

2.`State` 类:
  • 这是一个泛型类,通常与特定的 `StatefulWidget` 绑定。
  • 负责存储与 `StatefulWidget` 相关的数据,并包含构建 UI 的逻辑。
  • 当状态改变时,通过调用 `setState` 方法来触发 UI 的重建。

`StatefulWidget` 的生命周期

1.`createState`:
  • 当 Flutter 框架准备构建 `StatefulWidget` 时调用。
  • 返回一个 `State` 对象,持有组件的状态。

2.`initState`:
  • 在 `State` 对象第一次被插入到树中时调用。
  • 通常用于初始化数据或订阅服务。

3.`didChangeDependencies`:
  • 当 `State` 对象的依赖关系发生变化时调用。
  • 例如,`InheritedWidget` 中的数据改变。

4.`build`:
  • 必须实现的方法,构建 UI。
  • 当您调用 `setState` 时,`build` 方法会被重新调用。

5.setState`:
  • 用来通知框架状态已经改变,并且需要重建 UI。
  • 只更新最小范围内的 UI。

6.`deactivate`:
  • 当 `State` 对象被从树中移除时调用。

7.`dispose`:
  • 当 `State` 对象永久性被从树中移除时调用。
  • 用于释放资源,比如取消订阅或者关闭动画控制器。

`StatefulWidget` 设计理念

  • 不可变性:`StatefulWidget` 本身是不可变的,任何需要改变的数据都应该放在 `State` 对象中。这是因为 `StatefulWidget` 仅用于描述 UI 的布局和配置,而状态变化应该由 `State` 管理。
  • 分离逻辑和状态:将状态管理逻辑放在 `State` 类中,可以更好地组织代码,使得 UI 和业务逻辑更易于维护和测试。

`State` 生命周期方法的使用

`initState()`:
  • 在这里进行初始化操作,例如创建动画控制器、订阅服务、初始化变量等。
  • 调用 `super.initState()` 是必须的。
`didChangeDependencies()`:
  • 当 `State` 依赖的 `InheritedWidget` 发生变化时调用。
  • 通常用于在依赖的环境改变时,重新计算一些需要的状态。
`setState()`:
  • 调用此方法后,Flutter 框架会在下一个帧重新调用 `build()` 方法。
  • 只应在 `State` 类中调用 `setState`,而不应在 `build()` 方法中调用,以避免无限的重建循环。
`dispose()`:
  • 在这里释放资源,例如取消订阅、销毁动画控制器等。
  • 确保调用 `super.dispose()` 以遵循框架的正确处理流程。

性能优化

最小化 `setState` 范围:

只更新需要改变的部分,不要在 `setState` 中更新整个 widget 树,以提升性能。

避免不必要的重建:

通过提取组件和使用 `const` 构造函数来减少无意义的重建。

使用 `Keys`:

在需要保持 widget 状态的一致性时,使用 `Keys` 来帮助 Flutter 底层算法识别 widget。

状态管理的扩展

Provider:

在更复杂的应用中,使用 `Provider` 或其他状态管理解决方案来管理跨多个 widget 的状态。

BLoC:

使用 BLoC 模式来分离业务逻辑和 UI,适合处理更复杂的交互和数据流。

Riverpod:

一个现代化的状态管理库,可以提供更灵活和简洁的方式来管理应用状态。

AnimationController

`AnimationController` 是 Flutter 中用于创建动画效果的核心类之一。它负责管理动画的时间线,包括动画的启动、停止、方向和速度控制。`AnimationController` 通常与其他动画类(如 `Tween` 和 `Animation`)结合使用,以创建复杂的动画效果。

基本用法

初始化

`AnimationController` 需要一个 `TickerProvider`,通常通过在 `State` 类中使用 `SingleTickerProviderStateMixin` 或 `TickerProviderStateMixin` 来实现。

代码示例
import 'package:flutter/material.dart';

class MyAnimatedWidget extends StatefulWidget {
  const MyAnimatedWidget({super.key});

  @override
  _MyAnimatedWidgetState createState() {
    return _MyAnimatedWidgetState();
  }
}

class _MyAnimatedWidgetState extends State<MyAnimatedWidget>
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;

  @override
  Widget build(BuildContext context) {}

  @override
  void initState() {
    super.initState();
    _controller =
        AnimationController(vsync: this, duration: const Duration(seconds: 2));
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }
}

使用 `AnimationController`
启动动画:
  • `forward()`: 正向播放动画。
  • `reverse()`: 反向播放动画。
  • `repeat()`: 循环播放动画,可以指定是否反向。

控制动画:
  • `stop()`: 停止动画。
  • `reset()`: 重置动画到初始状态。
  • `animateTo()`: 将动画移动到特定的值。

监听动画:
  • `addListener()`: 添加回调函数,每帧都会调用。
  • `addStatusListener()`: 监听动画状态变化(如开始、结束、前进、反向)。
结合 `Tween` 和 `AnimatedBuilder`

`Tween` 用于定义动画值的范围和插值方式。`AnimatedBuilder` 则用于在每一帧重新构建 UI。

  @overrided
  Widget build(BuildContext context) {
    return AnimatedBuilder(
      animation: _controller,
      builder: (context, child) {
        return Transform.scale(
          scale: _controller.value,
          child: child,
        );
      },
      child: Container(
        width: 100,
        height: 200,
        color: Colors.grey,
      ),
    );
  }

进阶用法

使用 `CurvedAnimation`

`CurvedAnimation` 可以在 `AnimationController` 基础上应用不同的插值曲线(如加速、减速、弹性等)。

final Animation<double> _animation = CurvedAnimation(d
  parent: _controller,
  curve: Curves.easeInOut,
);

多控制器与 `TickerProviderStateMixin`

当需要同时管理多个动画时,可以使用 `TickerProviderStateMixin`,但要注意资源管理,确保在 `dispose()` 中释放所有 `AnimationController`。

注意事项

  • 资源释放:始终在 `dispose()` 方法中调用 `dispose()` 来释放 `AnimationController` 资源,以避免内存泄漏。
  • 性能优化:动画应尽量简化,不要在动画中执行复杂的计算或 I/O 操作。
  • 帧率:Flutter 尝试以每秒 60 帧的速率渲染动画,确保动画逻辑不会阻塞主线程以保持流畅。

通过灵活应用 `AnimationController`,可以在 Flutter 中创建丰富的动画效果,从简单的过渡到复杂的交互式动画。

GestureDetector

`GestureDetector` 是 Flutter 中用于检测用户手势的一个重要小部件。它提供了一种方式来捕获和响应屏幕上的各种触摸事件和手势,比如点击、双击、长按、拖动、缩放等。通过 `GestureDetector`,我们可以使应用对用户交互更具响应性和互动性。

基本功能

`GestureDetector` 通过一系列回调函数来处理不同类型的手势事件。

常用属性和回调

1.点击类手势:

  • `onTap`: 用户轻触屏幕时触发。
  • `onDoubleTap`: 用户双击屏幕时触发。
  • `onLongPress`: 用户长按屏幕时触发。

2.拖动类手势:

  • `onPanStart`: 用户开始拖动时触发。
  • `onPanUpdate`: 用户拖动时持续触发,返回拖动的位移。
  • `onPanEnd`: 用户结束拖动时触发。

3.缩放类手势:

  • `onScaleStart`: 用户开始进行缩放操作时触发。
  • `onScaleUpdate`: 用户缩放时持续触发,返回缩放比例。
  • `onScaleEnd`: 用户结束缩放操作时触发。

4.特定方向拖动手势:

  • `onVerticalDragStart` / `onVerticalDragUpdate` / `onVerticalDragEnd`: 检测垂直方向拖动。
  • `onHorizontalDragStart` / `onHorizontalDragUpdate` / `onHorizontalDragEnd`: 检测水平方向拖动。

代码示例

import 'package:flutter/material.dart';

class DraggableBox extends StatefulWidget {
  const DraggableBox({super.key});

  @override
  _DraggableBoxState createState() {
    return _DraggableBoxState();
  }
}

class _DraggableBoxState extends State<DraggableBox> {
  double _xOffset = 0.0;
  double _yOffset = 0.0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: GestureDetector(
          onPanUpdate: (details) {
            setState(() {
              _xOffset += details.delta.dx;
              _yOffset += details.delta.dy;
            });
          },
          child: Transform.translate(
            offset: Offset(_xOffset, _yOffset),
            child: Container(
              width: 100,
              height: 100,
              color: Colors.red,
            ),
          ),
        ),
      ),
    );
  }
}

高级用法和注意事项

事件冲突:

  • 在复杂的 UI 中,多个手势检测器可能重叠,导致事件冲突。可以使用 `GestureDetector` 的 `behavior` 属性来控制手势事件的分发,例如 `HitTestBehavior.opaque`、`HitTestBehavior.translucent`、`HitTestBehavior.deferToChild`。

手势优先级:

  • 当多个手势重叠时,Flutter 会根据手势识别机制来确定哪个手势有效。通过回调的返回值或 `GestureArena` 机制

手势优先级和冲突解决

手势识别冲突:

  • 当多个手势检测器同时监听同一事件流时,可能会发生冲突。Flutter 使用 `GestureArena` 来管理这种冲突。
  • `GestureDetector` 中的某些手势,如 `onTap` 和 `onDoubleTap`,可能会同时触发。在这种情况下,Flutter 会尝试根据手势的优先级和时间顺序来确定哪个手势应该生效。

使用 `behavior` 属性:

  • `behavior` 属性控制 `GestureDetector` 如何处理点击测试:
  • `HitTestBehavior.deferToChild`: 默认值,表示事件先传递给子组件。
  • `HitTestBehavior.opaque`: 组件即使透明也能接受事件。
  • `HitTestBehavior.translucent`: 透明区域可点击,但事件也会传递给下面的组件。

组合手势

组合多个手势:

  • 可以在一个 `GestureDetector` 中组合多个手势检测回调,以实现复杂的交互。例如,同时处理拖动和缩放:
GestureDetector(
  onPanUpdate: (details) {
    // 处理拖动
  },
  onScaleUpdate: (details) {
    // 处理缩放
  },
);

性能优化

避免不必要的重建:

  • 在手势回调中,尽量减少 `setState` 的调用范围,只更新需要改变的部分。
  • 对于复杂的计算或动画,考虑使用 `AnimationController` 或 `AnimatedBuilder` 来分离手势逻辑和 UI 重建。

其他注意事项

响应区域:

  • `GestureDetector` 默认的响应区域是其子组件的大小。如果想扩大响应区域,可以在 `GestureDetector` 外包裹一个较大的 `Container` 或使用 `Padding`。

与其他手势检测器的交互:

  • 当 `GestureDetector` 与其他手势检测器(如 `InkWell` 或 `RawGestureDetector`)一起使用时,需注意手势处理的顺序和优先级。

案例场景

  • 滑动删除:在列表项中使用 `GestureDetector` 实现滑动删除功能,结合 `Dismissible` 小部件。
  • 图片缩放与拖动:在画廊应用中,结合拖动和缩放手势,允许用户放大和移动图片。
  • 游戏中的手势控制:利用复杂的手势组合实现游戏中的角色移动、旋转和其他互动操作。

通过正确使用 `GestureDetector`,开发者可以为 Flutter 应用添加丰富的交互体验,使应用更加生动和用户友好。

Positioned

`Positioned` 是 Flutter 中用于在 `Stack` 小部件内精准定位子小部件的一个小部件。它允许你通过设置距离 `Stack` 边界的偏移量来定位子小部件。`Positioned` 只能作为 `Stack` 的子小部件使用。

基本用法

`Positioned` 提供了 `left`、`right`、`top` 和 `bottom` 属性,通过这些属性,你可以指定子部件相对于 `Stack` 容器的偏移量。这些属性可以组合使用,以便更精确地定位子部件。diam

代码示例

import 'package:flutter/material.dart';

class PositionedExamplePage extends StatelessWidget {
  const PositionedExamplePage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text("Positioned Example")),
      body: Center(
        child: Stack(
          children: <Widget>[
            Container(
              width: 200,
              height: 200,
              color: Colors.blue,
            ),
            Positioned(
                top: 10,
                left: 10,
                child: Container(
                  width: 100,
                  height: 100,
                  color: Colors.red,
                )),
            const Positioned(
              bottom: 10,
              right: 10,
              child: Text("Bottom Right"),
            )
          ],
        ),
      ),
    );
  }
}

 高级用法和注意事项

1. 使用多个属性
  • 可以同时设置对立的边界(如 `left` 和 `right`),这会导致 `Positioned` 子部件的大小被拉伸以适应指定的边距。
  • 例如,如果你同时指定了 `left` 和 `right`,就可以控制子部件的宽度。
2. 自动适应大小
  • 如果没有指定宽度或高度,`Positioned` 将根据其子部件的大小进行调整。
3. 与 `Align` 的对比
  • `Positioned` 是通过固定偏移量定位子部件,而 `Align` 则通过比例(0 到 1)定位。
  • 如果需要相对位置(如居中、居左上角),可以考虑使用 `Align`。
4. 动态布局
  • 在响应式布局中,可能需要结合 `MediaQuery` 或 `LayoutBuilder` 动态计算偏移量,以适应不同的屏幕尺寸和方向。

通过正确使用 `Positioned`,你可以在 `Stack` 中灵活地布局子部件,实现复杂的界面设计。它非常适合用于需要精确控制子部件位置的场景,比如覆盖、标注和自定义布局。

Row

`Row` 是 Flutter 中用于水平布局的一个小部件,允许你将多个子小部件沿水平轴排列。它是一个非常常用的布局小部件,适合用于创建水平排列的组件集合。

基本用法

`Row` 小部件的核心功能是水平排列其子小部件,并提供了一些属性来控制子小部件的布局方式。

代码示例

import 'package:flutter/material.dart';

class RowExamplePage extends StatelessWidget {
  const RowExamplePage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text("Row Example")),
      body: const Center(
        child: Row(
          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
          crossAxisAlignment: CrossAxisAlignment.center,
          children: <Widget>[
            Icon(
              Icons.star,
              color: Colors.red,
              size: 50,
            ),
            Icon(
              Icons.star,
              color: Colors.grey,
              size: 50,
            ),
            Icon(
              Icons.star,
              color: Colors.black,
              size: 50,
            )
          ],
        ),
      ),
    );
  }
}

代码解析

`mainAxisAlignment`

1.控制子小部件在主轴(水平轴)上的对齐方式。

2.常用选项包括:

  • `MainAxisAlignment.start`: 子部件在行的起始处排列。
  • `MainAxisAlignment.end`: 子部件在行的末尾处排列。
  • `MainAxisAlignment.center`: 子部件在行的中心排列。
  • `MainAxisAlignment.spaceBetween`: 子部件均匀分布,第一个和最后一个子部件贴边。
  • `MainAxisAlignment.spaceAround`: 子部件均匀分布,每个子部件周围有相等的空间。
  • `MainAxisAlignment.spaceEvenly`: 子部件均匀分布,且空隙相等。

`crossAxisAlignment`

1.控制子小部件在交叉轴(垂直轴)上的对齐方式。

2.常用选项包括:

  • `CrossAxisAlignment.start`: 子部件在交叉轴起始处对齐。
  • `CrossAxisAlignment.end`: 子部件在交叉轴末尾处对齐。
  • `CrossAxisAlignment.center`: 子部件在交叉轴居中对齐。
  • `CrossAxisAlignment.stretch`: 子部件在交叉轴上拉伸以填满父容器。
  • `CrossAxisAlignment.baseline`: 子部件基于文本基线对齐(需要指定 `TextBaseline`)。

高级用法和注意事项

子小部件的尺寸
  • Row` 会根据其父容器的约束来布局子小部件。子小部件可以是灵活的(如使用 `Flexible` 或 `Expanded`),也可以是固定宽度的。
  • 如果子小部件的宽度超出了 `Row` 的可用空间,则可能会出现布局溢出。

灵活布局

使用 `Flexible` 和 `Expanded` 可以创建自适应的子小部件:

  • `Flexible`: 允许子小部件在可用空间内灵活调整大小。
  • `Expanded`: 强制子小部件填满 `Row` 的可用空间。

代码示例:Expanded`: 强制子小部件填满 `Row` 的可用空间

import 'package:flutter/material.dart';

class RowExamplePageDemo1 extends StatelessWidget {
  const RowExamplePageDemo1({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text("Row Example")),
      body: Center(
        child: Row(
          children: <Widget>[
            Expanded(child: Container(color: Colors.red, height: 50)),
            Expanded(child: Container(color: Colors.green, height: 50)),
            Expanded(child: Container(color: Colors.blue, height: 50)),
          ],
        ),
      ),
    );
  }
}

灵活布局示例

使用 `Flexible` 和 `Expanded` 可以让 `Row` 的子小部件在水平空间上进行灵活的大小调整:

import 'package:flutter/material.dart';

class RowExamplePageDemo2 extends StatelessWidget {
  const RowExamplePageDemo2({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text("Row Example")),
      body: Center(
        child: Row(
            children: <Widget>[
              Expanded(
                flex: 1, // 占用1份可用空间
                child: Container(color: Colors.red, height: 50),
              ),
              Expanded(
                flex: 2, // 占用2份可用空间
                child: Container(color: Colors.green, height: 50),
              ),
              Expanded(
                flex: 1, // 占用1份可用空间
                child: Container(color: Colors.blue, height: 50),
              ),
            ]
        ),
      ),
    );
  }
}

 flex` 属性:`flex` 用于指定子小部件在 `Row` 的可用空间中占据的比例。上面的例子中,绿色的容器将占用红色和蓝色容器两倍的宽度。

使用 `Flexible` 的场景

`Flexible` 可以让子小部件在 `Row` 中占据一定比例的空间,而不强制其填满整个可用空间,这在某些需要固定或最小宽度的场景中特别有用。

import 'package:flutter/material.dart';

class RowExamplePageDemo3 extends StatelessWidget {
  const RowExamplePageDemo3({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(title: const Text("Row Example")),
        body: Center(
            child: Row(
          children: <Widget>[
            Flexible(
              flex: 1,
              fit: FlexFit.tight, // 强制子小部件填满可用空间
              child: Container(
                color: Colors.red,
                height: 50,
              ),
            ),
            Flexible(
              flex: 1,
              fit: FlexFit.loose,// 子小部件根据本身大小适应可用空间
              child: Container(
                color: Colors.green,
                height: 50,
              ),
            ),
            Flexible(
              fit: FlexFit.tight,
              flex: 1,
              child: Container(
                color: Colors.blue,
                height: 50,
              ),
            )
          ],
        )));
  }
}

注意事项

布局溢出


当 `Row` 中的子小部件总宽度超过 `Row` 的可用宽度时,会出现布局溢出错误(通常在调试模式下显示为红色的溢出警告)。这时可以考虑以下解决方案:

  • 使用 `Expanded` 或 `Flexible` 使子小部件自适应。
  • 通过 `SingleChildScrollView` 包裹 `Row`,提供水平滚动功能。
SingleChildScrollView(
  scrollDirection: Axis.horizontal,
  child: Row(
    children: <Widget>[
      Container(color: Colors.red, width: 400, height: 50),
      Container(color: Colors.green, width: 400, height: 50),
      Container(color: Colors.blue, width: 400, height: 50),
    ],
  ),
)

嵌套布局
  • 如果需要在 `Row` 中嵌套其他布局(如 `Column` 或 `Stack`),确保对齐方式和布局约束被正确处理,以避免不期望的布局结果。

总结

  • `Row` 是构建水平布局的基本工具,通过结合 `mainAxisAlignment` 和 `crossAxisAlignment` 可以实现丰富的布局对齐。
  • 使用 `Flexible` 和 `Expanded` 可以实现灵活和自适应的布局。
  • 注意处理可能的布局溢出,并根据需要结合其他布局小部件(如 `SingleChildScrollView`)来提供更好的用户体验。

Clip.hardEdge

`Clip.hardEdge` 是 Flutter 中的一个枚举值,用于决定如何裁剪(clip)一个小部件的边界。`Clip` 是一个重要的概念,尤其是在设计复杂的 UI 时,通过裁剪来控制子小部件的可视区域。

Clip 枚举

在 Flutter 中,`Clip` 枚举用于指定裁剪行为的类型,主要包含以下几种值:

  • `Clip.none`:默认值,不进行任何裁剪,子小部件可能会超出其父容器的边界。
  • `Clip.hardEdge`:使用硬边界裁剪,裁剪结果的边界是锐利的。
  • `Clip.antiAlias`:进行裁剪并抗锯齿,边缘会变得更加平滑。
  • `Clip.antiAliasWithSaveLayer`:与 `Clip.antiAlias` 类似,但它会在裁剪前保存图层,有助于在某些情况下提高性能,但可能会增加内存消耗。

`Clip.hardEdge` 详解

`Clip.hardEdge` 是一种简单高效的裁剪方法,适用于需要快速裁剪但对抗锯齿效果要求不高的场景。它是通过直接裁剪像素来实现的,因此边缘是锐利的。

使用场景

  • 性能优先:如果裁剪操作需要非常高效并且对边缘的光滑度没有严格要求,`Clip.hardEdge` 是一个很好的选择。
  • 简单形状裁剪:适用于简单的矩形或其他几何形状的裁剪,而不需要边缘过渡效果。

代码示例

import 'package:flutter/material.dart';

class ClipHardEdgeExample extends StatelessWidget {
  const ClipHardEdgeExample({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text("Clip.hardEdge Example")),
      body: Center(
        child: ClipRect(
          clipBehavior: Clip.hardEdge,
          child: Container(
            width: 200,
            height: 200,
            color: Colors.blue,
            child: Align(
              alignment: Alignment.topLeft,
              child: Container(
                width: 300,
                height: 300,
                color: Colors.red,
              ),
            ),
          ),
        ),
      ),
    );
  }
}

注意事项

  • 视觉效果:`Clip.hardEdge` 的裁剪边缘是锐利的,没有抗锯齿处理,因此在某些情况下可能会出现锯齿效果。
  • 性能:由于不进行抗锯齿处理,`Clip.hardEdge` 通常比其他裁剪方法(如 `Clip.antiAlias`)更高效。
  • 应用场景:适用于不需要光滑边缘的简单裁剪任务。对于需要光滑边缘的裁剪,考虑使用 `Clip.antiAlias`。

通过了解和正确使用 `Clip.hardEdge`,开发者可以在需要时实现高效的裁剪操作,同时确保应用性能和视觉效果的平衡。

AnimationController vsync参数含义

在 Flutter 中,`AnimationController` 是用于控制动画的一个类,而 `vsync` 参数在创建 `AnimationController` 时扮演着关键角色。理解 `vsync` 的作用有助于优化动画的性能和资源使用。

什么是 `vsync`?

`vsync` 是 `AnimationController` 的一个参数,它用来防止动画在不必要的情况下消耗资源。具体来说,`vsync` 是一种机制,用于同步动画的帧速率与屏幕的刷新率。

`vsync` 的作用

  • 节省资源:通过 `vsync`,Flutter 可以在动画不在屏幕上时暂停动画的帧更新。这意味着当动画不在需要被绘制时,它不会占用 CPU 资源进行计算。
  • 帧同步:`vsync` 确保动画的帧更新与设备的屏幕刷新同步,从而提供更平滑的动画效果。

如何实现 `vsync`

在 Flutter 中,`TickerProvider` 是负责提供 `vsync` 的对象。通常,你可以通过让你的类实现 `TickerProvider` 或者更常用的 `TickerProviderStateMixin` 来提供 `vsync`。

总结

  • `vsync` 是一个机制,用于将动画的帧速率与设备的屏幕刷新同步。
  • 实现 `vsync` 可以通过 `TickerProvider`,通常通过 `TickerProviderStateMixin` 来实现。
  • 正确使用 `vsync` 可以提高动画性能,节省设备资源,并提供平滑的动画体验。

通过理解和正确使用 `vsync`,开发者可以更有效地管理 Flutter 应用中的动画,确保应用流畅运行并优化资源使用。

CurvedAnimation

`CurvedAnimation` 是 Flutter 中一个用于创建非线性动画的类。通过使用曲线,开发者可以使动画的变化更加自然和流畅,模拟现实世界中的运动效果,比如加速、减速、弹跳等。

基本概念

`CurvedAnimation` 是一个包装器,它通过将 `Animation` 对象与 `Curve` 结合来生成具有特定速率变化的动画。`Curve` 描述了动画的速率变化模式。

关键属性

  • `parent`:一个 `Animation` 对象,通常是一个 `AnimationController`,它驱动 `CurvedAnimation` 的值变化。
  • `curve`:一个 `Curve` 对象,定义了动画的速率变化模式。
  • `reverseCurve`:一个可选的 `Curve` 对象,用于定义反向动画的曲线。

代码示例

import 'package:flutter/material.dart';

class CurvedAnimationExample extends StatefulWidget {
  const CurvedAnimationExample({super.key});

  @override
  _CurvedAnimationExampleState createState() {
    return _CurvedAnimationExampleState();
  }
}

class _CurvedAnimationExampleState extends State<CurvedAnimationExample>
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  late Animation<double> _animation;

  @override
  void initState() {
    super.initState();
    _controller =
        AnimationController(vsync: this, duration: const Duration(seconds: 2));
    // 浣跨敤鍐呯疆鐨?easeInOut 鏇茬嚎
    _animation = CurvedAnimation(parent: _controller, curve: Curves.easeInOut);
    _controller.forward();
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('CurvedAnimation Example')),
      body: Center(
        child: AnimatedBuilder(
          animation: _animation,
          builder: (context, child) {
            return Transform.scale(
              scale: _animation.value,
              child: child,
            );
          },
          child: Container(
            width: 100,
            height: 100,
            color: Colors.blue,
          ),
        ),
      ),
    );
  }
}

常用曲线

Flutter 提供了一些内置的曲线,常用于创建不同风格的动画效果:

  • `Curves.linear`:线性曲线,匀速变化。
  • `Curves.easeIn`:缓入曲线,动画开始时较慢。
  • `Curves.easeOut`:缓出曲线,动画结束时较慢。
  • `Curves.easeInOut`:缓入缓出曲线,动画的开始和结束都较慢。
  • `Curves.bounceIn`:动画开始时有弹跳效果。
  • `Curves.bounceOut`:动画结束时有弹跳效果。

AnimationController

`AnimationController` 是 Flutter 中用于控制动画的一个核心类。它提供了一种方式来驱动动画,控制其开始、停止、反向、重复等行为。`AnimationController` 本质上是一个特殊的 `Animation`,其值在给定时间段内从 0.0 变到 1.0 或者反过来。

关键属性和方法

  • `duration`:动画的持续时间,决定了动画从开始到结束的时长。
  • `value`:当前动画的值,通常在 0.0 到 1.0 之间变化,但可以通过 `Animation` 和 `Tween` 进行扩展。
  • `status`:表示动画的当前状态,可以是 `dismissed`(动画初始状态)、`forward`(动画正在前进)、`reverse`(动画正在反向)、`completed`(动画结束)。
  • `forward()`:启动动画并向前播放。
  • `reverse()`:反向播放动画。
  • `repeat()`:重复播放动画,可以指定是否反向。
  • `stop()`:暂停动画。
  • `reset()`:重置动画到初始状态。
  • `addListener()`:添加一个监听器,每当动画的值发生变化时调用。
  • `addStatusListener()`:添加一个状态监听器,每当动画的状态发生变化时调用。

使用步骤

1.创建 `AnimationController`:在 `initState` 方法中初始化控制器。

2.设置动画属性:指定 `duration` 和 `vsync`(通常由 `TickerProvider` 提供)。

3.控制动画:使用 `forward()`、`reverse()` 等方法来启动或控制动画。

4.清理资源:在 `dispose` 方法中调用 `dispose()` 来释放控制器。

代码示例

import 'package:flutter/material.dart';

class AnimationControllerExample extends StatefulWidget {
  const AnimationControllerExample({super.key});

  @override
  _AnimationControllerExampleState createState() {
    return _AnimationControllerExampleState();
  }
}

class _AnimationControllerExampleState extends State<AnimationControllerExample>
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  late Animation<double> _animation;

  @override
  void initState() {
    super.initState();
    _controller =
        AnimationController(vsync: this, duration: const Duration(seconds: 2));

    _animation = Tween<double>(begin: 0.5, end: 1.5).animate(_controller);

    _controller.forward();
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('AnimationController Example')),
      body: Center(
        child: AnimatedBuilder(
          animation: _animation,
          builder: (context, child) {
            return Transform.scale(
              scale: _animation.value,
              child: child,
            );
          },
          child: Container(
            width: 100,
            height: 100,
            color: Colors.blue,
          ),
        ),
      ),
    );
  }
}

代码解析

  • `SingleTickerProviderStateMixin`:提供 `vsync` 参数以优化动画性能,防止动画在不需要更新时仍然消耗资源。
  • `AnimationController`:控制动画的核心类,通过设置 `duration` 来定义动画时长。
  • `Tween`:定义了动画的范围。在示例中,`Tween` 的 `begin` 值为 0.5,`end` 值为 1.5。通过 `animate()` 方法,这些值被应用到 `AnimationController` 上,从而生成一个新的 `Animation` 对象。
  • `AnimatedBuilder`:用于将动画与 UI 组件分离,以优化性能。在构建 UI 时,`AnimatedBuilder` 监听动画的变化,并在每一帧更新时调用 `builder` 方法重建其子部件。
  • `Transform.scale`:根据动画的当前值缩放 `Container`。`_animation.value` 随着时间变化,因此 `Container` 会在动画过程中缩放。

反向动画

通过 `reverse()` 方法,您可以让动画反向播放。例如,当用户执行某个操作时,您可能希望动画从结束状态返回到初始状态。

// 反向动画
_controller.reverse();

循环动画

`AnimationController` 可以通过 `repeat()` 方法循环播放动画,并可以指定是否反向。

// 循环播放动画,反向播放
_controller.repeat(reverse: true);

动画完成事件

通过 `addStatusListener()`,您可以监听动画的状态变化,执行特定的逻辑。例如,动画完成后执行某个操作:

_controller.addStatusListener((status) {
  if (status == AnimationStatus.completed) {
    // 动画完成,执行某些操作
  }
});

多个动画组合

可以通过多个 `Tween` 和 `CurvedAnimation` 组合来创建复杂的动画效果。例如,创建一个具有不同曲线效果的动画序列:

_animation = Tween<double>(begin: 0.0, end: 1.0).animate(
  CurvedAnimation(
    parent: _controller,
    curve: Curves.easeIn,
  ),
);

动画的中途控制

可以在动画进行中,通过更改 `value` 动态控制动画。例如,将动画设置到特定位置:

// 设置动画到一半的位置
_controller.value = 0.5;

总结

`AnimationController` 是实现 Flutter 动画的核心工具,它提供了对动画生命周期的精细控制。通过合理使用 `AnimationController`,结合 `Tween` 和 `CurvedAnimation`,开发者可以在应用中创建丰富、动态的用户体验。

在实际开发中,理解 `AnimationController` 的工作机制和灵活运用其方法和属性,是提升 Flutter 动画效果和应用性能的关键。无论是简单的过渡动画还是复杂的交互动画,`AnimationController` 都是一个不可或缺的组件。

DragStartDetails

`DragStartDetails` 是 Flutter 中用于处理拖动手势的类之一。它主要用于描述拖动手势开始时的信息。在处理用户交互和手势识别时,`DragStartDetails` 提供了必要的细节来确定用户如何开始拖动操作。

关键属性

  • globalPosition`:`Offset` 类型,表示拖动开始时触点在全局坐标系中的位置。这个属性通常用于识别手势相对于整个屏幕的位置。
  • `localPosition`:`Offset` 类型,表示拖动开始时触点在局部坐标系中的位置。这个属性是相对于触发拖动事件的小部件的位置。
  • `sourceTimeStamp`:`Duration` 类型,表示事件发生的时间戳。这个属性可以用于计算拖动的时间间隔,但在某些平台上可能为 `null`。

使用场景

`DragStartDetails` 通常与 `GestureDetector` 或 `Listener` 小部件一起使用,以捕获和处理拖动手势。它在以下场景中非常有用:

  • 自定义拖拽行为:在实现自定义拖拽效果时,你可以使用 `DragStartDetails` 来确定拖动的起始位置,从而调整 UI 元素的初始状态。
  • 复杂手势识别:在实现需要更复杂手势处理的应用中(例如绘图应用或拖拽排序),`DragStartDetails` 提供的坐标信息可以帮助您识别并响应用户的拖动手势。

实际应用场景

1.拖动排序:在某些应用中,用户可能需要通过拖动来重新排序列表项。`DragStartDetails` 可以帮助你识别用户点击的位置,以便在拖动开始时提供视觉反馈。

2.绘图应用:在绘图或签名应用中,`DragStartDetails` 可以用于确定用户开始绘制的位置,从而在该点开始渲染绘图路径。

3.游戏开发:在某些游戏中,用户可能需要通过拖动来控制角色或对象的移动。利用 `DragStartDetails` 可以在用户拖动开始时准确调整游戏对象的初始状态。

提示和最佳实践

  • 响应速度:确保在手势开始时能够快速响应,以提供流畅的用户体验。使用 `DragStartDetails` 的位置信息来立即更新 UI,可以有效减少拖动时的延迟。
  • 坐标系转换:如果需要在不同坐标系(如全局与局部)之间转换位置,可以使用 `RenderBox` 的 `globalToLocal` 和 `localToGlobal` 方法。
  • 事件处理:在复杂的手势处理逻辑中,可能需要结合 `onPanStart`、`onPanUpdate` 和 `onPanEnd` 来实现完整的拖动体验。确保在每个阶段都正确处理用户输入。

总结

`DragStartDetails` 是一个重要的工具,在处理拖动手势时为开发者提供了精准的位置信息。通过理解和利用这些信息,可以实现更复杂和精细的用户交互,增强应用的用户体验。在设计与实现拖动相关功能时,灵活运用 `DragStartDetails` 和其他手势事件,可以极大地提升应用的交互性和响应速度。

实现滑块验证代码学习

import 'package:flutter/material.dart';

class SlideVerifyPage extends StatelessWidget {
  const SlideVerifyPage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("SlideVerifyPage"),
      ),
      body: const Center(
        child: SlideVerify(
          sliderImage: "static/demo.png",
          successText: "验证成功",
          initText: "滑动验证",
        ),
      ),
    );
  }
}

class SlideVerify extends StatefulWidget {
  final double height;
  final double width;
  final Color borderColor;
  final Color bgColor;
  final Color moveColor;
  final String? successText;
  final String? sliderImage;
  final String? initText;
  final String? initImage;
  final TextStyle successTextStyle;
  final TextStyle initTextStyle;
  final VoidCallback? successListener;

  const SlideVerify(
      {super.key,
      this.height = 60,
      this.width = 250,
      this.successText,
      this.initText,
      this.sliderImage,
      this.initImage,
      this.successTextStyle =
          const TextStyle(fontSize: 14, color: Colors.white),
      this.initTextStyle = const TextStyle(fontSize: 14, color: Colors.black12),
      this.bgColor = Colors.grey,
      this.moveColor = Colors.blue,
      this.borderColor = Colors.blueAccent,
      this.successListener});

  @override
  State<StatefulWidget> createState() {
    return SlideVerifyState();
  }

}

class SlideVerifyState extends State<SlideVerify>
    with TickerProviderStateMixin {
  AnimationController? _animController;
  Animation? _curve;
  //`initX`:记录拖动开始时的初始位置。
  double initX = 0.0;
  double height = 0;
  double width = 0;
  //`moveDistance`:记录滑块的移动距离。
  double moveDistance = 0;

  double sliderWidth = 0;

  //`verifySuccess`:标记验证是否成功。
  bool verifySuccess = false;

  //`enable`:控制滑块是否可用。
  bool enable = true;

  void _init() {
    //初始化滑块的宽度
    sliderWidth = widget.height - 4;
   // sliderWidth = widget.height;
    _animController = AnimationController(
        duration: const Duration(milliseconds: 400), vsync: this);
    _curve = CurvedAnimation(parent: _animController!, curve: Curves.easeOut);
    _curve?.addListener(() {
      setState(() {
        moveDistance = moveDistance - moveDistance * _curve!.value;
        if (moveDistance <= 0) {
          moveDistance = 0;
        }
      });
    });
    _animController?.addStatusListener((status) {
      //动画执行完成
      if (status == AnimationStatus.completed) {
        enable = true;
        //重置动画到初始状态。
        _animController?.reset();
      }
    });
  }

  @override
  void initState() {
    super.initState();
    //初始化width 和  height  记录控件的宽度  和 高度
    width = widget.width;
    height = widget.height;
    _init();
  }

  @override
  void dispose() {
    //资源释放
    _animController?.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onHorizontalDragStart: (DragStartDetails details) {
        if (!enable) {
          return;
        }
        //初始化开始点
        initX = details.globalPosition.dx;
      },
      onHorizontalDragUpdate: (DragUpdateDetails details) {
        if (!enable) {
          return;
        }
        //计算滑动距离
        moveDistance = details.globalPosition.dx - initX;
        if (moveDistance < 0) {
          moveDistance = 0;
        }

        if (moveDistance > width - sliderWidth) {
          moveDistance = width - sliderWidth;
          //滑块不可用的条件是:滑动距离 > 组件宽度-滑块宽度
          enable = false;
          verifySuccess = true;
          if (widget.successListener != null) {
            widget.successListener?.call();
          }
        }
        setState(() {});
      },
      onHorizontalDragEnd: (DragEndDetails details) {
        //只有未验证成功才需要回退
        if (enable) {
          enable = false;
        //启动动画并向前播放。
          _animController?.forward();
        }
      },
      child: Container(
        height: height,
        width: width,
        clipBehavior: Clip.hardEdge,
        decoration: BoxDecoration(
            color: widget.bgColor,
            border: Border.all(color: widget.borderColor),
            borderRadius: BorderRadius.all(Radius.circular(height))),
        child: Stack(
          alignment: Alignment.centerLeft,
          children: <Widget>[
            Positioned(
              top: 0,
              left: 0,
              child: Container(
                height: height - 2,
                //滑动的时候蓝色块的宽度(即滑动部分的宽度)
                width: moveDistance < 1 ? 0 : moveDistance + sliderWidth / 2,
                decoration: BoxDecoration(
                  color: widget.moveColor,
                ),
              ),
            ),
            Center(
              child: Text(
                verifySuccess
                    ? widget.successText ?? ""
                    : widget.initText ?? "",
                style: verifySuccess
                    ? widget.successTextStyle
                    : widget.initTextStyle,
              ),
            ),
            //下面部分表示滑块
            Positioned(
              top: 1,
              left:
                  moveDistance > sliderWidth ? moveDistance - 2 : moveDistance,
              child: Container(
                width: sliderWidth,
                height: sliderWidth,
                alignment: Alignment.center,
                clipBehavior: Clip.hardEdge,
                decoration: BoxDecoration(
                  color: Colors.white,
                  borderRadius: BorderRadius.all(
                    Radius.circular(sliderWidth),
                  ),
                ),
                child: Row(
                  mainAxisAlignment: MainAxisAlignment.center,
                  crossAxisAlignment: CrossAxisAlignment.center,
                  children: <Widget>[
                    if (widget.sliderImage != null)
                      Image.asset(
                        widget.sliderImage!,
                        height: sliderWidth,
                        width: sliderWidth,
                        fit: BoxFit.cover,
                      ),
                  ],
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2252015.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ERROR in [eslint] Invalid Options ‘extensions‘ has been removed.

看着这个报错 感觉是版本不对引起的 ERROR in [eslint] Invalid Options: - Unknown options: extensions - extensions has been removed. ERROR in Error: Child compilation failed: [eslint] Invalid Options: - Unknown options: extensions - extensions has b…

SpringCloud书单推荐

重新定义SpringCloud实战 129 疯狂SpringCloud微服务架构实战 SpringBootSpringCloud微服务开发实战 点餐系统 SpringCloud微服务架构实战派 日访问量3000W平台 SpringCloud Alibaba微服务原理与实战 Spring CloudNginx 极简spring cloud实战 Spring Cloud 微服…

试题转excel;试题整理工具;试卷转excel;word转excel

一、问题描述 我父亲是一名教师&#xff0c;偶尔会需要将试卷转excel&#xff0c;方便管理处理一些特别重要的题目 于是&#xff0c;就抽空写一个专门将试题转excel的工具&#xff0c;便于各位教师从业者和教育行业的朋友更好的整理试题&#xff0c;减少一点重复枯燥的工作 …

Node.js Fastify装饰器:提升你的API性能与功能

在Node.js的世界中&#xff0c;Fastify以其卓越的性能和插件化架构脱颖而出&#xff0c;成为构建高效API的首选框架之一。Fastify的装饰器功能&#xff0c;允许开发者以声明式的方式增强和扩展核心对象&#xff0c;如请求&#xff08;Request&#xff09;和响应&#xff08;Res…

数据库管理-第267期 23ai:Oracle Data Redaction演示(20241128)

数据库管理267期 2024-11-286 数据库管理-第267期 23ai&#xff1a;Oracle Data Redaction演示&#xff08;20241128&#xff09;1 示例表及数据2 创建编校策略2.1 名字全编校2.2 电话部分编校 3 DML演示3.1 场景13.2 场景2 总结 数据库管理-第267期 23ai&#xff1a;Oracle Da…

C#VB.Net项目一键多国语言显示

如何在项目什么都不做一键支持多国语言显示 开始我们的一键快捷使用之旅 01.创建多语言项目 02.一键批量窗口开启本地化,添加选中内容添加Mu方法 03.一键快捷翻译 04.运行查看效果 01.创建多语言项目 创建多语言项目前,请先下载安装&#xff0c;注册并登录. 为了便于演示这…

像素流送api ue多人访问需要什么显卡服务器

关于像素流送UE推流&#xff0c;在之前的文章里其实小芹和大家聊过很多&#xff0c;不过今天偶然搜索发现还是有很多小伙伴&#xff0c;在搜索像素流送相关的问题&#xff0c;搜索引擎给的提示有这些。当然这些都是比较短的词汇&#xff0c;可能每个人真正遇到的问题和想获取的…

构建高可用系统设计OpenStack、Docker、Mesos和Kubernetes(简称K8s)

如果构建高可用、高并发、高效运维的大型系统 大型系统架构设计包括业务层设计、服务层设计、基础架层设计、存储层设计、网络层协同设计来完成。 一、业务层 根据主要业务范畴的分类和特征提取&#xff0c;抽象出独立的业务系统&#xff0c;分别统计系统的用户角色群体、访…

零基础Python学习

1.环境搭建 1.1 安装运行环境python3.13 Welcome to Python.org 1.2 安装集成开发环境PyCharm PyCharm: the Python IDE for data science and web development 1.3 创建项目 && 设置字体 2.基础语法 2.1 常量与表达式 在python中整数除整数不会优化&#xff0c;所…

Java Map

Map——广义集合的子集 HashTable是早期Java类库提供的一个哈希表实现&#xff0c;扩展了Dictionary类&#xff0c;类结构上与HashMap明显不同&#xff0c;本身是同步的&#xff0c;不支持null键和值&#xff0c;由于同步导致的性能开销&#xff0c;已经很少被推荐使用。 Hash…

【MySQL — 数据库基础】MySQL的安装与配置 & 数据库简单介绍

数据库基础 本节目标 掌握关系型数据库&#xff0c;数据库的作用掌握在Windows和Linux系统下安装MySQL数据库了解客户端工具的基本使用和SQL分类了解MySQL架构和存储引擎 1. 数据库的安装与配置 1.1 确认MYSQL版本 处理无法在 cmd 中使用 mysql 命令的情况&a…

实测数据处理(BP算法处理)——SAR成像算法系列(九)

系列文章目录 《SAR学习笔记-SAR成像算法系列&#xff08;一&#xff09;》 《后向投影算法&#xff08;BPA&#xff09;-SAR成像算法系列&#xff08;二&#xff09;》 《后向投影算法&#xff08;续&#xff09;-SAR成像算法系列&#xff08;八&#xff09;》 文章目录 一…

(数据结构与算法)如何提高学习算法的效率?面试算法重点有哪些?面试需要哪些能力?

面试官眼中的求职者 通过对你算法的考察&#xff01;&#xff01;&#xff01;&#xff01; 缩进太多&#xff01;&#xff01;一般不要超过三层&#xff01;&#xff01;&#xff01;缩进越少&#xff0c;bug越少&#xff1b;逻辑比较复杂&#xff0c;把这些包装成为函数&…

Day05:缓存双写一致性

redis做为缓存&#xff0c;mysql的数据如何与redis进行同步呢&#xff1f;&#xff08;双写一致性–强一致&#xff09; 一种是一致性要求比较高的同步方案&#xff0c;另一种是允许延迟一致的异步通知。 什么是双写一致性&#xff1f; 双写一致性&#xff1a;当修改了数据库…

vue3+typescript自定义input组件

官方文档&#xff1a;https://cn.vuejs.org/guide/components/events#%E5%AE%9A%E4%B9%89%E8%87%AA%E5%AE%9A%E4%B9%89%E4%BA%8B%E4%BB%B6 触发与监听事件​ 在组件的模板表达式中&#xff0c;可以直接使用 $emit 方法触发自定义事件 (例如&#xff1a;在 v-on 的处理函数中)…

代码之丑第一期-缩进

各位小伙伴们,大家好!咱今天就算是正式开张了。实不相瞒,第一期的内容早已写好,但唯独这开篇方式,笔者想了好些时间,包括但不限于如下风格: 斗破苍穹式(已经三刷):代码优雅之力,三段!级别:低级!百年孤独式(困扰于错综复杂的人物关系,放弃):多年以后,面对吐槽…

idea2024加载flowable6.8.1.36遇到的问题-idea启动flowable问题flowable源码启动问题

代码下载地址&#xff1a; https://gitee.com/hanpenghu_admin_admin/flowable6.8.1.git 1.首先是通过顶层目录maven clean install 发现很多子模块并不会install本地mavenStore库&#xff0c;这导致了&#xff0c;一堆相互依赖的模块报错找不到&#xff0c;所以需要根据报错…

Vue.js 中 v-for 指令的三种常见用法详解及key、value、id的作用

作者&#xff1a;CSDN-PleaSure乐事 欢迎大家阅读我的博客 希望大家喜欢 使用环境&#xff1a;WebStorm 目录 遍历数组 介绍 代码 遍历对象数组 介绍 代码 遍历对象本身 介绍 代码 效果呈现 key、value、id的作用 1. value 2. key 3. id 在 Vue.js 中&#xff0c…

【论文投稿】国产游戏技术:迈向全球引领者的征途

【IEEE出版南方科技大学】第十一届电气工程与自动化国际会议&#xff08;IFEEA 2024)_艾思科蓝_学术一站式服务平台 更多学术会议论文投稿请看&#xff1a;https://ais.cn/u/nuyAF3 目录 国产游戏技术能否引领全球&#xff1f; 一、国产游戏技术的崛起之路 1.1 初期探索与积…

React的ts文件中通过createElement拼接一段内容出来

比如接口返回一个值 const values [23.00, 40.00/kg];想做到如下效果&#xff0c; 如果单纯的用render渲染会很简单&#xff0c; 但是在ts文件中处理&#xff0c;所以采用了createElement拼接 代码如下&#xff1a; format: (values: string[]) > {if (!values || !val…