snapshot of https://github.com/flutter/samples.git (main, 2026-09-16; blobs > 131072B omitted)
Some checks failed
Main Branch CI / Test Flutter beta on ubuntu-latest (push) Has been skipped
Main Branch CI / Test Flutter stable on ubuntu-latest (push) Has been skipped
Main Branch CI / Test Flutter beta on macos-latest (push) Has been cancelled
Main Branch CI / Test Flutter stable on macos-latest (push) Has been cancelled

This commit is contained in:
upstream-mirror
2026-09-16 01:43:26 +05:00
commit 9ed6a80afa
2886 changed files with 140184 additions and 0 deletions

201
animations/lib/main.dart Normal file
View File

@@ -0,0 +1,201 @@
// Copyright 2019 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'src/basics/basics.dart';
import 'src/misc/misc.dart';
void main() {
runApp(const AnimationSamples());
}
class Demo {
final String name;
final String route;
final WidgetBuilder builder;
const Demo({
required this.name,
required this.route,
required this.builder,
});
}
final basicDemos = [
Demo(
name: 'AnimatedContainer',
route: AnimatedContainerDemo.routeName,
builder: (context) => const AnimatedContainerDemo(),
),
Demo(
name: 'PageRouteBuilder',
route: PageRouteBuilderDemo.routeName,
builder: (context) => const PageRouteBuilderDemo(),
),
Demo(
name: 'Animation Controller',
route: AnimationControllerDemo.routeName,
builder: (context) => const AnimationControllerDemo(),
),
Demo(
name: 'Tweens',
route: TweenDemo.routeName,
builder: (context) => const TweenDemo(),
),
Demo(
name: 'AnimatedBuilder',
route: AnimatedBuilderDemo.routeName,
builder: (context) => const AnimatedBuilderDemo(),
),
Demo(
name: 'Custom Tween',
route: CustomTweenDemo.routeName,
builder: (context) => const CustomTweenDemo(),
),
Demo(
name: 'Tween Sequences',
route: TweenSequenceDemo.routeName,
builder: (context) => const TweenSequenceDemo(),
),
Demo(
name: 'Fade Transition',
route: FadeTransitionDemo.routeName,
builder: (context) => const FadeTransitionDemo(),
),
];
final miscDemos = [
Demo(
name: 'Expandable Card',
route: ExpandCardDemo.routeName,
builder: (context) => const ExpandCardDemo(),
),
Demo(
name: 'Carousel',
route: CarouselDemo.routeName,
builder: (context) => CarouselDemo(),
),
Demo(
name: 'Focus Image',
route: FocusImageDemo.routeName,
builder: (context) => const FocusImageDemo(),
),
Demo(
name: 'Card Swipe',
route: CardSwipeDemo.routeName,
builder: (context) => const CardSwipeDemo(),
),
Demo(
name: 'Flutter Animate',
route: FlutterAnimateDemo.routeName,
builder: (context) => const FlutterAnimateDemo(),
),
Demo(
name: 'Repeating Animation',
route: RepeatingAnimationDemo.routeName,
builder: (context) => const RepeatingAnimationDemo(),
),
Demo(
name: 'Spring Physics',
route: PhysicsCardDragDemo.routeName,
builder: (context) => const PhysicsCardDragDemo(),
),
Demo(
name: 'AnimatedList',
route: AnimatedListDemo.routeName,
builder: (context) => const AnimatedListDemo(),
),
Demo(
name: 'AnimatedPositioned',
route: AnimatedPositionedDemo.routeName,
builder: (context) => const AnimatedPositionedDemo(),
),
Demo(
name: 'AnimatedSwitcher',
route: AnimatedSwitcherDemo.routeName,
builder: (context) => const AnimatedSwitcherDemo(),
),
Demo(
name: 'Hero Animation',
route: HeroAnimationDemo.routeName,
builder: (context) => const HeroAnimationDemo(),
),
Demo(
name: 'Curved Animation',
route: CurvedAnimationDemo.routeName,
builder: (context) => const CurvedAnimationDemo(),
),
];
final router = GoRouter(
routes: [
GoRoute(
path: '/',
builder: (context, state) => const HomePage(),
routes: [
for (final demo in basicDemos)
GoRoute(
path: demo.route,
builder: (context, state) => demo.builder(context),
),
for (final demo in miscDemos)
GoRoute(
path: demo.route,
builder: (context, state) => demo.builder(context),
),
],
),
],
);
class AnimationSamples extends StatelessWidget {
const AnimationSamples({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp.router(
title: 'Animation Samples',
theme: ThemeData(colorSchemeSeed: Colors.deepPurple),
routerConfig: router,
);
}
}
class HomePage extends StatelessWidget {
const HomePage({super.key});
@override
Widget build(BuildContext context) {
final headerStyle = Theme.of(context).textTheme.titleLarge;
return Scaffold(
appBar: AppBar(title: const Text('Animation Samples')),
body: ListView(
children: [
ListTile(title: Text('Basics', style: headerStyle)),
...basicDemos.map((d) => DemoTile(demo: d)),
ListTile(title: Text('Misc', style: headerStyle)),
...miscDemos.map((d) => DemoTile(demo: d)),
],
),
);
}
}
class DemoTile extends StatelessWidget {
final Demo demo;
const DemoTile({required this.demo, super.key});
@override
Widget build(BuildContext context) {
return ListTile(
title: Text(demo.name),
onTap: () {
context.go('/${demo.route}');
},
);
}
}

View File

@@ -0,0 +1,78 @@
// Copyright 2019 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/material.dart';
class AnimatedBuilderDemo extends StatefulWidget {
const AnimatedBuilderDemo({super.key});
static const String routeName = 'basics/animated_builder';
@override
State<AnimatedBuilderDemo> createState() => _AnimatedBuilderDemoState();
}
class _AnimatedBuilderDemoState extends State<AnimatedBuilderDemo>
with SingleTickerProviderStateMixin {
static const Color beginColor = Colors.deepPurple;
static const Color endColor = Colors.deepOrange;
Duration duration = const Duration(milliseconds: 800);
late AnimationController controller;
late Animation<Color?> animation;
@override
void initState() {
super.initState();
controller = AnimationController(vsync: this, duration: duration);
animation = ColorTween(
begin: beginColor,
end: endColor,
).animate(controller);
}
@override
void dispose() {
controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('AnimatedBuilder')),
body: Center(
// AnimatedBuilder handles listening to a given animation and calling the builder
// whenever the value of the animation change. This can be useful when a Widget
// tree contains some animated and non-animated elements, as only the subtree
// created by the builder needs to be re-built when the animation changes.
child: AnimatedBuilder(
animation: animation,
builder: (context, child) {
return ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: animation.value,
),
child: child,
onPressed: () {
switch (controller.status) {
case AnimationStatus.completed:
controller.reverse();
default:
controller.forward();
}
},
);
},
// AnimatedBuilder can also accept a pre-built child Widget which is useful
// if there is a non-animated Widget contained within the animated widget.
// This can improve performance since this widget doesn't need to be rebuilt
// when the animation changes.
child: const Text(
'Change Color',
style: TextStyle(color: Colors.white),
),
),
),
);
}
}

