Flutter로 누르면 파문이 일어납니다.
개시하다
스마트폰 게임에서는 가볍게 두드리는 곳에 어떤 효과가 있는지 자주 볼 수 있는 UI.
이번에 그거 해보니까 소개할게요.
Animation 사용법에 익숙하지 않기 때문에 장황하게 쓸 수 있습니다.
더 좋은 방법이 있으면 알려주세요!
상세한 해설이 코드로 넘어가면 본문의 구성이 비교적 적다
만들어진 물건
방침.
대체로 아래의 순서에 따라 실시하였다.
각각의 절차에 대해 샘플 코드 등을 섞어 설명한다.
헤드의 위치를 감지하다
탭 위치에서 원 루프 확장하기
휠 애니메이션 사용하기
빙글빙글 돌게 하다
1. 헤드셋의 위치 측정
Gesture Detector 사용
Widget build(BuildContext context) {
return GestureDetector(
// onTap では position はとれないけれど、onTapDown ならとれる。
// 他にもいろいろな動きをとれて便利です。
// https://api.flutter.dev/flutter/widgets/GestureDetector-class.html
onTapDown: (details) => print(details.globalPosition),
child: const Scaffold(),
);
}
2. 탭 위치에서 링 펴기
Custom Paint 사용
코드
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
Widget build(BuildContext context) =>
MaterialApp(theme: ThemeData(), home: MyHomePage());
}
class MyHomePage extends StatefulWidget {
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
Widget build(BuildContext context) {
return GestureDetector(
onTapDown: ripplePointer, // 引数が同じだとこうやって短く書ける
child: Scaffold(
body: offset != null
? CustomPaint(painter: RipplePainter(offset: offset))
: const SizedBox(),
),
);
}
Offset offset;
void ripplePointer(TapDownDetails details) {
// 単純に offset を更新しているだけ
setState(() {
offset = details.globalPosition;
print(details.globalPosition);
});
}
}
class RipplePainter extends CustomPainter {
RipplePainter({ this.offset});
final Offset offset;
void paint(Canvas canvas, Size size) {
final paint = Paint()
..style = PaintingStyle.stroke // 図形をぬりつずつかどうか
..color = Colors.blue // 色の指定
..strokeWidth = 2; // 線の太さの指定
canvas.drawCircle(offset, 100, paint); // 位置と輪っかの大きさを指定
}
bool shouldRepaint(CustomPainter oldDelegate) => true;
}
3. 라운드 애니메이션 재생
Custom Paint에 Animation Controller 만들기
아직 동작이 없어서 외로워요.
이제 애니메이션을 시작합니다.
코드
class RipplePointer extends StatefulWidget {
const RipplePointer({Key key, this.offset, this.duration})
: super(key: key);
final Offset offset;
final Duration duration;
_RipplePointerState createState() => _RipplePointerState();
}
class _RipplePointerState extends State<RipplePointer>
with SingleTickerProviderStateMixin {
// これを忘れずに
Widget build(BuildContext context) {
return CustomPaint(
painter: RipplePainter(controller: controller, offset: widget.offset),
);
}
AnimationController controller;
void initState() {
super.initState();
// SingleTickerProviderStateMixin を使うことで, vsync に this で渡せる。
// これによりアニメーションの変化に合わせて画面が再描画されていく。
controller = AnimationController(vsync: this, duration: widget.duration);
controller.forward(); // アニメーションをスタート
}
void dispose() {
controller.dispose();
super.dispose();
}
}
class RipplePainter extends CustomPainter {
RipplePainter({ this.controller, this.offset})
: super(repaint: controller); // repaint に controller を渡さないと再描画されない
final Offset offset;
final Animation<double> controller;
void paint(Canvas canvas, Size size) {
/// ```dart
/// Tween<T>(begin: ,end: ) // はじめとおわりの値を指定できる
/// CurveTween(curve: ) // Curves. でアニメーションカーブを指定できる
/// ```
/// - 輪っかの大きさ
/// - 線の太さ
/// - 透明度
/// この 3 つをアニメーションで制御している。
final circleValue = Tween<double>(begin: 8, end: 80)
.animate(controller.drive(CurveTween(curve: Curves.easeOutExpo)))
.value;
final widthValue = Tween<double>(begin: 12, end: 2)
.animate(controller.drive(CurveTween(curve: Curves.easeInOut)))
.value;
final opacityValue = Tween<double>(begin: 1, end: 0)
.animate(controller.drive(CurveTween(curve: Curves.easeInOut)))
.value;
final paint = Paint()
..style = PaintingStyle.stroke
..color = Colors.blue.withOpacity(opacityValue)
..strokeWidth = widthValue;
canvas.drawCircle(offset, circleValue, paint);
}
bool shouldRepaint(CustomPainter oldDelegate) => true;
}
4. 동그라미 노출
이 의외의 어려움은 지금까지도 아직 가장 적합한 해를 모른다.
Stack에 리스트를 넘기면 여러 개의 물결이 생기고 애니메이션이 끝나면 remove를 할 수 있습니다.
예를 들어 Unity 같은 거,destroy나generate 같은 거 있는데 화면에서 대상을 생성하거나 폐기하는 건 간단하지만 Flutter는 어떻게 설치하는 게 좋을까요?
코드
class MyHomePage extends StatefulWidget {
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
Widget build(BuildContext context) {
return GestureDetector(
onTapDown: generateRipplePointer,
child: Scaffold(
body: Stack(children: ripplePointerList),
),
);
}
// RipplePointer のリストをつくってタップと同時にそこに追加している
// アニメーションの終了を Future<void>.delayed で待ち、終わった時に removeAt(0) でリストから取り出している
// 取り出すと そのタイミングで dispose が呼ばれる。
List<RipplePointer> ripplePointerList = <RipplePointer>[];
Future<void> generateRipplePointer(TapDownDetails details) async {
const duration = const Duration(milliseconds: 800);
final ripplePointer = RipplePointer(
key: UniqueKey(), // 必ずキーを与えること。これによりそれぞれが独立した描画になります。
offset: details.globalPosition,
duration: duration,
);
setState(() {
ripplePointerList.add(ripplePointer);
});
await Future<void>.delayed(duration);
setState(() {
ripplePointerList.removeAt(0);
});
}
}
전체 이미지
애니메이션 곡선, 선 굵기, 크기 등 매개 변수를 하세요!
코드 전체 텍스트
그럼 어디서 뵐까요?
Reference
이 문제에 관하여(Flutter로 누르면 파문이 일어납니다.), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://zenn.dev/pressedkonbu/articles/flutter-ripple-pointer텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)