简体   繁体   English

如何使用泛型将一个RecyclerView适配器用于不同类型的对象?

[英]How to use one RecyclerView adapter for objects of different types using generics?

I have a RecyclerView which I want to be populated with String objects sometimes & with Product objects some other time. 我有一个RecyclerView ,我希望有时用String对象和其他时间的Product对象填充。 So I started creating its manager adapter this way: 所以我开始以这种方式创建其管理适配器:

// BaseSearchAdapter is the class that contains the 'List<T> mItems' member variable
public class SearchAdapter<T> extends BaseSearchAdapter<SearchAdapter.ViewHolder, T> {

    private Context mContext;

    public SearchAdapter(Context context, List<T> items) {
        mContext = context;
        mItems = new ArrayList<>(items);
    }

    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        T item = mItems.get(position);
        holder.bind(item);
    }

    public class ViewHolder extends RecyclerView.ViewHolder {

        TextView textLabel;

        public ViewHolder(View v) {

        }

        public void bind(T item) {
            textLabel.setText(...); // How to handle T ?
        }
    }
}

where T could be String or Product according to the plan. 其中T可以是根据计划的StringProduct

My question is how can I appropriately bind the data (whether it's a String or a Product ) object to its corresponding view in this situation? 我的问题是如何在这种情况下将数据(无论是String还是Product )对象适当地绑定到相应的视图? Or is there a better way to handle this ? 或者有更好的方法来处理这个问题吗?

// BaseSearchAdapter is the class that contains the 'List<T> mItems' member variable
public class SearchAdapter<T> extends BaseSearchAdapter<SearchAdapter.ViewHolder<T>, T> {

    private Context mContext;
    private ViewHolderBinder<T> mBinder;

    public SearchAdapter(Context context, List<T> items, ViewHolderBinder<T> binder) {
        mContext = context;
        mItems = new ArrayList<>(items);
        mBinder = binder;
    }

    @Override
    public void onBindViewHolder(ViewHolder<T> holder, int position) {
        T item = mItems.get(position);
        holder.bind(item);
    }

    public static class ViewHolder<T> extends RecyclerView.ViewHolder {
        ViewHolderBinder<T> mBinder;

        TextView textLabel;


        public ViewHolder(View v, ViewHolderBinder<T> binder) {
            textLabel = (TextView)v.findViewById(R.id.text_label);
            this.mBinder = binder;
        }

        public void bind(T item) {
            binder.bind(this, item);
        }
    }

    public interface ViewHolderBinder<T> {
        void bind(ViewHolder<T> viewHolder, T item);
    }

    public static class StringViewHolderBinder implements ViewHolderBinder<String> {
        @Override
        public void bind(ViewHolder<String> viewHolder, String item) {
             viewHolder.textLabel.setText(item);
        }
    }

    public static class ProductViewHolderBinder implements ViewHolderBinder<Product> {
        @Override
        public void bind(ViewHolder<Product> viewHolder, Product item) {
             viewHolder.textLabel.setText(item.getName());
        }
    }
}

What I do on my projects is create a class BaseRecyclerAdapter that has all of my common operations. 我在我的项目中所做的是创建一个具有所有常见操作的BaseRecyclerAdapter类。 Then for most adapters all I have to define is the ViewHolder and the layout. 然后对于大多数适配器,我必须定义的是ViewHolder和布局。

UPDATE As Requested I posted a fuller version of my BaseRecyclerAdapter (it varies a bit based upon project need). UPDATE As Requested我发布了一个更全面的BaseRecyclerAdapter版本(根据项目需要有所不同)。 Also include is a simple gesture callback that allows you to easily enable swipe to remove or drag to reorder operations. 还包括一个简单的手势回调,允许您轻松启用滑动删除或拖动以重新排序操作。

NOTE: This version updates how the recycler item layouts are inflated. 注意:此版本更新回收器项目布局的膨胀方式。 I now prefer to inflate the in the BaseRecyclerAdapter.ViewHolder constructor allowing the layout to be specified in the extending ViewHolder's constructor. 我现在更喜欢在BaseRecyclerAdapter.ViewHolder构造函数中扩充,允许在扩展的ViewHolder构造函数中指定布局。

Example Base Adapter 示例基本适配器

public abstract class BaseRecyclerAdapter<T> extends RecyclerView.Adapter<BaseRecyclerAdapter.ViewHolder> {

    private final List<T> items = new ArrayList<>();
    OnItemSelectedListener<T> onItemSelectedListener = SmartNull.create(OnItemSelectedListener.class);

    public BaseRecyclerAdapter setOnItemSelectedListener(OnItemSelectedListener<T> onItemSelectedListener) {
        if (onItemSelectedListener == null) {
            this.onItemSelectedListener = SmartNull.create(OnItemSelectedListener.class);
        } else {
            this.onItemSelectedListener = onItemSelectedListener;
        }
        return this;
    }

    public boolean isEmpty() {
        return items.isEmpty();
    }

    public void setItems(List<T> items) {
        this.items.clear();
        this.items.addAll(items);
        notifyDataSetChanged();
    }

    public void addItems(T... items) {
        addItems(Arrays.asList(items));
    }

    public void addItems(List<T> items) {
        int startPosition = this.items.size() - 1;
        this.items.addAll(items);
        notifyItemRangeInserted(startPosition, items.size());
    }

    public void removeItem(int position) {
        T item = items.remove(position);
        if (itemRemovedListener != null) {
            itemRemovedListener.onItemRemoved(item);
        }
        notifyItemRemoved(position);
    }

    public void removeItem(T t) {
        int index = items.indexOf(t);
        if (index >= 0) {
            removeItem(index);
        }
    }

    public void addItem(T item) {
        items.add(item);
        notifyItemInserted(getItemCount() - 1);
    }

    public void moveItem(int startPosition, int targetPosition) {
        if (startPosition < targetPosition) {
            for (int i = startPosition; i < targetPosition; i++) {
                Collections.swap(items, i, i + 1);
            }
        } else {
            for (int i = startPosition; i > targetPosition; i--) {
                Collections.swap(items, i, i - 1);
            }
        }

        notifyItemMoved(startPosition, targetPosition);
    }

    public List<T> getItems() {
        return new ArrayList<>(items);
    }

    public void setItemAt(int position, T item){
        items.set(position, item);
        notifyItemChanged(position);
    }

    public void refreshItem(T item) {
        int i = items.indexOf(item);
        if (i >= 0) {
            notifyItemChanged(i);
        }
    }

    protected void setItemWithoutUpdate(int position, T item){
        items.set(position, item);
    }

    public int indexOf(T t) {
        return items.indexOf(t);
    }

    @SuppressWarnings("unchecked")
    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        holder.bindItemAt(getItemAt(position), position);
    }

    @Override
    public int getItemCount() {
        return items.size();
    }

    public T getItemAt(int position) {
        return items.get(position);
    }

    private void onItemSelected(int position) {
        if (isValidPosition(position)) {
            onItemSelectedListener.onItemSelected(getItemAt(position));
        }
    }

    boolean isValidPosition(int position) {
        return position >=0 && position < items.size();
    }

    public abstract class ViewHolder<T> extends RecyclerView.ViewHolder implements View.OnClickListener {

        public ViewHolder(ViewGroup parent, @LayoutRes int layoutId) {
            super(LayoutInflater.from(parent.getContext()).inflate(layoutId, parent, false));
            itemView.setOnClickListener(this);
        }

        public ViewHolder(View view) {
            super(view);
            itemView.setOnClickListener(this);
        }

        @Override
        public void onClick(View v) {
            onItemSelected(getAdapterPosition());
        }

        public abstract void bindItemAt(T t, int position);

    }

    public interface OnItemSelectedListener<T> {
        void onItemSelected(T t);
    }
}

