简体   繁体   English

如何在Recyclerview中使用Spinner?

[英]How to use Spinner in Recyclerview?

Which are the best practice to handle a Spinner in a RecyclerView Adapter? 哪个是在RecyclerView适配器中处理Spinner的最佳实践?

This is my RecyclerView Adapter: 这是我的RecyclerView适配器:

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;
    }
}
}

and this is my Spinner Adapter: 这是我的Spinner适配器:

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 this way when i scroll the RecyclerView i'm experiencing lag. 这样当我滚动RecyclerView我遇到了滞后。

If i remove these lines everything works fine: 如果我删除这些行一切正常:

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

So the problem is the way i handle the adapter of the Spinner , how can i handle this? 所以问题是我处理Spinner的适配器的方式,我该如何处理?

Thanks in advance. 提前致谢。

Short

To improve performance, 为了提高性能,

  1. Remove allocations from onBindViewHolder onBindViewHolder中删除分配
  2. Reuse LayoutInflater , instead of getting a new one every time. 重复使用LayoutInflater ,而不是每次都获得一个新的。
  3. Minimize repetitive work in onBindViewHolder implementation 最大限度地减少onBindViewHolder实现中的重复性工作
  4. Spinner Adapter should also recycle the views Spinner Adapter还应该回收视图

Background 背景

When using an adapter for scrolling, the most important thing to make sure is that we DON'T allocate new objects (or minimize it as possible). 使用适配器进行滚动时,最重要的是确保我们分配新对象(或尽可能减少它)。

The whole purpose of a RecyclerView with an Adapter is to make sure we Recycle our objects so that the work needed during scroll is minimal. 带有适配器的RecyclerView的全部目的是确保我们回收我们的对象,以便滚动期间所需的工作最小化。

Since allocating memory is very "expensive", to improve scrolling performance, the first thing to look for is allocations during the onBindViewHolder . 由于分配内存非常“昂贵”,为了提高滚动性能,首先要考虑的是onBindViewHolder期间的分配 All allocations if any should be made in the onCreateViewHolder . 所有分配(如果有的话)应该在onCreateViewHolder中进行

Once all allocations are cleared, if we still have lagging, it is time for some micro improvements. 一旦清除所有分配,如果我们仍然有滞后,现在是时候进行一些微观改进了。 These includes improving code quality, reuse logic results, etc. 这些包括提高代码质量,重用逻辑结果等。

What to do? 该怎么办?

1) Remove allocations from onBindViewHolder 1)从onBindViewHolder中删除分配

In the following code: 在以下代码中:

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;
    }
});

You currently have 3 direct allocations ( new ) and some indirect allocations ( inflate ). 您目前有3个直接分配( )和一些间接分配( 膨胀 )。 Change this code so that all allocations are in the onCreateViewHolder . 更改此代码,以便所有分配都在onCreateViewHolder中 For example: 例如:

In onCreateViewHolder do the allocations like so: onCreateViewHolder中执行如下分配:

// 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 relevant data like so: onBindViewHolder中绑定相关数据,如下所示:

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

2) Reuse LayoutInflater , instead of getting a new one every time. 2)重复使用LayoutInflater ,而不是每次都获得一个新的。

In the following code: 在以下代码中:

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);
}

You are getting a new LayoutInflater every time. 你每次都会得到一个新的LayoutInflater。 It is a waste. 这是一种浪费。 Better to get one in the Adapter constructor and save it as a member. 最好在Adapter构造函数中获取一个并将其保存为成员。

3) Minimize repetitive work in onBindViewHolder implementation 3)尽量减少onBindViewHolder实现中的重复性工作

For example, in the following code: 例如,在以下代码中:

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

You are calculating the same logic twice. 您正在计算两次相同的逻辑。 Better to calculate it once and reuse the outcome: 最好一次计算并重用结果:

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 Adapter should also recycle the views 4) Spinner Adapter还应该回收视图

In CartSpinnerAdapter.getView() you are also allocating memory. CartSpinnerAdapter.getView()您还分配内存。 It happens (every time * list item * count) - That is a lot of allocations. 它发生(每次*列表项*计数) - 这是很多分配。 Please use the convertView instead. 请改用convertView。 Have a look on this tutorial dzone.com/articles/android-listview-optimizations 看看这个教程dzone.com/articles/android-listview-optimizations

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM