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
|
// common animations
|
||||||
final Duration expansionTileAnimation;
|
final Duration expansionTileAnimation;
|
||||||
final Duration formTransition;
|
final Duration formTransition;
|
||||||
|
final Duration formTextStyleTransition;
|
||||||
final Duration chartTransition;
|
final Duration chartTransition;
|
||||||
final Duration iconAnimation;
|
final Duration iconAnimation;
|
||||||
final Duration staggeredAnimation;
|
final Duration staggeredAnimation;
|
||||||
|
@ -112,6 +113,7 @@ class DurationsData {
|
||||||
const DurationsData({
|
const DurationsData({
|
||||||
this.expansionTileAnimation = const Duration(milliseconds: 200),
|
this.expansionTileAnimation = const Duration(milliseconds: 200),
|
||||||
this.formTransition = const Duration(milliseconds: 200),
|
this.formTransition = const Duration(milliseconds: 200),
|
||||||
|
this.formTextStyleTransition = const Duration(milliseconds: 800),
|
||||||
this.chartTransition = const Duration(milliseconds: 400),
|
this.chartTransition = const Duration(milliseconds: 400),
|
||||||
this.iconAnimation = const Duration(milliseconds: 300),
|
this.iconAnimation = const Duration(milliseconds: 300),
|
||||||
this.staggeredAnimation = const Duration(milliseconds: 375),
|
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
|
// as of Flutter v2.5.1, `ExpansionPanelList` throws if animation duration is zero
|
||||||
expansionTileAnimation: const Duration(microseconds: 1),
|
expansionTileAnimation: const Duration(microseconds: 1),
|
||||||
formTransition: Duration.zero,
|
formTransition: Duration.zero,
|
||||||
|
formTextStyleTransition: Duration.zero,
|
||||||
chartTransition: Duration.zero,
|
chartTransition: Duration.zero,
|
||||||
iconAnimation: Duration.zero,
|
iconAnimation: Duration.zero,
|
||||||
staggeredAnimation: 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(
|
child: Stack(
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
children: [
|
children: [
|
||||||
Circle(
|
_Circle(
|
||||||
radius: widget.radius,
|
radius: widget.radius,
|
||||||
lineWidth: widget.lineWidth,
|
lineWidth: widget.lineWidth,
|
||||||
percent: 1.0,
|
percent: 1.0,
|
||||||
color: widget.background,
|
color: widget.background,
|
||||||
),
|
),
|
||||||
Circle(
|
_Circle(
|
||||||
radius: widget.radius,
|
radius: widget.radius,
|
||||||
lineWidth: widget.lineWidth,
|
lineWidth: widget.lineWidth,
|
||||||
percent: widget.percent,
|
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 double radius, lineWidth, percent;
|
||||||
final Color color;
|
final Color color;
|
||||||
|
|
||||||
const Circle({
|
const _Circle({
|
||||||
super.key,
|
super.key,
|
||||||
required this.radius,
|
required this.radius,
|
||||||
required this.lineWidth,
|
required this.lineWidth,
|
||||||
|
|
|
@ -33,7 +33,7 @@ class _MultiCrossFaderState extends State<MultiCrossFader> {
|
||||||
@override
|
@override
|
||||||
void didUpdateWidget(covariant MultiCrossFader oldWidget) {
|
void didUpdateWidget(covariant MultiCrossFader oldWidget) {
|
||||||
super.didUpdateWidget(oldWidget);
|
super.didUpdateWidget(oldWidget);
|
||||||
if (_first == oldWidget.child) {
|
if (oldWidget.child == _first) {
|
||||||
_second = widget.child;
|
_second = widget.child;
|
||||||
_fadeState = CrossFadeState.showSecond;
|
_fadeState = CrossFadeState.showSecond;
|
||||||
} else {
|
} 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
|
@override
|
||||||
void didUpdateWidget(covariant SearchPage oldWidget) {
|
void didUpdateWidget(covariant SearchPage oldWidget) {
|
||||||
super.didUpdateWidget(oldWidget);
|
super.didUpdateWidget(oldWidget);
|
||||||
if (widget.delegate != oldWidget.delegate) {
|
if (oldWidget.delegate != widget.delegate) {
|
||||||
_unregisterWidget(oldWidget);
|
_unregisterWidget(oldWidget);
|
||||||
_registerWidget(widget);
|
_registerWidget(widget);
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ import 'package:aves/theme/icons.dart';
|
||||||
import 'package:aves/theme/themes.dart';
|
import 'package:aves/theme/themes.dart';
|
||||||
import 'package:aves/widgets/common/basic/text_dropdown_button.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/extensions/build_context.dart';
|
||||||
|
import 'package:aves/widgets/common/identity/aves_caption.dart';
|
||||||
import 'package:aves/widgets/common/identity/highlight_title.dart';
|
import 'package:aves/widgets/common/identity/highlight_title.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
@ -76,12 +77,7 @@ class _TileViewDialogState<S, G, L> extends State<TileViewDialog<S, G, L>> with
|
||||||
_selectedSort = v;
|
_selectedSort = v;
|
||||||
_reverseSort = false;
|
_reverseSort = false;
|
||||||
},
|
},
|
||||||
bottom: _selectedSort != null
|
bottom: _selectedSort != null ? AvesCaption(widget.sortOrder(_selectedSort as S, _reverseSort)) : null,
|
||||||
? Text(
|
|
||||||
widget.sortOrder(_selectedSort as S, _reverseSort),
|
|
||||||
style: Theme.of(context).textTheme.caption,
|
|
||||||
)
|
|
||||||
: null,
|
|
||||||
),
|
),
|
||||||
AnimatedSwitcher(
|
AnimatedSwitcher(
|
||||||
duration: context.read<DurationsData>().formTransition,
|
duration: context.read<DurationsData>().formTransition,
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import 'package:aves/model/settings/settings.dart';
|
import 'package:aves/model/settings/settings.dart';
|
||||||
import 'package:aves/theme/durations.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:aves/widgets/dialogs/aves_selection_dialog.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
@ -103,7 +104,7 @@ class SettingsSelectionListTile<T extends Enum> extends StatelessWidget {
|
||||||
selector: selector,
|
selector: selector,
|
||||||
builder: (context, current, child) => ListTile(
|
builder: (context, current, child) => ListTile(
|
||||||
title: Text(tileTitle),
|
title: Text(tileTitle),
|
||||||
subtitle: Text(getName(context, current)),
|
subtitle: AvesCaption(getName(context, current)),
|
||||||
onTap: () => showSelectionDialog<T>(
|
onTap: () => showSelectionDialog<T>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => AvesSelectionDialog<T>(
|
builder: (context) => AvesSelectionDialog<T>(
|
||||||
|
|
|
@ -25,7 +25,7 @@ class _CrumbLineState extends State<CrumbLine> {
|
||||||
@override
|
@override
|
||||||
void didUpdateWidget(covariant CrumbLine oldWidget) {
|
void didUpdateWidget(covariant CrumbLine oldWidget) {
|
||||||
super.didUpdateWidget(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
|
// scroll to show last crumb
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
final extent = _controller.position.maxScrollExtent;
|
final extent = _controller.position.maxScrollExtent;
|
||||||
|
|
Loading…
Reference in a new issue