🐧
【Flutter入門】ぶつりシュミレーション的なものを使う
こんにちは、今回はflutter/cookbookに記載されている「Animate a widget using a physics simulation」を体感してみましょう!
カードをドラッグして離したら、バネ(スプリング)の力で元に戻る」っていう楽しい物理アニメーションのデモですね〜
こんな感じに楽しい↓
楽しいし、色々なアイデアが浮かんできそう!
とりあえず楽しもうくらいの温度感!
import 'package:flutter/material.dart';
import 'package:flutter/physics.dart';
void main() {
runApp(const MaterialApp(home: PhysicsCardDragDemo()));
}
class PhysicsCardDragDemo extends StatelessWidget {
const PhysicsCardDragDemo({super.key});
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: const DraggableCard(child: FlutterLogo(size: 128)),
);
}
}
class DraggableCard extends StatefulWidget {
const DraggableCard({required this.child, super.key});
final Widget child;
State<DraggableCard> createState() => _DraggableCardState();
}
class _DraggableCardState extends State<DraggableCard>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<Alignment> _animation;
Alignment _dragAlignment = Alignment.center;
//スプリングシュミレー=ション
/// Calculates and runs a [SpringSimulation].
void _runAnimation(Offset pixelsPerSecond, Size size) {
_animation = _controller.drive(
AlignmentTween(begin: _dragAlignment, end: Alignment.center),
);
final unitsPerSecondX = pixelsPerSecond.dx / size.width;
final unitsPerSecondY = pixelsPerSecond.dy / size.height;
final unitsPerSecond = Offset(unitsPerSecondX, unitsPerSecondY);
final unitVelocity = unitsPerSecond.distance;
const spring = SpringDescription(mass: 30, stiffness: 1, damping: 1);
final Simulation = SpringSimulation(spring, 0, 1, -unitVelocity);
_controller.animateWith(Simulation);
}
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: const Duration(seconds: 1),
);
_controller.addListener(() {
setState(() {
_dragAlignment = _animation.value;
});
});
}
void dispose() {
_controller.dispose();
super.dispose();
}
Widget build(BuildContext context) {
var size = MediaQuery.of(context).size;
return GestureDetector(
onPanDown: (details) {
_controller.stop();
},
onPanUpdate: (details) {
setState(() {
_dragAlignment += Alignment(
details.delta.dx / (size.width / 2),
details.delta.dy / (size.height / 2),
);
});
},
onPanEnd: (details) {
_runAnimation(details.velocity.pixelsPerSecond, size);
},
child: Align(alignment: _dragAlignment, child: Card(child: widget.child)),
);
}
}
【実装時に気をつけるポイント】
1.AnimationController の管理
-dispose() で解放を忘れずに。
-unbounded コントローラーは無限範囲を扱えるので暴走しないよう stop() を適切に呼ぶこと。
2.Velocity の取り扱い
-details.velocity.pixelsPerSecond から速度を計算しますが、極端に大きい値が入ることもあるので、
本番では安全のために clamp() で上限を設けても良いです。
3.スプリングパラメータの調整
-mass, stiffness, damping のバランスで自然さが決まります。
4.パフォーマンス
-毎フレーム setState() が走るので、重たいUIを載せる場合はビルドツリーを最適化する必要があるらしい
Discussion