web-dev-qa-db-ja.com

ListAdapterがrecyclerviewのアイテムを更新しない

新しいサポートライブラリListAdapterを使用しています。これがアダプター用の私のコードです

class ArtistsAdapter : ListAdapter<Artist, ArtistsAdapter.ViewHolder>(ArtistsDiff()) {
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        return ViewHolder(parent.inflate(R.layout.item_artist))
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        holder.bind(getItem(position))
    }

    class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
        fun bind(artist: Artist) {
            itemView.artistDetails.text = artist.artistAlbums
                    .plus(" Albums")
                    .plus(" \u2022 ")
                    .plus(artist.artistTracks)
                    .plus(" Tracks")
            itemView.artistName.text = artist.artistCover
            itemView.artistCoverImage.loadURL(artist.artistCover)
        }
    }
}

アダプターを更新しています

musicViewModel.getAllArtists().observe(this, Observer {
            it?.let {
                artistAdapter.submitList(it)
            }
        })

私のdiffクラス

class ArtistsDiff : DiffUtil.ItemCallback<Artist>() {
    override fun areItemsTheSame(oldItem: Artist?, newItem: Artist?): Boolean {
        return oldItem?.artistId == newItem?.artistId
    }

    override fun areContentsTheSame(oldItem: Artist?, newItem: Artist?): Boolean {
        return oldItem == newItem
    }
}

アダプターがすべてのアイテムを最初にレンダリングするときにsubmitListが呼び出されると、更新されたオブジェクトプロパティでsubmitListが再度呼び出されると、変更されたビューは再レンダリングされません。

リストをスクロールするとビューが再レンダリングされ、bindView()が呼び出されます

また、サブミットリストの後にadapter.notifyDatasSetChanged()を呼び出すと、更新された値でビューがレンダリングされることに気付きましたが、リストアダプターにはdiff utilsが組み込まれているため、notifyDataSetChanged()を呼び出したくありません

誰でもここで私を助けることができますか?

37

編集:私はこれが私の理由ではなかった理由を理解しています。私のポイントは、少なくとも警告を出すか、notifyDataSetChanged()関数を呼び出す必要があるということです。どうやら私は理由でsubmitList(...)関数を呼び出しているからです。 submitList()が呼び出しを黙って無視するまで、何時間もの間何がうまくいかなかったかを人々が理解しようとしているのは間違いありません。

これは、Googlesの奇妙なロジックのためです。したがって、同じリストをアダプターに渡すと、DiffUtilも呼び出されません。

public void submitList(final List<T> newList) {
    if (newList == mList) {
        // nothing to do
        return;
    }
....
}

同じリストの変更を処理できない場合、このListAdapterの重要な点を理解できません。リスト上の項目を変更してListAdapterに渡し、変更を確認するには、リストのディープコピーを作成するか、独自のRecyclerViewクラスで通常のDiffUtillを使用する必要があります。

50
insa_c

ライブラリは、更新されるたびに新しい非同期リストを提供するRoomまたはその他のORMを使用していると想定しているため、その上でsubmitListを呼び出すだけで機能します。

受け入れられた答えは正しいです、それは説明ではなく解決策を提供します。

そのようなライブラリを使用していない場合にできることは次のとおりです。

submitList(null);
submitList(myList);

別の解決策は、submitListをオーバーライドすることです(これにより、高速点滅は発生しません)。

@Override
public void submitList(final List<Author> list) {
    super.submitList(list != null ? new ArrayList<>(list) : null);
}

ちょっと遅らせたが、完璧に機能する。私の好ましい方法は、各行がonBind呼び出しを取得しないため、2番目の方法です。

27
RJFares

同様の問題がありましたが、setHasFixedSize(true)Android:layout_height="wrap_content"の組み合わせが原因で誤ったレンディングが発生しました。初めてアダプタに空のリストが提供されたため、高さは更新されず、0でした。とにかく、これは私の問題を解決しました。他の誰かが同じ問題を抱えている可能性があり、アダプタの問題だと考えます。

3
Jan Veselý

使用中に問題が発生した場合

recycler_view.setHasFixedSize(true)

このコメントを明確にチェックする必要があります。 https://github.com/thoughtbot/expandable-recycler-view/issues/53#issuecomment-362991531

私の側で問題を解決しました。

0
Yoann.G

今日、私はこの「問題」につまずいた。 insa_c's answer および RJFares's solution の助けを借りて、私は自分でKotlin拡張関数を作成しました。

/**
 * Update the [RecyclerView]'s [ListAdapter] with the provided list of items.
 *
 * Originally, [ListAdapter] will not update the view if the provided list is the same as
 * currently loaded one. This is by design as otherwise the provided DiffUtil.ItemCallback<T>
 * could never work - the [ListAdapter] must have the previous list if items to compare new
 * ones to using provided diff callback.
 * However, it's very convenient to call [ListAdapter.submitList] with the same list and expect
 * the view to be updated. This extension function handles this case by making a copy of the
 * list if the provided list is the same instance as currently loaded one.
 *
 * For more info see 'RJFares' and 'insa_c' answers on
 * https://stackoverflow.com/questions/49726385/listadapter-not-updating-item-in-reyclerview
 */
fun <T, VH : RecyclerView.ViewHolder> ListAdapter<T, VH>.updateList(list: List<T>?) {
    // ListAdapter<>.submitList() contains (stripped):
    //  if (newList == mList) {
    //      // nothing to do
    //      return;
    //  }
    this.submitList(if (list == this.currentList) list.toList() else list)
}

その後、どこでも使用できます。例:

viewModel.foundDevices.observe(this, Observer {
    binding.recyclerViewDevices.adapter.updateList(it)
})

そして、現在ロードされているリストと同じ場合にのみ(常に)リストをコピーします。

0
Bojan P.

公式によると docs

submitListを呼び出すたびに、diffedする新しいリストを送信して表示します。

これが、submitlistを呼び出すときはいつでもprevious(既に送信済みのリスト)で、差分を計算しませんデータセットの変更についてアダプターに通知しない

0
Ashu Tyagi