Ползунок в AnimatedList имеет неправильное значение
У меня есть виджет пузыря чата с поддержкой аудиоплеера с
Slider
виджет.
Значение ползунка изменяется в соответствии с прогрессом AudioPlayer, который, похоже, работает нормально.
Когда первый звук воспроизводится полностью (это означает, что значение ползунка теперь составляет 100%), и теперь вторая облачко чата добавляется к последнему, самый новый ползунок имеет значение 100, а предыдущий имеет значение 0.
Вот пример, чтобы лучше понять:
Сообщение 1 добавлено в список: Аудио воспроизведено завершено => Значение
ползунка - 100. Сообщение 2 добавлено в список: значение ползунка - 100 (должно быть 0), а ползунок из сообщения 1 имеет значение 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
но все равно бесполезно.
Любые мысли по этому поводу будут оценены.
2 ответа
Вероятно, это происходит из-за того, что ваши виджеты одного типа.
Пока flutter проверяет изменения в дереве виджетов, он проверяет наличие
type
виджета, а также предоставленного при создании этого виджета.
Из вашего примера видно, что нет
key
предоставляется при создании.
Итак, когда вы продвигаете новый
Widget
(и я предполагаю, что вы нажимаете этот виджет раньше, чем старый виджет в дереве), flutter считает, что это все еще старый виджет, и назначает более старый
State
возражать против этого.
Начните с отправки уникального ключа всякий раз, когда вы создаете новый
StatefulWidget
s, которые существуют внутри
List
введите виджеты вроде
Row
,
Column
так далее.,
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%
В этом случае вы должны либо сохранить значение проигрываемой индексной песни в списке, либо получить значение проигрываемой песни динамически из бэкэнда.
Пожалуйста, дайте мне знать, если это поможет.