显示后如何检测每个 RecyclerView item

How to detect each RecyclerView item after it is displayed

我想在 RecylerView 中的每个项目显示给用户后进行检测。

基本上,我试图在每个项目加载到屏幕上后播放声音。

但是我无法检测到屏幕上何时加载了每个项目!有没有我必须调用的方法来检测呈现的每个项目

E.g 1st RecyclerView item displayed -> play sound
    2st RecyclerView item displayed -> play sound...

我的适配器 class 看起来像这样 -

public class AdapterListAnimation extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

    private List<Multiples> items = new ArrayList<>();

    private Context ctx;
    private OnItemClickListener mOnItemClickListener;
    private int animation_type = 0;
    .........
    .........

我正在从 onCreated() 方法调用此 initComponent() 方法。您能否就我应该如何实现上述目标提出建议?

private void initComponent() {
    recyclerView = (RecyclerView) findViewById(R.id.recyclerView);
    recyclerView.setLayoutManager(new LinearLayoutManager(this));
    recyclerView.setHasFixedSize(true);
    items = DataGenerator.getPeopleData(this,of,value);
    setAdapter();

    /* MediaPlayer mp=MediaPlayer.create(this, R.raw.sword);
    if (mp.isPlaying()) {
        mp.stop();
        mp.release();
        mp = MediaPlayer.create(this, R.raw.sword);
    } mp.start();*/
}

private void setAdapter() {
    // Set data and list adapter
    mAdapter = new AdapterListAnimation(this, items, animation_type);
    recyclerView.setAdapter(mAdapter);

    // on item list clicked
    mAdapter.setOnItemClickListener(new AdapterListAnimation.OnItemClickListener() {
        @Override
        public void onItemClick(View view, com.math.multiplication.model.Multiples obj, int position) {
            Snackbar.make(parent_view, "Item " + obj.first + " clicked", Snackbar.LENGTH_SHORT).show();
        }
    });
}

您只需在适配器中使用 onViewAttachedToWindow(VH) 即可。 参见 https://developer.android.com/reference/android/support/v7/widget/RecyclerView.Adapter.html#onViewAttachedToWindow(VH)

更新:

如您所知,RecyclerView 将只为每个项目调用 OnBindViewHolder 一次。

RecyclerView will not call this method again if the position of the item changes in the data set unless the item itself is invalidated or the new position cannot be determined.

所以你可以使用onViewAttachedToWindow

(onViewAttachedToWindow) Called when a view created by this adapter has been attached to a window.

This can be used as a reasonable signal that the view is about to be seen by the user. If the adapter previously freed any resources in onViewDetachedFromWindow those resources should be restored here.

你可以这样使用它:

public class Adapter extends RecyclerView.Adapter<MyViewHolder> {
   
    // rest of your code

   
    @Override
    public void onViewAttachedToWindow(MyViewHolder holder) {
         super.onViewAttachedToWindow(holder);
        // play your sound
    }
}

希望对您有所帮助!

您需要为 TTS 添加监听器。然后更新您的 RecyclerView 以在演讲开始和结束时显示正确的图像。

我已经创建了一个测试项目来展示它是如何实现的。 Here you can see how it works. Here 是我的 github 存储库。

这是我的 MainActivity class.

的主要部分
private void initTts() {
    tts = new TextToSpeech(this, this);
    tts.setLanguage(Locale.US);
    tts.setOnUtteranceProgressListener(new MyListener());
}

@Override
public void onInit(int status) {
    playSound(0);
}

private void playSound(int index) {
    HashMap<String, String> hashMap = new HashMap<>();
    hashMap.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, String.valueOf(index));
    tts.speak(data.get(index), TextToSpeech.QUEUE_ADD, hashMap);
}

class MyListener extends UtteranceProgressListener {
    @Override
    public void onStart(String utteranceId) {
        int currentIndex = Integer.parseInt(utteranceId);
        mMainAdapter.setCurrentPosition(currentIndex);
        handler.post(new Runnable() {
            @Override
            public void run() {
                mMainAdapter.notifyDataSetChanged();
            }
        });
    }

