Come utilizzare Spinner in Recyclerview?

Quali sono le best practice per gestire una casella di selezione in un RecyclerView Adattatore?

Questo è il mio RecyclerView Scheda:

public class CartAdapter extends BaseAdapter<Object> {

public CartAdapter(AbstractBaseActivity activity) {
    super(activity);
}

public static final int TYPE_PRODOTTO = 1;
public static final int TYPE_SCONTO = 2;

@Override
public int getItemViewType(int position) {

    if (items.get(position) instanceof Article)
        return TYPE_PRODOTTO;
    else
        return TYPE_SCONTO;
}

@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    View rowView = LayoutInflater.from(parent.getContext()).inflate(viewType == TYPE_PRODOTTO ? R.layout.item_cart : R.layout.item_cart_sconto, parent, false);
    return new ViewHolder(rowView);
}

@Override
public void onBindViewHolder(final RecyclerView.ViewHolder holder, final int position) {
    final ViewHolder viewHolder = (ViewHolder) holder;

    final Object object = items.get(position);

    if (object instanceof Article) {

        viewHolder.getBinding().setVariable(BR.article, object);
        viewHolder.getBinding().executePendingBindings();

        assert viewHolder.quantitySpinner != null;
        assert viewHolder.cartoneQuantity != null;
        assert viewHolder.cartoneValue != null;

        CartSpinnerAdapter adapter = (CartSpinnerAdapter) viewHolder.quantitySpinner.getAdapter();
        adapter.clear();
        adapter.setCount(((Article) object).getQuantityAvailable());
        adapter.notifyDataSetChanged();

        viewHolder.quantitySpinner.setSelection(((Article) object).getQuantity() - 1); //In teoria qui la quantità non deve mai essere zero

        viewHolder.cartoneQuantity.setVisibility(position % 2 == 1 ? View.GONE : View.VISIBLE); //Controllo da togliere in futuro
        viewHolder.cartoneValue.setVisibility(position % 2 == 1 ? View.GONE : View.VISIBLE); //Controllo da togliere in futuro
    }

    final PopupMenu popup = new PopupMenu(getContext(), viewHolder.deleteMenu);
    MenuInflater inflater = popup.getMenuInflater();
    inflater.inflate(R.menu.delete_menu, popup.getMenu());

    viewHolder.deleteMenu.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            popup.show();
        }
    });

    popup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
        @Override
        public boolean onMenuItemClick(MenuItem item) {
            if (item.getItemId() == R.id.action_delete) {
                removeData(holder.getAdapterPosition());
                ((CartActivity) activity).checkIfEmpty();
            }

            return true;
        }
    });
}

public class ViewHolder extends RecyclerView.ViewHolder {

    @BindView(R.id.item)
    View item;
    @Nullable
    @BindView(R.id.cart_image)
    ImageView cartImage;
    @BindView(R.id.delete_menu)
    ImageView deleteMenu;
    @Nullable
    @BindView(R.id.product_cartone_quantity)
    TextView cartoneQuantity;
    @Nullable
    @BindView(R.id.product_cartone_value)
    TextView cartoneValue;
    @Nullable
    @BindView(R.id.quantity_spinner)
    AppCompatSpinner quantitySpinner;

    private ViewDataBinding binding;

    public ViewHolder(View itemView) {
        super(itemView);
        ButterKnife.bind(this, itemView);
        binding = DataBindingUtil.bind(itemView);
        if (quantitySpinner != null)
            quantitySpinner.setAdapter(new CartSpinnerAdapter(itemView.getContext(), R.layout.support_simple_spinner_dropdown_item));
    }

    public ViewDataBinding getBinding() {
        return binding;
    }
}
}

e questo è il mio Spinner Scheda:

