flutter gif图片加载 可监听总播发帧数 当前帧数 循环次数 可自由拓展
代码如下
import 'dart:io';
import 'dart:ui' as ui show Codec;
import 'dart:ui';
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
class GifCache {
final Map> caches = Map();
void clear() {
caches.clear();
}
bool evict(Object key) {
final List? pendingImage = caches.remove(key)!;
if (pendingImage != null) {
return true;
}
return false;
}
}
class GifController extends AnimationController {
GifController(
{required TickerProvider vsync,
double value = 0.0,
Duration? reverseDuration = const Duration(milliseconds: 1000),
Duration? duration = const Duration(milliseconds: 1000),
AnimationBehavior? animationBehavior})
: super.unbounded(
value: value,
reverseDuration: reverseDuration,
duration: duration,
animationBehavior: animationBehavior ?? AnimationBehavior.normal,
vsync: vsync);
int _frames = 0;
bindValues(int len) {
_frames = len;
}
int get frames {
return _frames;
}
int get frame {
return value.toInt();
}
int get playCount {
return _playCount;
}
int _playCount = 0;
@override
void stop({bool canceled = true}) {
super.stop(canceled: canceled);
}
void play() {
repeat(min: 0, max: frames.toDouble(), period: duration);
}
}
class GifImage extends StatefulWidget {
GifImage({
required this.image,
required this.controller,
this.semanticLabel,
this.excludeFromSemantics = false,
this.width,
this.height,
this.onFetchCompleted,
this.color,
this.colorBlendMode,
this.fit,
this.alignment = Alignment.center,
this.repeat = ImageRepeat.noRepeat,
this.centerSlice,
this.matchTextDirection = false,
this.initPlay = true,
});
final VoidCallback? onFetchCompleted;
final GifController controller;
final ImageProvider image;
final double? width;
final bool? initPlay;
final double? height;
final Color? color;
final BlendMode? colorBlendMode;
final BoxFit? fit;
final AlignmentGeometry alignment;
final ImageRepeat repeat;
final Rect? centerSlice;
final bool matchTextDirection;
final String? semanticLabel;
final bool excludeFromSemantics;
@override
State createState() {
return new GifImageState();
}
static GifCache cache = GifCache();
}
class GifImageState extends State {
List? _infos;
ValueNotifier _curIndex = ValueNotifier(-1);
bool _fetchComplete = false;
ImageInfo? get _imageInfo {
if (!_fetchComplete) return null;
return _infos == null ? null : _infos![_curIndex.value];
}
@override
void initState() {
super.initState();
widget.controller.addListener(_listener);
_curIndex.addListener(() {
if (widget.controller.frame == widget.controller.frames - 1) {
widget.controller._playCount++;
}
});
fetchGif(widget.image).then((imageInfors) {
if (mounted) {
_infos = imageInfors;
_fetchComplete = true;
widget.controller.bindValues(_infos!.length);
_curIndex.value = widget.controller.value.toInt();
if (widget.onFetchCompleted != null) {
widget.onFetchCompleted!();
}
if (widget.initPlay == true) {
widget.controller.play();
}
}
});
}
@override
void dispose() {
super.dispose();
widget.controller.removeListener(_listener);
}
@override
void didUpdateWidget(GifImage oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.controller != oldWidget.controller) {
oldWidget.controller.removeListener(_listener);
widget.controller.addListener(_listener);
}
}
void _listener() {
if (_curIndex.value != widget.controller.value && _fetchComplete) {
_curIndex.value = widget.controller.value.toInt();
}
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
}
@override
Widget build(BuildContext context) {
RawImage image = RawImage(
image: _imageInfo?.image,
width: widget.width,
height: widget.height,
scale: _imageInfo?.scale ?? 1.0,
color: widget.color,
colorBlendMode: widget.colorBlendMode,
fit: widget.fit,
alignment: widget.alignment,
repeat: widget.repeat,
centerSlice: widget.centerSlice,
matchTextDirection: widget.matchTextDirection,
);
if (widget.excludeFromSemantics) return image;
return ValueListenableBuilder(
valueListenable: _curIndex,
builder: (context, index, child) {
return Semantics(
container: widget.semanticLabel != null,
image: true,
label: widget.semanticLabel == null ? '' : widget.semanticLabel,
child: RawImage(
image: _imageInfo?.image,
width: widget.width,
height: widget.height,
scale: _imageInfo?.scale ?? 1.0,
color: widget.color,
colorBlendMode: widget.colorBlendMode,
fit: widget.fit,
alignment: widget.alignment,
repeat: widget.repeat,
centerSlice: widget.centerSlice,
matchTextDirection: widget.matchTextDirection,
),
);
},
);
}
}
final HttpClient _sharedHttpClient = HttpClient()..autoUncompress = false;
HttpClient get _httpClient {
HttpClient client = _sharedHttpClient;
assert(() {
if (debugNetworkImageHttpClientProvider != null)
client = debugNetworkImageHttpClientProvider!();
return true;
}());
return client;
}
Future> fetchGif(ImageProvider provider) async {
List infos = [];
dynamic data;
String key = provider is NetworkImage
? provider.url
: provider is AssetImage
? provider.assetName
: provider is MemoryImage
? provider.bytes.toString()
: "";
if (GifImage.cache.caches.containsKey(key)) {
infos = GifImage.cache.caches[key]!;
return infos;
}
if (provider is NetworkImage) {
final Uri resolved = Uri.base.resolve(provider.url);
final HttpClientRequest request = await _httpClient.getUrl(resolved);
provider.headers?.forEach((String name, String value) {
request.headers.add(name, value);
});
final HttpClientResponse response = await request.close();
data = await consolidateHttpClientResponseBytes(
response,
);
} else if (provider is AssetImage) {
AssetBundleImageKey key = await provider.obtainKey(ImageConfiguration());
data = await key.bundle.load(key.name);
} else if (provider is FileImage) {
data = await provider.file.readAsBytes();
} else if (provider is MemoryImage) {
data = provider.bytes;
}
ui.Codec codec = await PaintingBinding.instance!
.instantiateImageCodec(data.buffer.asUint8List());
infos = [];
for (int i = 0; i < codec.frameCount; i++) {
FrameInfo frameInfo = await codec.getNextFrame();
//scale ??
infos.add(ImageInfo(image: frameInfo.image));
}
GifImage.cache.caches.putIfAbsent(key, () => infos);
return infos;
}
好了 去玩吧
调用示范
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'gif_image.dart';
void main() => runApp(StartPage());
class StartPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: TestPage(),
);
}
}
class TestPage extends StatefulWidget {
@override
_TestPageState createState() => _TestPageState();
}
class _TestPageState extends State
with SingleTickerProviderStateMixin {
late GifController controller = GifController(vsync: this);
@override
void initState() {
super.initState();
controller.addListener(() {
setState(() {});
});
}
Widget get initFloatingActionButton {
return FloatingActionButton(
backgroundColor: Colors.grey,
elevation: 1,
focusElevation: 1,
onPressed: () {
if (controller.isAnimating) {
controller.stop();
} else {
controller.play();
}
setState(() {});
},
child: Icon(Icons.android),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: initFloatingActionButton,
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
extendBody: true,
body: Center(
child: Container(
width: 400,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'总帧数:${controller.frames}\n'
'当前帧数:${controller.frame}\n'
'当前循环次数:${controller.playCount}\n',
maxLines: 4,
overflow: TextOverflow.ellipsis,
),
GifImage(
controller: controller,
image: AssetImage("image/964F914FA551BCD8D9F1C2F9C9DA2E67.gif"),
)
],
),
)),
);
}
}