web-dev-qa-db-ja.com

onBindViewHolder()は、RecyclerView.findViewHolderForAdapterPosition()がその位置でnullを返したとしても、その位置のビューで呼び出されることはありません。

0〜12の位置に13個のアイテム(アイテムは追加または削除できますが)のリストがあります。 RecyclerViewを含むフラグメントが最初に表示されるとき、位置0から7のみがユーザーに表示されます(位置7は半分しか表示されません)。私のアダプターでは、ビューホルダーがバインド/バインドされるたびにLog(文法がここに当てはまる場合はidk)、その位置を記録します。

アダプター

_@Override
public void onBindViewHolder(final ViewHolder holder, final int position) {
    Log.d(TAG, "onBindViewHolder() position: " + position);
    ...
}
_

私のLogから、位置0〜7がバインドされていることがわかります。

Log from Adapter

アダプタの位置ごとに各ViewHolderを取得するselectAll()メソッドがあります。返されたholdernullではない場合、返されたholderを使用してビューを更新し、選択されていることを表示します。返されたホルダーがIS nullの場合、selectOnBind()を呼び出します。実際のバインドではなく、バインドされたときに選択されていることを示すためにその位置更新でビューにフラグを立てるメソッドです現在表示されてからの時間:

_public void selectAll() {
    for (int i = 0; i < numberOfItemsInList; i++) {
        MyAdapter.ViewHolder holder = (MyAdapter.ViewHolder)
                mRecyclerView.findViewHolderForAdapterPosition(i);

        Log.d(TAG, "holder at position " + i + " is " + holder);

        if (holder != null) {
            select(holder);
        } else {
            selectOnBind(i);
        }
    }
}
_

このメソッドでは、Logとその位置とともにholderを使用します。

Log from selectAll()

そのため、ここまではすべて正常に見えます。位置0〜7が示されており、Logによれば、これらはバインドされた位置です。表示ビューを変更せずに(スクロール)selectAll()を押すと、位置0-7が定義され、8-12がnullであることがわかります。ここまでは順調ですね。

ここが面白いところです。 selectAll()を呼び出した後、リスト位置8および9をさらに下にスクロールすると、それらが選択されていることが表示されません。

Logをチェックすると、nullであると報告されたにもかかわらず、バインドされないためだとわかります。

Adapter Log after scroll

さらに紛らわしいのは、これが毎回発生するわけではないということです。最初にアプリを起動してテストすると、動作する可能性があります。しかし、それはその後確実に起こるようです。リサイクルされているビューと関係があると思いますが、それでも束縛する必要はないでしょうか?