View File

@@ -0,0 +1,80 @@
// Copyright 2019 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:math';
import 'package:flutter/material.dart';
double generateBorderRadius() => Random().nextDouble() * 64;
double generateMargin() => Random().nextDouble() * 64;
Color generateColor() => Color(0xFFFFFFFF & Random().nextInt(0xFFFFFFFF));
class AnimatedContainerDemo extends StatefulWidget {
const AnimatedContainerDemo({super.key});
static String routeName = 'basics/animated_container';
@override
State<AnimatedContainerDemo> createState() => _AnimatedContainerDemoState();
}
class _AnimatedContainerDemoState extends State<AnimatedContainerDemo> {
late Color color;
late double borderRadius;
late double margin;
@override
void initState() {
super.initState();
color = Colors.deepPurple;
borderRadius = generateBorderRadius();
margin = generateMargin();
}
void change() {
setState(() {
color = generateColor();
borderRadius = generateBorderRadius();
margin = generateMargin();
});
}
@override
Widget build(BuildContext context) {
// This widget is built using an AnimatedContainer, one of the easiest to use
// animated Widgets. Whenever the AnimatedContainer's properties, such as decoration,
// change, it will handle animating from the previous value to the new value. You can
// specify both a Duration and a Curve for the animation.
// This Widget is useful for designing animated interfaces that just need to change
// the properties of a container. For example, you could use this to design expanding
// and shrinking cards.
return Scaffold(
appBar: AppBar(title: const Text('AnimatedContainer')),
body: Center(
child: Column(
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: SizedBox(
width: 128,
height: 128,
child: AnimatedContainer(
margin: EdgeInsets.all(margin),
decoration: BoxDecoration(
color: color,
borderRadius: BorderRadius.circular(borderRadius),
),
duration: const Duration(milliseconds: 400),
),
),
),
ElevatedButton(
child: const Text('change'),
onPressed: () => change(),
),
],
),
),
);
}
}

View File

@@ -0,0 +1,86 @@
// Copyright 2019 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/material.dart';
class AnimationControllerDemo extends StatefulWidget {
const AnimationControllerDemo({super.key});
static const String routeName = 'basics/animation_controller';
@override
State<AnimationControllerDemo> createState() =>
_AnimationControllerDemoState();
}
class _AnimationControllerDemoState extends State<AnimationControllerDemo>
with SingleTickerProviderStateMixin {
// Using the SingleTickerProviderStateMixin can ensure that our
// AnimationController only animates while the Widget is visible on the
// screen. This is a useful optimization that saves resources when the
// Widget is not visible.
static const Duration _duration = Duration(seconds: 1);
late final AnimationController controller;
@override
void initState() {
super.initState();
controller = AnimationController(vsync: this, duration: _duration)
// The Widget's build needs to be called every time the animation's
// value changes. So add a listener here that will call setState()
// and trigger the build() method to be called by the framework.
// If your Widget's build is relatively simple, this is a good option.
// However, if your build method returns a tree of child Widgets and
// most of them are not animated you should consider using
// AnimatedBuilder instead.
..addListener(() {
setState(() {});
});
}
@override
void dispose() {
// AnimationController is a stateful resource that needs to be disposed when
// this State gets disposed.
controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
// When building the widget you can read the AnimationController's value property
// when building child widgets. You can also check the status to see if the animation
// has completed.
return Scaffold(
appBar: AppBar(title: const Text('Animation Controller')),
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 200),
child: Text(
controller.value.toStringAsFixed(2),
style: Theme.of(context).textTheme.displaySmall,
textScaler: TextScaler.linear(1 + controller.value),
),
),
ElevatedButton(
child: const Text('animate'),
onPressed: () {
switch (controller.status) {
case AnimationStatus.completed:
controller.reverse();
default:
controller.forward();
}
},
),
],
),
),
);
}
}

View File

@@ -0,0 +1,8 @@
export 'animated_builder.dart';
export 'animated_container.dart';
export 'animation_controller.dart';
export 'custom_tween.dart';
export 'fade_transition.dart';
export 'page_route_builder.dart';
export 'tween_sequence.dart';
export 'tweens.dart';

View File

