mirror of
https://ghfast.top/https://github.com/StarCitizenToolBox/app.git
synced 2025-06-28 06:44:45 +08:00
feat: 42kit Nav
feat: Animation Optimization
This commit is contained in:
@ -1,5 +1,8 @@
|
||||
import 'package:extended_image/extended_image.dart';
|
||||
import 'package:fluent_ui/fluent_ui.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
|
||||
import 'cache_svg_image.dart';
|
||||
|
||||
class CacheNetImage extends StatelessWidget {
|
||||
final String url;
|
||||
@ -7,11 +10,18 @@ class CacheNetImage extends StatelessWidget {
|
||||
final double? height;
|
||||
final BoxFit? fit;
|
||||
|
||||
const CacheNetImage(
|
||||
{super.key, required this.url, this.width, this.height, this.fit});
|
||||
const CacheNetImage({super.key, required this.url, this.width, this.height, this.fit});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (url.endsWith(".svg")) {
|
||||
return CachedSvgImage(
|
||||
url,
|
||||
width: width,
|
||||
height: height,
|
||||
fit: fit ?? BoxFit.contain,
|
||||
);
|
||||
}
|
||||
return ExtendedImage.network(
|
||||
url,
|
||||
width: width,
|
||||
@ -20,14 +30,11 @@ class CacheNetImage extends StatelessWidget {
|
||||
loadStateChanged: (ExtendedImageState state) {
|
||||
switch (state.extendedImageLoadState) {
|
||||
case LoadState.loading:
|
||||
return const Center(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(8.0),
|
||||
child: Column(
|
||||
children: [
|
||||
ProgressRing(),
|
||||
],
|
||||
),
|
||||
return SizedBox(
|
||||
width: width,
|
||||
height: height,
|
||||
child: Center(
|
||||
child: ProgressRing(),
|
||||
),
|
||||
);
|
||||
case LoadState.failed:
|
||||
|
64
lib/widgets/src/cache_svg_image.dart
Normal file
64
lib/widgets/src/cache_svg_image.dart
Normal file
@ -0,0 +1,64 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:fluent_ui/fluent_ui.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:starcitizen_doctor/common/utils/file_cache_utils.dart';
|
||||
|
||||
class CachedSvgImage extends HookWidget {
|
||||
final String url;
|
||||
final double? width;
|
||||
final double? height;
|
||||
final BoxFit? fit;
|
||||
|
||||
const CachedSvgImage(this.url, {super.key, this.width, this.height, this.fit});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final cachedFile = useState<File?>(null);
|
||||
final errorInfo = useState<String?>(null);
|
||||
|
||||
useEffect(
|
||||
() {
|
||||
() async {
|
||||
try {
|
||||
cachedFile.value = await FileCacheUtils.getFile(url);
|
||||
} catch (e) {
|
||||
debugPrint("Error loading SVG: $e");
|
||||
errorInfo.value = "Error loading SVG: $e";
|
||||
}
|
||||
}();
|
||||
return null;
|
||||
},
|
||||
[url],
|
||||
);
|
||||
|
||||
if (errorInfo.value != null) {
|
||||
return SizedBox(
|
||||
width: width,
|
||||
height: height,
|
||||
child: Center(
|
||||
child: Text(
|
||||
errorInfo.value!,
|
||||
style: TextStyle(color: Colors.red),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return cachedFile.value != null
|
||||
? SvgPicture.file(
|
||||
cachedFile.value!,
|
||||
width: width,
|
||||
height: height,
|
||||
fit: fit ?? BoxFit.contain,
|
||||
)
|
||||
: SizedBox(
|
||||
width: width,
|
||||
height: height,
|
||||
child: Center(
|
||||
child: ProgressRing(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
71
lib/widgets/src/grid_item_animator.dart
Normal file
71
lib/widgets/src/grid_item_animator.dart
Normal file
@ -0,0 +1,71 @@
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
|
||||
class GridItemAnimator extends HookWidget {
|
||||
final Widget child; // 要添加动画效果的子组件
|
||||
final int index; // 条目在网格中的索引位置
|
||||
final Duration duration; // 动画持续时间
|
||||
final Duration delayPerItem; // 每个条目之间的延迟时间
|
||||
final double slideOffset; // 上移的偏移量(像素)
|
||||
|
||||
const GridItemAnimator({
|
||||
super.key,
|
||||
required this.child,
|
||||
required this.index,
|
||||
this.duration = const Duration(milliseconds: 230),
|
||||
this.delayPerItem = const Duration(milliseconds: 50),
|
||||
this.slideOffset = 20.0,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// 创建动画控制器
|
||||
final animationController = useAnimationController(
|
||||
duration: duration,
|
||||
);
|
||||
|
||||
// 创建不透明度动画
|
||||
final opacityAnimation = useAnimation(
|
||||
Tween<double>(
|
||||
begin: 0.0, // 开始时完全透明
|
||||
end: 1.0, // 结束时完全不透明
|
||||
).animate(CurvedAnimation(
|
||||
parent: animationController,
|
||||
curve: Curves.easeOut,
|
||||
)),
|
||||
);
|
||||
|
||||
// 创建位移动画
|
||||
final slideAnimation = useAnimation(
|
||||
Tween<double>(
|
||||
begin: 1.0, // 开始位置
|
||||
end: 0.0, // 结束位置
|
||||
).animate(CurvedAnimation(
|
||||
parent: animationController,
|
||||
curve: Curves.easeOutCubic,
|
||||
)),
|
||||
);
|
||||
|
||||
// 组件挂载后启动动画
|
||||
useEffect(() {
|
||||
// 根据索引计算延迟时间,实现逐个条目入场
|
||||
final delay = delayPerItem * index;
|
||||
|
||||
Future.delayed(delay, () {
|
||||
if (animationController.status != AnimationStatus.completed) {
|
||||
animationController.forward();
|
||||
}
|
||||
});
|
||||
return null;
|
||||
}, const []);
|
||||
|
||||
// 应用动画效果
|
||||
return Opacity(
|
||||
opacity: opacityAnimation,
|
||||
child: Transform.translate(
|
||||
offset: Offset(0, slideOffset * slideAnimation), // 向上平移动画
|
||||
child: child,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -15,6 +15,9 @@ import 'dart:ui' as ui;
|
||||
|
||||
export 'src/cache_image.dart';
|
||||
export 'src/countdown_time_text.dart';
|
||||
export 'src/cache_svg_image.dart';
|
||||
export 'src/grid_item_animator.dart';
|
||||
|
||||
export '../common/utils/async.dart';
|
||||
export '../common/utils/base_utils.dart';
|
||||
export 'package:starcitizen_doctor/generated/l10n.dart';
|
||||
@ -137,14 +140,13 @@ ColorFilter makeSvgColor(Color color) {
|
||||
return ui.ColorFilter.mode(color, ui.BlendMode.srcIn);
|
||||
}
|
||||
|
||||
CustomTransitionPage<T> myPageBuilder<T>(
|
||||
BuildContext context, GoRouterState state, Widget child) {
|
||||
CustomTransitionPage<T> myPageBuilder<T>(BuildContext context, GoRouterState state, Widget child) {
|
||||
return CustomTransitionPage(
|
||||
child: child,
|
||||
transitionDuration: const Duration(milliseconds: 150),
|
||||
reverseTransitionDuration: const Duration(milliseconds: 150),
|
||||
transitionsBuilder: (BuildContext context, Animation<double> animation,
|
||||
Animation<double> secondaryAnimation, Widget child) {
|
||||
transitionsBuilder:
|
||||
(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child) {
|
||||
return SlideTransition(
|
||||
position: Tween<Offset>(
|
||||
begin: const Offset(0.0, 1.0),
|
||||
@ -164,12 +166,7 @@ class LoadingWidget<T> extends HookConsumerWidget {
|
||||
final Widget Function(BuildContext context, T data) childBuilder;
|
||||
final Duration? autoRefreshDuration;
|
||||
|
||||
const LoadingWidget(
|
||||
{super.key,
|
||||
this.data,
|
||||
required this.childBuilder,
|
||||
this.onLoadData,
|
||||
this.autoRefreshDuration});
|
||||
const LoadingWidget({super.key, this.data, required this.childBuilder, this.onLoadData, this.autoRefreshDuration});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
@ -204,8 +201,7 @@ class LoadingWidget<T> extends HookConsumerWidget {
|
||||
return childBuilder(context, (data ?? dataState.value) as T);
|
||||
}
|
||||
|
||||
void _loadData(
|
||||
ValueNotifier<T?> dataState, ValueNotifier<String> errorMsg) async {
|
||||
void _loadData(ValueNotifier<T?> dataState, ValueNotifier<String> errorMsg) async {
|
||||
errorMsg.value = "";
|
||||
try {
|
||||
final r = await onLoadData!();
|
||||
|
Reference in New Issue
Block a user