对于RecyclerView更加基础的理解,请看RecyclerView-1

引言

以下将介绍使用RecyclerView实现:

  • 多类型item显示
  • 瀑布流效果。

多类型的item

RecyclerView-1中,只实现了简单的文本item,即item类型是单类型的,也就意味着只需要一种ViewHolder。但RecylerView的高扩展性不是盖的,它能做的不只是这些。

假设有两种类型的item,一种是只有Button,另一种是有TextViewImageView

自定义名为MultiItemAdapter,继承于RecyclerAdapter<RecylerView.ViewHolder>的适配器,需要编写两个ViewHolder,并重写getItemViewTypeonCreateViewHolderonBindViewHolder方法。

1. 编写两个ViewHolder

ButtonViewHolder:

public static class ButtonViewHolder extends RecyclerView.ViewHolder {
    Button mButton;

    public ButtonViewHolder(View itemView) {
        super(itemView);
        mButton = (Button) itemView.findViewById(R.id.id_button);
    }
}

ImageViewHolder:

public static class ImageViewHolder extends RecyclerView.ViewHolder {
    TextView mTextView;
    ImageView mImageView;

    public ImageViewHolder(View itemView) {
        super(itemView);
        mTextView = (TextView) itemView.findViewById(R.id.id_title);
        mImageView = (ImageView) itemView.findViewById(R.id.id_image);
    }
}

2. 重写getItemViewType方法

public int getItemViewType(int position) {
    return mItemList.get(position).getItemType();
}

3. 重写onCreateViewHolder方法

public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    if (viewType == Item.ITEM_TYPE.ITEM_TYPE_BUTTON.ordinal())
        return new ButtonViewHolder(mInflater.inflate(R.layout.button_item, parent, false));
    if (viewType == Item.ITEM_TYPE.ITEM_TYPE_IMAGE.ordinal()) {
        return new ImageViewHolder(mInflater.inflate(R.layout.image_item, parent, false));
    }
    return null;
}

需要对传入的viewType进行判断,返回不同类型的ViewHolderViewHolder的实例化方式与RecylerView-1中的相同。

这里的viewType就是getItemViewType方法返回的值。

4. 重写onBindViewHolder方法

public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
    Item item = mItemList.get(position);

    if (holder instanceof ButtonViewHolder){
        ((ButtonViewHolder) holder).mButton.setText(item.getTitle());
    }else if (holder instanceof ImageViewHolder){
        ((ImageViewHolder) holder).mTextView.setText(item.getTitle());
        ((ImageViewHolder) holder).mImageView.setImageDrawable(item.getImage());
    }
}

需要对传入的ViewHolder对象进行判断,判断是ButtonViewHolder还是ImageViewHolder的实例,再根据不同情况进行UI的设定。

5. 效果

瀑布流

1. 效果

2. 利用StaggerLayoutManager

利用StaggerLayoutManager可以轻松地实现瀑布流效果。

mRecyclerView.setLayoutManager(new StaggerLayoutManager(3, StaggeredGridLayoutManager.VERTICAL));

参数代表沿垂直方向扩展,每行有3item

3. 修改高度

为了实现每个item的高度都不同,需要强制设定,但这在实际开发中是无须的,这里仅仅为了实现较好的视觉效果。

ViewHolder为每个TextView设定随机高度:

public class MyViewHolder extends RecyclerView.ViewHolder {
    TextView mTextView;

    public MyViewHolder(View itemView) {
        super(itemView);
        mTextView = (TextView) itemView.findViewById(R.id.id_text);
        ViewGroup.LayoutParams params = mTextView.getLayoutParams();
        params.height = (int) (200 + Math.random() * 200);
        mTextView.setLayoutParams(params);
    }
}

需要注意的是,不要在onBindViewHolder中设置随机高度,不然会出现滑动过程item高度发生变化的现象。

4. 添加监听器

当然,如果想要实现长按item删除的功能,也可以做到。

onBindViewHolder方法中:

public void onBindViewHolder(final MyViewHolder holder, final int position) {
    holder.mTextView.setText(data.get(position));
    holder.mTextView.setOnLongClickListener(new View.OnLongClickListener() {
        @Override
        public boolean onLongClick(View v) {
            int pos = holder.getLayoutPosition();       //do NOT use the parameter 'position' here
            notifyItemRemoved(pos);
            data.remove(pos);
            return false;
        }
    });
}

注意到使用notifyItemRemoved()即可删除特定位置的item,同时调用remove()方法删除数据源的相应数据。需要十分注意的是,这里的位置不能使用方法参数中的position,而需要由holder.getLayoutPosition()重新获得。官方的解释是这样的:

For performance and animation reasons, RecyclerView batches all adapter updates until the next layout pass. This may cause mismatches between the Adapter position of the item and the position it had in the latest layout calculations.

LayoutManagers should always call this method while doing calculations based on item positions. All methods in RecyclerView.LayoutManager, RecyclerView.State, RecyclerView.Recycler that receive a position expect it to be the layout position of the item.

翻译过来就是:

出于性能和动画的原因,RecyclerView 在下一个布局传递进来之前,批量完成了所有适配器的更新。这可能会导致item的适配器的位置和它在最新的布局计算的位置之间的失配。

LayoutManager在做基于item位置的运算时,应该始终调用此方法。在RecyclerView.LayoutManager,RecyclerView.State,RecyclerView.Recycler所有方法中接收的位置必须是该项目的正确布局位置。

总结

RecyclerView玩年。

关于RecyclerView监听器部分,请看RecyclerView-3,关于拖拽和滑动部分,请看RecyclerView-4