@@ -0,0 +1,123 @@
// Copyright 2019 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/material.dart';
class TypewriterTween extends Tween<String> {
TypewriterTween({String begin = '', String end = ''})
: super(begin: begin, end: end);
@override
String lerp(double t) {
var cutoff = (end!.length * t).round();
return end!.substring(0, cutoff);
}
}
class CustomTweenDemo extends StatefulWidget {
const CustomTweenDemo({super.key});
static const String routeName = 'basics/custom_tweens';
@override
State<CustomTweenDemo> createState() => _CustomTweenDemoState();
}
class _CustomTweenDemoState extends State<CustomTweenDemo>
with SingleTickerProviderStateMixin {
static const Duration _duration = Duration(seconds: 3);
static const String message = loremIpsum;
late final AnimationController controller;
late final Animation<String> animation;
@override
void initState() {
super.initState();
controller = AnimationController(vsync: this, duration: _duration);
animation = TypewriterTween(end: message).animate(controller);
}
@override
void dispose() {
controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Custom Tween'),
actions: [
TextButton(
onPressed: () {
if (controller.status == AnimationStatus.completed) {
controller.reverse().whenComplete(() {
setState(() {});
});
} else {
controller.forward().whenComplete(() {
setState(() {});
});
}
},
child: Text(
controller.status == AnimationStatus.completed
? 'Delete Essay'
: 'Write Essay',
),
),
],
),
body: SafeArea(
child: Container(
alignment: Alignment.bottomCenter,
child: SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Card(
child: Container(
padding: const EdgeInsets.all(8.0),
child: AnimatedBuilder(
animation: animation,
builder: (context, child) {
return Text(
animation.value,
style: const TextStyle(
fontSize: 16,
fontFamily: 'SpecialElite',
),
);
},
),
),
),
),
],
),
),
),
),
);
}
}
const String loremIpsum = '''
Sed ut perspiciatis, unde omnis iste natus error sit voluptatem accusantium
doloremque laudantium, totam rem aperiam eaque ipsa, quae ab illo inventore
veritatis et quasi architecto beatae vitae dicta sunt, explicabo. Nemo enim
ipsam voluptatem, quia voluptas sit, aspernatur aut odit aut fugit, sed quia
consequuntur magni dolores eos, qui ratione voluptatem sequi nesciunt, neque
porro quisquam est, qui dolorem ipsum, quia dolor sit amet consectetur
adipisci[ng] velit, sed quia non-numquam [do] eius modi tempora inci[di]dunt, ut
labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam,
quis nostrum[d] exercitationem ullam corporis suscipit laboriosam, nisi ut
aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit, qui in
ea voluptate velit esse, quam nihil molestiae consequatur, vel illum, qui
dolorem eum fugiat, quo voluptas nulla pariatur?
''';

View File

@@ -0,0 +1,73 @@
// Copyright 2019 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/material.dart';
// Refer to the AnimatedWidget docs here - https://api.flutter.dev/flutter/widgets/AnimatedWidget-class.html
// for examples of other common animated widgets.
class FadeTransitionDemo extends StatefulWidget {
const FadeTransitionDemo({super.key});
static const String routeName = 'basics/fade_transition';
@override
State<FadeTransitionDemo> createState() => _FadeTransitionDemoState();
}
class _FadeTransitionDemoState extends State<FadeTransitionDemo>
with SingleTickerProviderStateMixin {
late final AnimationController _controller;
late final Animation<double> _animation;
late final CurvedAnimation _curve;
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 500),
);
_curve = CurvedAnimation(parent: _controller, curve: Curves.easeIn);
_animation = Tween(begin: 1.0, end: 0.0).animate(_curve);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Fade Transition')),
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
FadeTransition(
opacity: _animation,
child: const Icon(
Icons.star,
color: Colors.amber,
size: 300,
),
),
ElevatedButton(
child: const Text('animate'),
onPressed: () => setState(() {
_controller
.animateTo(1.0)
.then<TickerFuture>(
(value) => _controller.animateBack(0.0),
);
}),
),
],
),
),
);
}
}

View File

@@ -0,0 +1,58 @@
// Copyright 2019 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/material.dart';
class PageRouteBuilderDemo extends StatelessWidget {
const PageRouteBuilderDemo({super.key});
static const String routeName = 'basics/page_route_builder';
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Page 1')),
body: Center(
child: ElevatedButton(
child: const Text('Go!'),
onPressed: () {
Navigator.of(context).push<void>(_createRoute());
},
),
),
);
}
}
Route _createRoute() {
return PageRouteBuilder<SlideTransition>(
pageBuilder: (context, animation, secondaryAnimation) => _Page2(),
transitionsBuilder: (context, animation, secondaryAnimation, child) {
var tween = Tween<Offset>(
begin: const Offset(0.0, 1.0),
end: Offset.zero,
);
var curveTween = CurveTween(curve: Curves.ease);
return SlideTransition(
position: animation.drive(curveTween).drive(tween),
child: child,
);
},
);
}
class _Page2 extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Page 2')),
body: Center(
child: Text(
'Page 2!',
style: Theme.of(context).textTheme.headlineMedium,
),
),
);
}
}

View File

@@ -0,0 +1,85 @@
// Copyright 2019 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/material.dart';
class TweenSequenceDemo extends StatefulWidget {
const TweenSequenceDemo({super.key});
static const String routeName = 'basics/chaining_tweens';
@override
State<TweenSequenceDemo> createState() => _TweenSequenceDemoState();
}
class _TweenSequenceDemoState extends State<TweenSequenceDemo>
with SingleTickerProviderStateMixin {
static const Duration duration = Duration(seconds: 3);
late final AnimationController controller;
late final Animation<Color?> animation;
static final colors = [
Colors.red,
Colors.orange,
Colors.yellow,
Colors.green,
Colors.blue,
Colors.indigo,
Colors.purple,
];
@override
void initState() {
super.initState();
final sequenceItems = <TweenSequenceItem<Color?>>[];
for (var i = 0; i < colors.length; i++) {
final beginColor = colors[i];
final endColor = colors[(i + 1) % colors.length];
final weight = 1 / colors.length;
sequenceItems.add(
TweenSequenceItem<Color?>(
tween: ColorTween(begin: beginColor, end: endColor),
weight: weight,
),
);
}
controller = AnimationController(duration: duration, vsync: this);
animation = TweenSequence<Color?>(sequenceItems).animate(controller);
}
@override
void dispose() {
controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Tween Sequences')),
body: Center(
child: AnimatedBuilder(
animation: animation,
builder: (context, child) {
return MaterialButton(
color: animation.value,
onPressed: () async {
await controller.forward();
controller.reset();
},
child: child,
);
},
child: const Text(
'Animate',
style: TextStyle(color: Colors.white),
),
),
),
);
}
}

