151 lines
3.3 KiB
Dart
151 lines
3.3 KiB
Dart
import 'package:flutter/material.dart';
|
|
|
|
/// 磁吸进度状态
|
|
class MagneticSnapState {
|
|
/// 吸附进度 0.0 ~ 1.0
|
|
final double progress;
|
|
|
|
/// 媒体库透明度
|
|
double get mediaOpacity {
|
|
// 0% ~ 70%: 1.0 → 0.45
|
|
// 70% ~ 100%: 0.45 → 0
|
|
if (progress <= 0.7) {
|
|
return 1.0 - (progress / 0.7) * 0.55;
|
|
} else {
|
|
final t = (progress - 0.7) / 0.3;
|
|
return 0.45 * (1 - t);
|
|
}
|
|
}
|
|
|
|
/// 收藏标题位移(从 +12px → 0)
|
|
double get favoriteTranslateY {
|
|
return (1 - progress) * 12;
|
|
}
|
|
|
|
/// 收藏列表展开程度 0.0 ~ 1.0
|
|
double get listReveal {
|
|
if (progress <= 0.7) return 0.0;
|
|
return (progress - 0.7) / 0.3;
|
|
}
|
|
|
|
/// 是否完全吸附
|
|
bool get isSnapped => progress >= 1.0;
|
|
|
|
/// 是否在磁吸区(进度 > 70%)
|
|
bool get isInMagneticZone => progress > 0.7;
|
|
|
|
const MagneticSnapState(this.progress);
|
|
|
|
factory MagneticSnapState.initial() => const MagneticSnapState(0.0);
|
|
|
|
MagneticSnapState copyWith({double? progress}) {
|
|
return MagneticSnapState(progress ?? this.progress);
|
|
}
|
|
}
|
|
|
|
/// 磁吸控制器
|
|
class MagneticScrollController extends ChangeNotifier {
|
|
/// 吸附进度
|
|
double _progress = 0.0;
|
|
double get progress => _progress;
|
|
|
|
/// 是否已吸附
|
|
bool _isSnapped = false;
|
|
bool get isSnapped => _isSnapped;
|
|
|
|
/// 动画控制器(用于吸附动画)
|
|
AnimationController? _animationController;
|
|
TickerProvider? _tickerProvider;
|
|
|
|
/// 状态
|
|
MagneticSnapState get state => MagneticSnapState(_progress);
|
|
|
|
void init(TickerProvider vsync) {
|
|
_animationController = AnimationController(
|
|
vsync: vsync,
|
|
duration: const Duration(milliseconds: 300),
|
|
);
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_animationController?.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
/// 更新滚动进度(在滚动时调用)
|
|
void updateProgress(double newProgress) {
|
|
if (_isSnapped) return;
|
|
_progress = newProgress.clamp(0.0, 1.0);
|
|
notifyListeners();
|
|
}
|
|
|
|
/// 处理松手事件
|
|
void onDragEnd(double velocity) {
|
|
if (_isSnapped) return;
|
|
|
|
final threshold = 0.45; // 45% 触发吸附
|
|
final shouldSnap = _progress > threshold || velocity > 800;
|
|
|
|
if (shouldSnap) {
|
|
snapOpen();
|
|
} else {
|
|
snapBack();
|
|
}
|
|
}
|
|
|
|
/// 吸附展开
|
|
void snapOpen() {
|
|
if (_isSnapped) return;
|
|
_isSnapped = true;
|
|
|
|
_animationController?.reset();
|
|
_animationController
|
|
?.animateTo(
|
|
1.0,
|
|
duration: const Duration(milliseconds: 300),
|
|
curve: Curves.easeOutBack,
|
|
)
|
|
.then((_) {
|
|
_progress = 1.0;
|
|
notifyListeners();
|
|
});
|
|
|
|
// 实时更新进度
|
|
_animationController?.addListener(() {
|
|
_progress = _animationController!.value;
|
|
notifyListeners();
|
|
});
|
|
}
|
|
|
|
/// 回弹
|
|
void snapBack() {
|
|
if (_isSnapped) return;
|
|
|
|
_animationController?.reset();
|
|
_animationController
|
|
?.animateTo(
|
|
0.0,
|
|
duration: const Duration(milliseconds: 250),
|
|
curve: Curves.easeOut,
|
|
)
|
|
.then((_) {
|
|
_progress = 0.0;
|
|
notifyListeners();
|
|
});
|
|
|
|
_animationController?.addListener(() {
|
|
_progress = _animationController!.value;
|
|
notifyListeners();
|
|
});
|
|
}
|
|
|
|
/// 重置状态(退出页面时)
|
|
void reset() {
|
|
_isSnapped = false;
|
|
_progress = 0.0;
|
|
_animationController?.reset();
|
|
notifyListeners();
|
|
}
|
|
}
|