[英]AnimatedList call - RangeError (index): Invalid value: Valid value range is empty: 0
[英]Slider in AnimatedList has wrong value
我有一個聊天氣泡小部件,它支持帶有Slider
小部件的音頻播放器。
滑塊的值會根據 AudioPlayer 的進度進行更改,這似乎工作正常。
當第一個音頻完全播放時(意味着滑塊的值現在是 100%),並且現在第二個聊天氣泡被添加到AnimatedList
中,那么最新的 Slider 的值是 100 而之前的值是 0。
這是一個更好理解的示例:
消息 1 添加到列表:音頻播放完成 => Slider 值為 100。
消息 2 添加到列表:Slider 值為 100(應為 0),消息 1 中的 slider 值為 0。
這是小部件:
import 'package:audioplayers/audioplayers.dart';
import 'package:flutter/material.dart';
class MessageBubbleAudioPlayer extends StatefulWidget {
final Color color;
final String audioUrl;
const MessageBubbleAudioPlayer({
@required this.audioUrl,
@required this.color,
});
@override
_MessageBubbleAudioPlayerState createState() =>
_MessageBubbleAudioPlayerState();
}
class _MessageBubbleAudioPlayerState extends State<MessageBubbleAudioPlayer> {
bool loading = false;
bool isPlaying = false;
double audioSeekValue = 0;
final AudioPlayer audioPlayer = AudioPlayer();
Duration totalDuration = Duration(milliseconds: 0);
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) async {
audioPlayer.onPlayerStateChanged.listen((event) {
if (mounted) setState(() => isPlaying = event == PlayerState.PLAYING);
});
audioPlayer.onAudioPositionChanged.listen((event) {
final percent =
((event.inMilliseconds * 100) / totalDuration.inMilliseconds) ?? 0;
if (mounted) setState(() => audioSeekValue = percent);
});
});
}
@override
Widget build(BuildContext context) {
return Row(
mainAxisSize: MainAxisSize.min,
children: [
loading
? Container(
height: 30,
width: 30,
padding: const EdgeInsets.all(8),
child: CircularProgressIndicator(
color: widget.color,
strokeWidth: 1.8,
),
)
: Container(
width: 30,
child: IconButton(
icon: Icon(isPlaying ? Icons.pause : Icons.play_arrow,
color: widget.color),
onPressed: () async {
if (audioPlayer.state == PlayerState.PAUSED) {
audioPlayer.resume();
return;
}
if (!isPlaying) {
setState(() => loading = true);
await audioPlayer.play(widget.audioUrl);
audioPlayer.getDuration().then((value) {
totalDuration = Duration(milliseconds: value);
setState(() => loading = false);
});
} else
await audioPlayer.pause();
},
splashRadius: 25,
),
),
SliderTheme(
data: SliderThemeData(
trackHeight: 1.4,
thumbShape: RoundSliderThumbShape(enabledThumbRadius: 7)),
child: Slider(
label: "Audio",
activeColor: widget.color,
inactiveColor: widget.color.withAlpha(100),
// this (value) should be 0 for a newly added widget
// but is 100 for the newer one & 0 for the previous one,
// which infact should be opposite
value: audioSeekValue,
min: 0,
max: 100,
onChanged: (_) {},
),
)
],
);
}
}
該小部件又用於另一個處理消息類型並顯示適當用戶界面的小部件。
這里是:
class MessageBubble extends StatelessWidget {
final bool isSender, isAudio;
final String message;
const MessageBubble(this.message, this.isSender, this.isAudio, Key key)
: super(key: key);
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 3),
child: Align(
alignment: isSender ? Alignment.centerRight : Alignment.centerLeft,
child: message.contains(Constants.emojiRegex, 0) &&
!message.contains(Constants.alphaNumericRegex)
? Padding(
padding: EdgeInsets.only(
top: 6,
bottom: 6,
left: isSender ? 16 : 0,
right: isSender ? 0 : 32),
child: Text(message,
style: TextStyle(fontSize: 45, color: Colors.white)),
)
: Material(
borderRadius: BorderRadius.circular(30),
elevation: 4,
color: isSender
? Colors.deepPurpleAccent.shade100.darken()
: Colors.white,
child: isAudio
? Padding(
padding: const EdgeInsets.symmetric(
horizontal: 20, vertical: 6),
child: MessageBubbleAudioPlayer(
key: ValueKey(message.hashCode.toString()),
audioUrl: message,
color: isSender
? Colors.white
: Colors.deepPurpleAccent,
),
)
: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 20, vertical: 14),
child: Linkify(
onOpen: (link) async {
if ((await canLaunch(link.url)))
await launch(link.url);
},
options: LinkifyOptions(humanize: false),
linkStyle: TextStyle(
color: isSender
? Colors.white
: Colors.deepPurpleAccent),
text: message,
style: TextStyle(
fontSize: 17,
color: isSender ? Colors.white : Colors.black),
),
),
)),
);
}
}
這是AnimatedList
:
class ChatAnimatedList extends StatefulWidget {
final bool isInfoShown, isSender;
const ChatAnimatedList(
{@required Key key, @required this.isInfoShown, this.isSender})
: super(key: key);
@override
ChatAnimatedListState createState() => ChatAnimatedListState();
}
class ChatAnimatedListState extends State<ChatAnimatedList> {
final _messageList = <MessageBubble>[];
final _animatedListState = GlobalKey<AnimatedListState>();
get messageLength => _messageList.length;
insertMessageBubble(MessageBubble messageBubble) =>
_messageList.insert(0, messageBubble);
insertViaState() {
if (_animatedListState.currentState != null)
_animatedListState.currentState.insertItem(0);
}
@override
Widget build(BuildContext context) {
return widget.isInfoShown
? InfoPlaceholder(isSender: widget.isSender)
: Expanded(
child: AnimatedList(
reverse: true,
key: _animatedListState,
initialItemCount: _messageList.length,
itemBuilder: (_, index, animation) {
return index == 0
? Padding(
padding: const EdgeInsets.only(bottom: 6),
child: _messageList[index])
: index == _messageList.length - 1
? Padding(
padding: const EdgeInsets.only(top: 30),
child: _messageList[index])
: _messageList[index];
}),
);
}
}
我也嘗試過使用AutomaticKeepAliveClientMixin
但仍然沒有用。
對此的任何想法將不勝感激。
這可能是由於您的小部件屬於同一類型而發生的。
當 flutter 檢查小部件樹中的更改時,它會檢查小部件的type
以及創建該小部件時提供的key
。
從您的示例中,很明顯在創建StatefulWidget
時沒有提供任何key
。
因此,當您推送新的Widget
時(我假設您在樹中推送此小部件的時間早於舊的小部件),flutter 認為這仍然是舊的小部件並將舊的State
ZA8CFDE6331BD59EB2AC96F8911C4B6 分配給它。
開始,每當您創建存在於List
類型小部件(如Row
、 Column
等)中的新StatefulWidget
時發送一個唯一鍵,
class MessageBubbleAudioPlayer extends StatefulWidget {
const MessageBubbleAudioPlayer({
@required this.audioUrl,
@required this.color,
Key key.
}) : super(key: key);
在創造一個新的時,
MessageBubbleAudioPlayer(audioUrl: '', color: '', key: ValueKey(#some unique int or string#)
代替#some unique int or string#
放置對該小部件唯一的內容,而不是索引,因為它可以更改,但您可以使用audioUrl
本身作為鍵。
當您的音頻播放完成后,使audioSeekValue = 0 。 這將從一開始就開始。
如果要跟蹤: 播放的歌曲 1 = 70% 播放的歌曲 2 = 50%
在這種情況下,您必須將您的索引歌曲播放值保留在列表中,或者從后端動態獲取歌曲播放值。
如果有幫助,請告訴我。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.