View File

@@ -0,0 +1,76 @@
// Copyright 2019 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/material.dart';
class TweenDemo extends StatefulWidget {
const TweenDemo({super.key});
static const String routeName = 'basics/tweens';
@override
State<TweenDemo> createState() => _TweenDemoState();
}
class _TweenDemoState extends State<TweenDemo>
with SingleTickerProviderStateMixin {
static const Duration _duration = Duration(seconds: 1);
static const double accountBalance = 1000000;
late final AnimationController controller;
late final Animation<double> animation;
@override
void initState() {
super.initState();
controller = AnimationController(vsync: this, duration: _duration)
..addListener(() {
// Marks the widget tree as dirty
setState(() {});
});
animation = Tween(begin: 0.0, end: accountBalance).animate(controller);
}
@override
void dispose() {
controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Tweens')),
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 200),
child: Text(
'\$${animation.value.toStringAsFixed(2)}',
style: const TextStyle(fontSize: 24),
),
),
ElevatedButton(
child: Text(switch (controller.status) {
AnimationStatus.completed => 'Buy a Mansion',
AnimationStatus.forward => 'Accruing...',
AnimationStatus.reverse => 'Spending...',
_ => 'Win the lottery',
}),
onPressed: () {
switch (controller.status) {
case AnimationStatus.completed:
controller.reverse();
default:
controller.forward();
}
},
),
],
),
),
);
}
}

View File

@@ -0,0 +1,107 @@
// Copyright 2020 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/material.dart';
class AnimatedListDemo extends StatefulWidget {
const AnimatedListDemo({super.key});
static String routeName = 'misc/animated_list';
@override
State<AnimatedListDemo> createState() => _AnimatedListDemoState();
}
class _AnimatedListDemoState extends State<AnimatedListDemo> {
final GlobalKey<AnimatedListState> _listKey = GlobalKey();
final listData = [
UserModel(0, 'Govind', 'Dixit'),
UserModel(1, 'Greta', 'Stoll'),
UserModel(2, 'Monty', 'Carlo'),
UserModel(3, 'Petey', 'Cruiser'),
UserModel(4, 'Barry', 'Cade'),
];
final initialListSize = 5;
void addUser() {
setState(() {
var index = listData.length;
listData.add(UserModel(++_maxIdValue, 'New', 'Person'));
_listKey.currentState!.insertItem(
index,
duration: const Duration(milliseconds: 300),
);
});
}
void deleteUser(int id) {
setState(() {
final index = listData.indexWhere((u) => u.id == id);
var user = listData.removeAt(index);
_listKey.currentState!.removeItem(index, (context, animation) {
return FadeTransition(
opacity: CurvedAnimation(
parent: animation,
curve: const Interval(0.5, 1.0),
),
child: SizeTransition(
sizeFactor: CurvedAnimation(
parent: animation,
curve: const Interval(0.0, 1.0),
),
// ignore: deprecated_member_use
axisAlignment: 0.0,
child: _buildItem(user),
),
);
}, duration: const Duration(milliseconds: 600));
});
}
Widget _buildItem(UserModel user) {
return ListTile(
key: ValueKey<UserModel>(user),
title: Text(user.firstName),
subtitle: Text(user.lastName),
leading: const CircleAvatar(child: Icon(Icons.person)),
trailing: IconButton(
icon: const Icon(Icons.delete),
onPressed: () => deleteUser(user.id),
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('AnimatedList'),
actions: [
IconButton(icon: const Icon(Icons.add), onPressed: addUser),
],
),
body: SafeArea(
child: AnimatedList(
key: _listKey,
initialItemCount: 5,
itemBuilder: (context, index, animation) {
return FadeTransition(
opacity: animation,
child: _buildItem(listData[index]),
);
},
),
),
);
}
}
class UserModel {
UserModel(this.id, this.firstName, this.lastName);
final int id;
final String firstName;
final String lastName;
}
int _maxIdValue = 4;

View File

@@ -0,0 +1,84 @@
// Copyright 2020 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:math';
import 'package:flutter/material.dart';
class AnimatedPositionedDemo extends StatefulWidget {
const AnimatedPositionedDemo({super.key});
static String routeName = 'misc/animated_positioned';
@override
State<AnimatedPositionedDemo> createState() =>
_AnimatedPositionedDemoState();
}
class _AnimatedPositionedDemoState extends State<AnimatedPositionedDemo> {
late double topPosition;
late double leftPosition;
double generateTopPosition(double top) => Random().nextDouble() * top;
double generateLeftPosition(double left) => Random().nextDouble() * left;
@override
void initState() {
super.initState();
topPosition = generateTopPosition(30);
leftPosition = generateLeftPosition(30);
}
void changePosition(double top, double left) {
setState(() {
topPosition = generateTopPosition(top);
leftPosition = generateLeftPosition(left);
});
}
@override
Widget build(BuildContext context) {
final size = MediaQuery.of(context).size;
final appBar = AppBar(title: const Text('AnimatedPositioned'));
final topPadding = MediaQuery.of(context).padding.top;
// AnimatedPositioned animates changes to a widget's position within a Stack
return Scaffold(
appBar: appBar,
body: SizedBox(
height: size.height,
width: size.width,
child: Stack(
children: [
AnimatedPositioned(
top: topPosition,
left: leftPosition,
duration: const Duration(seconds: 1),
child: InkWell(
onTap: () => changePosition(
size.height -
(appBar.preferredSize.height + topPadding + 50),
size.width - 150,
),
child: Container(
alignment: Alignment.center,
width: 150,
height: 50,
color: Theme.of(context).primaryColor,
child: Text(
'Click Me',
style: TextStyle(
color: Theme.of(
context,
).buttonTheme.colorScheme!.onPrimary,
),
),
),
),
),
],
),
),
);
}
}

