使用RecyclerView创建动态列表:

当我们创建一个RecyclerView时。适配器我们必须指定ViewHolder,它将绑定到适配器。

public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {

    private String[] mDataset;

    public MyAdapter(String[] myDataset) {
        mDataset = myDataset;
    }

    public static class ViewHolder extends RecyclerView.ViewHolder {
        public TextView mTextView;
        public ViewHolder(TextView v) {
            super(v);
            mTextView = v;
        }
    }

    @Override
    public MyAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.some_layout, parent, false);

        //findViewById...

        ViewHolder vh = new ViewHolder(v);
        return vh;
    }

    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        holder.mTextView.setText(mDataset[position]);
    }

    @Override
    public int getItemCount() {
        return mDataset.length;
    }
}

有可能创建多个视图类型的RecyclerView吗?


当前回答

比以往任何时候都简单,忘记ViewTypes。不建议在一个适配器中使用多个视图类型。这将使代码混乱,并打破单一责任原则,因为现在适配器需要处理逻辑来知道要填充哪个视图。

现在,想象一下在大型团队中工作,每个团队都必须在这些视图类型中的一种功能中工作。如果在不同视图类型中工作的所有团队都使用同一个适配器,那将是一团糟。这是使用ConcatAdapter解决的,其中隔离了适配器。逐个编码,然后将它们合并到一个视图中。

在recyclerview:1.2.0-alpha04中,您现在可以使用ConcatAdapter。

如果你需要一个具有不同viewTypes的视图,你可以为每个部分编写适配器,并使用ConcatAdapter将它们合并到一个recyclerview中。

ConcatAdapter

这张图片显示了一个recyclerview拥有的三种不同的视图类型,页眉,内容和页脚。

你只需要为每个section创建一个适配器,然后使用ConcatAdapter将它们合并到一个recyclerview中:

val firstAdapter: FirstAdapter = …
val secondAdapter: SecondAdapter = …
val thirdAdapter: ThirdAdapter = …
val concatAdapter = ConcatAdapter(firstAdapter, secondAdapter,
                                  thirdAdapter)
recyclerView.adapter = concatAdapter

这就是你需要知道的。如果您想处理加载状态,例如在某些加载发生后删除最后一个适配器,您可以使用LoadState。

欲了解更多深入信息,请关注Florina Muntenescu的帖子https://medium.com/androiddevelopers/merge-adapters-sequentially-with-mergeadapter-294d2942127a

其他回答

这是非常简单直接的。

只需在适配器中重写getItemViewType()方法。根据数据返回不同的itemViewType值。例如,考虑一个Person类型的对象,其成员为male,如果isMale为真,则返回1,isMale为假,则在getItemViewType()方法中返回2。

现在来到createViewHolder (ViewGroup parent, int viewType),在不同的viewType的基础上,你可以膨胀不同的布局文件。像下面这样:

 if (viewType == 1){
    View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.male, parent, false);
    return new AdapterMaleViewHolder(view);
}
else{
    View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.female, parent, false);
    return new AdapterFemaleViewHolder(view);
}

在onBindViewHolder (VH holder,int position)检查holder是AdapterFemaleViewHolder或AdapterMaleViewHolder的实例,并相应地分配值。

ViewHolder可能是这样的

    class AdapterMaleViewHolder extends RecyclerView.ViewHolder {
            ...
            public AdapterMaleViewHolder(View itemView){
            ...
            }
        }

    class AdapterFemaleViewHolder extends RecyclerView.ViewHolder {
         ...
         public AdapterFemaleViewHolder(View itemView){
            ...
         }
    }

如果视图类型的布局很少,而且绑定逻辑很简单,那么请遵循Anton的解决方案。但是,如果需要管理复杂的布局和绑定逻辑,代码就会很混乱。

我相信下面的解决方案对于需要处理复杂视图类型的人是有用的。

基本DataBinder类

abstract public class DataBinder<T extends RecyclerView.ViewHolder> {

    private DataBindAdapter mDataBindAdapter;

    public DataBinder(DataBindAdapter dataBindAdapter) {
        mDataBindAdapter = dataBindAdapter;
    }

    abstract public T newViewHolder(ViewGroup parent);

    abstract public void bindViewHolder(T holder, int position);

    abstract public int getItemCount();

......

}

在这个类中需要定义的函数与创建单一视图类型时的适配器类几乎相同。

对于每个视图类型,通过扩展这个DataBinder来创建类。

样例DataBinder类

public class Sample1Binder extends DataBinder<Sample1Binder.ViewHolder> {

    private List<String> mDataSet = new ArrayList();

    public Sample1Binder(DataBindAdapter dataBindAdapter) {
        super(dataBindAdapter);
    }

    @Override
    public ViewHolder newViewHolder(ViewGroup parent) {
        View view = LayoutInflater.from(parent.getContext()).inflate(
            R.layout.layout_sample1, parent, false);
        return new ViewHolder(view);
    }

    @Override
    public void bindViewHolder(ViewHolder holder, int position) {
        String title = mDataSet.get(position);
        holder.mTitleText.setText(title);
    }

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