public class CartSpinnerAdapter extends ArrayAdapter<String> {

LayoutInflater inflater;

int count;

public CartSpinnerAdapter(Context context, int resource) {
    super(context, resource);

    inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}

public CartSpinnerAdapter(Context context, int resource, int count) {
    super(context, resource);

    this.count = count;
    inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}

public void setCount(int count) {
    this.count = count;
}

@Override
public View getDropDownView(int position, View convertView, ViewGroup parent) {
    return getStandardView(position, parent, true);
}

@Override
public View getView(int position, View convertView, ViewGroup parent) {
    return getStandardView(position, parent, false);
}

@Override
public int getCount() {
    return count;
}

private View getStandardView(int position, ViewGroup parent, boolean dropdown) {
    View row = inflater.inflate(R.layout.support_simple_spinner_dropdown_item, parent, false);

    TextView title = (TextView) row.findViewById(android.R.id.text1);

    title.setText(String.valueOf(position + 1));

    if (dropdown)
        title.setMinWidth(Utils.dpToPx(getContext(), 64));
    else
        title.setAlpha(0.5f);

    return row;
}
}

In questo modo quando ho scorrere verso il RecyclerView ho riscontrato lag.

Se posso rimuovere queste righe tutto funziona bene:

CartSpinnerAdapter adapter = (CartSpinnerAdapter) viewHolder.quantitySpinner.getAdapter();
adapter.clear();
adapter.setCount(((Article) object).getQuantityAvailable());
adapter.notifyDataSetChanged();

Quindi il problema è il modo di gestire la scheda del Spinner, come posso gestire questo?

Grazie in anticipo.

OriginaleL’autore Bronx | 2016-08-04