Example Implementation 示例实现

public class ExampleAdapter extends com.stratospherequality.mobileworkforce.modules.common.BaseRecyclerAdapter<Long> {

    @Override
    protected RecyclerViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        return new ViewHolder(parent);
    }

    private class ViewHolder extends RecyclerViewHolder<Long> {

        TextView text;

        public ViewHolder(ViewGroup parent) {
            super(parent, R.layout.row_my_layout);
            // I typically use ButterKnife here but this works as well
            text = (TextView) itemView.findViewById(R.id.text); 
        }

        @Override
        public void setItem(Long value, int position) {
            text.setText("#" + value);
        }
    }
}

Base GestureCallback Base GestureCallback

public class AdapterGestureCallback extends ItemTouchHelper.SimpleCallback {

    public interface OnRemoveItemCallback<T> {
        void onRemoveItem(BaseRecyclerAdapter<T> adapter, T t);
    }

    public enum Direction {
        UP(ItemTouchHelper.UP),
        DOWN(ItemTouchHelper.DOWN),
        LEFT(ItemTouchHelper.LEFT),
        RIGHT(ItemTouchHelper.RIGHT),
        START(ItemTouchHelper.START),
        END(ItemTouchHelper.END);

        public final int value;

        Direction(int value) {
            this.value = value;
        }
    }

