我有一个聊天气泡小部件,它支持Slider
小部件的音频播放器
滑块的值会根据AudioPlayer的进度进行更改,看起来效果不错。
当第一个音频被完全播放时(意味着滑块的值现在是100%(,&现在第二聊天气泡被添加到CCD_ 2,则最新的Slider具有值100&前一个的值为0。
下面是一个更好理解的示例:
添加到列表的消息1:音频播放完成=>Slider值为100。
添加到列表的消息2:Slider值是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: (_) {},
),
)
],
);
}
}
这个小部件又被用在另一个小部件中,该小部件处理消息类型&显示适当的ui
在这里:
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
,但仍然没有用
如有任何想法,我们将不胜感激。
这可能是因为您的Widgets属于同一类型。
当flutter检查小部件树中的更改时,它会检查小部件的type
以及创建该小部件时提供的key
。
从您的示例中可以清楚地看到,在创建StatefulWidget
时没有提供key
。
因此,当你推送一个新的Widget
(我假设你推送这个小部件的时间比树中的旧小部件早(时,flutter认为这仍然是旧的小部件,并将旧的State
对象分配给它。
开始,每当您创建存在于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%
在这种情况下,您必须在列表中保持索引歌曲播放值,或者从后端动态获取歌曲播放值。
如果有帮助,请告诉我。