preface

Requirements:

1. The image of the check-in entry is GIF. Play the GIF animation twice, pause for 3 seconds and then play it twice.

2. Click the GIF image to enter the check-in page. After the GIF image is returned, only the first frame image will be displayed.

The Image component of Flutter does not support GIF playback. The simplest and most crude solution is as follows:

Plan 1: Prepare a static PNG image and a dynamic GIF image, and use the timer to switch the image display every three seconds. The Visibility component controls the display of the GIF image first and the static image 3 seconds later.

 Visibility(
      visible: isPlay,
      child: Image.asset("images/gif_player_demo.gif"),
      replacement: Image.asset("images/gif_player_demo.png"),
    );
Copy the code

This solution is not perfect. In practice, you will find that the static image and dynamic GIF do not switch smoothly and the animation will be truncated and the GIF display will sometimes not play the first frame of the animation. By source analysis found that the reason is that GIF animation to refresh the next is through SchedulerBinding instance. ScheduleFrameCallback updated.

MultiFrameImageStreamCompleter part source code:

void _scheduleAppFrame() {
    if (_frameCallbackScheduled) {
      return;
    }
    _frameCallbackScheduled = true;
 SchedulerBinding.instance.scheduleFrameCallback(_handleAppFrame); } Copy the code

The animation is determined by the page refresh FPS, which leads to imprecise animation connection, and the display and hide timer control cannot do GIF playback timing.

Direct design scheme 2: a play two animated GIF images and static 3 seconds, but the GIF images are particularly big as stationary 3 seconds animation does not optimize compressed into a frame, namely each frame animation is an image in 3 seconds, combined with the GIF also need support to stop playing only display the first frame image, so this scheme can also be abandoned.

Source code analysis

Custom implementation control GIF playback before understanding the Image component source code is how to achieve multi-frame Image playback. Here is the source code analysis through Image.net Work: Find the key class image_provider.NetworkImage to load image resources, load is an important method to load and obtain image data. Through MultiFrameImageStreamCompleter access to the data flow result image resources through which to achieve more frame image data broadcast. Then look at the implementation of the MultiFrameImageStreamCompleter.

MultiFrameImageStreamCompleter analysis

MultiFrameImageStreamCompleter essentially image data managers, can be understood as image playback is controlled by its management, and after the object for the modification of the GIF control playback.

MultiFrameImageStreamCompleter by image Codec Codec of image, image decoder results through the timer and scheduleFrameCallback refresh cycle for the next frame image ImageInfo _emitFrame sent.

  void _handleAppFrame(Duration timestamp) {
    _frameCallbackScheduled = false;
    if(! hasListeners)      return;
    if (_isFirstFrame() || _hasFrameDurationPassed(timestamp)) {
 _emitFrame(ImageInfo(image: _nextFrame.image, scale: _scale));  _shownTimestamp = timestamp;  _frameDuration = _nextFrame.duration;  _nextFrame = null;  final int completedCycles = _framesEmitted ~/ _codec.frameCount;  if (_codec.repetitionCount == -1 || completedCycles <= _codec.repetitionCount) {  _decodeNextFrameAndSchedule();  }  return;  }  final Duration delay = _frameDuration - (timestamp - _shownTimestamp);  _timer = Timer(delay * timeDilation, () {  _scheduleAppFrame();  });  }   void _scheduleAppFrame() {  if (_frameCallbackScheduled) {  return;  }  _frameCallbackScheduled = true;  SchedulerBinding.instance.scheduleFrameCallback(_handleAppFrame);  } Copy the code

View the image decoder Codec source code can obtain the number of image frames, the next frame of image data, but can only obtain the next frame of data and can not specify the acquisition of a frame of image data.

  
  int get frameCount native 'Codec_frameCount';


  int get repetitionCount native 'Codec_repetitionCount';
  Future<FrameInfo> getNextFrame() {  return _futurize(_getNextFrame);  }   String _getNextFrame(_Callback<FrameInfo> callback) native 'Codec_getNextFrame';  Copy the code

Source transformation

