Ползунок в 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 возражать против этого.

Начните с отправки уникального ключа всякий раз, когда вы создаете новый StatefulWidgets, которые существуют внутри 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 сам как ключ.

  1. Когда воспроизведение звука будет завершено, сделайте audioSeekValue = 0 . Это начнется с самого начала.

  2. Если вы хотите отслеживать: проиграна композиция 1 = 70% воспроизведена композиция 2 = 50%

В этом случае вы должны либо сохранить значение проигрываемой индексной песни в списке, либо получить значение проигрываемой песни динамически из бэкэнда.

Пожалуйста, дайте мне знать, если это поможет.

Другие вопросы по тегам