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
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:
201
animations/lib/main.dart
Normal file
201
animations/lib/main.dart
Normal 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}');
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
78
animations/lib/src/basics/animated_builder.dart
Normal file
78
animations/lib/src/basics/animated_builder.dart
Normal 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),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
80
animations/lib/src/basics/animated_container.dart
Normal file
80
animations/lib/src/basics/animated_container.dart
Normal 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(),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
86
animations/lib/src/basics/animation_controller.dart
Normal file
86
animations/lib/src/basics/animation_controller.dart
Normal 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();
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
8
animations/lib/src/basics/basics.dart
Normal file
8
animations/lib/src/basics/basics.dart
Normal 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';
|
||||
123
animations/lib/src/basics/custom_tween.dart
Normal file
123
animations/lib/src/basics/custom_tween.dart
Normal 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?
|
||||
''';
|
||||
73
animations/lib/src/basics/fade_transition.dart
Normal file
73
animations/lib/src/basics/fade_transition.dart
Normal 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),
|
||||
);
|
||||
}),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
58
animations/lib/src/basics/page_route_builder.dart
Normal file
58
animations/lib/src/basics/page_route_builder.dart
Normal 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,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
85
animations/lib/src/basics/tween_sequence.dart
Normal file
85
animations/lib/src/basics/tween_sequence.dart
Normal 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),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
76
animations/lib/src/basics/tweens.dart
Normal file
76
animations/lib/src/basics/tweens.dart
Normal 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();
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
107
animations/lib/src/misc/animated_list.dart
Normal file
107
animations/lib/src/misc/animated_list.dart
Normal 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;
|
||||
84
animations/lib/src/misc/animated_positioned.dart
Normal file
84
animations/lib/src/misc/animated_positioned.dart
Normal 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,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
70
animations/lib/src/misc/animated_switcher.dart
Normal file
70
animations/lib/src/misc/animated_switcher.dart
Normal 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),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
212
animations/lib/src/misc/card_swipe.dart
Normal file
212
animations/lib/src/misc/card_swipe.dart
Normal 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();
|
||||
}
|
||||
}
|
||||
115
animations/lib/src/misc/carousel.dart
Normal file
115
animations/lib/src/misc/carousel.dart
Normal 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();
|
||||
}
|
||||
}
|
||||
159
animations/lib/src/misc/curved_animation.dart
Normal file
159
animations/lib/src/misc/curved_animation.dart
Normal 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();
|
||||
}
|
||||
}
|
||||
87
animations/lib/src/misc/expand_card.dart
Normal file
87
animations/lib/src/misc/expand_card.dart
Normal 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,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
40
animations/lib/src/misc/flutter_animate.dart
Normal file
40
animations/lib/src/misc/flutter_animate.dart
Normal 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),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
114
animations/lib/src/misc/focus_image.dart
Normal file
114
animations/lib/src/misc/focus_image.dart
Normal 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),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
66
animations/lib/src/misc/hero_animation.dart
Normal file
66
animations/lib/src/misc/hero_animation.dart
Normal 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(),
|
||||
);
|
||||
}
|
||||
12
animations/lib/src/misc/misc.dart
Normal file
12
animations/lib/src/misc/misc.dart
Normal 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';
|
||||
111
animations/lib/src/misc/physics_card_drag.dart
Normal file
111
animations/lib/src/misc/physics_card_drag.dart
Normal 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),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
63
animations/lib/src/misc/repeating_animation.dart
Normal file
63
animations/lib/src/misc/repeating_animation.dart
Normal 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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user