こんにちはEveryDaySoft代表の永田です。
Flutterではgifファイル再生スピードをコントロールするプログラムはデフォルト機能では用意されていません。
3年前に公開された昔のバージョンのソースコードをAssetsから画像設定した場合の機能に絞り、最新ソースコードにアップデートしました。
作成したソースコード FlutterVersion 3.3.10 GitHubリンク
最新ではGoogleが提供している webpという拡張子を使用し、
FlutterプログラムではAnimatedcontainer、AnimatedBuilderなどでスピード変化のコード対応も可能かと思います。
このソースコードはディレクションで稼働していた案件内で、スプラッシュ動画の再生スピードをコントロールするために作りました。作成したソースコードは昔のOSと最新のOSの挙動を合わせるためにもう少し改造しています。僕の場合はディレクションもしつつ、プログラムも出来るという能力をスキルの一部として兼ね備えています。
挙動
Gifのスピード可変するソースコード アニメーションのコントローラー
この箇所で再生などの機能を持つAnimationControllerを継承しています。
class GifController extends AnimationController {
GifController({required this.vsync})
: super.unbounded(
value: 0,
vsync: vsync,
);
final TickerProvider vsync;
final streamSize = StreamController<int>();
@override
void reset() {
value = 0.0;
}
}
AnimationControllerを継承すると再生機能のforward関数が使用できません。
理由はAnimationControllerでforward機能を持つ関数内で
upperBoundという値が継承する場合は隠蔽実装をしていて、初期時の設定ができないのと、
値が1という数値が付与されますが、最後にinfinityという値を設定しているようで、
実行している、_animateToInternal(upperBound)がアニメーション動作しません。
upperBoundの箇所に数字の100などを付与するとアニメーション動作はしました。
なぜupperBoundが1では動かないのかという部分まではこの機能に関して追求しませんでした。
なのでforward関数の代わりにループするrepeat関数を使用しています。widget.c.repeat(min: 0, max: 100, period: Duration(milliseconds: value));
100というのは今回用意したgifのフレーム数です。プログラムで取得することも可能ですが、今回は直接値を設定しました。のちのコードでフレーム数を取得するプログラムも紹介します。
どうしても再生を実装した場合は、AnimationControllerをクラス継承せずに、AnimationControllerを初期化時にそのまま使用してください。
継承した利点はクラス内に機能を拡張できる点ですかね。Extensionを使えばAnimationControllerを使用しても機能拡張はできると思います。
gifのスピード可変するソースコード 初期化実行。
@override
void initState() {
super.initState();
widget.controller.addListener(_listener);
}
widget.controller.addListener(_listener); 先ほどの継承したコントローラが_listenerという関数を監視状態に入れます。
void _listener() {
if (_curIndex != widget.controller.value &&
!widget.controller.value.isInfinite) {
if (mounted) {
setState(() {
if ((widget.controller.value <= _curIndex)) {
widget.controller.streamSize.sink.add(0);
}
_curIndex = widget.controller.value.toInt();
});
}
}
}
ifが多くて申し訳ないのですが、この関数は再生中に値を付与し続けて、widget.controller.valueの値もアニメーションが1周するまで増加し続けます。なのでifで判定する場合、アニメーションが完了前は増加しているので、sink.addを実行せず、_curIndex値が同じもしくは多くなった場合にwidget.controller.streamSize.sink.add(0);を実行します。実行すると画面遷移する機能がプログラムを呼び出します。
初期化時にdidChangeDependencies関数も実行されます。
@override
void didChangeDependencies() {
super.didChangeDependencies();
fetchGif(widget.image).then((imageInfors) {
if (mounted) {
setState(() {
_infos = imageInfors;
});
}
});
}
thenという部分は関数実行完了時にインデント内を実行します。_infosはImageInfoというList型です。初期化時に最初の値をinfosに付与する役目です。
fetchGif関数がフレームレートを計算し、取得した画像データからフレーム数を設定し、そのフレーム数に応じてコマ送り画像のような画像データをinfos配列に付与します。
Future<List<ImageInfo>?> fetchGif(ImageProvider provider) async {
List<ImageInfo>? infos = [];
dynamic data;
if (provider is AssetImage) {
AssetBundleImageKey key =
await provider.obtainKey(const ImageConfiguration());
data = await key.bundle.load(key.name);
}
final codec = await instantiateImageCodec(data.buffer.asUint8List());
infos = [];
for (int i = 0; i < codec.frameCount; i++) {
var frameInfo = await codec.getNextFrame();
infos.add(ImageInfo(image: frameInfo.image));
}
return infos;
}
if (provider is AssetImage)の箇所。今回はAsset設定した画像のみ指定していますが、URLなどのimageも実装可能です。
data = await key.bundle.load(key.name); プログラム内の画像URLパスを取得します。
final codec = await instantiateImageCodec(data.buffer.asUint8List()); 取得したdataはCodecというgifデータ、フレーム数を取得する型に代入してます。
ここでフレームレート数をfor文で回し、gif内のデータ画像を一コマづつinfosに設定しています。フレーム数設定後 infosの値を先ほどのthenの箇所に設定します。
値が変化するとgetパラメ-タ-も同時に呼び出します。その際に_infosと現在のフレーム要素のイメージを設定します。
ImageInfo? get _imageInfo {
return _infos == null ? null : _infos?[_curIndex];
}
設定された_imageInfoはimageで表示しているデータとしてレンダリングします。
@override
Widget build(BuildContext context) {
final RawImage image = RawImage(
image: _imageInfo?.image,
width: widget.width,
height: widget.height,
scale: _imageInfo?.scale ?? 1.0,
);
return image;
}
@override
void dispose() {
widget.c.dispose();
super.dispose();
}
画面遷移する場合などdispose();を実行します。
このコードはgifの機能とは直接関係ないです。
_listener関数の紹介箇所でwidget.controller.streamSize.sink.add(0);をしていますが、_controller.streamSize.stream.listenのインデント内が実行します。付与した値が0なので画面遷移します。0ではない場合は再生スピードを付与してます。
@override
void initState() {
super.initState();
_controller = widget.c;
_controller.streamSize.stream.listen((value) {
if (value == 0) {
widget.c
..reset()
..stop();
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const SubMainPage(
title: 'EveryDaySoft',
),
));
} else {
widget.c
.repeat(min: 0, max: 100, period: Duration(milliseconds: value));
}
});
}
全体の機能イメージは画像データごとに高速回転しながらデータを設定し、設定したことにより画像を設定、表示し、データのフレーム数に応じて完了を判定するようなアーキテクチャです。
さらに細かく書くのと、gif以外の部分も解説すると、題名の内容ではなくなってしまうので、gif再生表示させる箇所にフォーカスさせていただきました。
皆様も来年も良いお年をお過ごしください。
貴重なお時間お読みくださいまして、誠にありがとうございます。
No responses yet