使用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吗?


当前回答

我是这样做的。我传递了“fragmentType”,并创建了两个ViewHolders,在此基础上,我在一个适配器中相应地分类了我的布局,可以有不同的布局和布局管理器

private Context mContext;
protected IOnLoyaltyCardCategoriesItemClicked mListener;
private String fragmentType;
private View view;

public LoyaltyCardsCategoriesRecyclerViewAdapter(Context context, IOnLoyaltyCardCategoriesItemClicked itemListener, String fragmentType) {
    this.mContext = context;
    this.mListener = itemListener;
    this.fragmentType = fragmentType;
}

public class LoyaltyCardCategoriesFragmentViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {

    private ImageView lc_categories_iv;
    private TextView lc_categories_name_tv;
    private int pos;

    public LoyaltyCardCategoriesFragmentViewHolder(View v) {
        super(v);
        view.setOnClickListener(this);
        lc_categories_iv = (ImageView) v.findViewById(R.id.lc_categories_iv);
        lc_categories_name_tv = (TextView) v.findViewById(R.id.lc_categories_name_tv);
    }

    public void setData(int pos) {
        this.pos = pos;
        lc_categories_iv.setImageResource(R.mipmap.ic_launcher);
        lc_categories_name_tv.setText("Loyalty Card Categories");
    }

    @Override
    public void onClick(View view) {
        if (mListener != null) {
            mListener.onLoyaltyCardCategoriesItemClicked(pos);
        }
    }
}

public class MyLoyaltyCardsFragmentTagViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {

    public ImageButton lc_categories_btn;
    private int pos;

    public MyLoyaltyCardsFragmentTagViewHolder(View v) {
        super(v);
        lc_categories_btn = (ImageButton) v.findViewById(R.id.lc_categories_btn);
        lc_categories_btn.setOnClickListener(this);
    }

    public void setData(int pos) {
        this.pos = pos;
        lc_categories_btn.setImageResource(R.mipmap.ic_launcher);
    }

    @Override
    public void onClick(View view) {
        if (mListener != null) {
            mListener.onLoyaltyCardCategoriesItemClicked(pos);
        }
    }
}

@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    if (fragmentType.equalsIgnoreCase(Constants.LoyaltyCardCategoriesFragmentTag)) {
        view = LayoutInflater.from(mContext).inflate(R.layout.loyalty_cards_categories_frag_item, parent, false);
        return new LoyaltyCardCategoriesFragmentViewHolder(view);
    } else if (fragmentType.equalsIgnoreCase(Constants.MyLoyaltyCardsFragmentTag)) {
        view = LayoutInflater.from(mContext).inflate(R.layout.my_loyalty_cards_categories_frag_item, parent, false);
        return new MyLoyaltyCardsFragmentTagViewHolder(view);
    } else {
        return null;
    }
}

@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
    if (fragmentType.equalsIgnoreCase(Constants.LoyaltyCardCategoriesFragmentTag)) {
        ((LoyaltyCardCategoriesFragmentViewHolder) holder).setData(position);
    } else if (fragmentType.equalsIgnoreCase(Constants.MyLoyaltyCardsFragmentTag)) {
        ((MyLoyaltyCardsFragmentTagViewHolder) holder).setData(position);
    }
}

@Override
public int getItemCount() {
    return 7;
}

其他回答

我看到有很多很棒的答案,令人难以置信的详细和广泛。就我而言,如果我几乎从头开始,一步一步地推理,我总是能更好地理解事情。我建议你看看这个链接,每当你有类似的问题,搜索任何解决这个问题的代码实验室。

Android Kotlin基本原理:在RecyclerView头

如果你想将它与Android数据绑定结合使用,请查看https://github.com/evant/binding-collection-adapter -它是迄今为止我所见过的多种视图类型RecyclerView的最佳解决方案。

你可以这样使用它

var items: AsyncDiffPagedObservableList<BaseListItem> =
        AsyncDiffPagedObservableList(GenericDiff)

    val onItemBind: OnItemBind<BaseListItem> =
        OnItemBind { itemBinding, _, item -> itemBinding.set(BR.item, item.layoutRes) }

然后在列表所在的布局中

 <androidx.recyclerview.widget.RecyclerView
                android:layout_width="match_parent"
                android:layout_height="0dp"
                android:layout_weight="1"
                app:enableAnimations="@{false}"
                app:scrollToPosition="@{viewModel.scrollPosition}"

                app:itemBinding="@{viewModel.onItemBind}"
                app:items="@{viewModel.items}"

                app:reverseLayoutManager="@{true}"/>

你的列表项必须实现BaseListItem接口,它看起来像这样:

interface BaseListItem {
    val layoutRes: Int
}

项目视图应该看起来像这样:

<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <data>

        <variable
                name="item"
                type="...presentation.somescreen.list.YourListItem"/>
    </data>

   ...

</layout>

YourListItem实现BaseListItem。

首先,必须创建两个布局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));
    }
}

遵循Anton的解决方案,我想出了这个ViewHolder,它持有/处理/委托不同类型的布局。

但是我不确定当回收视图的ViewHolder不是数据滚入的类型时,替换新的布局是否会工作。

所以基本上, onCreateViewHolder(ViewGroup父视图,int viewType)只在需要新的视图布局时调用;

getItemViewType(int position)将为viewType调用;

onBindViewHolder(ViewHolder holder, int position)总是在回收视图时被调用(新数据被引入并尝试与ViewHolder一起显示)。

当onBindViewHolder被调用时,它需要被放在正确的视图布局中并更新ViewHolder。

public int getItemViewType(int position) {
    TypedData data = mDataSource.get(position);
    return data.type;
}

public ViewHolder onCreateViewHolder(ViewGroup parent,
    int viewType) {
    return ViewHolder.makeViewHolder(parent, viewType);
}

public void onBindViewHolder(ViewHolder holder,
    int position) {
    TypedData data = mDataSource.get(position);
    holder.updateData(data);
}

///
public static class ViewHolder extends
    RecyclerView.ViewHolder {

    ViewGroup mParentViewGroup;
    View mCurrentViewThisViewHolderIsFor;
    int mDataType;

    public TypeOneViewHolder mTypeOneViewHolder;
    public TypeTwoViewHolder mTypeTwoViewHolder;

    static ViewHolder makeViewHolder(ViewGroup vwGrp,
        int dataType) {
        View v = getLayoutView(vwGrp, dataType);
        return new ViewHolder(vwGrp, v, viewType);
    }

    static View getLayoutView(ViewGroup vwGrp,
        int dataType) {
        int layoutId = getLayoutId(dataType);
        return LayoutInflater.from(vwGrp.getContext())
                             .inflate(layoutId, null);
    }

    static int getLayoutId(int dataType) {
        if (dataType == TYPE_ONE) {
            return R.layout.type_one_layout;
        } else if (dataType == TYPE_TWO) {
            return R.layout.type_two_layout;
        }
    }

    public ViewHolder(ViewGroup vwGrp, View v,
        int dataType) {
        super(v);
        mDataType = dataType;
        mParentViewGroup = vwGrp;
        mCurrentViewThisViewHolderIsFor = v;

        if (data.type == TYPE_ONE) {
            mTypeOneViewHolder = new TypeOneViewHolder(v);
        } else if (data.type == TYPE_TWO) {
            mTypeTwoViewHolder = new TypeTwoViewHolder(v);
        }
    }

    public void updateData(TypeData data) {
        mDataType = data.type;
        if (data.type == TYPE_ONE) {
            mTypeTwoViewHolder = null;
            if (mTypeOneViewHolder == null) {
                View newView = getLayoutView(mParentViewGroup,
                               data.type);

                /**
                 *  How can I replace a new view with
                    the view in the parent
                    view container?
                 */
                replaceView(mCurrentViewThisViewHolderIsFor,
                            newView);
                mCurrentViewThisViewHolderIsFor = newView;

                mTypeOneViewHolder =
                    new TypeOneViewHolder(newView);
            }
            mTypeOneViewHolder.updateDataTypeOne(data);

        } else if (data.type == TYPE_TWO){
            mTypeOneViewHolder = null;
            if (mTypeTwoViewHolder == null) {
                View newView = getLayoutView(mParentViewGroup,
                               data.type);

                /**
                 *  How can I replace a new view with
                    the view in the parent view
                    container?
                 */
                replaceView(mCurrentViewThisViewHolderIsFor,
                            newView);
                mCurrentViewThisViewHolderIsFor = newView;

                mTypeTwoViewHolder =
                    new TypeTwoViewHolder(newView);
            }
            mTypeTwoViewHolder.updateDataTypeOne(data);
        }
    }
}

public static void replaceView(View currentView,
    View newView) {
    ViewGroup parent = (ViewGroup)currentView.getParent();
    if(parent == null) {
        return;
    }
    final int index = parent.indexOfChild(currentView);
    parent.removeView(currentView);
    parent.addView(newView, index);
}

ViewHolder有成员mItemViewType来保存视图。

它看起来像在onBindViewHolder(ViewHolder holder, int position)中传递进来的ViewHolder已经通过查看getItemViewType(int position)来获取(或创建),以确保它是匹配的,所以它可能不需要担心ViewHolder的类型不匹配数据[position]的类型。

