Skip to content

Instantly share code, notes, and snippets.

@V1ki
Created May 26, 2021 14:39
Show Gist options
  • Select an option

  • Save V1ki/3d6d239742e36bed1c36899b93e7c2a2 to your computer and use it in GitHub Desktop.

Select an option

Save V1ki/3d6d239742e36bed1c36899b93e7c2a2 to your computer and use it in GitHub Desktop.
Flutter 翻页时钟效果
import 'dart:math';
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> with TickerProviderStateMixin {
late Animation<double> animation;
late AnimationController controller;
late Animation<double> animation2;
late AnimationController controller2;
bool _isReversePhase = false;
int value = 0;
@override
void initState() {
super.initState();
const duration = Duration(milliseconds: 2000);
controller = AnimationController(duration: duration, vsync: this)
..addStatusListener((status) {
print('$status');
if (status == AnimationStatus.completed) {
controller.reverse();
_isReversePhase = true;
controller2.forward();
}
if (status == AnimationStatus.dismissed) {}
if (status == AnimationStatus.reverse) {}
})
..addListener(() {
setState(() {});
});
animation = Tween<double>(begin: 0.0001, end: pi / 2).animate(controller);
controller.forward();
controller2 = AnimationController(duration: duration, vsync: this)
..addStatusListener((status) {
print('$status');
if (status == AnimationStatus.completed) {
_isReversePhase = false;
controller2.reverse();
setState(() {
value = value + 1;
if (value >= 9) {
value = 0;
}
});
Future.delayed(duration).then((value) => {controller.forward()});
}
if (status == AnimationStatus.dismissed) {}
if (status == AnimationStatus.reverse) {}
})
..addListener(() {
setState(() {});
});
animation2 = Tween<double>(begin: -pi / 2, end: 0).animate(controller2);
}
@override
void dispose() {
controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
padding: EdgeInsets.all(5),
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
ShadowStackClipRectText(
animValue: value,
staticValue: value + 1,
isReversePhase: _isReversePhase,
animation: animation),
Padding(padding: EdgeInsets.only(top: 4)),
ShadowStackClipRectText(
animValue: value + 1,
staticValue: value,
textAlignment: Alignment.bottomCenter,
animAlignment: Alignment.topCenter,
isReversePhase: !_isReversePhase,
animation: animation2),
],
),
),
));
}
}
class ShadowStackClipRectText extends StatelessWidget {
const ShadowStackClipRectText({
Key? key,
required this.animValue,
required this.staticValue,
this.textAlignment = Alignment.topCenter,
this.animAlignment = Alignment.bottomCenter,
required bool isReversePhase,
required this.animation,
}) : _isReversePhase = isReversePhase,
super(key: key);
final int animValue;
final int staticValue;
final bool _isReversePhase;
final Animation<double> animation;
final Alignment textAlignment;
final Alignment animAlignment;
@override
Widget build(BuildContext context) {
return Stack(children: [
Stack(
alignment: animAlignment, //阴影 的对齐
children: [
ClipRectText(
value: staticValue,
alignment: textAlignment, // 文字的对齐
bgColor: Colors.red,
),
Container(
width: 200,
height: _isReversePhase
? 0
: cos(-animation.value) * 100 + 20 > 90
? 90
: cos(-animation.value) * 100 + 20,
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(20)),
boxShadow: [
BoxShadow(
spreadRadius: 1,
color: Colors.black12,
offset: Offset(0.0, 0.0), //阴影xy轴偏移量
blurRadius: 5.0, //阴影模糊程度
)
],
),
),
],
),
Transform(
transform: Matrix4.identity()
..setEntry(3, 2, 0.002)
..rotateX(_isReversePhase ? pi / 2 : animation.value),
alignment: animAlignment, // 动画的中心点
child: ClipRectText(
value: animValue,
alignment: textAlignment, // 文字的对齐
bgColor: Colors.red,
),
),
]);
}
}
class ClipRectText extends StatelessWidget {
final int value;
final Alignment alignment;
final Color bgColor;
const ClipRectText({
Key? key,
required this.value,
required this.alignment,
required this.bgColor,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return ClipRect(
child: Align(
alignment: alignment,
heightFactor: 0.5,
child: Container(
margin: EdgeInsets.all(8.0),
alignment: Alignment.center,
width: 200,
decoration: BoxDecoration(
color: bgColor,
borderRadius: BorderRadius.all(Radius.circular(20))),
child: Text(
"$value",
maxLines: 1,
style: TextStyle(
color: Colors.white,
fontSize: 150,
fontWeight: FontWeight.w600),
),
),
),
);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment