web-dev-qa-db-ja.com

DPADで高速スクロールすると、RecyclerViewonCreateViewHolderが過度に呼び出されます

私はAmazonFireTVで開発しています。

TVアプリ(タッチなし)なので、行のレイアウト内をナビゲートできるようにフォーカス可能なものが必要です。

画像、テキスト、フォーカス可能な非常にシンプルなRecyclerviewがあります。上または下を押すと、すべて正しくスクロールされますが、スクロールが追いつかないほど速くナビゲートすると、新しいビューホルダー(画面外)が作成され、UIが遅れることに気付きました。

作成番号が記載されたアクティビティを作成しました。ゆっくりスクロールすると、最高の作成番号は10です。しかし、速くスクロールすると、1秒で作成番号60のカードが表示されます。これにより、大きなラグが発生し、アプリケーションは多くのフレームをドロップします。私のアプローチは完全に間違っていますか?

以下のコードを使用して、これをテストしてください。

/**
 * Created by sylversphere on 15-04-15.
 */
public class LandfillActivity extends Activity{

private Context context;

private static int ticketNumber;
private static int getTicket(){
    ticketNumber ++;
    return ticketNumber;
}

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    context = this;
    setContentView(R.layout.landfill_activity);
    RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recyclerView);
    GridLayoutManager glm = new GridLayoutManager(context, 2);
    recyclerView.setLayoutManager(glm);
    SickAdapter sickAdapter = new SickAdapter();
    recyclerView.setAdapter(sickAdapter);
}

public class SickViewHolder extends RecyclerView.ViewHolder{
    TextView ticketDisplayer;
    public ImageView imageView;
    public SickViewHolder(View itemView) {
        super(itemView);
        ticketDisplayer = (TextView) itemView.findViewById(R.id.ticketDisplayer);
        imageView = (ImageView) itemView.findViewById(R.id.imageView);

        itemView.findViewById(R.id.focus_glass).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                context.startActivity(new Intent(context, LouisVuittonActivity.class));
            }
        });
    }
    public void setTicket(int value){
        ticketDisplayer.setText(""+value);
    }
}

public class SickAdapter extends RecyclerView.Adapter<SickViewHolder>{

    @Override
    public SickViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        SickViewHolder svh = new SickViewHolder(getLayoutInflater().inflate(R.layout.one_row_element, null));
        svh.setTicket(getTicket());
        return svh;
    }

    @Override
    public void onBindViewHolder(SickViewHolder holder, int position) {
        String[] image_url_array = getResources().getStringArray(R.array.test_image_urls);
        Picasso.with(context).load(image_url_array[position % image_url_array.length] ).fit().centerCrop().into(holder.imageView);
    }

    @Override
    public int getItemCount() {
        return 100000;
    }
}
}

one_row_element.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:Android="http://schemas.Android.com/apk/res/Android"
    Android:layout_width="match_parent"
    Android:layout_height="wrap_content"
    Android:orientation="horizontal">
    <FrameLayout
        Android:layout_width="match_parent"
        Android:layout_height="200dp"
        Android:orientation="horizontal">
        <ImageView
            Android:id="@+id/imageView"
            Android:layout_width="match_parent"
            Android:layout_height="match_parent"
            Android:adjustViewBounds="true"
            Android:scaleType="centerCrop"
            Android:src="@mipmap/sick_view_row_bg" />
        <LinearLayout
            Android:layout_width="wrap_content"
            Android:layout_height="wrap_content"
            Android:layout_gravity="left|center_vertical"
            Android:layout_marginLeft="15dp"
            Android:orientation="horizontal">
            <TextView
                Android:id="@+id/virusTextView"
                Android:layout_width="wrap_content"
                Android:layout_height="wrap_content"
                Android:text="Creation #"
                Android:textColor="#fff"
                Android:textSize="40sp" />
            <TextView
                Android:id="@+id/ticketDisplayer"
                Android:layout_width="wrap_content"
                Android:layout_height="wrap_content"
                Android:text="1"
                Android:textColor="#fff"
                Android:textSize="40sp" />
        </LinearLayout>
        <FrameLayout
            Android:layout_width="match_parent"
            Android:layout_height="match_parent"
            Android:id="@+id/focus_glass"
            Android:background="@drawable/subtle_focus_glass"
            Android:focusable="true"
            Android:focusableInTouchMode="true"/>
    </FrameLayout>
</FrameLayout>

test_image_urls.xml(私が所有していないURL)

