commit 043112a574778bd398a122754f70a29397b049f4 Author: Konstantin Kollar Date: Fri Sep 30 20:12:39 2022 +0200 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9f87252 --- /dev/null +++ b/.gitignore @@ -0,0 +1,72 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +.dart_tool/ +.flutter-plugins +.packages +.pub-cache/ +.pub/ +build/ + +# Android related +**/android/**/gradle-wrapper.jar +**/android/.gradle +**/android/captures/ +**/android/gradlew +**/android/gradlew.bat +**/android/local.properties +**/android/**/GeneratedPluginRegistrant.java + +# iOS/XCode related +**/ios/**/*.mode1v3 +**/ios/**/*.mode2v3 +**/ios/**/*.moved-aside +**/ios/**/*.pbxuser +**/ios/**/*.perspectivev3 +**/ios/**/*sync/ +**/ios/**/.sconsign.dblite +**/ios/**/.tags* +**/ios/**/.vagrant/ +**/ios/**/DerivedData/ +**/ios/**/Icon? +**/ios/**/Pods/ +**/ios/**/.symlinks/ +**/ios/**/profile +**/ios/**/xcuserdata +**/ios/.generated/ +**/ios/Flutter/App.framework +**/ios/Flutter/Flutter.framework +**/ios/Flutter/Generated.xcconfig +**/ios/Flutter/app.flx +**/ios/Flutter/app.zip +**/ios/Flutter/flutter_assets/ +**/ios/ServiceDefinitions.json +**/ios/Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!**/ios/**/default.mode1v3 +!**/ios/**/default.mode2v3 +!**/ios/**/default.pbxuser +!**/ios/**/default.perspectivev3 +!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages diff --git a/.metadata b/.metadata new file mode 100644 index 0000000..9903133 --- /dev/null +++ b/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: b712a172f9694745f50505c93340883493b505e5 + channel: stable + +project_type: package diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..4a18dca --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,9 @@ +## [0.0.1] - TODO: Add release date. + +* TODO: Describe initial release. + +## 1.0.0 +* init release + +## 1.0.1 +* add check mounted when setState diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..2c25915 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 Jpeng + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..d89ec67 --- /dev/null +++ b/README.md @@ -0,0 +1,86 @@ +# flutter_gifimage + +We should know that in order to achieve Gif in flutter, we can use Image, but we have no way to manipulate Gif, for example: change its speed, control it has been playing in a frame, + in which frame range loop. These problems can be solved by this widget,it also help you contain gif cache,avoid load frame every time. + +# Screenshots + +![](arts/gif.gif) + +# Usage(Simple) + add in pubspec + + ```dart + + flutter_gifimage: ^1.0.0 + + ``` + + simple usage + + ```dart + GifController controller= GifController(vsync: this); + + + GifImage( + controller: controller, + image: AssetImage("images/animate.gif"), + ) + + ``` + + list the most common operate in GifController: + + + ```dart + // loop from 0 frame to 29 frame + controller.repeat(min:0,max:29,period:Duration(millseconds:300)); + + // jumpTo thrid frame(index from 0) + controller.value = 0; + + // from current frame to 26 frame + controller.animateTo(26); + + ``` + + If you need to preCache gif,try this + + ```dart + // put imageProvider + fetchGif(AssetImage("images/animate.gif")); + + ``` + + + + +# Thanks +* [gif_ani](https://github.com/hyz1992/gif_ani) (thanks for giving me idea) + +# License + +``` +MIT License + +Copyright (c) 2019 Jpeng + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +``` \ No newline at end of file diff --git a/arts/gif.gif b/arts/gif.gif new file mode 100644 index 0000000..a08756b Binary files /dev/null and b/arts/gif.gif differ diff --git a/lib/flutter_gifimage.dart b/lib/flutter_gifimage.dart new file mode 100644 index 0000000..a6cc595 --- /dev/null +++ b/lib/flutter_gifimage.dart @@ -0,0 +1,245 @@ +/* + author: Jpeng + email: peng8350@gmail.com + time:2019-7-26 3:30 +*/ + +library flutter_gifimage; + +import 'dart:io'; +import 'dart:ui' as ui show Codec; +import 'dart:ui'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/widgets.dart'; + +/// cache gif fetched image +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; + } +} + +/// controll gif +class GifController extends AnimationController { + GifController( + {required TickerProvider vsync, + double value = 0.0, + Duration? reverseDuration, + Duration? duration, + AnimationBehavior? animationBehavior}) + : super.unbounded( + value: value, + reverseDuration: reverseDuration, + duration: duration, + animationBehavior: animationBehavior ?? AnimationBehavior.normal, + vsync: vsync); + + @override + void reset() { + value = 0.0; + } +} + +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.gaplessPlayback = false, + }); + final VoidCallback? onFetchCompleted; + final GifController controller; + final ImageProvider image; + final double? width; + 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 bool gaplessPlayback; + final String? semanticLabel; + final bool excludeFromSemantics; + + @override + State createState() { + return new GifImageState(); + } + + static GifCache cache = GifCache(); +} + +class GifImageState extends State { + List? _infos; + int _curIndex = 0; + bool _fetchComplete = false; + ImageInfo? get _imageInfo { + if (!_fetchComplete) return null; + return _infos == null ? null : _infos![_curIndex]; + } + + @override + void initState() { + super.initState(); + widget.controller.addListener(_listener); + } + + @override + void dispose() { + super.dispose(); + widget.controller.removeListener(_listener); + } + + @override + void didUpdateWidget(GifImage oldWidget) { + super.didUpdateWidget(oldWidget); + if (widget.image != oldWidget.image) { + fetchGif(widget.image).then((imageInfors) { + if (mounted) + setState(() { + _infos = imageInfors; + _fetchComplete = true; + _curIndex = widget.controller.value.toInt(); + if (widget.onFetchCompleted != null) { + widget.onFetchCompleted!(); + } + }); + }); + } + if (widget.controller != oldWidget.controller) { + oldWidget.controller.removeListener(_listener); + widget.controller.addListener(_listener); + } + } + + void _listener() { + if (_curIndex != widget.controller.value && _fetchComplete) { + if (mounted) + setState(() { + _curIndex = widget.controller.value.toInt(); + }); + } + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + if (_infos == null) { + fetchGif(widget.image).then((imageInfors) { + if (mounted) + setState(() { + _infos = imageInfors; + _fetchComplete = true; + _curIndex = widget.controller.value.toInt(); + if (widget.onFetchCompleted != null) { + widget.onFetchCompleted!(); + } + }); + }); + } + } + + @override + Widget build(BuildContext context) { + final RawImage image = new 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 new Semantics( + container: widget.semanticLabel != null, + image: true, + label: widget.semanticLabel == null ? '' : widget.semanticLabel, + child: image, + ); + } +} + +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 = []; + late 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; +} diff --git a/pubspec.lock b/pubspec.lock new file mode 100644 index 0000000..db1eaf9 --- /dev/null +++ b/pubspec.lock @@ -0,0 +1,50 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + characters: + dependency: transitive + description: + name: characters + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.0" + collection: + dependency: transitive + description: + name: collection + url: "https://pub.dartlang.org" + source: hosted + version: "1.16.0" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.4" + meta: + dependency: transitive + description: + name: meta + url: "https://pub.dartlang.org" + source: hosted + version: "1.7.0" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.99" + vector_math: + dependency: transitive + description: + name: vector_math + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.2" +sdks: + dart: ">=2.14.0 <3.0.0" diff --git a/pubspec.yaml b/pubspec.yaml new file mode 100644 index 0000000..423f4ea --- /dev/null +++ b/pubspec.yaml @@ -0,0 +1,14 @@ +name: flutter_gifimage +description: a gifimage for flutter,for solving gif cannot be controlled speed,progress +version: 1.0.1 +homepage: https://github.com/peng8350/flutter_gifimage + +environment: + sdk: ">=2.12.0 <3.0.0" + +dependencies: + flutter: + sdk: flutter + +dev_dependencies: +flutter: