web-dev-qa-db-fra.com

RecyclerView onCreateViewHolder appelé trop souvent lors d'un défilement rapide avec DPAD

Je développe sur Amazon Fire TV.

Parce que c'est une application de télévision (pas de contact), j'ai besoin de focusables dans la disposition de la ligne pour pouvoir naviguer.

J'ai une Recyclerview très simple avec une image, du texte et un focusable. Lorsque j'appuie sur le haut ou le bas, tout défile correctement, mais j'ai remarqué que lorsque je navigais plus vite que le défilement pouvait se prolonger, cela créait de nouveaux visionneuses (écran Off) et prolongeait l'interface utilisateur.

J'ai créé une activité avec des numéros de création dessus. Lorsque je fais défiler lentement, le # de création le plus élevé est 10. Mais lorsque je défile rapidement, je reçois des cartes avec le numéro de création 60 en une seconde. Cela provoque un énorme retard et l'application supprime beaucoup d'images. Est-ce que mon approche est totalement fausse?

Utilisez le code ci-dessous pour le tester.

/**
 * 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 n'appartenant pas à moi)

<?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 est #9000

glass_focus est #0000

19
Dreamingwhale

Essayez d'augmenter le nombre maximum de vues recyclées dans le pool:

recyclerView.getRecycledViewPool().setMaxRecycledViews(50);

50 est un nombre arbitraire, vous pouvez essayer plus haut ou plus bas et voir ce qui se passe.

RecyclerView essaie d'éviter de réutiliser des vues ayant état transitoire . Par conséquent, si les vues sont invalidées rapidement ou sont animées, elles ne peuvent pas être réutilisées immédiatement.

De même, si vous avez beaucoup de vues plus petites, vous risquez de vous retrouver avec plus de choses à l'écran que la taille de pool par défaut ne peut en gérer (plus commun avec des mises en forme de grille).

16
Sam Judd

Picasso vous tient debout, mais la solution suggérée pour construire votre propre mécanisme n’est pas la solution.

Picasso a pris du retard depuis environ un an et, de nos jours, il existe de bien meilleures alternatives sous la forme de Google Glide et Facebook Fresco , qui a spécifiquement publié des mises à jour qui fonctionnent mieux avec RecyclerView et se sont révélées plus rapides et plus efficaces. lors du chargement, de la mise en cache et du stockage dans de nombreux tests disponibles en ligne tels que:

J'espère que cela a aidé. Bonne chance.

3
Royi Benyossef

Comme les commentateurs l'ont souligné, les réponses en attente de Picasso pourraient vous retarder. Si tel est le cas, vous pouvez le résoudre en développant ImageView et en remplaçant la méthode suivante. Je pense que cela vaut la peine d'essayer.

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

Mettre à jour:

Il s'avère que ce n'est pas la bonne façon. Toute personne souhaitant annuler une demande devrait le faire dans le rappel onViewRecycled(), comme indiqué dans les commentaires ci-dessous.

2
Oguz Babaoglu

En approfondissant la réponse de Sam Judd , j’ai forcé le recycleur à recycler les vues en implémentant les éléments suivants dans son adaptateur.

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

Comme vous pouvez le voir ici :

Appelé par RecyclerView si un ViewHolder créé par cet adaptateur ne peut pas être recyclé en raison de son état transitoire. Lors de la réception de ce rappel, Adapter peut effacer les animations qui affectent l'état transitoire de la vue et renvoyer la valeur true afin que la vue puisse être recyclée. N'oubliez pas que la vue en question est déjà supprimée de RecyclerView.

Dans certains cas, il est acceptable de recycler une vue même si son état est transitoire. La plupart du temps, il s'agit d'un cas où l'état transitoire sera effacé lors d'un appel onBindViewHolder (ViewHolder, int) lorsque View est en train de rebondir vers une nouvelle position. Pour cette raison, RecyclerView laisse la décision à l'adaptateur et utilise la valeur de retour de cette méthode pour décider si la vue doit être recyclée ou non.

Notez que lorsque toutes les animations sont créées par RecyclerView.ItemAnimator, vous ne devriez jamais recevoir ce rappel, car RecyclerView conserve ces vues sous la forme d'enfants jusqu'à la fin de leurs animations. Ce rappel est utile lorsque les enfants des vues d'élément créent des animations difficiles à implémenter à l'aide de RecyclerView.ItemAnimator.

Vous ne devriez jamais résoudre ce problème en appelant holder.itemView.setHasTransientState (false); à moins que vous n'ayez déjà appelé holder.itemView.setHasTransientState (true) ;. Chaque appel View.setHasTransientState (true) doit être mis en correspondance avec un appel View.setHasTransientState (false), sinon l'état de la vue peut devenir incohérent. Vous devez toujours préférer mettre fin ou annuler les animations déclenchant l’état transitoire au lieu de le gérer manuellement.

0
hadilq