View File

@@ -0,0 +1,70 @@
// Copyright 2020 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:math';
import 'package:flutter/material.dart';
Color generateColor() => Color(0xFFFFFFFF & Random().nextInt(0xFFFFFFFF));
Widget generateContainer(int keyCount) => Container(
key: ValueKey<int>(keyCount),
height: Random().nextDouble() * 200,
width: Random().nextDouble() * 200,
decoration: BoxDecoration(
color: generateColor(),
borderRadius: BorderRadius.circular(Random().nextDouble() * 100),
border: Border.all(
color: generateColor(),
width: Random().nextDouble() * 5,
),
),
);
class AnimatedSwitcherDemo extends StatefulWidget {
const AnimatedSwitcherDemo({super.key});
static String routeName = 'misc/animated_switcher';
@override
State<AnimatedSwitcherDemo> createState() => _AnimatedSwitcherDemoState();
}
class _AnimatedSwitcherDemoState extends State<AnimatedSwitcherDemo> {
late Widget container;
late int keyCount;
@override
void initState() {
super.initState();
keyCount = 0;
container = generateContainer(keyCount);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('AnimatedSwitcher'),
actions: [
TextButton(
onPressed: () =>
setState(() => container = generateContainer(++keyCount)),
child: const Text('Change Widget'),
),
],
),
body: Center(
// AnimatedSwitcher Widget is used to switch between different widgets
// with a given transition. You can change the transitions by using
// transitionBuilder property.
child: AnimatedSwitcher(
duration: const Duration(seconds: 1),
child: container,
transitionBuilder: (child, animation) =>
ScaleTransition(scale: animation, child: child),
),
),
);
}
}

View File

@@ -0,0 +1,212 @@
// Copyright 2019 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/material.dart';
import 'package:flutter/physics.dart';
class CardSwipeDemo extends StatefulWidget {
const CardSwipeDemo({super.key});
static String routeName = 'misc/card_swipe';
@override
State<CardSwipeDemo> createState() => _CardSwipeDemoState();
}
class _CardSwipeDemoState extends State<CardSwipeDemo> {
late List<String> fileNames;
@override
void initState() {
super.initState();
_resetCards();
}
void _resetCards() {
fileNames = [
'assets/eat_cape_town_sm.jpg',
'assets/eat_new_orleans_sm.jpg',
'assets/eat_sydney_sm.jpg',
];
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Card Swipe')),
body: Padding(
padding: const EdgeInsets.all(12.0),
child: Center(
child: Column(
children: [
Expanded(
child: ClipRect(
child: Stack(
children: [
for (final fileName in fileNames)
SwipeableCard(
imageAssetName: fileName,
onSwiped: () {
setState(() {
fileNames.remove(fileName);
});
},
),
],
),
),
),
ElevatedButton(
child: const Text('Refill'),
onPressed: () {
setState(() {
_resetCards();
});
},
),
],
),
),
),
);
}
}
class Card extends StatelessWidget {
final String imageAssetName;
const Card({required this.imageAssetName, super.key});
@override
Widget build(BuildContext context) {
return AspectRatio(
aspectRatio: 3 / 5,
child: DecoratedBox(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20.0),
image: DecorationImage(
image: AssetImage(imageAssetName),
fit: BoxFit.cover,
),
),
),
);
}
}
class SwipeableCard extends StatefulWidget {
final String imageAssetName;
final VoidCallback onSwiped;
const SwipeableCard({
required this.onSwiped,
required this.imageAssetName,
super.key,
});
@override
State<SwipeableCard> createState() => _SwipeableCardState();
}
class _SwipeableCardState extends State<SwipeableCard>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<Offset> _animation;
late double _dragStartX;
bool _isSwipingLeft = false;
@override
void initState() {
super.initState();
_controller = AnimationController.unbounded(vsync: this);
_animation = _controller.drive(
Tween<Offset>(begin: Offset.zero, end: const Offset(1, 0)),
);
}
@override
Widget build(BuildContext context) {
return SlideTransition(
position: _animation,
child: GestureDetector(
onHorizontalDragStart: _dragStart,
onHorizontalDragUpdate: _dragUpdate,
onHorizontalDragEnd: _dragEnd,
child: Card(imageAssetName: widget.imageAssetName),
),
);
}
/// Sets the starting position the user dragged from.
void _dragStart(DragStartDetails details) {
_dragStartX = details.localPosition.dx;
}
/// Changes the animation to animate to the left or right depending on the
/// swipe, and sets the AnimationController's value to the swiped amount.
void _dragUpdate(DragUpdateDetails details) {
var isSwipingLeft = (details.localPosition.dx - _dragStartX) < 0;
if (isSwipingLeft != _isSwipingLeft) {
_isSwipingLeft = isSwipingLeft;
_updateAnimation(details.localPosition.dx);
}
setState(() {
final size = context.size;
if (size == null) {
return;
}
// Calculate the amount dragged in unit coordinates (between 0 and 1)
// using this widgets width.
_controller.value =
(details.localPosition.dx - _dragStartX).abs() / size.width;
});
}
/// Runs the fling / spring animation using the final velocity of the drag
/// gesture.
void _dragEnd(DragEndDetails details) {
final size = context.size;
if (size == null) {
return;
}
var velocity = (details.velocity.pixelsPerSecond.dx / size.width).abs();
_animate(velocity: velocity);
}
void _updateAnimation(double dragPosition) {
_animation = _controller.drive(
Tween<Offset>(
begin: Offset.zero,
end: _isSwipingLeft ? const Offset(-1, 0) : const Offset(1, 0),
),
);
}
void _animate({double velocity = 0}) {
var description = const SpringDescription(
mass: 50,
stiffness: 1,
damping: 1,
);
var simulation = SpringSimulation(
description,
_controller.value,
1,
velocity,
);
_controller.animateWith(simulation).then<void>((_) {
widget.onSwiped();
});
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
}

