Flutter动画效果插件motion的使用

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

Flutter动画效果插件motion的使用

简介

motion 是一个为Flutter小部件添加陀螺仪基础效果的插件。在桌面端或当陀螺仪不可用时,该效果基于指针悬停。你可以通过 在线演示 来查看效果。

"Demo of the Motion plugin"

要查看设备或模拟器上的以下效果,请执行:

cd example/
flutter run --release

使用方法

添加依赖

首先,在你的 pubspec.yaml 文件中添加依赖:

dependencies:
  motion: ^<latest-version>

初始化插件

然后,在Dart运行时初始化Motion插件,再运行你的Flutter应用程序:

Future<void> main() async {
  // Initialize the plugin.
  await Motion.instance.initialize();

  // Run your app.
  runApp(...);
}

包装目标小部件

最后,将目标小部件作为子项包装在一个 Motion 小部件中。你还可以选择提供一个 MotionController 实例,用于保存单个小部件的变换(对于基于指针的事件很有用)。最简单的用法如下:

import 'package:motion/motion.dart';

...

return Motion(child: myWidget);

自定义行为

海拔高度(Elevation)

为了与Material Design语言保持一致,你可以通过使用 Motion.elevated 构造函数传递一个 海拔高度 参数给小部件。它将影响阴影的偏移、透明度和模糊度。可能的海拔高度值范围是 0100

"Elevations examples"

阴影(Shadow)

阴影 是可选的,并且在使用 Motion.elevated 构造函数时取决于 elevation 值。海拔越高,阴影越模糊,离小部件背面越低。阴影移动的幅度也取决于小部件的海拔。默认情况下,shadow 是启用的,但你可以通过构造 Motion 小部件时设置 shadow: null 或使用 Motion.only 构造函数来禁用它。

"Shadow effect comparison"

光泽(Glare)

光泽 效果也是可选的。它是一个非常微妙的渐变覆盖,赋予小部件反射感。此效果在Safari iOS上未渲染,因为存在渐变性能限制。

"Glare effect comparison"

默认情况下,glare 是启用的,但你可以通过构造 Motion 小部件时设置 glare: false 或使用 Motion.only 构造函数来禁用它。

自定义效果配置

你可以通过传递 GlareConfigurationShadowConfigurationTranslationConfigurationMotionMotion.only 构造函数来提供自定义配置。当省略时,默认值将被使用,除非你使用 Motion.only 构造函数,它默认仅应用倾斜效果。

阻尼(Damping)

默认情况下,如果手机停止旋转,小部件会自然地旋转回其原始位置。这旨在尽可能提高用户体验,同时尽可能保留小部件的易读性。此属性仅适用于陀螺仪事件,并忽略指针事件。

MotionControllerdamping 属性允许你微调或禁用此行为。通过提供 01 之间的值,你可以更改小部件返回其原始位置的速度。显式提供 null 将禁用此效果。默认值为 0.2

更新间隔(Update interval)

默认情况下,传感器更新和指针事件的时间间隔设置为 60帧每秒。更高的值将导致更准确的运动数据,从而实现更平滑的小部件运动,但这也会对性能产生更大的影响。理想情况下,更新间隔应匹配 Flutter 推荐的 60 FPS (帧每秒)。然而,在某些较旧的设备上,可能需要进行性能折衷。在这种情况下,你可以使用标准的30 FPS或24 FPS,后者是使运动看起来自然所需的最低帧率。

最佳实践是在初始化应用程序时全局设置传感器速率,例如:

void main() {
  // Initialize the plugin.
  await Motion.instance.initialize();

  // Globally set the sensors sampling rate to 60 frames per second.
  Motion.instance.setUpdateInterval(60.fps);

  // Run your app.
  runApp(...);
}

滤镜质量(Filter quality)

Motion 小部件的实现将子级包装在一个基于矩阵的 Transform 小部件中。在大多数平台上,将其设置为 FilterQuality.mediumFilterQuality.high 是一个良好的做法。你可以直接从小部件的构造函数中指定它:

Motion(
  filterQuality: FilterQuality.high,
  child: ...
)

但是请注意,由于Safari iOS上的限制,它总是强制设置为 null。一些开发者报告了在某些Flutter 3版本的Web上使用 FilterQuality.high 时遇到的问题。你可能需要检查 kIsWeb 并根据你使用的Flutter版本使用不同的值。

默认值是 FilterQuality.high

示例代码

下面是一个完整的示例代码,展示了如何在Flutter项目中使用 motion 插件:

import 'package:motion/motion.dart';
import 'package:flutter/material.dart' hide Card;
import 'package:motion_example/card.dart';

const cardBorderRadius = BorderRadius.all(Radius.circular(25));

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  /// Initialize the plugin to determine gyroscope availability.
  await Motion.instance.initialize();

  /// Globally set Motion's update interval to 60 frames per second.
  Motion.instance.setUpdateInterval(60.fps);

  /// ... and run the sample app.
  runApp(const MotionDemoApp());
}

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

  @override
  Widget build(BuildContext context) => const MaterialApp(
        title: 'Motion Demo',
        debugShowCheckedModeBanner: false,
        home: MotionDemoPage(),
      );
}

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

  @override
  State<MotionDemoPage> createState() => _MotionDemoPageState();
}

