Flutter动画效果插件motion的使用
Flutter动画效果插件motion的使用
简介
motion
是一个为Flutter小部件添加陀螺仪基础效果的插件。在桌面端或当陀螺仪不可用时,该效果基于指针悬停。你可以通过 在线演示 来查看效果。
要查看设备或模拟器上的以下效果,请执行:
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
构造函数传递一个 海拔高度 参数给小部件。它将影响阴影的偏移、透明度和模糊度。可能的海拔高度值范围是 0
到 100
。
阴影(Shadow)
阴影 是可选的,并且在使用 Motion.elevated
构造函数时取决于 elevation
值。海拔越高,阴影越模糊,离小部件背面越低。阴影移动的幅度也取决于小部件的海拔。默认情况下,shadow
是启用的,但你可以通过构造 Motion
小部件时设置 shadow: null
或使用 Motion.only
构造函数来禁用它。
光泽(Glare)
光泽 效果也是可选的。它是一个非常微妙的渐变覆盖,赋予小部件反射感。此效果在Safari iOS上未渲染,因为存在渐变性能限制。
默认情况下,glare
是启用的,但你可以通过构造 Motion
小部件时设置 glare: false
或使用 Motion.only
构造函数来禁用它。
自定义效果配置
你可以通过传递 GlareConfiguration
、ShadowConfiguration
和 TranslationConfiguration
给 Motion
和 Motion.only
构造函数来提供自定义配置。当省略时,默认值将被使用,除非你使用 Motion.only
构造函数,它默认仅应用倾斜效果。
阻尼(Damping)
默认情况下,如果手机停止旋转,小部件会自然地旋转回其原始位置。这旨在尽可能提高用户体验,同时尽可能保留小部件的易读性。此属性仅适用于陀螺仪事件,并忽略指针事件。
MotionController
的 damping
属性允许你微调或禁用此行为。通过提供 0
到 1
之间的值,你可以更改小部件返回其原始位置的速度。显式提供 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.medium
或 FilterQuality.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
更多关于Flutter动画效果插件motion的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
当然,下面是一个关于如何在Flutter中使用motion
插件来实现动画效果的代码案例。motion
插件是一个功能强大的库,它简化了Flutter中的动画创建过程。虽然motion
插件本身可能不是官方Flutter库的一部分,但类似的动画库(如flutter_hooks
和animations
)提供了类似的功能。为了示范,我们假设你使用的是一个类似功能的库。
首先,确保你已经在pubspec.yaml
文件中添加了所需的依赖项(假设我们使用的是flutter_hooks
和animations
库作为替代):
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);
}
注意:
- 上面的代码是一个简化的示例,用于演示如何使用动画控制器和共享值来实现动画。
useAnimationController
和useSharedValue
是flutter_hooks
和animations
库中的钩子函数,用于管理动画状态。GestureDetector
和GestureRecognizer
用于监听按钮点击事件。OffsetExtensions
是一个扩展函数,用于简化Offset
的创建。
请根据你的具体需求和motion
插件的API文档调整代码。如果motion
插件的API有所不同,请参考其官方文档获取更多信息和示例。