93 lines
2.1 KiB
Dart
93 lines
2.1 KiB
Dart
import 'dart:async';
|
||
|
||
import 'package:flutter/material.dart';
|
||
|
||
import 'dart:ui' as ui;
|
||
|
||
import 'package:flutter/services.dart';
|
||
|
||
class SpeedControlledGif extends StatefulWidget {
|
||
final String assetPath;
|
||
final double speedFactor;
|
||
final BoxFit? fit;
|
||
|
||
/// [speedFactor] 播放速度倍数,默认1.0,
|
||
/// 大于1表示加速播放,小于1表示减速播放
|
||
const SpeedControlledGif(
|
||
this.assetPath, {
|
||
Key? key,
|
||
this.speedFactor = 1.0,
|
||
this.fit,
|
||
}) : super(key: key);
|
||
|
||
@override
|
||
_SpeedControlledGifState createState() => _SpeedControlledGifState();
|
||
}
|
||
|
||
class _SpeedControlledGifState extends State<SpeedControlledGif> {
|
||
ui.Codec? _codec;
|
||
ui.FrameInfo? _currentFrame;
|
||
Timer? _timer;
|
||
bool _isDisposed = false;
|
||
|
||
@override
|
||
void initState() {
|
||
super.initState();
|
||
_loadGif();
|
||
}
|
||
|
||
Future<void> _loadGif() async {
|
||
final data = await rootBundle.load(widget.assetPath);
|
||
_codec = await ui.instantiateImageCodec(data.buffer.asUint8List());
|
||
|
||
if (_isDisposed) return;
|
||
|
||
_showNextFrame();
|
||
}
|
||
|
||
Future<void> _showNextFrame() async {
|
||
if (_isDisposed || _codec == null) return;
|
||
|
||
_currentFrame = await _codec!.getNextFrame();
|
||
|
||
if (_isDisposed) return;
|
||
|
||
if (mounted) {
|
||
setState(() {});
|
||
}
|
||
|
||
// 取当前帧持续时间,单位毫秒
|
||
final baseDuration = _currentFrame?.duration.inMilliseconds ?? 100;
|
||
|
||
// 限制最小帧间隔,防止刷新过快
|
||
const minFrameDuration = 50;
|
||
|
||
// 计算实际播放帧间隔,speedFactor越大速度越快
|
||
final adjustedDuration = (baseDuration / widget.speedFactor)
|
||
.round()
|
||
.clamp(minFrameDuration, 10000);
|
||
|
||
_timer = Timer(Duration(milliseconds: adjustedDuration), _showNextFrame);
|
||
}
|
||
|
||
@override
|
||
void dispose() {
|
||
_isDisposed = true;
|
||
_timer?.cancel();
|
||
_codec?.dispose();
|
||
super.dispose();
|
||
}
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
if (_currentFrame == null) {
|
||
// 加载中或无帧时显示空容器或占位
|
||
return Container();
|
||
}
|
||
return RawImage(
|
||
image: _currentFrame!.image,
|
||
fit: widget.fit,
|
||
);
|
||
}
|
||
}
|