395 lines
11 KiB
Dart
395 lines
11 KiB
Dart
import 'package:flutter/material.dart';
|
||
import 'package:flutter/foundation.dart';
|
||
import 'package:flutter/services.dart';
|
||
|
||
class PAGView extends StatefulWidget {
|
||
/// 宽高,不建议不设置
|
||
final double? width;
|
||
final double? height;
|
||
|
||
/// 二进制动画数据
|
||
final Uint8List? bytesData;
|
||
|
||
/// 网络资源,动画链接
|
||
final String? url;
|
||
|
||
/// flutter动画资源路径
|
||
final String? assetName;
|
||
|
||
/// asset package
|
||
final String? package;
|
||
|
||
/// 初始化时播放进度
|
||
final double initProgress;
|
||
|
||
/// 初始化后自动播放
|
||
final bool autoPlay;
|
||
|
||
/// 循环次数
|
||
final int repeatCount;
|
||
|
||
/// 初始化完成
|
||
final PAGCallback? onInit;
|
||
|
||
/// Notifies the start of the animation.
|
||
final PAGCallback? onAnimationStart;
|
||
|
||
/// Notifies the end of the animation.
|
||
final PAGCallback? onAnimationEnd;
|
||
|
||
/// Notifies the cancellation of the animation.
|
||
final PAGCallback? onAnimationCancel;
|
||
|
||
/// Notifies the repetition of the animation.
|
||
final PAGCallback? onAnimationRepeat;
|
||
|
||
/// 加载失败时的默认控件构造器
|
||
final Widget Function(BuildContext context)? defaultBuilder;
|
||
|
||
/// Replace editable PAG image layers by layer name before playback starts.
|
||
final Map<String, Uint8List> imageReplacementsByName;
|
||
|
||
/// Uses the native iOS PAGView instead of the texture renderer.
|
||
///
|
||
/// This is intended for local PAG assets on iOS where the texture renderer can
|
||
/// report playback events while presenting transparent frames.
|
||
final bool usePlatformView;
|
||
|
||
static const int REPEAT_COUNT_LOOP = -1; //无限循环
|
||
static const int REPEAT_COUNT_DEFAULT = 1; //默认仅播放一次
|
||
|
||
const PAGView.network(
|
||
this.url, {
|
||
this.width,
|
||
this.height,
|
||
this.repeatCount = REPEAT_COUNT_DEFAULT,
|
||
this.initProgress = 0,
|
||
this.autoPlay = false,
|
||
this.onInit,
|
||
this.onAnimationStart,
|
||
this.onAnimationEnd,
|
||
this.onAnimationCancel,
|
||
this.onAnimationRepeat,
|
||
this.defaultBuilder,
|
||
this.imageReplacementsByName = const <String, Uint8List>{},
|
||
this.usePlatformView = false,
|
||
Key? key,
|
||
}) : this.bytesData = null,
|
||
this.assetName = null,
|
||
this.package = null,
|
||
super(key: key);
|
||
|
||
const PAGView.asset(
|
||
this.assetName, {
|
||
this.width,
|
||
this.height,
|
||
this.repeatCount = REPEAT_COUNT_DEFAULT,
|
||
this.initProgress = 0,
|
||
this.autoPlay = false,
|
||
this.package,
|
||
this.onInit,
|
||
this.onAnimationStart,
|
||
this.onAnimationEnd,
|
||
this.onAnimationCancel,
|
||
this.onAnimationRepeat,
|
||
this.defaultBuilder,
|
||
this.imageReplacementsByName = const <String, Uint8List>{},
|
||
this.usePlatformView = false,
|
||
Key? key,
|
||
}) : this.bytesData = null,
|
||
this.url = null,
|
||
super(key: key);
|
||
|
||
const PAGView.bytes(
|
||
this.bytesData, {
|
||
this.width,
|
||
this.height,
|
||
this.repeatCount = REPEAT_COUNT_DEFAULT,
|
||
this.initProgress = 0,
|
||
this.autoPlay = false,
|
||
this.package,
|
||
this.onInit,
|
||
this.onAnimationStart,
|
||
this.onAnimationEnd,
|
||
this.onAnimationCancel,
|
||
this.onAnimationRepeat,
|
||
this.defaultBuilder,
|
||
this.imageReplacementsByName = const <String, Uint8List>{},
|
||
this.usePlatformView = false,
|
||
Key? key,
|
||
}) : this.url = null,
|
||
this.assetName = null,
|
||
super(key: key);
|
||
|
||
@override
|
||
PAGViewState createState() => PAGViewState();
|
||
}
|
||
|
||
class PAGViewState extends State<PAGView> {
|
||
bool _hasLoadTexture = false;
|
||
int _textureId = -1;
|
||
|
||
double rawWidth = 0;
|
||
double rawHeight = 0;
|
||
|
||
static const double defaultSize = 50;
|
||
|
||
// 原生接口
|
||
static const String _nativeInit = 'initPag';
|
||
static const String _nativeRelease = 'release';
|
||
static const String _nativeStart = 'start';
|
||
static const String _nativeStop = 'stop';
|
||
static const String _nativePause = 'pause';
|
||
static const String _nativeSetProgress = 'setProgress';
|
||
static const String _nativeGetPointLayer = 'getLayersUnderPoint';
|
||
static const String _nativeEnableCache = "enableCache";
|
||
static const String _nativeSetCacheSize = "setCacheSize";
|
||
static const String _nativeEnableMultiThread = "enableMultiThread";
|
||
|
||
// 参数
|
||
static const String _argumentTextureId = 'textureId';
|
||
static const String _argumentAssetName = 'assetName';
|
||
static const String _argumentPackage = 'package';
|
||
static const String _argumentUrl = 'url';
|
||
static const String _argumentBytes = 'bytesData';
|
||
static const String _argumentRepeatCount = 'repeatCount';
|
||
static const String _argumentInitProgress = 'initProgress';
|
||
static const String _argumentAutoPlay = 'autoPlay';
|
||
static const String _argumentWidth = 'width';
|
||
static const String _argumentHeight = 'height';
|
||
static const String _argumentPointX = 'x';
|
||
static const String _argumentPointY = 'y';
|
||
static const String _argumentProgress = 'progress';
|
||
static const String _argumentEvent = 'PAGEvent';
|
||
static const String _argumentCacheEnabled = "cacheEnabled";
|
||
static const String _argumentCacheSize = "cacheSize";
|
||
static const String _argumentMultiThreadEnabled = "multiThreadEnabled";
|
||
static const String _argumentImageReplacementsByName =
|
||
'imageReplacementsByName';
|
||
static const String _platformViewType = 'flutter_pag_plugin_view';
|
||
|
||
// 监听该函数
|
||
static const String _playCallback = 'PAGCallback';
|
||
static const String _eventStart = 'onAnimationStart';
|
||
static const String _eventEnd = 'onAnimationEnd';
|
||
static const String _eventCancel = 'onAnimationCancel';
|
||
static const String _eventRepeat = 'onAnimationRepeat';
|
||
static const String _eventUpdate = 'onAnimationUpdate';
|
||
|
||
// 回调监听
|
||
static MethodChannel _channel = (const MethodChannel('flutter_pag_plugin')
|
||
..setMethodCallHandler((result) {
|
||
if (result.method == _playCallback) {
|
||
callbackHandlers[result.arguments[_argumentTextureId]]
|
||
?.call(result.arguments[_argumentEvent]);
|
||
}
|
||
|
||
return Future<dynamic>.value();
|
||
}));
|
||
|
||
static Map<int, Function(String event)?> callbackHandlers = {};
|
||
|
||
@override
|
||
void initState() {
|
||
super.initState();
|
||
if (!_usesPlatformView) {
|
||
newTexture();
|
||
}
|
||
}
|
||
|
||
bool get _usesPlatformView =>
|
||
widget.usePlatformView && defaultTargetPlatform == TargetPlatform.iOS;
|
||
|
||
Map<String, dynamic> get _creationParams {
|
||
final repeatCount = widget.repeatCount <= 0 &&
|
||
widget.repeatCount != PAGView.REPEAT_COUNT_LOOP
|
||
? PAGView.REPEAT_COUNT_DEFAULT
|
||
: widget.repeatCount;
|
||
final initProcess = widget.initProgress < 0 ? 0 : widget.initProgress;
|
||
return <String, dynamic>{
|
||
_argumentAssetName: widget.assetName,
|
||
_argumentPackage: widget.package,
|
||
_argumentUrl: widget.url,
|
||
_argumentBytes: widget.bytesData,
|
||
_argumentRepeatCount: repeatCount,
|
||
_argumentInitProgress: initProcess,
|
||
_argumentAutoPlay: widget.autoPlay,
|
||
_argumentImageReplacementsByName:
|
||
_validImageReplacements(widget.imageReplacementsByName),
|
||
};
|
||
}
|
||
|
||
Map<String, Uint8List> _validImageReplacements(
|
||
Map<String, Uint8List> replacements,
|
||
) {
|
||
if (replacements.isEmpty) {
|
||
return const <String, Uint8List>{};
|
||
}
|
||
final result = <String, Uint8List>{};
|
||
replacements.forEach((key, value) {
|
||
final layerName = key.trim();
|
||
if (layerName.isNotEmpty && value.isNotEmpty) {
|
||
result[layerName] = value;
|
||
}
|
||
});
|
||
return result;
|
||
}
|
||
|
||
// 初始化
|
||
void newTexture() async {
|
||
try {
|
||
dynamic result =
|
||
await _channel.invokeMethod(_nativeInit, _creationParams);
|
||
if (result is Map) {
|
||
_textureId = result[_argumentTextureId];
|
||
rawWidth = result[_argumentWidth] ?? 0;
|
||
rawHeight = result[_argumentHeight] ?? 0;
|
||
} else {
|
||
throw StateError('initPag failed: $result');
|
||
}
|
||
if (mounted) {
|
||
setState(() {
|
||
_hasLoadTexture = true;
|
||
});
|
||
widget.onInit?.call();
|
||
} else {
|
||
_channel.invokeMethod(_nativeRelease, {_argumentTextureId: _textureId});
|
||
}
|
||
} catch (e) {
|
||
print('PAGViewState error: $e');
|
||
}
|
||
|
||
// 事件回调
|
||
if (_textureId >= 0) {
|
||
var events = <String, PAGCallback?>{
|
||
_eventStart: widget.onAnimationStart,
|
||
_eventEnd: widget.onAnimationEnd,
|
||
_eventCancel: widget.onAnimationCancel,
|
||
_eventRepeat: widget.onAnimationRepeat,
|
||
};
|
||
callbackHandlers[_textureId] = (event) {
|
||
events[event]?.call();
|
||
};
|
||
}
|
||
}
|
||
|
||
/// 开始
|
||
void start() {
|
||
if (_usesPlatformView) {
|
||
return;
|
||
}
|
||
if (!_hasLoadTexture) {
|
||
return;
|
||
}
|
||
_channel.invokeMethod(_nativeStart, {_argumentTextureId: _textureId});
|
||
}
|
||
|
||
/// 停止
|
||
void stop() {
|
||
if (_usesPlatformView) {
|
||
return;
|
||
}
|
||
if (!_hasLoadTexture) {
|
||
return;
|
||
}
|
||
_channel.invokeMethod(_nativeStop, {_argumentTextureId: _textureId});
|
||
}
|
||
|
||
/// 暂停
|
||
void pause() {
|
||
if (_usesPlatformView) {
|
||
return;
|
||
}
|
||
if (!_hasLoadTexture) {
|
||
return;
|
||
}
|
||
_channel.invokeMethod(_nativePause, {_argumentTextureId: _textureId});
|
||
}
|
||
|
||
/// 设置进度
|
||
void setProgress(double progress) {
|
||
if (_usesPlatformView) {
|
||
return;
|
||
}
|
||
if (!_hasLoadTexture) {
|
||
return;
|
||
}
|
||
_channel.invokeMethod(_nativeSetProgress,
|
||
{_argumentTextureId: _textureId, _argumentProgress: progress});
|
||
}
|
||
|
||
/// 获取某一位置的图层
|
||
Future<List<String>> getLayersUnderPoint(double x, double y) async {
|
||
if (_usesPlatformView || !_hasLoadTexture) {
|
||
return [];
|
||
}
|
||
return (await _channel.invokeMethod(_nativeGetPointLayer, {
|
||
_argumentTextureId: _textureId,
|
||
_argumentPointX: x,
|
||
_argumentPointY: y
|
||
}) as List)
|
||
.map((e) => e.toString())
|
||
.toList();
|
||
}
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
if (_usesPlatformView) {
|
||
return SizedBox(
|
||
width: widget.width ?? defaultSize,
|
||
height: widget.height ?? defaultSize,
|
||
child: UiKitView(
|
||
viewType: _platformViewType,
|
||
creationParams: _creationParams,
|
||
creationParamsCodec: const StandardMessageCodec(),
|
||
),
|
||
);
|
||
}
|
||
if (_hasLoadTexture) {
|
||
return SizedBox(
|
||
width: widget.width ?? (rawWidth / 2),
|
||
height: widget.height ?? (rawHeight / 2),
|
||
child: Texture(textureId: _textureId),
|
||
);
|
||
} else {
|
||
return widget.defaultBuilder?.call(context) ??
|
||
SizedBox(
|
||
width: widget.width ?? defaultSize,
|
||
height: widget.height ?? defaultSize,
|
||
);
|
||
}
|
||
}
|
||
|
||
@override
|
||
void dispose() {
|
||
super.dispose();
|
||
if (!_usesPlatformView) {
|
||
_channel.invokeMethod(_nativeRelease, {_argumentTextureId: _textureId});
|
||
callbackHandlers.remove(_textureId);
|
||
}
|
||
}
|
||
}
|
||
|
||
typedef PAGCallback = void Function();
|
||
|
||
// PAG设置
|
||
class PAG {
|
||
// 是否开启缓存,默认true
|
||
static void enableCache(bool enable) {
|
||
PAGViewState._channel.invokeMethod(PAGViewState._nativeEnableCache,
|
||
{PAGViewState._argumentCacheEnabled: enable});
|
||
}
|
||
|
||
// 是否多线程加载和释放资源,默认true
|
||
static void enableMultiThread(bool enable) {
|
||
PAGViewState._channel.invokeMethod(PAGViewState._nativeEnableMultiThread,
|
||
{PAGViewState._argumentMultiThreadEnabled: enable});
|
||
}
|
||
|
||
// 设置缓存数量,默认10
|
||
static void setCacheSize(int size) {
|
||
PAGViewState._channel.invokeMethod(PAGViewState._nativeSetCacheSize,
|
||
{PAGViewState._argumentCacheSize: size});
|
||
}
|
||
}
|