    @Override
    public void onDone(String utteranceId) {
        int currentIndex = Integer.parseInt(utteranceId);
        mMainAdapter.setCurrentPosition(-1);
        handler.post(new Runnable() {
            @Override
            public void run() {
                mMainAdapter.notifyDataSetChanged();
            }
        });
        if (currentIndex < data.size() - 1) {
            playSound(currentIndex + 1);
        }
    }

    @Override
    public void onError(String utteranceId) {
    }
}

如果我对您的问题的理解正确,您希望在加载 RecyclerView 中的项目时播放声音。

因此我想在这里考虑一个解决方法。您可能会考虑在播放完前一个项目的声音后添加一个项目。

我想为我要解释的内容提供一个伪代码。如果您的声音停止播放,您可能会考虑使用 MediaPlayer.OnCompletionListener 来收听,然后将下一个项目添加到 RecyclerView,然后在您的适配器上调用 notifyDataSetChanged 以将下一个项目加载到您的 RecyclerView

因此您可能希望首先在您的适配器中进行以下修改。

// Declare a function in your adapter which adds new item in the RecyclerView
public void addItem(Multiples item) {
    items.add(item);
    notifyItemInserted(items.size());
}

现在,在设置 RecyclerViewActivity 中,您需要具有以下数组。

ArrayList<Multiples> allItems = new ArrayList<Multiples>(); // This list will contain all the data
int soundToBePlayed = -1; // Initialize the value with -1
ArrayList<Integer> soundList = getAllSoundsInAList(); 

修改你的函数如下。

private void initComponent() {
    recyclerView = (RecyclerView) findViewById(R.id.recyclerView);
    recyclerView.setLayoutManager(new LinearLayoutManager(this));
    recyclerView.setHasFixedSize(true);
    allItems = DataGenerator.getPeopleData(this,of,value);
    setAdapter();
}

private void setAdapter() {
    mAdapter = new AdapterListAnimation(this, animation_type); // Do not pass the items list
    recyclerView.setAdapter(mAdapter);

    // .. Your item click listener goes here
}

由于 items 已从构造函数中删除,您还需要修改适配器的构造函数。只需删除带有 items 的变量即可。因为我们稍后会向 items 添加元素。

然后您需要编写另一个函数,需要在调用 initComponent 函数后在 activity 的 onCreate 函数中调用。该函数应如下所示。

private void addItemInRecyclerViewAndPlaySound() {

    soundToBePlayed++;

    adapter.addItem(allItems.get(soundToBePlayed));
    recyclerView.scrollToPosition(adapter.getItemCount() - 1);

    Integer soundId = soundList.get(soundToBePlayed);

    MediaPlayer mp = MediaPlayer.create(this, soundId);

    if (mp.isPlaying()) {
        mp.stop();
        mp.release();
        mp = MediaPlayer.create(this, soundId);
        mp.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
            public void onCompletion(MediaPlayer mp) {
                // Call this function again to add next item
                // and play the next sound at the same time
                if(soundToBePlayed < allItems.size() - 1)
                    addItemInRecyclerViewAndPlaySound();
            }
        });
    }

    mp.start();
}

我还没有测试代码,但是,我想你已经明白了。这也可以在适配器内部使用 interface 优雅地实现。实施技术是您的选择。

您需要覆盖 onViewAttachedToWindowonViewDetachedFromWindow。但是为了检测 holer 类型,你需要 getItemViewType() 就像这样:

public class PostAdapter extends RecyclerView.Adapter {

@Override
public int getItemViewType(int position) {
    switch (types.get(position)){
        case 1:
            return 1;
        case 2:
            return 2;
        default:
            return position;


    }
}
@Override
public void onViewAttachedToWindow(@NonNull RecyclerView.ViewHolder holder) {
    super.onViewAttachedToWindow(holder);
    if (holder.getItemViewType() == 1){
        //play song

    }

}

@Override
public void onViewDetachedFromWindow(@NonNull RecyclerView.ViewHolder holder) {
    super.onViewDetachedFromWindow(holder);
    if (holder.getItemViewType() == 1){
        //pause song

    }
}