问题描述
我制作了一个聊天应用程序,在其中使用了RecyclerView
。
消息可以是文本消息或音频消息。
一切正常,除非更改了TextView
上的计时器文本(对于我制作的音频播放器布局),即歌曲已播放多长时间。
我在Runnable
执行此操作。
但是,当我滚动RecyclerView
,计时器TextView
会在随机位置更改文本。
这是我更改TextView
文本的方式:
public void updateTimer(final int position) {
View view = mRecyclerViewChat.getLayoutManager().findViewByPosition(position);
timer = (TextView) view.findViewById(R.id.timer);
r = new Runnable() {
public void run() {
int currentDuration;
if (player.isPlaying()) {
currentDuration = player.getCurrentPosition();
timer.setText("" + milliSecondsToTimer((long) currentDuration));
timer.postDelayed(this, 1000);
} else {
timer.removeCallbacks(this);
}
}
};
timer.post(r);
}
这里position
是我从onBindViewHolder
获得的位置值。
编辑
这是onBindViewHolder
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if (TextUtils.equals(mChats.get(position).senderUid,
FirebaseAuth.getInstance().getCurrentUser().getUid())) {
if (mChats.get(position).mediaUrlLocal == null) {
configureMyChatViewHolder((MyChatViewHolder) holder, position);
} else {
configureMyChatMediaViewHolder((MyChatMediaViewHolder) holder, position);
}
} else {
if (mChats.get(position).mediaUrlLocal == null) {
configureOtherChatViewHolder((OtherChatViewHolder) holder, position);
} else {
configureOtherChatMediaViewHolder((OtherChatMediaViewHolder) holder, position);
}
}
}
这是从configureMyChatMediaViewHolder
方法调用的playMedia
方法:
private void playMyMedia(final MyChatMediaViewHolder myChatViewHolder, final Chat chat, final int position) {
MediaMetadataRetriever metaRetriever = new MediaMetadataRetriever();
metaRetriever.setDataSource(chat.mediaUrlLocal);
String duration =
metaRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION);
long dur = Long.parseLong(duration);
String seconds = String.valueOf((dur % 60000) / 1000);
String minutes = String.valueOf(dur / 60000);
String out = minutes + ":" + seconds;
myChatViewHolder.timer.setText(out);
if (chat.isPlay) {
myChatViewHolder.play.setVisibility(View.GONE);
myChatViewHolder.pause.setVisibility(View.VISIBLE);
} else {
myChatViewHolder.play.setVisibility(View.VISIBLE);
myChatViewHolder.pause.setVisibility(View.GONE);
}
myChatViewHolder.play.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
chat.isPlay = !chat.isPlay;
if (previousChat != position && previousChat != -1) {
previousChatObj = mChats.get(previousChat);
}
previousChat = position;
myChatViewHolder.play.setVisibility(View.GONE);
myChatViewHolder.pause.setVisibility(View.VISIBLE);
callback.onPlayClickListener(chat, previousChatObj, position);
}
});
myChatViewHolder.pause.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
chat.isPlay = !chat.isPlay;
myChatViewHolder.play.setVisibility(View.VISIBLE);
myChatViewHolder.pause.setVisibility(View.GONE);
callback.onPauseClickListener(chat, position);
}
});
}
片段中只有一个媒体播放器实例,从该实例可以设置聊天适配器。
1楼
回收者视图正在重新使用当前不可见的视图。 因此,如果您的音频播放器开始更新一个Text View,则在滚动时该TextView是相同的引用,但具有不同的文本(Song),但是您的可运行实例仍处于活动状态,并正在使用相同的引用来更新Text View。
您应该创建一个自定义Runnable类,该类将保存一个位置值,并且仅负责应更新的TextView。
另一种方法(效率较低)是使用ListView,而不是重复使用单元格为每个项目创建新的单元格。 这将导致性能问题
如果您可以发送一些代码,我想为您提供帮助。
***编辑****
这是一些代码部分,可以使事情更清楚
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
holder.songDurationView.setTag(position);
//TODO: implement some more logic here and start the MusicSongRunnable
}
class MusicSongRunnable implements Runnable {
int positionOfSong;
TextView textView;
public MusicSongRunnable(int positionOfSong, TextView textView) {
this.positionOfSong = positionOfSong;
this.textView = textView;
}
@Override
public void run() {
if (player.isPlaying() && positionOfSong == textView.getTag()) {
//TODO: update the song;
}
}
2楼
目前尚不清楚在哪里调用updateTimer
,但我假设您是从callback.onPlayClickListener(..)
和callback.onPauseClickListener(..)
内部callback.onPlayClickListener(..)
。
首先 ,不通过它的position
你有在点击监听器的参数,但 ,你应该使用方法。
请注意,当您使用位置值时,还应该每次都检查它是否与RecuclerView.NO_POSITION
不同,然后才使用它。
使您的位置参数在所有方法中都不是最终的。
这样可以防止您犯错误。
每次您想在点击侦听器中使用位置时,请使用myChatViewHolder.getAdapterPosition()
其次 ,由于无论如何都要在Runnable中缓存position
值,所以这可能还不够。
因此,您应该将myChatViewHolder.timer
作为参数传递给updateTimer
并摆脱前两行。
最后,您像这样调用updateTimer
:
updateTimer(myChatViewHolder.timer);
现在您的“ updateTimer”为:
public void updateTimer(final TextView timer) {
r = new Runnable() {
public void run() {
int currentDuration;
if (player.isPlaying()) {
currentDuration = player.getCurrentPosition();
timer.setText("" + milliSecondsToTimer((long) currentDuration));
timer.postDelayed(this, 1000);
} else {
timer.removeCallbacks(this);
}
}
};
timer.post(r);
}
文档中有关此问题的更多信息:
如果项目的位置在数据集中更改,则RecyclerView将不会再次调用onBindViewHolder()方法,除非该项目本身无效或无法确定新位置。 因此,您仅应在获取此方法内的相关数据项时使用position参数,而不应保留其副本。