perf(ui): enable KeepAlive on subscription list, tune prefetch, and reduce list/gesture animations
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'dart:math' as math;
|
||||
import '../utils/reduce_motion.dart';
|
||||
|
||||
/// 스태거 애니메이션이 적용된 리스트 위젯
|
||||
class StaggeredListAnimation extends StatefulWidget {
|
||||
@@ -95,13 +96,14 @@ class _StaggeredListAnimationState extends State<StaggeredListAnimation>
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (ReduceMotion.platform()) {
|
||||
return widget.direction == Axis.vertical
|
||||
? Column(
|
||||
children: _buildAnimatedChildren(),
|
||||
)
|
||||
: Row(
|
||||
children: _buildAnimatedChildren(),
|
||||
);
|
||||
? Column(children: widget.children)
|
||||
: Row(children: widget.children);
|
||||
}
|
||||
return widget.direction == Axis.vertical
|
||||
? Column(children: _buildAnimatedChildren())
|
||||
: Row(children: _buildAnimatedChildren());
|
||||
}
|
||||
|
||||
List<Widget> _buildAnimatedChildren() {
|
||||
@@ -156,8 +158,9 @@ class _StaggeredAnimationItemState extends State<StaggeredAnimationItem>
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
final reduced = ReduceMotion.platform();
|
||||
_controller = AnimationController(
|
||||
duration: widget.duration,
|
||||
duration: reduced ? Duration.zero : widget.duration,
|
||||
vsync: this,
|
||||
);
|
||||
|
||||
@@ -170,7 +173,9 @@ class _StaggeredAnimationItemState extends State<StaggeredAnimationItem>
|
||||
));
|
||||
|
||||
_slideAnimation = Tween<Offset>(
|
||||
begin: const Offset(0, 0.3),
|
||||
begin: ReduceMotion.platform()
|
||||
? const Offset(0, 0.05)
|
||||
: const Offset(0, 0.3),
|
||||
end: Offset.zero,
|
||||
).animate(CurvedAnimation(
|
||||
parent: _controller,
|
||||
@@ -185,11 +190,11 @@ class _StaggeredAnimationItemState extends State<StaggeredAnimationItem>
|
||||
curve: widget.curve,
|
||||
));
|
||||
|
||||
// 지연 후 애니메이션 시작
|
||||
Future.delayed(widget.delay * widget.index, () {
|
||||
if (mounted) {
|
||||
_controller.forward();
|
||||
}
|
||||
// 지연 후 애니메이션 시작 (모션 축소 시 지연 없음)
|
||||
final startDelay =
|
||||
ReduceMotion.platform() ? Duration.zero : widget.delay * widget.index;
|
||||
Future.delayed(startDelay, () {
|
||||
if (mounted) _controller.forward();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -201,6 +206,7 @@ class _StaggeredAnimationItemState extends State<StaggeredAnimationItem>
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (ReduceMotion.platform()) return widget.child;
|
||||
return AnimatedBuilder(
|
||||
animation: _controller,
|
||||
builder: (context, child) {
|
||||
|
||||
@@ -71,6 +71,7 @@ class SubscriptionListWidget extends StatelessWidget {
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
shrinkWrap: true,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
cacheExtent: 500,
|
||||
prototypeItem: const SizedBox(height: 156),
|
||||
itemCount: subscriptions.length,
|
||||
itemBuilder: (context, subIndex) {
|
||||
@@ -102,6 +103,7 @@ class SubscriptionListWidget extends StatelessWidget {
|
||||
child: RepaintBoundary(
|
||||
child: SwipeableSubscriptionCard(
|
||||
subscription: subscriptions[subIndex],
|
||||
keepAlive: true,
|
||||
onTap: () {
|
||||
Log.d(
|
||||
'[SubscriptionListWidget] SwipeableSubscriptionCard onTap 호출됨');
|
||||
|
||||
@@ -2,12 +2,14 @@ import 'package:flutter/material.dart';
|
||||
import '../models/subscription_model.dart';
|
||||
import '../utils/haptic_feedback_helper.dart';
|
||||
import 'subscription_card.dart';
|
||||
import '../utils/reduce_motion.dart';
|
||||
|
||||
class SwipeableSubscriptionCard extends StatefulWidget {
|
||||
final SubscriptionModel subscription;
|
||||
final VoidCallback? onEdit;
|
||||
final Future<void> Function()? onDelete;
|
||||
final VoidCallback? onTap;
|
||||
final bool keepAlive;
|
||||
|
||||
const SwipeableSubscriptionCard({
|
||||
super.key,
|
||||
@@ -15,6 +17,7 @@ class SwipeableSubscriptionCard extends StatefulWidget {
|
||||
this.onEdit,
|
||||
this.onDelete,
|
||||
this.onTap,
|
||||
this.keepAlive = false,
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -23,7 +26,7 @@ class SwipeableSubscriptionCard extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _SwipeableSubscriptionCardState extends State<SwipeableSubscriptionCard>
|
||||
with SingleTickerProviderStateMixin {
|
||||
with SingleTickerProviderStateMixin, AutomaticKeepAliveClientMixin {
|
||||
// 상수 정의
|
||||
static const double _tapTolerance = 20.0; // 탭 허용 범위
|
||||
static const double _actionThresholdPercent = 0.15;
|
||||
@@ -49,7 +52,9 @@ class _SwipeableSubscriptionCardState extends State<SwipeableSubscriptionCard>
|
||||
void initState() {
|
||||
super.initState();
|
||||
_controller = AnimationController(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
duration: ReduceMotion.platform()
|
||||
? const Duration(milliseconds: 0)
|
||||
: const Duration(milliseconds: 300),
|
||||
vsync: this,
|
||||
);
|
||||
_animation = Tween<double>(
|
||||
@@ -215,10 +220,14 @@ class _SwipeableSubscriptionCardState extends State<SwipeableSubscriptionCard>
|
||||
right: isLeft ? 0 : 24,
|
||||
),
|
||||
child: AnimatedOpacity(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
duration: ReduceMotion.platform()
|
||||
? const Duration(milliseconds: 0)
|
||||
: const Duration(milliseconds: 200),
|
||||
opacity: showIcon ? 1.0 : 0.0,
|
||||
child: AnimatedScale(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
duration: ReduceMotion.platform()
|
||||
? const Duration(milliseconds: 0)
|
||||
: const Duration(milliseconds: 200),
|
||||
scale: showIcon ? 1.0 : 0.5,
|
||||
child: Icon(
|
||||
isDeleteThreshold
|
||||
@@ -236,9 +245,10 @@ class _SwipeableSubscriptionCardState extends State<SwipeableSubscriptionCard>
|
||||
return Transform.translate(
|
||||
offset: Offset(_currentOffset, 0),
|
||||
child: Transform.scale(
|
||||
scale: 1.0 - (_currentOffset.abs() / 2000),
|
||||
scale:
|
||||
ReduceMotion.platform() ? 1.0 : 1.0 - (_currentOffset.abs() / 2000),
|
||||
child: Transform.rotate(
|
||||
angle: _currentOffset / 2000,
|
||||
angle: ReduceMotion.platform() ? 0.0 : _currentOffset / 2000,
|
||||
child: SubscriptionCard(
|
||||
subscription: widget.subscription,
|
||||
onTap: widget
|
||||
@@ -251,6 +261,7 @@ class _SwipeableSubscriptionCardState extends State<SwipeableSubscriptionCard>
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
super.build(context);
|
||||
// 웹과 모바일 모두 동일한 스와이프 기능 제공
|
||||
return Stack(
|
||||
children: [
|
||||
@@ -266,4 +277,7 @@ class _SwipeableSubscriptionCardState extends State<SwipeableSubscriptionCard>
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
bool get wantKeepAlive => widget.keepAlive;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user