<?xml version="1.0" encoding="utf-8"?>
<resources>
<string-array name="test_image_urls"
    formatted="false">
    <item>http://farm4.static.flickr.com/3175/2737866473_7958dc8760.jpg</item>
    <item>http://farm4.static.flickr.com/3276/2875184020_9944005d0d.jpg</item>
    <item>http://farm3.static.flickr.com/2531/4094333885_e8462a8338.jpg</item>
    <item>http://farm4.static.flickr.com/3289/2809605169_8efe2b8f27.jpg</item>
    <item>http://2.bp.blogspot.com/_SrRTF97Kbfo/SUqT9y-qTVI/AAAAAAAABmg/saRXhruwS6M/s400/bARADEI.jpg</item>
    <item>http://fortunaweb.com.ar/wp-content/uploads/2009/10/Caroline-Atkinson-FMI.jpg</item>
    <item>http://farm4.static.flickr.com/3488/4051378654_238ca94313.jpg</item>
    <item>http://farm4.static.flickr.com/3368/3198142470_6eb0be5f32.jpg</item>
    <item>http://www.powercai.net/Photo/UploadPhotos/200503/20050307172201492.jpg</item>
    <item>http://www.web07.cn/uploads/Photo/c101122/12Z3Y54RZ-22027.jpg</item>
    <item>http://www.mitravel.com.tw/html/asia/2011/Palau-4/index_clip_image002_0000.jpg</item>
    <item>http://news.xinhuanet.com/mil/2007-05/19/xinsrc_36205041914150623191153.jpg</item>
    <item>http://ib.berkeley.edu/labs/koehl/images/hannah.jpg</item>
    <item>http://down.tutu001.com/d/file/20110307/ef7937c2b70bfc2da539eea9df_560.jpg</item>
    <item>http://farm3.static.flickr.com/2278/2300491905_5272f77e56.jpg</item>
    <item>http://www.pic35.com/uploads/allimg/100526/1-100526224U1.jpg</item>
    <item>http://img.99118.com/Big2/1024768/20101211/1700013.jpg</item>
    <item>http://farm1.static.flickr.com/45/139488995_bd06578562.jpg</item>
</string-array>
</resources>

subtle_focus

    <?xml version="1.0" encoding="utf-8"?>
<selector xmlns:Android="http://schemas.Android.com/apk/res/Android">
    <item Android:state_focused="true" Android:drawable="@color/glass_focus"/>
    <item Android:drawable="@color/glass_normal"/>
</selector>

glass_normal#9000

glass_focus#0000

21
Dreamingwhale

プール内の リサイクルされたビューの最大数 を増やしてみてください:

recyclerView.getRecycledViewPool().setMaxRecycledViews(50);

50は任意の数です。高くしたり低くしたりして、何が起こるかを確認できます。

RecyclerViewは、 一時的な状態 のビューの再利用を回避しようとするため、ビューがすぐに無効化されたり、アニメーション化されたりすると、すぐに再利用されない場合があります。

同様に、小さいビューが多数ある場合は、デフォルトのプールサイズで処理できるよりも多くの画面が表示される可能性があります(グリッドのようなレイアウトでより一般的です)。

18
Sam Judd

ピカソはあなたを抑えていますが、あなた自身のメカニズムを構築するための提案された解決策は行く方法ではありません。

ピカソは昨年かそこらで遅れをとっており、今日では Google GlideFacebook Fresco の形ではるかに優れた選択肢があり、RecyclerViewと次のようなオンラインで利用できる多くのテストで、読み込み、キャッシュ、保存がより高速かつ効率的に行われることが証明されています。

それがお役に立てば幸いです。幸運を。

3
Royi Benyossef

コメント提供者が指摘したように、ピカソからの保留中の応答があなたを妨げている可能性があります。その場合は、ImageViewを拡張し、次のメソッドをオーバーライドすることで解決できます。試してみる価値はあると思います。

_@Override
protected void onDetachedFromWindow() {
    Picasso.with(context).cancelRequest(this);
    super.onDetachedFromWindow();
}
_

更新:

これは正しい方法ではないことが判明しました。リクエストをキャンセルしたい場合は、以下のコメントで指摘されているように、onViewRecycled()コールバックでキャンセルする必要があります。

2
Oguz Babaoglu

Sam Judd の答えを深く掘り下げることで、アダプターに以下を実装することで、リサイクラーにビューのリサイクルを強制しました。

@Override
public boolean onFailedToRecycleView(@NonNull VH holder) 
      return true;
}

ご覧のとおり ここ

このアダプタによって作成されたViewHolderが一時的な状態のためにリサイクルできない場合、RecyclerViewによって呼び出されます。このコールバックを受信すると、アダプタはビューの一時的な状態に影響を与えるアニメーションをクリアしてtrueを返し、ビューをリサイクルできるようにします。問題のビューはすでにRecyclerViewから削除されていることに注意してください。

場合によっては、ビューは一時的な状態ですが、リサイクルしてもかまいません。ほとんどの場合、これは、ビューが新しい位置にリバウンドされたときに、onBindViewHolder(ViewHolder、int)呼び出しで一時状態がクリアされる場合です。このため、RecyclerViewは決定をアダプターに任せ、このメソッドの戻り値を使用して、ビューをリサイクルする必要があるかどうかを決定します。

すべてのアニメーションがRecyclerView.ItemAnimatorによって作成されている場合、RecyclerViewはアニメーションが完了するまでこれらのビューを子として保持するため、このコールバックを受け取ることはありません。このコールバックは、アイテムビューの子が、RecyclerView.ItemAnimatorを使用して実装するのが簡単ではない可能性があるアニメーションを作成する場合に役立ちます。

Holder.itemView.setHasTransientState(false);を呼び出してこの問題を修正しないでください。以前にholder.itemView.setHasTransientState(true);を呼び出したことがない限り。各View.setHasTransientState(true)呼び出しは、View.setHasTransientState(false)呼び出しと一致する必要があります。一致しない場合、ビューの状態が不整合になる可能性があります。一時的な状態を手動で処理するのではなく、トリガーしているアニメーションを終了またはキャンセルすることを常にお勧めします。

0
hadilq