    private final BaseRecyclerAdapter adapter;
    private OnRemoveItemCallback onRemoveItemCallback;
    private boolean enabled = true;

    public AdapterGestureCallback(BaseRecyclerAdapter adapter) {
        super(0, 0);
        this.adapter = adapter;
    }

    public AdapterGestureCallback setOnRemoveItemCallback(OnRemoveItemCallback onRemoveItemCallback) {
        this.onRemoveItemCallback = onRemoveItemCallback;
        return this;
    }

    public AdapterGestureCallback setEnabled(boolean enabled) {
        this.enabled = enabled;
        return this;
    }

    @Override
    public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
        adapter.moveItem(viewHolder.getAdapterPosition(), target.getAdapterPosition());
        return true;
    }

    @Override
    public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
        int position = viewHolder.getAdapterPosition();
        if (onRemoveItemCallback == null){
            adapter.removeItem(position);
        } else {
            onRemoveItemCallback.onRemoveItem(adapter, adapter.getItemAt(position));
    }
    }

    public AdapterGestureCallback withDragDirections(Direction... dragDirections) {
        setDefaultDragDirs(valueFor(dragDirections));
        return this;
    }

    public AdapterGestureCallback withSwipeDirections(Direction... swipeDirections) {
        setDefaultSwipeDirs(valueFor(swipeDirections));
        return this;
    }

    @Override
    public boolean isItemViewSwipeEnabled() {
        return enabled;
    }

    @Override
    public boolean isLongPressDragEnabled() {
        return enabled;
    }

    public int valueFor(Direction... directions) {
        int val = 0;
        for (Direction d : directions) {
            val |= d.value;
        }

        return val;
    }

    public AdapterGestureCallback attach(RecyclerView recyclerView) {
        new ItemTouchHelper(this).attachToRecyclerView(recyclerView);
        return this;
    }
}

GestureCallback with swiping 用滑动的GestureCallback

new AdapterGestureCallback(adapter)
            .withSwipeDirections(AdapterGestureCallback.Direction.LEFT, AdapterGestureCallback.Direction.RIGHT)
            .setOnRemoveItemCallback(this)
            .attach(recyclerView);

The approach that, for example, ArrayAdapter<T> uses is to call toString() on whatever T you pass. 例如, ArrayAdapter<T>使用的方法是在传递的任何T上调用toString() This will of course work for a String , and you'll have to implement toString() in your Product to return a meaningful representation. 这当然适用于String ,您必须在Product实现toString()以返回有意义的表示。

Can use generic for Holder like this: 可以像这样使用Holder的泛型:

public abstract class ActionBarAdapter<T,VH extends RecyclerView.ViewHolder> extends RecyclerView.Adapter<VH> {

 private Context mContext;

 public SearchAdapter(Context context, List<T> items) {
    mContext = context;
    mItems = new ArrayList<>(items);
 }
}

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

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