RecyclerView在滚动时随机改变位置

时间:2017-02-08 19:49:59

标签: android android-recyclerview

我已经使用了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);
        }
    }
}

以下是从playMedia方法调用的configureMyChatMediaViewHolder方法:

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);
        }
    });
}

我在片段中只有一个媒体播放器实例,用于设置聊天适配器。

2 个答案:

答案 0 :(得分:0)

Recycler视图正在重复使用当前不可见的视图。因此,如果您的音频播放器开始更新一个文本视图,当您滚动该文本视图是相同的引用但具有不同的文本(歌曲),但您的可运行实例仍处于活动状态并且正在执行其工作以使用相同的引用更新文本视图。

  1. 您应该创建一个自定义的Runnable类 将持有头寸价值,并且只负责 它应该更新TextView。

  2. 其他方法(效率较低)是使用ListView而不是重复使用单元格为每个项目创建新单元格。 这会导致性能问题

  3. 如果您可以发送一些代码我想帮助您。

    *** EDITED ****

    以下是一些可以使事情更清晰的代码部分

    @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;
        }
    }
    

答案 1 :(得分:0)

目前尚不清楚您拨打updateTimer的位置,但我认为您是从callback.onPlayClickListener(..)callback.onPauseClickListener(..)内拨打的。

首先,请不要将您在点击监听器中作为参数的position传递给它,但是documentation states应该使用ViewHolder.getAdapterPosition()方法。请注意,当您使用该位置的值时,您还应该检查它每次都与RecuclerView.NO_POSITION不同,然后再使用它。在所有方法中使您的位置参数不是最终的。它可以防止你犯错误。每次要在点击监听器中使用位置时,请使用myChatViewHolder.getAdapterPosition()

第二次,因为无论如何都要在Runnable中缓存position值,这可能还不够。因此,您应将myChatViewHolder.timer作为参数传递给updateTimer并删除前两行。最后,您可以像这样致电updateTimer

updateTimer(myChatViewHolder.timer);

以及您的“更新时间”'现在是:

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参数,并且不应保留其副本。