View File

@@ -0,0 +1,115 @@
// Copyright 2019 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:ui' as ui;
import 'package:flutter/material.dart';
class CarouselDemo extends StatelessWidget {
CarouselDemo({super.key});
static String routeName = 'misc/carousel';
static const List<String> fileNames = [
'assets/eat_cape_town_sm.jpg',
'assets/eat_new_orleans_sm.jpg',
'assets/eat_sydney_sm.jpg',
];
final List<Widget> images = fileNames
.map((file) => Image.asset(file, fit: BoxFit.cover))
.toList();
@override
Widget build(context) {
return Scaffold(
appBar: AppBar(title: const Text('Carousel Demo')),
body: Center(
child: Padding(
padding: const EdgeInsets.all(16),
child: AspectRatio(
aspectRatio: 1,
child: Carousel(itemBuilder: widgetBuilder),
),
),
),
);
}
Widget widgetBuilder(BuildContext context, int index) {
return images[index % images.length];
}
}
typedef OnCurrentItemChangedCallback = void Function(int currentItem);
class Carousel extends StatefulWidget {
final IndexedWidgetBuilder itemBuilder;
const Carousel({super.key, required this.itemBuilder});
@override
State<Carousel> createState() => _CarouselState();
}
class _CarouselState extends State<Carousel> {
late final PageController _controller;
late int _currentPage;
bool _pageHasChanged = false;
@override
void initState() {
super.initState();
_currentPage = 0;
_controller = PageController(
viewportFraction: .85,
initialPage: _currentPage,
);
}
@override
Widget build(context) {
var size = MediaQuery.of(context).size;
return PageView.builder(
onPageChanged: (value) {
setState(() {
_pageHasChanged = true;
_currentPage = value;
});
},
controller: _controller,
scrollBehavior: ScrollConfiguration.of(context).copyWith(
dragDevices: {
ui.PointerDeviceKind.touch,
ui.PointerDeviceKind.mouse,
},
),
itemBuilder: (context, index) => AnimatedBuilder(
animation: _controller,
builder: (context, child) {
var result = _pageHasChanged
? _controller.page!
: _currentPage * 1.0;
// The horizontal position of the page between a 1 and 0
var value = result - index;
value = (1 - (value.abs() * .5)).clamp(0.0, 1.0);
return Center(
child: SizedBox(
height: Curves.easeOut.transform(value) * size.height,
width: Curves.easeOut.transform(value) * size.width,
child: child,
),
);
},
child: widget.itemBuilder(context, index),
),
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
}

View File

@@ -0,0 +1,159 @@
// Copyright 2020 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:math' as math;
import 'package:flutter/material.dart';
class CurvedAnimationDemo extends StatefulWidget {
const CurvedAnimationDemo({super.key});
static const String routeName = 'misc/curved_animation';
@override
State<CurvedAnimationDemo> createState() => _CurvedAnimationDemoState();
}
class CurveChoice {
final Curve curve;
final String name;
const CurveChoice({required this.curve, required this.name});
}
class _CurvedAnimationDemoState extends State<CurvedAnimationDemo>
with SingleTickerProviderStateMixin {
late final AnimationController controller;
late final Animation<double> animationRotation;
late final Animation<Offset> animationTranslation;
static const _duration = Duration(seconds: 4);
List<CurveChoice> curves = const [
CurveChoice(curve: Curves.bounceIn, name: 'Bounce In'),
CurveChoice(curve: Curves.bounceOut, name: 'Bounce Out'),
CurveChoice(curve: Curves.easeInCubic, name: 'Ease In Cubic'),
CurveChoice(curve: Curves.easeOutCubic, name: 'Ease Out Cubic'),
CurveChoice(curve: Curves.easeInExpo, name: 'Ease In Expo'),
CurveChoice(curve: Curves.easeOutExpo, name: 'Ease Out Expo'),
CurveChoice(curve: Curves.elasticIn, name: 'Elastic In'),
CurveChoice(curve: Curves.elasticOut, name: 'Elastic Out'),
CurveChoice(curve: Curves.easeInQuart, name: 'Ease In Quart'),
CurveChoice(curve: Curves.easeOutQuart, name: 'Ease Out Quart'),
CurveChoice(curve: Curves.easeInCirc, name: 'Ease In Circle'),
CurveChoice(curve: Curves.easeOutCirc, name: 'Ease Out Circle'),
];
late CurveChoice selectedForwardCurve, selectedReverseCurve;
late final CurvedAnimation curvedAnimation;
@override
void initState() {
super.initState();
controller = AnimationController(duration: _duration, vsync: this);
selectedForwardCurve = curves[0];
selectedReverseCurve = curves[0];
curvedAnimation = CurvedAnimation(
parent: controller,
curve: selectedForwardCurve.curve,
reverseCurve: selectedReverseCurve.curve,
);
animationRotation =
Tween<double>(begin: 0, end: 2 * math.pi).animate(curvedAnimation)
..addListener(() {
setState(() {});
})
..addStatusListener((status) {
if (status == AnimationStatus.completed) {
controller.reverse();
}
});
animationTranslation =
Tween<Offset>(
begin: const Offset(-1, 0),
end: const Offset(1, 0),
).animate(curvedAnimation)
..addListener(() {
setState(() {});
})
..addStatusListener((status) {
if (status == AnimationStatus.completed) {
controller.reverse();
}
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Curved Animation')),
body: Column(
children: [
const SizedBox(height: 20.0),
Text(
'Select Curve for forward motion',
style: Theme.of(context).textTheme.titleLarge,
),
DropdownButton<CurveChoice>(
items: curves.map((curve) {
return DropdownMenuItem<CurveChoice>(
value: curve,
child: Text(curve.name),
);
}).toList(),
onChanged: (newCurve) {
if (newCurve != null) {
setState(() {
selectedForwardCurve = newCurve;
curvedAnimation.curve = selectedForwardCurve.curve;
});
}
},
value: selectedForwardCurve,
),
const SizedBox(height: 15.0),
Text(
'Select Curve for reverse motion',
style: Theme.of(context).textTheme.titleLarge,
),
DropdownButton<CurveChoice>(
items: curves.map((curve) {
return DropdownMenuItem<CurveChoice>(
value: curve,
child: Text(curve.name),
);
}).toList(),
onChanged: (newCurve) {
if (newCurve != null) {
setState(() {
selectedReverseCurve = newCurve;
curvedAnimation.reverseCurve = selectedReverseCurve.curve;
});
}
},
value: selectedReverseCurve,
),
const SizedBox(height: 35.0),
Transform.rotate(
angle: animationRotation.value,
child: const Center(child: FlutterLogo(size: 100)),
),
const SizedBox(height: 35.0),
FractionalTranslation(
translation: animationTranslation.value,
child: const FlutterLogo(size: 100),
),
const SizedBox(height: 25.0),
ElevatedButton(
onPressed: () {
controller.forward();
},
child: const Text('Animate'),
),
],
),
);
}
@override
void dispose() {
controller.dispose();
super.dispose();
}
}

View File

@@ -0,0 +1,87 @@
// Copyright 2019 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/material.dart';
class ExpandCardDemo extends StatelessWidget {
const ExpandCardDemo({super.key});
static const String routeName = 'misc/expand_card';
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Expandable Card')),
body: const Center(child: ExpandCard()),
);
}
}
class ExpandCard extends StatefulWidget {
const ExpandCard({super.key});
@override
State<ExpandCard> createState() => _ExpandCardState();
}
class _ExpandCardState extends State<ExpandCard>
with SingleTickerProviderStateMixin {
static const Duration duration = Duration(milliseconds: 300);
bool selected = false;
double get size => selected ? 256 : 128;
void toggleExpanded() {
setState(() {
selected = !selected;
});
}
@override
Widget build(context) {
return GestureDetector(
onTap: () => toggleExpanded(),
child: Card(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: AnimatedContainer(
duration: duration,
width: size,
height: size,
curve: Curves.ease,
child: AnimatedCrossFade(
duration: duration,
firstCurve: Curves.easeInOutCubic,
secondCurve: Curves.easeInOutCubic,
crossFadeState: selected
? CrossFadeState.showSecond
: CrossFadeState.showFirst,
// Use Positioned.fill() to pass the constraints to its children.
// This allows the Images to use BoxFit.cover to cover the correct
// size
layoutBuilder:
(topChild, topChildKey, bottomChild, bottomChildKey) {
return Stack(
children: [
Positioned.fill(
key: bottomChildKey,
child: bottomChild,
),
Positioned.fill(key: topChildKey, child: topChild),
],
);
},
firstChild: Image.asset(
'assets/eat_cape_town_sm.jpg',
fit: BoxFit.cover,
),
secondChild: Image.asset(
'assets/eat_new_orleans_sm.jpg',
fit: BoxFit.cover,
),
),
),
),
),
);
}
}