One Reply
  1. 14

    Breve

    Per migliorare le prestazioni,

    1. Rimuovere l’assegnazione di onBindViewHolder
    2. Riutilizzo LayoutInflater, invece di ottenere uno nuovo ogni volta.
    3. Ridurre al minimo il lavoro ripetitivo in onBindViewHolder attuazione
    4. Spinner Adattatore dovrebbero riciclare il punto di vista

    Sfondo

    Quando si utilizza un adattatore per lo scorrimento, la cosa più importante da verificare è che ci NON allocare nuovi oggetti (o ridurre al minimo possibile).

    Lo scopo di un RecyclerView con un Adattatore è quello di assicurarsi di Riciclare i nostri oggetti in modo che il lavoro necessario durante lo scorrimento è minima.

    Poiché l’allocazione di memoria è molto “costoso”, per migliorare lo scorrimento delle prestazioni, la prima cosa da guardare per è gli stanziamenti nel corso dell’ onBindViewHolder. Tutte le allocazioni, se presente, dovrebbe essere fatto in onCreateViewHolder.

    Una volta che tutte le assegnazioni vengono cancellati, se abbiamo ancora in ritardo di sviluppo, è il momento per un po ‘ di micro miglioramenti. Questi include il miglioramento della qualità del codice, il riutilizzo logica risultati, etc.

    Cosa fare?

    1) Rimuovere l’assegnazione di onBindViewHolder

    Nel codice seguente:

    final PopupMenu popup = new PopupMenu(getContext(), viewHolder.deleteMenu);
    MenuInflater inflater = popup.getMenuInflater();
    inflater.inflate(R.menu.delete_menu, popup.getMenu());
    
    viewHolder.deleteMenu.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            popup.show();
        }
    });
    
    popup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
        @Override
        public boolean onMenuItemClick(MenuItem item) {
            if (item.getItemId() == R.id.action_delete) {
                removeData(holder.getAdapterPosition());
                ((CartActivity) activity).checkIfEmpty();
            }
    
            return true;
        }
    });

    Si dispone attualmente di 3 assegnazioni dirette (nuovo) e indiretta accantonamenti (gonfiare). Modificare il codice in modo che tutte le allocazioni sono in onCreateViewHolder. Per esempio:

    In onCreateViewHolder fare le assegnazioni in questo modo:

    //Allocate Listener only ONCE per recycled view 
    viewHolder.deleteMenu.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            //Get needed data from the view TAG, we will set it later
            final int itemPosition = (Integer)view.getTag();
    
            //Do work only when needed - when user clicked the button
            final PopupMenu popup = new PopupMenu(getContext(), viewHolder.deleteMenu);
            MenuInflater inflater = popup.getMenuInflater();
            inflater.inflate(R.menu.delete_menu, popup.getMenu());
    
            popup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
                @Override
                public boolean onMenuItemClick(MenuItem item) {
                    //Do logic using itemPosition etc
                    return true;
                }
            });
    
            popup.show();
        }
    });

    In onBindViewHolder bind dei dati rilevanti come:

    viewHolder.deleteMenu.setTag(holder.getAdapterPosition());

    2) Riutilizzo LayoutInflater, invece di ottenere uno nuovo ogni volta.

    Nel codice seguente:

    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View rowView = LayoutInflater.from(parent.getContext()).inflate(viewType == TYPE_PRODOTTO ? R.layout.item_cart : R.layout.item_cart_sconto, parent, false);
        return new ViewHolder(rowView);
    }

    Hai trovato un nuovo LayoutInflater ogni volta. È uno spreco. Meglio avere un Adattatore costruttore e salvarlo come un membro.

    3) Ridurre al minimo il lavoro ripetitivo in onBindViewHolder attuazione

    Per esempio, nel codice seguente:

    viewHolder.cartoneQuantity.setVisibility(position % 2 == 1 ? View.GONE : View.VISIBLE); //Controllo da togliere in futuro
    viewHolder.cartoneValue.setVisibility(position % 2 == 1 ? View.GONE : View.VISIBLE); //Controllo da togliere in futuro

    Si calcola la stessa logica di due volte. Meglio calcolare una sola volta e riutilizzare il risultato:

    int cartoneVisibility = position % 2 == 1 ? View.GONE : View.VISIBLE;
    viewHolder.cartoneQuantity.setVisibility(cartoneVisibility); //Controllo da togliere in futuro
    viewHolder.cartoneValue.setVisibility(cartoneVisibility); //Controllo da togliere in futuro

    4)Spinner Adattatore dovrebbero riciclare il punto di vista

    In CartSpinnerAdapter.getView() si sono anche l’allocazione di memoria. Succede ogni volta * voce di elenco * conteggio) – Che è un sacco di allocazioni. Si prega di utilizzare il convertView invece. Guardate questo tutorial dzone.com/articles/android-listview-optimizations

    Ciao, grazie per la risposta. Ho riscontrato il problema del lag da quando ho aggiunto la scheda logica per la spinner e anche con questi miglioramenti, il problema del lag non risolvere. Quindi la mia domanda è, come posso utilizzare una casella di selezione adattatore all’interno di un recyclerview adattatore?
    Si prega di vedere la mia modifica alla sezione (1) soluzione.
    Inoltre, applicare i suggerimenti per la CartSpinnerAdapter. Lì non si sta utilizzando l’adattatore in modo corretto in quanto non riciclare il punto di vista. Utilizzare il convertView.
    Così, come ho detto, in CartSpinnerAdapter. getView si sono allocazione di memoria. Succede ogni volta che * voce di elenco * conte – Che è un sacco di allocazioni. Dove, infatti, è necessario 0 stanziamenti. Si prega di utilizzare il convertView invece. Guardate questo tutorial dzone.com/articles/android-listview-optimizations
    in questo caso il “legame” è il setCount funzione. In altri casi si potrebbero avere altre associazioni. In ogni caso, la cosa più importante da ricordare è che non bisogna allocare la memoria all’interno di funzioni che sono chiamati rapidamente durante il layout dell’interfaccia utente. Trovare le allocazioni, farli fuori, e si sono dorati 🙂

    OriginaleL’autore Eyal Biran

Lascia un commento