154 lines
4.0 KiB
Dart
154 lines
4.0 KiB
Dart
// Author: fruit.com.cn
|
|
|
|
import 'package:flutter/material.dart';
|
|
|
|
import '../../../core/theme/app_colors.dart';
|
|
|
|
const _fadeDuration = Duration(milliseconds: 140);
|
|
const _motionDuration = Duration(milliseconds: 1350);
|
|
const _surfaceSize = Size(56, 36);
|
|
const _logoSize = Size(48, 29);
|
|
const _minimumScale = 0.98;
|
|
const _staticProgress = 0.5;
|
|
const _waveMultiplier = 2.0;
|
|
const _sweepStart = -1.2;
|
|
const _sweepTravel = 3.4;
|
|
const _sweepFeather = 0.16;
|
|
|
|
/// 剧集播放页首帧出现前的非阻塞 YMB 品牌加载层。
|
|
class PlayerLoadingOverlay extends StatelessWidget {
|
|
const PlayerLoadingOverlay({
|
|
super.key,
|
|
required this.opacity,
|
|
required this.onFadeEnd,
|
|
});
|
|
|
|
final double opacity;
|
|
final VoidCallback onFadeEnd;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return AnimatedOpacity(
|
|
opacity: opacity,
|
|
duration: _fadeDuration,
|
|
curve: Curves.easeOut,
|
|
onEnd: onFadeEnd,
|
|
child: Container(
|
|
key: const Key('player-loading-surface'),
|
|
width: _surfaceSize.width,
|
|
height: _surfaceSize.height,
|
|
alignment: Alignment.center,
|
|
child: const _YmbLoadingMark(),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _YmbLoadingMark extends StatefulWidget {
|
|
const _YmbLoadingMark();
|
|
|
|
@override
|
|
State<_YmbLoadingMark> createState() => _YmbLoadingMarkState();
|
|
}
|
|
|
|
class _YmbLoadingMarkState extends State<_YmbLoadingMark>
|
|
with SingleTickerProviderStateMixin, WidgetsBindingObserver {
|
|
late final AnimationController _controller;
|
|
bool _disableAnimations = false;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
WidgetsBinding.instance.addObserver(this);
|
|
_controller = AnimationController(vsync: this, duration: _motionDuration);
|
|
}
|
|
|
|
@override
|
|
void didChangeDependencies() {
|
|
super.didChangeDependencies();
|
|
final disableAnimations = MediaQuery.disableAnimationsOf(context);
|
|
if (_disableAnimations == disableAnimations && _controller.isAnimating) {
|
|
return;
|
|
}
|
|
_disableAnimations = disableAnimations;
|
|
_syncMotion(WidgetsBinding.instance.lifecycleState);
|
|
}
|
|
|
|
@override
|
|
void didChangeAppLifecycleState(AppLifecycleState state) {
|
|
_syncMotion(state);
|
|
}
|
|
|
|
void _syncMotion(AppLifecycleState? state) {
|
|
if (_disableAnimations) {
|
|
_controller
|
|
..stop()
|
|
..value = _staticProgress;
|
|
return;
|
|
}
|
|
if (state == null || state == AppLifecycleState.resumed) {
|
|
if (!_controller.isAnimating) {
|
|
_controller.repeat();
|
|
}
|
|
return;
|
|
}
|
|
_controller.stop();
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
WidgetsBinding.instance.removeObserver(this);
|
|
_controller.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return AnimatedBuilder(
|
|
animation: _controller,
|
|
builder: (context, child) {
|
|
final progress = _controller.value;
|
|
return _buildLogo(progress);
|
|
},
|
|
);
|
|
}
|
|
|
|
Widget _buildLogo(double progress) {
|
|
final pulse = Curves.easeInOut.transform(_triangleWave(progress));
|
|
final scale = _minimumScale + (1 - _minimumScale) * pulse;
|
|
return Transform.scale(
|
|
key: const Key('player-entry-loading-motion'),
|
|
scale: scale,
|
|
child: ShaderMask(
|
|
blendMode: BlendMode.srcATop,
|
|
shaderCallback: (bounds) =>
|
|
_logoGradient(progress).createShader(bounds),
|
|
child: Image.asset(
|
|
'assets/icon/splash_logo.png',
|
|
key: const Key('player-entry-loading'),
|
|
width: _logoSize.width,
|
|
height: _logoSize.height,
|
|
fit: BoxFit.contain,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
LinearGradient _logoGradient(double progress) {
|
|
final center = _sweepStart + progress * _sweepTravel;
|
|
return LinearGradient(
|
|
begin: Alignment.centerLeft,
|
|
end: Alignment.centerRight,
|
|
colors: const [
|
|
Colors.white,
|
|
Colors.white,
|
|
AppColors.brandRed,
|
|
Colors.white,
|
|
],
|
|
stops: [0, (center - _sweepFeather).clamp(0, 1), center.clamp(0, 1), 1],
|
|
);
|
|
}
|
|
|
|
double _triangleWave(double value) => 1 - (value * _waveMultiplier - 1).abs();
|
|
}
|