import 'dart:async'; import 'package:EasyDartModule/EasyDartModule.dart' as edm; import 'package:ef/ef.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_svg/svg.dart'; import 'package:flutterflow_ui/flutterflow_ui.dart'; import 'package:vbvs_app/common/color/appConstants.dart'; import 'package:vbvs_app/common/util/CommonVariables.dart'; import 'package:vbvs_app/common/util/FitTool.dart'; import 'package:vbvs_app/common/util/MyUtils.dart'; import 'package:vbvs_app/component/tool/ClickableContainer.dart'; import 'package:vbvs_app/controller/device/blueteeth_bind_controller.dart'; import 'package:vbvs_app/controller/device/device_type_controller.dart'; import 'package:vbvs_app/controller/main_bottom/global_controller.dart'; import 'package:vbvs_app/controller/theme_controller/ThemeController.dart'; import 'package:vbvs_app/controller/user_info_controller.dart'; import 'package:vbvs_app/model/WebSocketMessage.dart'; import 'package:vbvs_app/pages/device/component/DeviceStatusInfoWidget.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/painting.dart'; import 'dart:ui' as ui; class InstantBodyPage extends StatefulWidget { var personInfo; InstantBodyPage({super.key, required this.personInfo}); @override State createState() => _InstantBodyPageState(); } class _InstantBodyPageState extends State with WidgetsBindingObserver { GlobalController globalController = Get.find(); UserInfoController userInfoController = Get.find(); BlueteethBindController blueteethBindController = Get.find(); ThemeController themeController = Get.find(); DeviceTypeController deviceTypeController = Get.find(); int maxBodyMotion = 1; // String breathState = "否"; // String inBed = "离床".tr; // String onlineState = "离线".tr; // Timer? _onlineTimer; // 添加 Timer 引用 // int bodyMotion = 0; // int breathrate = 0; // String snores = "否".tr; // int heartrate = 0; String breathState = "未知数据".tr; String inBed = "未知数据".tr; String onlineState = "离线".tr; Timer? _onlineTimer; // 添加 Timer 引用 int bodyMotion = -1; int breathrate = -1; String snores = "未知数据".tr; int heartrate = -1; @override void initState() { WidgetsBinding.instance.addObserver(this); // 添加生命周期观察者 // edm.EasyDartModule.websocket.sendData(jsonEncode(WebSocketMessage( // path: "/vsbs/web/rt/marttress", // type: 1, // data: {"mac": widget.personInfo['mac']}))); // _startOnlineTimer(); // 初始化时启动定时器 _initWebSocket(); super.initState(); } void _initWebSocket() { // 发送WebSocket请求 edm.EasyDartModule.websocket.sendData(jsonEncode(WebSocketMessage( path: "/vsbs/web/rt/marttress", type: 1, data: {"mac": widget.personInfo['mac']}))); _startOnlineTimer(); } //y @override void dispose() { WidgetsBinding.instance.removeObserver(this); // 移除生命周期观察者 _onlineTimer?.cancel(); _closeWebSocket(); super.dispose(); } // 监听应用生命周期变化 @override void didChangeAppLifecycleState(AppLifecycleState state) { if (state == AppLifecycleState.resumed) { // 应用回到前台时重新连接WebSocket edm.EasyDartModule.logger.info("app切回页面,重连websocket"); _initWebSocket(); } else if (state == AppLifecycleState.paused) { // 应用进入后台时关闭WebSocket _closeWebSocket(); } } void _startOnlineTimer() { _onlineTimer?.cancel(); // 取消之前的定时器 _onlineTimer = Timer.periodic(Duration(seconds: 30), (timer) { if (mounted) { setState(() { edm.EasyDartModule.logger.info("30 秒内没有接收到数据,设置为离线"); onlineState = "离线".tr; // 30 秒内没有接收到数据,设置为离线 inBed = "未知数据".tr; bodyMotion = -1; heartrate = -1; snores = "未知数据".tr; breathrate = -1; breathState = "未知数据".tr; }); } }); } @override Widget build(BuildContext context) { Map device = widget.personInfo; CommonVariables.callMap["/vsbs/web/rt/marttress"] = (data) { inBed = data["inBed"]; // 心率 呼吸 体动 呼吸暂停 if ("离床" == inBed) { breathState = "否"; data["breathRate"] = 0; data["heartRate"] = 0; data["bodyMotion"] = 0; } else { breathState = data["breathState"]; bodyMotion = data['bodyMotion']; breathrate = data["breathRate"]; heartrate = data['heartRate']; snores = data['snores']; } if (mounted) { setState(() { onlineState = "在线".tr; // 接收到数据,设置为在线 }); } _startOnlineTimer(); // 重置定时器 }; return LayoutBuilder( builder: (context, bodySize) => GestureDetector( onTap: () => FocusScope.of(context).unfocus(), child: Container( decoration: BoxDecoration( image: DecorationImage( image: AssetImage('assets/img/bgNoImg.png'), fit: BoxFit.fill, ), ), child: Scaffold( backgroundColor: Colors.transparent, appBar: AppBar( backgroundColor: themeController.currentColor.sc17, automaticallyImplyLeading: false, iconTheme: IconThemeData(color: themeController.currentColor.sc3), titleSpacing: 0.rpx, title: Container( width: double.infinity, height: 180.rpx, child: Stack( alignment: Alignment.center, children: [ RichText( text: TextSpan( style: TextStyle( fontFamily: 'Readex Pro', color: themeController.currentColor.sc3, fontSize: 30.rpx, letterSpacing: 0.0, ), children: [ TextSpan( text: '实时体征.标题'.tr, ), TextSpan( text: "(${onlineState})", style: TextStyle( color: onlineState == '在线' ? themeController.currentColor.sc2 : themeController .currentColor.sc9, // 👈 单独设置颜色 ), ), ], ), ), Positioned( left: 0.rpx, child: returnIconButtom, ), ], ), ), actions: [], centerTitle: false, ), body: SafeArea( top: true, // child: Container( // decoration: BoxDecoration( // image: DecorationImage( // image: AssetImage( // (onlineState == "离线".tr || inBed == '离床'.tr) // ? 'assets/img/black_body_still.png' // 静态图 // : 'assets/img/body_black.gif', // 动图 // ), // fit: BoxFit.cover, // ), // ), // child: Padding( // padding: EdgeInsetsDirectional.fromSTEB( // 0.rpx, 29.rpx, 0.rpx, 0.rpx), // child: Column( // mainAxisSize: MainAxisSize.max, // children: [ // Padding( // padding: EdgeInsetsDirectional.fromSTEB( // 30.rpx, 0.rpx, 30.rpx, 120.rpx), // child: ClickableContainer( // backgroundColor: themeController.currentColor.sc5, // highlightColor: // themeController.currentColor.sc5, // 或你希望的点击水波纹颜色 // borderRadius: AppConstants() // .normal_container_radius, // 如果你想加圆角可以设置 eg. 12.rpx // padding: EdgeInsets.zero, // onTap: () { // print('点击了体征卡片'); // }, // child: Row( // mainAxisSize: MainAxisSize.max, // children: [ // Flexible( // flex: 2, // child: Column( // crossAxisAlignment: CrossAxisAlignment.start, // children: [ // Row( // children: [ // Column( // crossAxisAlignment: // CrossAxisAlignment.end, // children: [ // Text( // '实时体征.姓名'.tr, // style: // FlutterFlowTheme.of(context) // .bodyMedium // .override( // fontFamily: 'Inter', // fontSize: 26.rpx, // letterSpacing: 0.0, // color: themeController // .currentColor.sc4, // ), // ), // Text( // '实时体征.年龄'.tr, // style: // FlutterFlowTheme.of(context) // .bodyMedium // .override( // fontFamily: 'Inter', // fontSize: 26.rpx, // letterSpacing: 0.0, // color: themeController // .currentColor.sc4, // ), // ), // ].divide(SizedBox(height: 34.rpx)), // ), // Column( // crossAxisAlignment: // CrossAxisAlignment.start, // children: [ // Text( // '${device['person']?['name'] ?? '未命名'.tr}', // style: // FlutterFlowTheme.of(context) // .bodyMedium // .override( // fontFamily: 'Inter', // fontSize: 26.rpx, // letterSpacing: 0.0, // color: themeController // .currentColor.sc3, // ), // ), // Text( // '${MyUtils.getAgeByDate(MyUtils.formatBirthdayTime(device['person']?['birthday'])) ?? '未知数据'.tr}', // style: // FlutterFlowTheme.of(context) // .bodyMedium // .override( // fontFamily: 'Inter', // fontSize: 26.rpx, // letterSpacing: 0.0, // color: themeController // .currentColor.sc3, // ), // ), // ].divide(SizedBox(height: 34.rpx)), // ), // ] // .divide(SizedBox(width: 33.rpx)) // .addToStart(SizedBox(width: 37.rpx)), // ), // ] // .addToStart(SizedBox(height: 36.rpx)) // .addToEnd(SizedBox(height: 36.rpx)), // ), // ), // Flexible( // flex: 3, // child: Column( // crossAxisAlignment: CrossAxisAlignment.start, // children: [ // Row( // children: [ // Column( // crossAxisAlignment: // CrossAxisAlignment.end, // children: [ // Text( // '实时体征.设备ID'.tr, // style: // FlutterFlowTheme.of(context) // .bodyMedium // .override( // fontFamily: 'Inter', // fontSize: 26.rpx, // letterSpacing: 0.0, // color: themeController // .currentColor.sc4, // ), // ), // Text( // '实时体征.体重'.tr, // style: // FlutterFlowTheme.of(context) // .bodyMedium // .override( // fontFamily: 'Inter', // fontSize: 26.rpx, // letterSpacing: 0.0, // color: themeController // .currentColor.sc4, // ), // ), // ].divide(SizedBox(height: 34.rpx)), // ), // Expanded( // child: Column( // crossAxisAlignment: // CrossAxisAlignment.start, // children: [ // Text( // '${device['code'] ?? '未知数据'.tr}', // // "D11250300003", // style: // FlutterFlowTheme.of(context) // .bodyMedium // .override( // fontFamily: 'Inter', // fontSize: 26.rpx, // letterSpacing: 0.0, // color: themeController // .currentColor.sc3, // ), // maxLines: 1, // overflow: TextOverflow.ellipsis, // ), // Text( // '${device['person']?['weight'] ?? '未知数据'.tr}kg', // style: // FlutterFlowTheme.of(context) // .bodyMedium // .override( // fontFamily: 'Inter', // fontSize: 26.rpx, // letterSpacing: 0.0, // color: themeController // .currentColor.sc3, // ), // ), // ].divide(SizedBox(height: 34.rpx)), // ), // ), // ] // .divide(SizedBox(width: 33.rpx)) // .addToStart(SizedBox(width: 37.rpx)), // ), // ] // .addToStart(SizedBox(height: 36.rpx)) // .addToEnd(SizedBox(height: 36.rpx)), // ), // ), // ], // ), // ), // ), // Expanded( // child: SingleChildScrollView( // child: Column( // children: [ // Padding( // padding: EdgeInsetsDirectional.fromSTEB( // 66.rpx, 0, 66.rpx, 0), // child: Container( // // decoration: BoxDecoration( // // image: DecorationImage( // // image: AssetImage( // // onlineState == "离线".tr // // ? 'assets/img/black_body_still.png' // 静态图 // // : 'assets/img/body_black.gif', // 动图 // // ), // // fit: BoxFit.cover, // // ), // // ), // child: Column( // children: [ // Row( // mainAxisAlignment: // MainAxisAlignment.spaceBetween, // children: [ // DeviceStatusInfoWidget( // title: "在离床".tr, // iconAsset: // "assets/img/icon/bed_status.svg", // value: inBed, // ), // DeviceStatusInfoWidget( // title: "体动".tr, // iconAsset: // "assets/img/icon/bodymotion.svg", // value: (bodyMotion == null || // bodyMotion == -1) // ? "未知数据".tr // : "$bodyMotion", // ), // ], // ), // Row( // mainAxisAlignment: // MainAxisAlignment.spaceBetween, // children: [ // DeviceStatusInfoWidget( // title: "心率".tr, // iconAsset: // "assets/img/icon/heart.svg", // value: (heartrate == null || // heartrate == -1) // ? "未知数据".tr // : "$heartrate", // ), // DeviceStatusInfoWidget( // title: "打鼾".tr, // iconAsset: // "assets/img/icon/snore.svg", // value: '${snores}'.tr, // ), // ], // ), // Row( // mainAxisAlignment: // MainAxisAlignment.spaceBetween, // children: [ // DeviceStatusInfoWidget( // title: "呼吸".tr, // iconAsset: // "assets/img/icon/breathe.svg", // value: (breathrate == null || // breathrate == -1) // ? "未知数据".tr // : "$breathrate", // ), // DeviceStatusInfoWidget( // title: "呼吸暂停".tr, // iconAsset: // "assets/img/icon/breathe_pause.svg", // value: '${breathState}', // ), // ], // ), // ].divide(SizedBox(height: 49.rpx)), // ), // ), // ), // Padding( // padding: EdgeInsetsDirectional.fromSTEB( // 0.rpx, 67.rpx, 0.rpx, 0.rpx), // child: Container( // height: 40.rpx, // child: Text( // bodyMotion >= maxBodyMotion ? '请保持静止'.tr : "", // style: FlutterFlowTheme.of(context) // .bodyMedium // .override( // fontFamily: 'Inter', // fontSize: 26.rpx, // letterSpacing: 0.0, // color: themeController.currentColor.sc9, // ), // ), // ), // ), // // SizedBox( // // height: 207.rpx, // // ), // ], // ), // )), // ClickableContainer( // backgroundColor: Colors.transparent, // 可自定义背景色 // highlightColor: Colors.white, // 点击涟漪颜色 // borderRadius: 16.rpx, // 圆角大小,可按需调整 // padding: EdgeInsetsDirectional.fromSTEB( // 30.rpx, 0.rpx, 30.rpx, 0.rpx), // onTap: () {}, // child: Container( // padding: EdgeInsetsDirectional.fromSTEB( // 26.rpx, 26.rpx, 26.rpx, 26.rpx), // decoration: BoxDecoration( // // color: FlutterFlowTheme.of(context) // // .primaryBackground // // .withOpacity(0.6), // 半透明背景 // borderRadius: BorderRadius.circular(16.rpx), // border: Border.all( // color: themeController.currentColor.sc4 // .withOpacity(0.5), // width: 0.5.rpx, // ), // ), // child: Row( // crossAxisAlignment: CrossAxisAlignment.start, // children: [ // Padding( // padding: EdgeInsetsDirectional.fromSTEB( // 0, 8.rpx, 0, 0), // child: Container( // width: 23.rpx, // height: 23.rpx, // // width: double.infinity, // decoration: BoxDecoration(), // child: SvgPicture.asset( // 'assets/img/icon/tips.svg', // fit: BoxFit.cover, // color: themeController.currentColor.sc4, // ), // ), // ), // Expanded( // child: Text( // '实时体征.提示'.tr, // style: FlutterFlowTheme.of(context) // .bodyMedium // .override( // fontFamily: 'Inter', // letterSpacing: 0.0, // color: themeController.currentColor.sc4, // ), // ), // ), // ].divide(SizedBox(width: 23.rpx)), // ), // ), // ), // SizedBox( // height: 26.rpx, // ), // ], // ), // ), // ), child: Stack( children: [ // 背景图只占一半高度 // Align( // alignment: Alignment.center, // child: SizedBox( // height: 1000.rpx, // 你可以根据需要调整图片最大高度 // width: 1000.rpx, // 或设置 double.infinity 占满宽度 // child: Image.asset( // (onlineState == "离线".tr || inBed == '离床'.tr) // ? 'assets/img/black_body_still.png' // : 'assets/img/body_black.gif', // fit: BoxFit.contain, // 保持原始比例,不拉伸 // ), // ), // ), Align( alignment: Alignment.center, child: FractionallySizedBox( heightFactor: 0.5, widthFactor: 0.5, child: Opacity( opacity: 0.7, child: (onlineState == "离线".tr || inBed == '离床'.tr) ? Image.asset( 'assets/img/black_body_still.png', fit: BoxFit.contain, ) : SpeedControlledGif( 'assets/img/body_black.gif', speedFactor: 2, // 2.0 for 2x speed, 0.5 for half speed fit: BoxFit.contain, ), ), ), ), // 其余内容放在上层 Positioned.fill( child: Padding( padding: EdgeInsetsDirectional.fromSTEB( 0.rpx, 29.rpx, 0.rpx, 0.rpx), child: Column( mainAxisSize: MainAxisSize.max, children: [ Padding( padding: EdgeInsetsDirectional.fromSTEB( 30.rpx, 0.rpx, 30.rpx, 120.rpx), child: ClickableContainer( backgroundColor: themeController.currentColor.sc5, highlightColor: themeController .currentColor.sc5, // 或你希望的点击水波纹颜色 borderRadius: AppConstants() .normal_container_radius, // 如果你想加圆角可以设置 eg. 12.rpx padding: EdgeInsets.zero, onTap: () { print('点击了体征卡片'); }, child: Row( mainAxisSize: MainAxisSize.max, children: [ Flexible( flex: 2, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Column( crossAxisAlignment: CrossAxisAlignment.end, children: [ Text( '实时体征.姓名'.tr, style: TextStyle( fontFamily: 'Inter', fontSize: 26.rpx, letterSpacing: 0.0, color: themeController .currentColor.sc4, ), ), Text( '实时体征.年龄'.tr, style: TextStyle( fontFamily: 'Inter', fontSize: 26.rpx, letterSpacing: 0.0, color: themeController .currentColor.sc4, ), ), ].divide( SizedBox(height: 34.rpx)), ), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( '${device['person']?['name'] ?? '未命名'.tr}', style: TextStyle( fontFamily: 'Inter', fontSize: 26.rpx, letterSpacing: 0.0, color: themeController .currentColor.sc3, ), ), Text( '${MyUtils.getAgeByDate(MyUtils.formatBirthdayTime(device['person']?['birthday'])) ?? '未知数据'.tr}', style: TextStyle( fontFamily: 'Inter', fontSize: 26.rpx, letterSpacing: 0.0, color: themeController .currentColor.sc3, ), ), ].divide( SizedBox(height: 34.rpx)), ), ] .divide(SizedBox(width: 33.rpx)) .addToStart( SizedBox(width: 37.rpx)), ), ] .addToStart(SizedBox(height: 36.rpx)) .addToEnd(SizedBox(height: 36.rpx)), ), ), Flexible( flex: 3, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Column( crossAxisAlignment: CrossAxisAlignment.end, children: [ Text( '实时体征.设备ID'.tr, style: TextStyle( fontFamily: 'Inter', fontSize: 26.rpx, letterSpacing: 0.0, color: themeController .currentColor.sc4, ), ), Text( '实时体征.体重'.tr, style: TextStyle( fontFamily: 'Inter', fontSize: 26.rpx, letterSpacing: 0.0, color: themeController .currentColor.sc4, ), ), ].divide( SizedBox(height: 34.rpx)), ), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( '${device['code'] ?? '未知数据'.tr}', // "D11250300003", style: TextStyle( fontFamily: 'Inter', fontSize: 26.rpx, letterSpacing: 0.0, color: themeController .currentColor.sc3, ), maxLines: 1, overflow: TextOverflow.ellipsis, ), Text( '${device['person']?['weight'] ?? '未知数据'.tr}kg', style: TextStyle( fontFamily: 'Inter', fontSize: 26.rpx, letterSpacing: 0.0, color: themeController .currentColor.sc3, ), ), ].divide( SizedBox(height: 34.rpx)), ), ), ] .divide(SizedBox(width: 33.rpx)) .addToStart( SizedBox(width: 37.rpx)), ), ] .addToStart(SizedBox(height: 36.rpx)) .addToEnd(SizedBox(height: 36.rpx)), ), ), ], ), ), ), Expanded( child: SingleChildScrollView( child: Column( children: [ Padding( padding: EdgeInsetsDirectional.fromSTEB( 66.rpx, 0, 66.rpx, 0), child: Container( // decoration: BoxDecoration( // image: DecorationImage( // image: AssetImage( // onlineState == "离线".tr // ? 'assets/img/black_body_still.png' // 静态图 // : 'assets/img/body_black.gif', // 动图 // ), // fit: BoxFit.cover, // ), // ), child: Column( children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ DeviceStatusInfoWidget( title: "在离床".tr, iconAsset: "assets/img/icon/bed_status.svg", value: inBed, ), DeviceStatusInfoWidget( title: "体动".tr, iconAsset: "assets/img/icon/bodymotion.svg", value: (bodyMotion == null || bodyMotion == -1) ? "未知数据".tr : "$bodyMotion", ), ], ), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ DeviceStatusInfoWidget( title: "心率".tr, iconAsset: "assets/img/icon/heart.svg", value: (heartrate == null || heartrate == -1) ? "未知数据".tr : "$heartrate", ), DeviceStatusInfoWidget( title: "打鼾".tr, iconAsset: "assets/img/icon/snore.svg", value: '${snores}'.tr, ), ], ), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ DeviceStatusInfoWidget( title: "呼吸".tr, iconAsset: "assets/img/icon/breathe.svg", value: (breathrate == null || breathrate == -1) ? "未知数据".tr : "$breathrate", ), DeviceStatusInfoWidget( title: "呼吸暂停".tr, iconAsset: "assets/img/icon/breathe_pause.svg", value: '${breathState}', ), ], ), ].divide(SizedBox(height: 49.rpx)), ), ), ), Padding( padding: EdgeInsetsDirectional.fromSTEB( 0.rpx, 67.rpx, 0.rpx, 0.rpx), child: Container( height: 40.rpx, child: Text( bodyMotion >= maxBodyMotion ? '请保持静止'.tr : "", style: TextStyle( fontFamily: 'Inter', fontSize: 26.rpx, letterSpacing: 0.0, color: themeController.currentColor.sc9, ), ), ), ), // SizedBox( // height: 207.rpx, // ), ], ), )), ClickableContainer( backgroundColor: Colors.transparent, // 可自定义背景色 highlightColor: Colors.white, // 点击涟漪颜色 borderRadius: 16.rpx, // 圆角大小,可按需调整 padding: EdgeInsetsDirectional.fromSTEB( 30.rpx, 0.rpx, 30.rpx, 0.rpx), onTap: () {}, child: Container( padding: EdgeInsetsDirectional.fromSTEB( 26.rpx, 26.rpx, 26.rpx, 26.rpx), decoration: BoxDecoration( // color: FlutterFlowTheme.of(context) // .primaryBackground // .withOpacity(0.6), // 半透明背景 borderRadius: BorderRadius.circular(16.rpx), border: Border.all( color: themeController.currentColor.sc4 .withOpacity(0.5), width: 0.5.rpx, ), ), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( padding: EdgeInsetsDirectional.fromSTEB( 0, 8.rpx, 0, 0), child: Container( width: 23.rpx, height: 23.rpx, // width: double.infinity, decoration: BoxDecoration(), child: SvgPicture.asset( 'assets/img/icon/tips.svg', fit: BoxFit.cover, color: themeController.currentColor.sc4, ), ), ), Expanded( child: Text( '实时体征.提示'.tr, style: TextStyle( fontFamily: 'Inter', letterSpacing: 0.0, color: themeController.currentColor.sc4, ), ), ), ].divide(SizedBox(width: 23.rpx)), ), ), ), SizedBox( height: 26.rpx, ), ], ), ), ), ], ), ), ), ), ), ); } void _closeWebSocket() { // 取消WebSocket订阅 edm.EasyDartModule.websocket.sendData( jsonEncode(WebSocketMessage(path: "/vsbs/web/rt/marttress", type: 2))); _onlineTimer?.cancel(); } } class SpeedControlledGif extends StatefulWidget { final String assetPath; final double speedFactor; final BoxFit? fit; /// [speedFactor] 播放速度倍数,默认1.0, /// 大于1表示加速播放,小于1表示减速播放 const SpeedControlledGif( this.assetPath, { Key? key, this.speedFactor = 1.0, this.fit, }) : super(key: key); @override _SpeedControlledGifState createState() => _SpeedControlledGifState(); } class _SpeedControlledGifState extends State { ui.Codec? _codec; ui.FrameInfo? _currentFrame; Timer? _timer; bool _isDisposed = false; @override void initState() { super.initState(); _loadGif(); } Future _loadGif() async { final data = await rootBundle.load(widget.assetPath); _codec = await ui.instantiateImageCodec(data.buffer.asUint8List()); if (_isDisposed) return; _showNextFrame(); } Future _showNextFrame() async { if (_isDisposed || _codec == null) return; _currentFrame = await _codec!.getNextFrame(); if (_isDisposed) return; if (mounted) { setState(() {}); } // 取当前帧持续时间,单位毫秒 final baseDuration = _currentFrame?.duration.inMilliseconds ?? 100; // 限制最小帧间隔,防止刷新过快 const minFrameDuration = 50; // 计算实际播放帧间隔,speedFactor越大速度越快 final adjustedDuration = (baseDuration / widget.speedFactor) .round() .clamp(minFrameDuration, 10000); _timer = Timer(Duration(milliseconds: adjustedDuration), _showNextFrame); } @override void dispose() { _isDisposed = true; _timer?.cancel(); _codec?.dispose(); super.dispose(); } @override Widget build(BuildContext context) { if (_currentFrame == null) { // 加载中或无帧时显示空容器或占位 return Container(); } return RawImage( image: _currentFrame!.image, fit: widget.fit, ); } }