By analyzing the source code control class on GIF is controlled by MultiFrameImageStreamCompleter known. So the network images load can be realized through the custom ImageProvider transformation MultiFrameImageStreamCompleter can rewrite the load method.

GifImage

Load GIF Image function for Image component by extension method. ImageProvider is the Image component Image resource loader, is also to do GIF loading transformation class.

extension GifImage on Image {
  static gif({
    @required ImageProvider image,
    Key key,
    ImageFrameBuilder frameBuilder,
 ImageLoadingBuilder loadingBuilder,  ImageErrorWidgetBuilder errorBuilder,  String semanticLabel,  bool excludeFromSemantics = false. double width,  double height,  Color color,  BlendMode colorBlendMode,  BoxFit fit,  Alignment alignment = Alignment.center,  ImageRepeat repeat = ImageRepeat.noRepeat,  Rect centerSlice,  bool matchTextDirection = false. bool gaplessPlayback = false. FilterQuality filterQuality = FilterQuality.low, {}) return Image(  key: key,  frameBuilder: frameBuilder,  loadingBuilder: loadingBuilder,  errorBuilder: errorBuilder,  semanticLabel: semanticLabel,  excludeFromSemantics: excludeFromSemantics,  width: width,  height: height,  color: color,  colorBlendMode: colorBlendMode,  fit: fit,  alignment: alignment,  repeat: repeat,  centerSlice: centerSlice,  matchTextDirection: matchTextDirection,  gaplessPlayback: gaplessPlayback,  filterQuality: filterQuality,  image: image,  );  } } Copy the code

GifNetworkImage

Copy NetworkImage (image_provider.ImageProvider) source code

1. The GifNetworkImage constructor increases the image cache check. Remove the existing cache before loading the image to avoid getting the ImageProvider from the cache and nullifying the new configuration parameters (a better way to create a separate GIF cache singleton to keep the data is omitted here).

2. Modify the load method of the returned MultiFrameImageStreamCompleter object, Replace custom GifMultiFrameImageStreamCompleter classes and increases the replayDuration and repetitionCount parameters.