    public void setDataSet(List<String> dataSet) {
        mDataSet.addAll(dataSet);
    }

    static class ViewHolder extends RecyclerView.ViewHolder {

        TextView mTitleText;

        public ViewHolder(View view) {
            super(view);
            mTitleText = (TextView) view.findViewById(R.id.title_type1);
        }
    }
}

为了管理DataBinder类,需要创建一个适配器类。

基DataBindAdapter类

abstract public class DataBindAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        return getDataBinder(viewType).newViewHolder(parent);
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) {
        int binderPosition = getBinderPosition(position);
        getDataBinder(viewHolder.getItemViewType()).bindViewHolder(viewHolder, binderPosition);
    }

    @Override
    public abstract int getItemCount();

    @Override
    public abstract int getItemViewType(int position);

    public abstract <T extends DataBinder> T getDataBinder(int viewType);

    public abstract int getPosition(DataBinder binder, int binderPosition);

    public abstract int getBinderPosition(int position);

......

}

通过扩展这个基类来创建类,然后实例化DataBinder类并覆盖抽象方法

getItemCount 返回DataBinders的项目总数 getItemViewType 定义适配器位置和视图类型之间的映射逻辑。 getDataBinder 根据视图类型返回DataBinder实例 getPosition 定义从指定DataBinder中的位置到适配器位置的转换逻辑 getBinderPosition 定义从适配器位置到DataBinder中的位置的转换逻辑

我在GitHub上留下了更详细的解决方案和示例,所以如果你需要,请参考RecyclerView-MultipleViewTypeAdapter。

首先,必须创建两个布局XML文件。之后,在recyclerview适配器中,TYPE_CALL和TYPE_EMAIL是两个静态值,分别为适配器类中的1和2。

现在在Recycler视图Adapter类级别定义两个静态值,例如:private static int TYPE_EMAIL = 2;

现在创建多个视图的视图持有者,如下所示:

class CallViewHolder extends RecyclerView.ViewHolder {

    private TextView txtName;
    private TextView txtAddress;

    CallViewHolder(@NonNull View itemView) {
        super(itemView);
        txtName = itemView.findViewById(R.id.txtName);
        txtAddress = itemView.findViewById(R.id.txtAddress);
    }
}

class EmailViewHolder extends RecyclerView.ViewHolder {

    private TextView txtName;
    private TextView txtAddress;

    EmailViewHolder(@NonNull View itemView) {
        super(itemView);
        txtName = itemView.findViewById(R.id.txtName);
        txtAddress = itemView.findViewById(R.id.txtAddress);
    }
}

现在在recyclerview适配器的onCreateViewHolder和onBindViewHolder方法中编写如下代码:

@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int viewType) {
    View view;
    if (viewType == TYPE_CALL) { // for call layout
        view = LayoutInflater.from(context).inflate(R.layout.item_call, viewGroup, false);
        return new CallViewHolder(view);

    } else { // for email layout
        view = LayoutInflater.from(context).inflate(R.layout.item_email, viewGroup, false);
        return new EmailViewHolder(view);
    }
}

@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int position) {
    if (getItemViewType(position) == TYPE_CALL) {
        ((CallViewHolder) viewHolder).setCallDetails(employees.get(position));
    } else {
        ((EmailViewHolder) viewHolder).setEmailDetails(employees.get(position));
    }
}

你可以使用这个图书馆:https://github.com/vivchar/RendererRecyclerViewAdapter

mRecyclerViewAdapter = new RendererRecyclerViewAdapter(); /* Included from library */
mRecyclerViewAdapter.registerRenderer(new SomeViewRenderer(SomeModel.TYPE, this));
mRecyclerViewAdapter.registerRenderer(...); /* You can use several types of cells */

对于每一个项目,你应该实现一个ViewRenderer, ViewHolder, SomeModel:

ViewHolder -它是回收器视图的一个简单视图持有者。

SomeModel -它是你的模型与ItemModel接口

public class SomeViewRenderer extends ViewRenderer<SomeModel, SomeViewHolder> {

    public SomeViewRenderer(final int type, final Context context) {
        super(type, context);
    }

    @Override
    public void bindView(@NonNull final SomeModel model, @NonNull final SomeViewHolder holder) {
        holder.mTitle.setText(model.getTitle());
    }

    @NonNull
    @Override
    public SomeViewHolder createViewHolder(@Nullable final ViewGroup parent) {
        return new SomeViewHolder(LayoutInflater.from(getContext()).inflate(R.layout.some_item, parent, false));
    }
}

有关更多详细信息,您可以查看文档。

实际上,我想改进一下安东的回答。

由于getItemViewType(int position)返回一个整数值,您可以返回需要膨胀的布局资源ID。这样你就可以在onCreateViewHolder(ViewGroup parent, int viewType)方法中保存一些逻辑。

此外,我不建议在getItemCount()中进行密集的计算,因为在呈现列表时,以及在呈现可见项之外的每个项时,该特定函数至少被调用了5次。遗憾的是,由于notifyDatasetChanged()方法是final方法,所以不能重写它,但是可以从适配器中的另一个函数调用它。