2017-02-08 39 views
0

我已经创建了一个聊天应用程序,并使用了RecyclerView。消息可以是文本消息或音频消息。除了当我更改TextView(对于我制作的音频播放器布局)中的计时器文本,歌曲播放了多长时间以外,一切正常。我在Runnable中这样做。但是当我滚动RecyclerView时,计时器TextView将随机位置改变文本。RecyclerView在滚动时随机更改位置

这里是我正在改变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); 
     } 
    }); 
} 

我只有在同一个媒体播放器实例从中设置聊天适配器的片段。

+0

请发布您'ViewHolder'和'onBindViewHolder()'实现。像你这样使用'final int position'是错误的 - 你永远不应该缓存RecyclerView的位置。此外,当每次查找计时器TextView时都不清楚 - 它应该存储在ViedHolder等中。 –

+0

我已经发布了一个答案,请让我知道它是否有帮助 –

+0

还有一个想法:在回收后,您的TextView可能仍有跑步者附着在它上面。所以**之前**你调用'myChatViewHolder.timer.setText(out);'调用'myChatViewHolder.timer.removeCallbacks(myChatViewHolder.timer.getTag());'。在'updateTimer()'添加最后一行'timer.setTag(r)' –

回答

0

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

  1. 你应该创建一个自定义的Runnable类 将举行一个位置值,将只为 的TextView应该更新负责。

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

如果你可以发送一些代码,我想帮助你。

*** **** 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; 
    } 
} 
+0

那么你说什么和我所做的之间有什么区别?我也在该方法中保留一个位置值,它应该只负责更新相应的TextView –

+0

不是在commet中,而是另一个实现了Runnable的类,现在正是TextView的更新位置。我已编辑我的答案希望它有帮助 –

+0

请参阅我编辑的编辑 –

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

和你的 '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 ()方法,如果项目的位置在数据集中发生变化,除非项目本身无效或无法确定新位置。出于这个原因,您应该只在获取该方法中的相关数据项时使用位置参数,并且不应保留其副本。

+0

好吧,我照你说的,并通过TextView作为参数updateTimer()方法,仍然我有同样的问题,随机TextView随机positons也更新 –

+0

也许我'你必须看到你的所有代码。这可能不仅仅是RecyclerView问题 –

相关问题