View File

@@ -0,0 +1,40 @@
// Copyright 2023 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/material.dart';
import 'package:flutter_animate/flutter_animate.dart';
// Demonstrating the `flutter_animate` package
class FlutterAnimateDemo extends StatelessWidget {
static const String routeName = 'misc/flutter_animate';
const FlutterAnimateDemo({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Flutter Animate Demo')),
body: Center(
child: Padding(
padding: const EdgeInsets.all(16),
child:
Text(
"Hello Flutter Animate",
style: Theme.of(context).textTheme.headlineLarge,
)
.animate(onPlay: (controller) => controller.repeat())
.then(delay: 250.ms)
.fadeIn(duration: 500.ms)
.then(delay: 250.ms)
.shimmer(duration: 400.ms)
.then(delay: 250.ms)
.slide()
.then(delay: 250.ms)
.blur(duration: 500.ms)
.then(delay: 100.ms),
),
),
);
}
}

View File

@@ -0,0 +1,114 @@
// Copyright 2019 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/material.dart';
class FocusImageDemo extends StatelessWidget {
const FocusImageDemo({super.key});
static String routeName = 'misc/focus_image';
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Focus Image')),
body: const Grid(),
);
}
}
class Grid extends StatelessWidget {
const Grid({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body: GridView.builder(
itemCount: 40,
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 4,
),
itemBuilder: (context, index) {
return (index >= 20)
? const SmallCard(
imageAssetName: 'assets/eat_cape_town_sm.jpg',
)
: const SmallCard(
imageAssetName: 'assets/eat_new_orleans_sm.jpg',
);
},
),
);
}
}
Route _createRoute(BuildContext parentContext, String image) {
return PageRouteBuilder<void>(
pageBuilder: (context, animation, secondaryAnimation) {
return _SecondPage(image);
},
transitionsBuilder: (context, animation, secondaryAnimation, child) {
var rectAnimation = _createTween(
parentContext,
).chain(CurveTween(curve: Curves.ease)).animate(animation);
return Stack(
children: [
PositionedTransition(rect: rectAnimation, child: child),
],
);
},
);
}
Tween<RelativeRect> _createTween(BuildContext context) {
var windowSize = MediaQuery.of(context).size;
var box = context.findRenderObject() as RenderBox;
var rect = box.localToGlobal(Offset.zero) & box.size;
var relativeRect = RelativeRect.fromSize(rect, windowSize);
return RelativeRectTween(begin: relativeRect, end: RelativeRect.fill);
}
class SmallCard extends StatelessWidget {
const SmallCard({required this.imageAssetName, super.key});
final String imageAssetName;
@override
Widget build(BuildContext context) {
return Card(
child: Material(
child: InkWell(
onTap: () {
var nav = Navigator.of(context);
nav.push<void>(_createRoute(context, imageAssetName));
},
child: Image.asset(imageAssetName, fit: BoxFit.cover),
),
),
);
}
}
class _SecondPage extends StatelessWidget {
final String imageAssetName;
const _SecondPage(this.imageAssetName);
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
body: Center(
child: Material(
child: InkWell(
onTap: () => Navigator.of(context).pop(),
child: AspectRatio(
aspectRatio: 1,
child: Image.asset(imageAssetName, fit: BoxFit.cover),
),
),
),
),
);
}
}