GifNetworkImage(
    this.url, {
    this.repetitionCount = - 1.    this.replayDuration,
    this.scale = 1.0. this.headers, {}) assert(url ! =null);  assert(scale ! =null);  // *******Add  // Check the cache resources before loading  // Otherwise, fetching the resource from the cache may not start from the first frame  if (PaintingBinding.instance.imageCache.containsKey(this)) {  print("<gifImage> imageCache containsKey ");  PaintingBinding.instance.imageCache.evict(this, includeLive: true);  } } .@override  ImageStreamCompleter load(NetworkImage key, DecoderCallback decode) {  final StreamController<ImageChunkEvent> chunkEvents =  StreamController<ImageChunkEvent>();  streamCompleter = GifMultiFrameImageStreamCompleter(  codec: _loadAsync(key, chunkEvents, decode),  chunkEvents: chunkEvents.stream,  scale: key.scale,  informationCollector: () {  return <DiagnosticsNode>[  DiagnosticsProperty<ImageProvider>('Image provider'.this),  DiagnosticsProperty<NetworkImage>('Image key', key),  ];  },  replayDuration: replayDuration,  repetitionCount: repetitionCount,  );  return streamCompleter;  } .Copy the code

GifMultiFrameImageStreamCompleter

GifMultiFrameImageStreamCompleter main _handleCodecReady and _handleAppFrame method.

1. Preload all frame image caches to List< ui.frameinfo > _frames when obtaining the image decoder Codec. The main function is to facilitate the acquisition of each frame of image data, better calculation of animation playback to which frame.

2. Rewrite the _nextFrame acquisition mode to obtain a frame data through _frames to avoid frame hopping and frame loss in animation.

Reconfigure the Timer method to re-instantiate a Timer with the replayDuration parameter. The countdown method resets the _framesEmitted count and restarts playing giFs.

.void _handleCodecReady(ui.Codec codec) async {
    _codec = codec;
    assert(_codec ! =null);
    print(
 "<gifImage> repetitionCount ${_codec.repetitionCount} frameCount ${_codec.frameCount}");  if (_codec.frameCount > 1) {  _frames = List(a); for (int i = 0; i < _codec.frameCount; i++) {  ui.FrameInfo frameInfo = await _codec.getNextFrame();  _frames.add(frameInfo);  }  }  if (hasListeners) {  _decodeNextFrameAndSchedule();  } }  void _handleAppFrame(Duration timestamp) {  _frameCallbackScheduled = false;  if(! hasListeners)return;  if (_isFirstFrame() || _hasFrameDurationPassed(timestamp)) {  _emitFrame(ImageInfo(image: _nextFrame.image, scale: _scale));  _shownTimestamp = timestamp;  _frameDuration = _nextFrame.duration;  _nextFrame = null;  final int completedCycles = _framesEmitted ~/ _codec.frameCount;  // *******Add  // Customize the number of loops  // Do not use the loop number of _codec itself // if (_codec.repetitionCount == -1 || // completedCycles <= _codec.repetitionCount) { // _decodeNextFrameAndSchedule(); // }  print("<gifImage> _handleAppFrame completedCycles $completedCycles");  if (repetitionCount == - 1 || completedCycles <= repetitionCount) {  _decodeNextFrameAndSchedule();  } else if(replayDuration ! =null) { _timer? .cancel(); _timer = null;  _timer = Timer(replayDuration, () {  _framesEmitted = 0;  _decodeNextFrameAndSchedule();  });  }  return;  }  print("<gifImage> _handleAppFrame _timer");  final Duration delay = _frameDuration - (timestamp - _shownTimestamp);   _timer = Timer(delay * timeDilation, () {  _scheduleAppFrame();  });  }  . Copy the code

GifAssetImage

The above implementation of the Web GIF animation transformation, as well as local Asset images. Also copy ImageProvider source to the load method is modified to MultiFrameImageStreamCompleter replaced with GifMultiFrameImageStreamCompleter can.

The use of GifImage. GIF

Finally, if real-time GIF playback parameters need to be changed, the GifNetworkImage and GifAssetImage objects need to be instantiated separately to modify their parameters. The reason is that the ImageProvider is only instantiated once during image loading, and the ImageProvider object is not updated even if the setState update parameter is executed but the ImageProvider instance already exists.

So we’ve added the updatePlayConfig method for the custom ImageProvider to update the GIF interval and playback times in real time.

class _GifPlayerDemoState extends State<GifPlayerDemo> {
  String picRes1 =
      "http://wx2.sinaimg.cn/bmiddle/ceeb653ely1g4xhw7xasrg207d054njs.gif";
  int repetitionCount = 1;
  Duration duration = Duration(seconds: 3);
 GifNetworkImage gifNetworkImage;   @override  void initState() {  super.initState();  gifNetworkImage = GifNetworkImage(  picRes1,  repetitionCount: repetitionCount,  replayDuration: duration,  );  }   @override  Widget build(BuildContext context) {  return ListView(  children: <Widget>[  GifImage.gif(image: gifNetworkImage),  Row(  children: <Widget>[  RaisedButton(  child: Text("Infinite loop"),  onPressed: () {  repetitionCount = -1;  duration = null; gifNetworkImage? .updatePlayConfig( repetitionCount: repetitionCount,  replayDuration: duration,  );  setState(() {});  },  ),  RaisedButton(  child: Text("Loop 3+ replay"),  onPressed: () {  repetitionCount = 1;  duration = Duration(seconds: 3); gifNetworkImage? .updatePlayConfig( repetitionCount: repetitionCount,  replayDuration: duration,  );  setState(() {});  },  ) ]. ), ]. );  }   @override  void dispose() {  super.dispose(); gifNetworkImage? .dispose(); } } Copy the code

🚀 See the full code here 🚀

This custom way can be GIF images to achieve a single play times and play interval. Of course, there are also flutter_GIFimage third-party component library to provide a more comprehensive GIF playback control scheme to choose, according to the needs of the demander targeted selection.

reference

flutter_gifimage