class _MotionDemoPageState extends State<MotionDemoPage> {
  @override
  Widget build(BuildContext context) {
    if (Motion.instance.isPermissionRequired &&
        !Motion.instance.isPermissionGranted) {
      showPermissionRequestDialog(
        context,
        onDone: () {
          setState(() {});
        },
      );
    }

    return Scaffold(
        body: Stack(children: [
      Center(
          child: Column(mainAxisSize: MainAxisSize.min, children: [
        Padding(
            padding: const EdgeInsets.only(bottom: 50),
            child: Text(
              'Motion example',
              style: Theme.of(context).textTheme.headlineMedium?.copyWith(
                  fontWeight: FontWeight.w800,
                  color: const Color.fromARGB(255, 0, 0, 0)),
            )),
        const Card(width: 280, height: 170, borderRadius: cardBorderRadius),
        Padding(
            padding: const EdgeInsets.only(top: 30, bottom: 30),
            child: Text(
              'without Motion',
              style: Theme.of(context).textTheme.bodyLarge,
            )),
        Motion.elevated(
          elevation: 70,
          borderRadius: cardBorderRadius,
          child: const Card(
              width: 280, height: 170, borderRadius: cardBorderRadius),
        ),
        Padding(
            padding: const EdgeInsets.only(top: 30),
            child: Text(
              'with Motion',
              style: Theme.of(context).textTheme.bodyLarge,
            )),
      ]))
    ]));
  }

  Future<void> showPermissionRequestDialog(BuildContext context,
      {required Function() onDone}) async {
    return showDialog<void>(
        context: context,
        builder: (BuildContext context) => AlertDialog(
              title: const Text('Permission required'),
              content: const Text(
                  'On iOS 13+, you need to grant access to the gyroscope. A permission will be requested to proceed.'),
              actions: <Widget>[
                TextButton(
                  onPressed: () => Navigator.pop(context, 'Cancel'),
                  child: const Text('Cancel'),
                ),
                TextButton(
                  onPressed: () {
                    Navigator.pop(context);
                    Motion.instance.requestPermission();
                  },
                  child: const Text('OK'),
                ),
              ],
            ));
  }
}

希望这些信息对你有帮助!如果你有任何问题或需要进一步的帮助,请随时提问。


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

1 回复

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


当然,下面是一个关于如何在Flutter中使用motion插件来实现动画效果的代码案例。motion插件是一个功能强大的库,它简化了Flutter中的动画创建过程。虽然motion插件本身可能不是官方Flutter库的一部分,但类似的动画库(如flutter_hooksanimations)提供了类似的功能。为了示范,我们假设你使用的是一个类似功能的库。

首先,确保你已经在pubspec.yaml文件中添加了所需的依赖项(假设我们使用的是flutter_hooksanimations库作为替代):

dependencies:
  flutter:
    sdk: flutter
  flutter_hooks: ^0.18.0  # 请检查最新版本
  animations: ^2.0.1      # 请检查最新版本

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

接下来,我们来看一个使用这些库实现简单动画效果的示例。在这个例子中,我们将创建一个按钮,当点击它时,一个容器会在屏幕上移动。

import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:animations/animations.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Motion Animation Example'),
        ),
        body: Center(
          child: AnimatedContainerWidget(),
        ),
      ),
    );
  }
}

class AnimatedContainerWidget extends HookWidget {
  final double initialX = 0.0;
  final double initialY = 200.0;
  final double targetX = 300.0;
  final double targetY = 200.0;

  @override
  Widget build(BuildContext context) {
    final controller = useAnimationController(
      duration: const Duration(seconds: 2),
      vsync: useVsync(),
    );

    final animation = useSharedValue<Offset>(0.0.toOffset());

    useEffect(() {
      controller.animateTo(
        1.0,
        curve: Curves.easeInOut,
        duration: const Duration(seconds: 2),
        onEnd: () {
          // Reset animation on completion
          animation.value = initialX.toOffset();
        },
      );

      void handleTap() {
        animation.value = (controller.value * (targetX - initialX) + initialX).toOffset();
        controller.reset();
        controller.animateTo(
          1.0,
          curve: Curves.easeInOut,
          duration: const Duration(seconds: 2),
        );
      }

      // Listen for taps on the button
      final tapGestureRecognizer = GestureRecognizerFactoryWithHandlers<TapGestureRecognizer>(
        () => TapGestureRecognizer(),
        (TapGestureRecognizer instance) {
          instance
            ..onTapDown = (_) {}
            ..onTapUp = (_) => handleTap();
        },
      );

      useEffectOnChanged(tapGestureRecognizer, () {
        GestureDetector.instance!.addPointerGestureRecognizer(tapGestureRecognizer);
        return () {
          GestureDetector.instance!.removePointerGestureRecognizer(tapGestureRecognizer);
        };
      }, []);
    }, []);

    return Stack(
      alignment: Alignment.center,
      children: [
        Positioned(
          left: animation.value.dx,
          top: animation.value.dy,
          child: Container(
            width: 100,
            height: 100,
            color: Colors.blue,
          ),
        ),
        Positioned(
          bottom: 50,
          child: ElevatedButton(
            onPressed: () {
              // Trigger the animation
              controller.reset();
              controller.animateTo(
                1.0,
                curve: Curves.easeInOut,
                duration: const Duration(seconds: 2),
              );
            },
            child: Text('Animate'),
          ),
        ),
      ],
    );
  }
}

extension OffsetExtensions on num {
  Offset toOffset() => Offset(this, 0.0);
}

注意

  1. 上面的代码是一个简化的示例,用于演示如何使用动画控制器和共享值来实现动画。
  2. useAnimationControlleruseSharedValueflutter_hooksanimations库中的钩子函数,用于管理动画状态。
  3. GestureDetectorGestureRecognizer用于监听按钮点击事件。
  4. OffsetExtensions是一个扩展函数,用于简化Offset的创建。

请根据你的具体需求和motion插件的API文档调整代码。如果motion插件的API有所不同,请参考其官方文档获取更多信息和示例。

回到顶部