mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00

This auto-formats all *.dart files in the repository outside of the `engine` subdirectory and enforces that these files stay formatted with a presubmit check. **Reviewers:** Please carefully review all the commits except for the one titled "formatted". The "formatted" commit was auto-generated by running `dev/tools/format.sh -a -f`. The other commits were hand-crafted to prepare the repo for the formatting change. I recommend reviewing the commits one-by-one via the "Commits" tab and avoiding Github's "Files changed" tab as it will likely slow down your browser because of the size of this PR. --------- Co-authored-by: Kate Lovett <katelovett@google.com> Co-authored-by: LongCatIsLooong <31859944+LongCatIsLooong@users.noreply.github.com>
193 lines
6.1 KiB
Dart
193 lines
6.1 KiB
Dart
// Copyright 2014 The Flutter Authors. 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';
|
|
|
|
/// An example of [AnimationController] and [SlideTransition].
|
|
|
|
// Occupies the same width as the widest single digit used by AnimatedDigit.
|
|
//
|
|
// By stacking this widget behind AnimatedDigit's visible digit, we
|
|
// ensure that AnimatedWidget's width will not change when its value
|
|
// changes. Typically digits like '8' or '9' are wider than '1'. If
|
|
// an app arranges several AnimatedDigits in a centered Row, we don't
|
|
// want the Row to wiggle when the digits change because the overall
|
|
// width of the Row changes.
|
|
class _PlaceholderDigit extends StatelessWidget {
|
|
const _PlaceholderDigit();
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final TextStyle textStyle = Theme.of(
|
|
context,
|
|
).textTheme.displayLarge!.copyWith(fontWeight: FontWeight.w500);
|
|
|
|
final Iterable<Widget> placeholderDigits = <int>[0, 1, 2, 3, 4, 5, 6, 7, 8, 9].map<Widget>((
|
|
int n,
|
|
) {
|
|
return Text('$n', style: textStyle);
|
|
});
|
|
|
|
return Opacity(opacity: 0, child: Stack(children: placeholderDigits.toList()));
|
|
}
|
|
}
|
|
|
|
// Displays a single digit [value].
|
|
//
|
|
// When the value changes the old value slides upwards and out of sight
|
|
// at the same as the new value slides into view.
|
|
class AnimatedDigit extends StatefulWidget {
|
|
const AnimatedDigit({super.key, required this.value});
|
|
|
|
final int value;
|
|
|
|
@override
|
|
State<AnimatedDigit> createState() => _AnimatedDigitState();
|
|
}
|
|
|
|
class _AnimatedDigitState extends State<AnimatedDigit> with SingleTickerProviderStateMixin {
|
|
static const Duration defaultDuration = Duration(milliseconds: 300);
|
|
|
|
late final AnimationController controller;
|
|
late int incomingValue;
|
|
late int outgoingValue;
|
|
List<int> pendingValues =
|
|
<int>[]; // widget.value updates that occurred while the animation is underway
|
|
Duration duration = defaultDuration;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
controller = AnimationController(duration: duration, vsync: this);
|
|
controller.addStatusListener(handleAnimationCompleted);
|
|
incomingValue = widget.value;
|
|
outgoingValue = widget.value;
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
controller.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
void handleAnimationCompleted(AnimationStatus status) {
|
|
if (status.isCompleted) {
|
|
if (pendingValues.isNotEmpty) {
|
|
// Display the next pending value. The duration was scaled down
|
|
// in didUpdateWidget by the total number of pending values so
|
|
// that all of the pending changes are shown within
|
|
// defaultDuration of the last one (the past pending change).
|
|
controller.duration = duration;
|
|
animateValueUpdate(incomingValue, pendingValues.removeAt(0));
|
|
} else {
|
|
controller.duration = defaultDuration;
|
|
}
|
|
}
|
|
}
|
|
|
|
void animateValueUpdate(int outgoing, int incoming) {
|
|
setState(() {
|
|
outgoingValue = outgoing;
|
|
incomingValue = incoming;
|
|
controller.forward(from: 0);
|
|
});
|
|
}
|
|
|
|
// Rebuilding the widget with a new value causes the animations to run.
|
|
// If the widget is updated while the value is being changed the new
|
|
// value is added to pendingValues and is taken care of when the current
|
|
// animation is complete (see handleAnimationCompleted()).
|
|
@override
|
|
void didUpdateWidget(AnimatedDigit oldWidget) {
|
|
super.didUpdateWidget(oldWidget);
|
|
if (widget.value != oldWidget.value) {
|
|
if (controller.isAnimating) {
|
|
// We're in the middle of animating outgoingValue out and
|
|
// incomingValue in. Shorten the duration of the current
|
|
// animation as well as the duration for animations that
|
|
// will show the pending values.
|
|
pendingValues.add(widget.value);
|
|
final double percentRemaining = 1 - controller.value;
|
|
duration = defaultDuration * (1 / (percentRemaining + pendingValues.length));
|
|
controller.animateTo(1.0, duration: duration * percentRemaining);
|
|
} else {
|
|
animateValueUpdate(incomingValue, widget.value);
|
|
}
|
|
}
|
|
}
|
|
|
|
// When the controller runs forward both SlideTransitions' children
|
|
// animate upwards. This takes the outgoingValue out of sight and the
|
|
// incoming value into view. See animateValueUpdate().
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final TextStyle textStyle = Theme.of(context).textTheme.displayLarge!;
|
|
return ClipRect(
|
|
child: Stack(
|
|
children: <Widget>[
|
|
const _PlaceholderDigit(),
|
|
SlideTransition(
|
|
position: controller.drive(
|
|
Tween<Offset>(
|
|
begin: Offset.zero,
|
|
end: const Offset(0, -1), // Out of view above the top.
|
|
),
|
|
),
|
|
child: Text(key: ValueKey<int>(outgoingValue), '$outgoingValue', style: textStyle),
|
|
),
|
|
SlideTransition(
|
|
position: controller.drive(
|
|
Tween<Offset>(
|
|
begin: const Offset(0, 1), // Out of view below the bottom.
|
|
end: Offset.zero,
|
|
),
|
|
),
|
|
child: Text(key: ValueKey<int>(incomingValue), '$incomingValue', style: textStyle),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class AnimatedDigitApp extends StatelessWidget {
|
|
const AnimatedDigitApp({super.key});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return const MaterialApp(title: 'AnimatedDigit', home: AnimatedDigitHome());
|
|
}
|
|
}
|
|
|
|
class AnimatedDigitHome extends StatefulWidget {
|
|
const AnimatedDigitHome({super.key});
|
|
|
|
@override
|
|
State<AnimatedDigitHome> createState() => _AnimatedDigitHomeState();
|
|
}
|
|
|
|
class _AnimatedDigitHomeState extends State<AnimatedDigitHome> {
|
|
int value = 0;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Scaffold(
|
|
body: Center(child: AnimatedDigit(value: value % 10)),
|
|
floatingActionButton: FloatingActionButton(
|
|
onPressed: () {
|
|
setState(() {
|
|
value += 1;
|
|
});
|
|
},
|
|
tooltip: 'Increment Digit',
|
|
child: const Icon(Icons.add),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
void main() {
|
|
runApp(const AnimatedDigitApp());
|
|
}
|