Flutter BottomSheet内Slider.adaptive不更新,_position数据无法传递
问题描述
我在BottomSheet中使用Slider.adaptive组件,即便已经使用了StatefulBuilder,视图仍无法更新。简化后的代码如下:
Duration _duration = const Duration(milliseconds: 0); Duration _position = const Duration(milliseconds: 0); @override Widget build(BuildContext context) { return PopScope( canPop: false, onPopInvoked: (bool didPop) async { if (isPlaying == true) { setState(() async { await audioPlayer.stop(); }); } }, child: Scaffold( body: SafeArea( child: Center( child: Text('Testing Screen'), ) ), bottomNavigationBar: BottomAppBar( padding: EdgeInsets.zero, color: Colors.black, height: 130, child: Column( children: [ IconButton( icon: const FaIcon( FontAwesomeIcons.headphones, color: Colors.white70, ), onPressed: () async { _openAudioBottomSheet(); }, ), ] ), ), ), ); } void _openAudioBottomSheet() { showModalBottomSheet( shape: const RoundedRectangleBorder( borderRadius: BorderRadius.vertical(top: Radius.circular(20.0)), ), context: context, showDragHandle: true, builder: (BuildContext context) { return StatefulBuilder( builder: (BuildContext context, StateSetter setState) { return SingleChildScrollView( child: Padding( padding: const EdgeInsets.all(20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ Slider.adaptive( min: 0, max: _duration.inSeconds.toDouble(), value: _position.inSeconds.toDouble(), onChanged: (value) async { debugPrint('value is:: $value'); setState(() { _position = Duration(seconds: value.toInt()); }); }, ), ], ), ), ); } ); } ); }
请问如何解决视图更新的问题?
更新
我刚刚发现问题大概率并非onChanged方法的问题,而是_position数据无法传递到showModalBottomSheet中!看起来_position的更新时长无法同步到底部弹窗,导致滑块无法前进。以下是我的音频播放器初始化代码:
@override void initState() { super.initState(); initAudioPlayer(); } initAudioPlayer() async { audioPlayer.setAudioContext( const AudioContext( android: AudioContextAndroid(), iOS: AudioContextIOS(), ) ); audioPlayer.onPlayerStateChanged.listen((PlayerState event) { setState(() { isPlaying = event == PlayerState.playing; audioPlayer.setVolume(_volume); if (PlayerState == PlayerState.completed || PlayerState == PlayerState.stopped || PlayerState == PlayerState.paused) { setState(() { isLoading = false; // Loading is complete }); } else if (PlayerState == PlayerState.playing) { setState(() { isLoading = true; // Audio loading is in progress }); } }); }); audioPlayer.onDurationChanged.listen((Duration event) { setState(() { _duration = event; }); }); audioPlayer.onPositionChanged.listen((Duration event) { // this does get updated, but wont pass to bottomsheet! debugPrint('value is: $event'); setState(() { _position = event; }); }); }
解决方法
问题核心是:showModalBottomSheet创建的弹窗是独立于原页面的Widget树分支,原页面的setState只能更新原页面UI,无法触发弹窗内StatefulBuilder的刷新;同时弹窗初始化时捕获的_position是静态快照,后续原页面的_position更新无法自动同步到弹窗。
方法一:用ValueNotifier实现跨Widget状态共享
这是最简洁的方案,通过ValueNotifier实现状态的全局监听:
- 将原页面的
_position和_duration替换为ValueNotifier:
final ValueNotifier<Duration> _positionNotifier = ValueNotifier(Duration.zero); final ValueNotifier<Duration> _durationNotifier = ValueNotifier(Duration.zero);
- 修改音频监听逻辑,直接更新ValueNotifier:
audioPlayer.onPositionChanged.listen((Duration event) { debugPrint('value is: $event'); _positionNotifier.value = event; }); audioPlayer.onDurationChanged.listen((Duration event) { _durationNotifier.value = event; });
- 弹窗中使用ValueListenableBuilder监听状态变化:
void _openAudioBottomSheet() { showModalBottomSheet( shape: const RoundedRectangleBorder( borderRadius: BorderRadius.vertical(top: Radius.circular(20.0)), ), context: context, showDragHandle: true, builder: (BuildContext context) { return SingleChildScrollView( child: Padding( padding: const EdgeInsets.all(20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ ValueListenableBuilder<Duration>( valueListenable: _durationNotifier, builder: (context, duration, child) { return ValueListenableBuilder<Duration>( valueListenable: _positionNotifier, builder: (context, position, child) { return Slider.adaptive( min: 0, max: duration.inSeconds.toDouble(), value: position.inSeconds.toDouble(), onChanged: (value) { final newPosition = Duration(seconds: value.toInt()); _positionNotifier.value = newPosition; // 同步到音频播放器 audioPlayer.seek(newPosition); }, ); }, ); }, ), ], ), ), ); } ); }
方法二:用GlobalKey控制弹窗内的状态刷新
如果不想用ValueNotifier,可以通过GlobalKey直接调用弹窗内的更新方法:
- 定义弹窗的独立State类和GlobalKey:
final GlobalKey<_BottomSheetSliderState> _sliderKey = GlobalKey(); class BottomSheetSlider extends StatefulWidget { final Duration initialPosition; final Duration initialDuration; const BottomSheetSlider({ super.key, required this.initialPosition, required this.initialDuration, }); @override _BottomSheetSliderState createState() => _BottomSheetSliderState(); } class _BottomSheetSliderState extends State<BottomSheetSlider> { late StateSetter _setState; late Duration _position; late Duration _duration; @override void initState() { super.initState(); _position = widget.initialPosition; _duration = widget.initialDuration; } @override Widget build(BuildContext context) { return StatefulBuilder( builder: (context, setState) { _setState = setState; return Slider.adaptive( min: 0, max: _duration.inSeconds.toDouble(), value: _position.inSeconds.toDouble(), onChanged: (value) { _position = Duration(seconds: value.toInt()); setState(() {}); // 同步到原页面和音频播放器 (context.findAncestorStateOfType<_YourPageState>() as _YourPageState)._position = _position; audioPlayer.seek(_position); }, ); }, ); } // 供外部调用的更新方法 void updatePosition(Duration newPosition) { _setState(() { _position = newPosition; }); } void updateDuration(Duration newDuration) { _setState(() { _duration = newDuration; }); } }
- 原页面音频监听中调用弹窗的更新方法:
audioPlayer.onPositionChanged.listen((Duration event) { debugPrint('value is: $event'); setState(() { _position = event; }); // 弹窗已打开时更新滑块 if (_sliderKey.currentState != null) { _sliderKey.currentState!.updatePosition(event); } }); audioPlayer.onDurationChanged.listen((Duration event) { setState(() { _duration = event; }); if (_sliderKey.currentState != null) { _sliderKey.currentState!.updateDuration(event); } });
- 打开弹窗时传入初始值和Key:
void _openAudioBottomSheet() { showModalBottomSheet( shape: const RoundedRectangleBorder( borderRadius: BorderRadius.vertical(top: Radius.circular(20.0)), ), context: context, showDragHandle: true, builder: (BuildContext context) { return SingleChildScrollView( child: Padding( padding: const EdgeInsets.all(20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ BottomSheetSlider( key: _sliderKey, initialPosition: _position, initialDuration: _duration, ), ], ), ), ); } ); }
额外修复点
- 原代码
onPopInvoked中setState包裹异步函数是错误的,应改为:
onPopInvoked: (bool didPop) async { if (isPlaying == true) { await audioPlayer.stop(); setState(() { isPlaying = false; }); } },
onPlayerStateChanged中错误使用了PlayerState类型而非event变量,应改为:
if (event == PlayerState.completed || event == PlayerState.stopped || event == PlayerState.paused) { setState(() { isLoading = false; }); } else if (event == PlayerState.playing) { setState(() { isLoading = true; }); }
内容的提问来源于stack exchange,提问作者mafortis
相关产品推荐
相关产品推荐