View File

@@ -0,0 +1,66 @@
// Copyright 2019 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/material.dart';
class HeroAnimationDemo extends StatelessWidget {
const HeroAnimationDemo({super.key});
static const String routeName = 'misc/hero_animation';
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Hero Animation')),
body: GestureDetector(
child: Hero(
tag: 'hero-page-child',
child: _createHeroContainer(
size: 50.0,
color: Colors.grey.shade300,
),
),
onTap: () =>
Navigator.of(
context,
).push<void>(
MaterialPageRoute(builder: (context) => const HeroPage()),
),
),
);
}
}
class HeroPage extends StatelessWidget {
const HeroPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.lightBlue,
appBar: AppBar(),
body: Center(
child: Hero(
tag: 'hero-page-child',
child: _createHeroContainer(size: 100.0, color: Colors.white),
),
),
);
}
}
StatelessWidget _createHeroContainer({
required double size,
required Color color,
}) {
return Container(
height: size,
width: size,
padding: const EdgeInsets.all(10.0),
margin: size < 100.0
? const EdgeInsets.all(10.0)
: const EdgeInsets.all(0),
decoration: BoxDecoration(shape: BoxShape.circle, color: color),
child: const FlutterLogo(),
);
}

View File

@@ -0,0 +1,12 @@
export 'animated_list.dart';
export 'animated_positioned.dart';
export 'animated_switcher.dart';
export 'card_swipe.dart';
export 'carousel.dart';
export 'curved_animation.dart';
export 'expand_card.dart';
export 'flutter_animate.dart';
export 'focus_image.dart';
export 'hero_animation.dart';
export 'physics_card_drag.dart';
export 'repeating_animation.dart';

View File

@@ -0,0 +1,111 @@
// Copyright 2019 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/material.dart';
import 'package:flutter/physics.dart';
class PhysicsCardDragDemo extends StatelessWidget {
const PhysicsCardDragDemo({super.key});
static const String routeName = 'misc/physics_card';
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Spring Physics')),
body: const DraggableCard(child: FlutterLogo(size: 128)),
);
}
}
/// A draggable card that moves back to [Alignment.center] when it's
/// released.
class DraggableCard extends StatefulWidget {
const DraggableCard({required this.child, super.key});
final Widget child;
@override
State<DraggableCard> createState() => _DraggableCardState();
}
class _DraggableCardState extends State<DraggableCard>
with SingleTickerProviderStateMixin {
late final AnimationController _controller;
/// The alignment of the card as it is dragged or being animated.
///
/// While the card is being dragged, this value is set to the values computed
/// in the GestureDetector onPanUpdate callback. If the animation is running,
/// this value is set to the value of the [_animation].
var _dragAlignment = Alignment.center;
late Animation<Alignment> _animation;
final _spring = const SpringDescription(
mass: 10,
stiffness: 1000,
damping: 0.7,
);
/// Calculate the velocity relative to the unit interval, [0,1],
/// used by the animation controller.
double _normalizeVelocity(Offset velocity, Size size) {
final normalizedVelocity = Offset(
velocity.dx / size.width,
velocity.dy / size.height,
);
// Returning negative implies dragging away from center
return -normalizedVelocity.distance;
}
/// Calculates and runs a [SpringSimulation]
void _runAnimation(Offset velocity, Size size) {
_animation = _controller.drive(
AlignmentTween(begin: _dragAlignment, end: Alignment.center),
);
final simulation = SpringSimulation(
_spring,
0,
1,
_normalizeVelocity(velocity, size),
);
_controller.animateWith(simulation);
}
@override
void initState() {
super.initState();
_controller = AnimationController.unbounded(vsync: this)
..addListener(
() => setState(() => _dragAlignment = _animation.value),
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final size = MediaQuery.of(context).size;
return GestureDetector(
onPanStart: (details) => _controller.stop(canceled: true),
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),
),
);
}
}

View File

@@ -0,0 +1,63 @@
// Copyright 2019 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/material.dart';
class RepeatingAnimationDemo extends StatefulWidget {
const RepeatingAnimationDemo({super.key});
static String routeName = 'misc/repeating_animation';
@override
State<RepeatingAnimationDemo> createState() =>
_RepeatingAnimationDemoState();
}
class _RepeatingAnimationDemoState extends State<RepeatingAnimationDemo>
with SingleTickerProviderStateMixin {
late final AnimationController _controller;
late final Animation<BorderRadius?> _borderRadius;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(seconds: 2),
vsync: this,
)..repeat(reverse: true);
_borderRadius = BorderRadiusTween(
begin: BorderRadius.circular(100.0),
end: BorderRadius.circular(0.0),
).animate(_controller);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Repeating Animation')),
body: Center(
child: AnimatedBuilder(
animation: _borderRadius,
builder: (context, child) {
return Container(
width: 200,
height: 200,
decoration: BoxDecoration(
color: Colors.deepPurple,
borderRadius: _borderRadius.value,
),
);
},
),
),
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
}