caption change effect
This commit is contained in:
parent
b4fe7781dc
commit
6005f016e3
9 changed files with 142 additions and 14 deletions
|
@ -96,6 +96,7 @@ class DurationsData {
|
|||
// common animations
|
||||
final Duration expansionTileAnimation;
|
||||
final Duration formTransition;
|
||||
final Duration formTextStyleTransition;
|
||||
final Duration chartTransition;
|
||||
final Duration iconAnimation;
|
||||
final Duration staggeredAnimation;
|
||||
|
@ -112,6 +113,7 @@ class DurationsData {
|
|||
const DurationsData({
|
||||
this.expansionTileAnimation = const Duration(milliseconds: 200),
|
||||
this.formTransition = const Duration(milliseconds: 200),
|
||||
this.formTextStyleTransition = const Duration(milliseconds: 800),
|
||||
this.chartTransition = const Duration(milliseconds: 400),
|
||||
this.iconAnimation = const Duration(milliseconds: 300),
|
||||
this.staggeredAnimation = const Duration(milliseconds: 375),
|
||||
|
@ -126,6 +128,7 @@ class DurationsData {
|
|||
// as of Flutter v2.5.1, `ExpansionPanelList` throws if animation duration is zero
|
||||
expansionTileAnimation: const Duration(microseconds: 1),
|
||||
formTransition: Duration.zero,
|
||||
formTextStyleTransition: Duration.zero,
|
||||
chartTransition: Duration.zero,
|
||||
iconAnimation: Duration.zero,
|
||||
staggeredAnimation: Duration.zero,
|
||||
|
|
86
lib/widgets/common/basic/animated_text.dart
Normal file
86
lib/widgets/common/basic/animated_text.dart
Normal file
|
@ -0,0 +1,86 @@
|
|||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class AnimatedText extends StatefulWidget {
|
||||
final String data;
|
||||
final TextStyle style, changedStyle;
|
||||
final Curve curve;
|
||||
final Duration duration;
|
||||
|
||||
const AnimatedText(
|
||||
this.data, {
|
||||
super.key,
|
||||
required this.style,
|
||||
required this.changedStyle,
|
||||
this.curve = Curves.linear,
|
||||
required this.duration,
|
||||
});
|
||||
|
||||
@override
|
||||
State<AnimatedText> createState() => _AnimatedTextState();
|
||||
}
|
||||
|
||||
class _AnimatedTextState extends State<AnimatedText> with SingleTickerProviderStateMixin {
|
||||
late final AnimationController _controller;
|
||||
late final Animation<TextStyle> _style;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_controller = AnimationController(
|
||||
duration: widget.duration,
|
||||
vsync: this,
|
||||
)
|
||||
..value = 1
|
||||
..addListener(() => setState(() {}));
|
||||
_style = _ShadowedTextStyleTween(begin: widget.changedStyle, end: widget.style).animate(CurvedAnimation(
|
||||
parent: _controller,
|
||||
curve: widget.curve,
|
||||
));
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(AnimatedText oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (oldWidget.data != widget.data) {
|
||||
_controller
|
||||
..value = 0
|
||||
..forward();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Text(
|
||||
widget.data,
|
||||
style: _style.value,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _ShadowedTextStyleTween extends Tween<TextStyle> {
|
||||
_ShadowedTextStyleTween({super.begin, super.end});
|
||||
|
||||
@override
|
||||
TextStyle lerp(double t) {
|
||||
final textStyle = TextStyle.lerp(begin, end, t)!;
|
||||
final beginShadows = begin!.shadows;
|
||||
final endShadows = end!.shadows;
|
||||
if (beginShadows != null && endShadows != null && beginShadows.length == endShadows.length) {
|
||||
return textStyle.copyWith(
|
||||
shadows: beginShadows.mapIndexed((i, a) {
|
||||
final b = endShadows[i];
|
||||
return Shadow.lerp(a, b, t)!;
|
||||
}).toList(),
|
||||
);
|
||||
} else {
|
||||
return textStyle;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -29,13 +29,13 @@ class _CircularIndicatorState extends State<CircularIndicator> {
|
|||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
Circle(
|
||||
_Circle(
|
||||
radius: widget.radius,
|
||||
lineWidth: widget.lineWidth,
|
||||
percent: 1.0,
|
||||
color: widget.background,
|
||||
),
|
||||
Circle(
|
||||
_Circle(
|
||||
radius: widget.radius,
|
||||
lineWidth: widget.lineWidth,
|
||||
percent: widget.percent,
|
||||
|
@ -48,11 +48,11 @@ class _CircularIndicatorState extends State<CircularIndicator> {
|
|||
}
|
||||
}
|
||||
|
||||
class Circle extends StatelessWidget {
|
||||
class _Circle extends StatelessWidget {
|
||||
final double radius, lineWidth, percent;
|
||||
final Color color;
|
||||
|
||||
const Circle({
|
||||
const _Circle({
|
||||
super.key,
|
||||
required this.radius,
|
||||
required this.lineWidth,
|
||||
|
|
|
@ -33,7 +33,7 @@ class _MultiCrossFaderState extends State<MultiCrossFader> {
|
|||
@override
|
||||
void didUpdateWidget(covariant MultiCrossFader oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (_first == oldWidget.child) {
|
||||
if (oldWidget.child == _first) {
|
||||
_second = widget.child;
|
||||
_fadeState = CrossFadeState.showSecond;
|
||||
} else {
|
||||
|
|
42
lib/widgets/common/identity/aves_caption.dart
Normal file
42
lib/widgets/common/identity/aves_caption.dart
Normal file
|
@ -0,0 +1,42 @@
|
|||
import 'package:aves/theme/durations.dart';
|
||||
import 'package:aves/widgets/common/basic/animated_text.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class AvesCaption extends StatelessWidget {
|
||||
final String data;
|
||||
|
||||
const AvesCaption(
|
||||
this.data, {
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final subtitleStyle = theme.textTheme.caption!;
|
||||
final subtitleChangeShadowColor = theme.colorScheme.onPrimary;
|
||||
return AnimatedText(
|
||||
// provide key to refresh on theme brightness change
|
||||
key: ValueKey(subtitleChangeShadowColor),
|
||||
data,
|
||||
style: subtitleStyle.copyWith(
|
||||
shadows: [
|
||||
Shadow(
|
||||
color: subtitleChangeShadowColor.withOpacity(0),
|
||||
blurRadius: 0,
|
||||
)
|
||||
],
|
||||
),
|
||||
changedStyle: subtitleStyle.copyWith(
|
||||
shadows: [
|
||||
Shadow(
|
||||
color: subtitleChangeShadowColor,
|
||||
blurRadius: 3,
|
||||
)
|
||||
],
|
||||
),
|
||||
duration: context.read<DurationsData>().formTextStyleTransition,
|
||||
);
|
||||
}
|
||||
}
|
|
@ -38,7 +38,7 @@ class _SearchPageState extends State<SearchPage> {
|
|||
@override
|
||||
void didUpdateWidget(covariant SearchPage oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (widget.delegate != oldWidget.delegate) {
|
||||
if (oldWidget.delegate != widget.delegate) {
|
||||
_unregisterWidget(oldWidget);
|
||||
_registerWidget(widget);
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ import 'package:aves/theme/icons.dart';
|
|||
import 'package:aves/theme/themes.dart';
|
||||
import 'package:aves/widgets/common/basic/text_dropdown_button.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:aves/widgets/common/identity/aves_caption.dart';
|
||||
import 'package:aves/widgets/common/identity/highlight_title.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
@ -76,12 +77,7 @@ class _TileViewDialogState<S, G, L> extends State<TileViewDialog<S, G, L>> with
|
|||
_selectedSort = v;
|
||||
_reverseSort = false;
|
||||
},
|
||||
bottom: _selectedSort != null
|
||||
? Text(
|
||||
widget.sortOrder(_selectedSort as S, _reverseSort),
|
||||
style: Theme.of(context).textTheme.caption,
|
||||
)
|
||||
: null,
|
||||
bottom: _selectedSort != null ? AvesCaption(widget.sortOrder(_selectedSort as S, _reverseSort)) : null,
|
||||
),
|
||||
AnimatedSwitcher(
|
||||
duration: context.read<DurationsData>().formTransition,
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import 'package:aves/model/settings/settings.dart';
|
||||
import 'package:aves/theme/durations.dart';
|
||||
import 'package:aves/widgets/common/identity/aves_caption.dart';
|
||||
import 'package:aves/widgets/dialogs/aves_selection_dialog.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
@ -103,7 +104,7 @@ class SettingsSelectionListTile<T extends Enum> extends StatelessWidget {
|
|||
selector: selector,
|
||||
builder: (context, current, child) => ListTile(
|
||||
title: Text(tileTitle),
|
||||
subtitle: Text(getName(context, current)),
|
||||
subtitle: AvesCaption(getName(context, current)),
|
||||
onTap: () => showSelectionDialog<T>(
|
||||
context: context,
|
||||
builder: (context) => AvesSelectionDialog<T>(
|
||||
|
|
|
@ -25,7 +25,7 @@ class _CrumbLineState extends State<CrumbLine> {
|
|||
@override
|
||||
void didUpdateWidget(covariant CrumbLine oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (widget.directory.relativeDir.length > oldWidget.directory.relativeDir.length) {
|
||||
if (oldWidget.directory.relativeDir.length < widget.directory.relativeDir.length) {
|
||||
// scroll to show last crumb
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
final extent = _controller.position.maxScrollExtent;
|
||||
|
|
Loading…
Reference in a new issue