[〜#〜] edit [〜#〜](6-29-16)
AndroidStudioの更新後、バグを再現できないようです。期待どおりに機能し、nullビューをバインドします。この問題が再発する場合は、この投稿に戻ります。

22

これは次の理由で発生しています。

  • ビューはrecyclerviewに追加されません(getChildAtは機能せず、その位置に対してnullを返します)
  • それらもキャッシュされます(onBindは呼び出されません)

recyclerView.setItemViewCacheSize(0)を呼び出すと、この「問題」が修正されます。

デフォルト値は2(private static final int DEFAULT_CACHE_SIZE = 2; in RecyclerView.Recycler)、常にonBindを呼び出さないビューを2つ取得しますが、リサイクラーには追加されません

16
Pedro Oliveira

あなたの場合、位置8と9のビューはリサイクルされていません。それらはウィンドウから切り離されており、再び取り付けられます。そして、これらのデタッチされたビューのonBindViewHolderは呼び出されず、onViewAttachedToWindowのみが呼び出されます。アダプタでこれらの機能をオーバーライドすると、私が話していることがわかります。

@Override
    public void onViewRecycled(ViewHolder vh){
        Log.wtf(TAG,"onViewRecycled "+vh);
    }

    @Override
    public void onViewDetachedFromWindow(ViewHolder viewHolder){
        Log.wtf(TAG,"onViewDetachedFromWindow "+viewHolder);
    }

問題を解決するために、リサイクルするはずだったビューを追跡する必要がありますが、切り離されてからセクション処理を実行する必要があります

@Override
    public void onViewAttachedToWindow(ViewHolder viewHolder){
        Log.wtf(TAG,"onViewAttachedToWindow "+viewHolder);
    }
6
Zaartha

Pedro OliveiraとZarthaの回答は問題を理解するのに最適ですが、満足できる解決策は見当たりません。

あなたがしていることに応じて、2つの良い選択肢があると思います:

オプション1

キャッシュ/デタッチされているかどうかに関係なく、オフスクリーンビューに対してonBindViewHolder()が呼び出されるようにするには、次のようにします。

RecyclerView.ViewHolder view_holder = recycler_view.findViewHolderForAdapterPosition( some_position );

if ( view_holder != null )
{
    //manipulate the attached view
}
else //view is either non-existant or detached waiting to be reattached
    notifyItemChanged( some_position );

ビューがキャッシュ/デタッチされると、notifyItemChanged()はアダプターにビューが無効であることを通知し、onBindViewHolder()が呼び出されます。

オプション2

部分的な変更(onBindViewHolder()内のすべてではなく)のみを実行する場合は、onBindViewHolder( ViewHolder view_holder, int position )内で、positionview_holderに格納する必要があります。 onViewAttachedToWindow( ViewHolder view_holder )で必要な変更を実行します。

onBindViewHolder()がビットマップをいじるような集中的なことをしていない限り、簡単にするためにオプション1をお勧めします。

2
Kacy

ですから、あなたの質問は、@ Pedro Oliveiraが以下で答えていると思います。彼はいつでもViewHolderをキャッシュするために特別なアルゴリズムを使用しているという、RecycleViewの主な意味。そのため、次のonBindViewHolder(...)は機能しない場合があります。ビューが静的な場合、または何か他のもの。

また、質問については、動的に変更されたビューにRecycleViewを使用すると思います。 DO N'T DO IT! RecycleViewはビューを無効にし、キャッシュシステムを備えているため、多くの問題が発生します。

このタスクに LinkedListView を使用!

0
GensaGames

リサイクルの観点から見ると、ビューで遊ぶのは得策ではないと思います。私がいつもRecyclerViewに使用するモデルにフラグを導入するために従うために使用するアプローチ。モデルが次のようなものだと仮定しましょう-

_class MyModel{
    String name;
    int age;
}
_

追跡している場合は、ビューが選択されているかどうかに関係なく、1つのブール値をモデルに導入します。次のようになります-

_class MyModel{
    String name;
    int age;
    boolean isSelected;
}
_

これで、新しいフラグisSelected(onBindViewHolder())に基づいてチェックボックスが選択/選択解除されます。ビュー上のすべての選択で、対応するモデルの選択値の値がtrueに変更され、選択されていない場合はfalseに変更されます。あなたの場合、ループを実行してすべてのモデルのisSelected値をtrueに変更し、notifyDataSetChanged()を呼び出します。

たとえば、リストが

_ArrayList<MyModel> recyclerList;
private void selectAll(){
    for(MyModel myModel:recyclerList)
        myModel.isSelected = true;
    notifyDataSetChanged();
}
_

私の提案では、recyclerViewまたはListViewを使用して、ビューを再生しようとすることを少なくしています。

あなたの場合-

_@Override
public void onBindViewHolder(final ViewHolder holder, final int position) {
   holder.clickableView.setTag(position);
   holder.selectableView.setTag(position);
   holder.checkedView.setChecked(recyclerList.get(position).isSelected);
    Log.d(TAG, "onBindViewHolder() position: " + position);
    ...
}

@Override
public void onClick(View view){
    int position = (int)view.getTag();
    recyclerList.get(position).isSelected = !recyclerList.get(position).isSelected;
}

@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
      int position = (int)buttonView.getTag();
      recyclerList.get(position).isSelected = isChecked;
}
_

それがあなたを助けることを願っています、あなたがさらに説明が必要かどうか教えてください:)

0
Neo

_recyclerview adapter_に渡したリストに多数のアイテムがある場合、スクロール中にonBindViewHolder()が実行されないという問題は発生しません。

しかし、リストの項目が少ない場合(リストサイズ5で確認しました)、この問題が発生する可能性があります。

より良い解決策は、_list size_をチェックすることです。

以下のサンプルコードをご覧ください。

_private void setupAdapter(){
    if (list.size() <= 10){
        recycler.setItemViewCacheSize(0);
     }
     recycler.setAdapter(adapter);
     recycler.setLayoutManager(linearLayoutManager);
}
_
0