看起来循环ViewHolder是由类型选择的,所以这里没有战士。

构建一个RecyclerView LayoutManager -第1部分回答了这个问题。

它像这样得到循环ViewHolder:

holder = getRecycledViewPool().getRecycledView(mAdapter.getItemViewType(offsetPosition));

如果没有找到正确类型的recycle ViewHolder,也可以创建一个新的。

public ViewHolder getRecycledView(int viewType) {
        final ArrayList<ViewHolder> scrapHeap = mScrap.get(viewType);
        if (scrapHeap != null && !scrapHeap.isEmpty()) {
            final int index = scrapHeap.size() - 1;
            final ViewHolder scrap = scrapHeap.get(index);
            scrapHeap.remove(index);
            return scrap;
        }
        return null;
    }

View getViewForPosition(int position, boolean dryRun) {
    ......

    if (holder == null) {
            final int offsetPosition = mAdapterHelper.findPositionOffset(position);
            if (offsetPosition < 0 || offsetPosition >= mAdapter.getItemCount()) {
                throw new IndexOutOfBoundsException("Inconsistency detected. Invalid item "
                        + "position " + position + "(offset:" + offsetPosition + ")."
                        + "state:" + mState.getItemCount());
            }

            final int type = mAdapter.getItemViewType(offsetPosition);
            // 2) Find from scrap via stable ids, if exists
            if (mAdapter.hasStableIds()) {
                holder = getScrapViewForId(mAdapter.getItemId(offsetPosition), type, dryRun);
                if (holder != null) {
                    // update position
                    holder.mPosition = offsetPosition;
                    fromScrap = true;
                }
            }
            if (holder == null && mViewCacheExtension != null) {
                // We are NOT sending the offsetPosition because LayoutManager does not
                // know it.
                final View view = mViewCacheExtension
                        .getViewForPositionAndType(this, position, type);
                if (view != null) {
                    holder = getChildViewHolder(view);
                    if (holder == null) {
                        throw new IllegalArgumentException("getViewForPositionAndType returned"
                                + " a view which does not have a ViewHolder");
                    } else if (holder.shouldIgnore()) {
                        throw new IllegalArgumentException("getViewForPositionAndType returned"
                                + " a view that is ignored. You must call stopIgnoring before"
                                + " returning this view.");
                    }
                }
            }
            if (holder == null) { // fallback to recycler
                // try recycler.
                // Head to the shared pool.
                if (DEBUG) {
                    Log.d(TAG, "getViewForPosition(" + position + ") fetching from shared "
                            + "pool");
                }
                holder = getRecycledViewPool()
                        .getRecycledView(mAdapter.getItemViewType(offsetPosition));
                if (holder != null) {
                    holder.resetInternal();
                    if (FORCE_INVALIDATE_DISPLAY_LIST) {
                        invalidateDisplayListInt(holder);
                    }
                }
            }
            if (holder == null) {
                holder = mAdapter.createViewHolder(RecyclerView.this,
                        mAdapter.getItemViewType(offsetPosition));
                if (DEBUG) {
                    Log.d(TAG, "getViewForPosition created new ViewHolder");
                }
            }
        }
        boolean bound = false;
        if (mState.isPreLayout() && holder.isBound()) {
            // do not update unless we absolutely have to.
            holder.mPreLayoutPosition = position;
        } else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {
            if (DEBUG && holder.isRemoved()) {
                throw new IllegalStateException("Removed holder should be bound and it should"
                        + " come here only in pre-layout. Holder: " + holder);
            }
            final int offsetPosition = mAdapterHelper.findPositionOffset(position);
            mAdapter.bindViewHolder(holder, offsetPosition);
            attachAccessibilityDelegate(holder.itemView);
            bound = true;
            if (mState.isPreLayout()) {
                holder.mPreLayoutPosition = position;
            }
        }

        final ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
        final LayoutParams rvLayoutParams;
        if (lp == null) {
            rvLayoutParams = (LayoutParams) generateDefaultLayoutParams();
            holder.itemView.setLayoutParams(rvLayoutParams);
        } else if (!checkLayoutParams(lp)) {
            rvLayoutParams = (LayoutParams) generateLayoutParams(lp);
            holder.itemView.setLayoutParams(rvLayoutParams);
        } else {
            rvLayoutParams = (LayoutParams) lp;
        }
        rvLayoutParams.mViewHolder = holder;
        rvLayoutParams.mPendingInvalidate = fromScrap && bound;
        return holder.itemView;
}

这是非常简单直接的。

只需在适配器中重写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){
            ...
         }
    }