当前位置: 代码迷 >> Android >> RecyclerView滚动时随机更改位置
  详细解决方案

RecyclerView滚动时随机更改位置

热度:38   发布时间:2023-08-04 09:56:10.0

我制作了一个聊天应用程序,在其中使用了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);
        }
    });
}

片段中只有一个媒体播放器实例,从该实例可以设置聊天适配器。

回收者视图正在重新使用当前不可见的视图。 因此,如果您的音频播放器开始更新一个Text View,则在滚动时该TextView是相同的引用,但具有不同的文本(Song),但是您的可运行实例仍处于活动状态,并正在使用相同的引用来更新Text View。

  1. 您应该创建一个自定义Runnable类,该类将保存一个位置值,并且仅负责应更新的TextView。

  2. 另一种方法(效率较低)是使用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;
    }
}

目前尚不清楚在哪里调用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参数,而不应保留其副本。

  相关解决方案