Android-RecyclerViewAndroid开发实战总结优秀案例

RecyclerView 实现复杂布局

2017-07-05  本文已影响8002人  博弈史密斯

大家都知道,很多App都有类似这样的布局:

下厨房
简书 哔哩哔哩动画

那如果想实现前几行是第一张图片的布局,再几行是第二个的布局,后面的是第三种布局呢?或者说像淘宝界面一样 眼花缭乱的布局?
(阿里巴巴已经把淘宝等自家App使用的布局方案开源啦!原理也是通过 RecyclerView 实现的,想了解的可以参考这篇文章:https://mp.weixin.qq.com/s/BYtF_Kzy7OWePJRNpfoHWQ

其实当 item 的布局方式不一样的时候,譬如想实现上面截图的布局时,需在 onCreateViewHolder 中 通过参数 viewType 判断布局类型,此 viewType 和通过 getItemViewType 中返回的类型所匹配,getItemViewType 比 onCreateViewHolder 早调用,可以在之前的文章中了解 RecyclerView Adapter 函数的调用顺序。

下面的代码,实现了如图所示的效果:

Paste_Image.png

前三行是头像在左边,文字在右边,后面的都是头像在上面,文字在下面。

这里只列出了两种布局,目的是为了让大家理解这种方法即可。

首先是图片在左,文字在右 item 布局:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:layout_margin="1dp"
             android:background="#add8e6">

    <TextView
        android:id="@+id/text"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:drawableStart="@mipmap/premeeting_login_focused"
        android:gravity="center"/>

</FrameLayout>

然后是图片在上,文字在下的布局:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:layout_margin="1dp"
             android:background="#add8e6">

    <TextView
        android:id="@+id/text"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:drawableTop="@mipmap/premeeting_login_focused"
        android:gravity="center"/>

</FrameLayout>

如果是每个 item 的布局一样,例如上面举例的 前三行之后的item 都一样,但是item的列数不一样,可以通过 GridLayoutManager 来设置每个 item 有不同的列数,GridLayoutManager为我们提供了动态改变每个item所占列数的方法:

gridManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
    @Override
    public int getSpanSize(int position) {
      return gridManager.getSpanCount();
    }
}

getSpanSize方法,返回值就表示当前item占多少列,例如如果我们列数设置的为3列,返回3就表示铺满,也就是和列表一样了。

如图所示,我们给RecyclerView设置一个列数为6的GridLayoutManager,然后再动态地为不同部位的item分别设置SpanSize为6(铺满)、3(1/2)、2(1/3)就行了。

在 Adapter 的 onAttachedToRecyclerView 方法中动态为不同position设置不同的SpanSize,下面是 Adapter 的代码:

/**
 * Created by zhangyb on 2017/7/3.
 */
public class MultiGridRecycleAdapter extends RecyclerView.Adapter<MultiGridRecycleAdapter.MyViewHolder> {

    public static final int TYPE_ITEM_ONE_LEFT = 0; //item一列,图片在左,文字在右
    public static final int TYPE_ITEM_ONE_UP = 1; //item一列,图片在上,文字在下
    public static final int TYPE_ITEM_TWO_UP = 2; //item两列,图片在上,文字在下

    private List<String> mDataList;
    private LayoutInflater mInflater;

    public MultiGridRecycleAdapter(Context context, List<String> dataList) {
        mDataList = dataList;
        mInflater = LayoutInflater.from(context);
    }

    @Override
    public MultiGridRecycleAdapter.MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view;
        if (viewType == TYPE_ITEM_ONE_LEFT) {
            view = mInflater.inflate(R.layout.item_picture_left, parent, false);
        } else {
            view = mInflater.inflate(R.layout.item_picture_up, parent, false);
        }
        return new MyViewHolder(view);
    }

    @Override
    public void onBindViewHolder(MultiGridRecycleAdapter.MyViewHolder holder, int position) {
        holder.mTextView.setText(mDataList.get(position));
    }

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

    @Override
    public void onAttachedToRecyclerView(RecyclerView recyclerView) {
        super.onAttachedToRecyclerView(recyclerView);

        RecyclerView.LayoutManager manager = recyclerView.getLayoutManager();
        if (manager instanceof GridLayoutManager) {
            final GridLayoutManager gridManager = ((GridLayoutManager) manager);

            gridManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
                @Override
                public int getSpanSize(int position) {
                    int type = getItemViewType(position);
                    switch (type) {
                        case TYPE_ITEM_ONE_LEFT:
                        case TYPE_ITEM_ONE_UP: //这两种方式都是一列的,所以返回6
                            return 6;

                        case TYPE_ITEM_TWO_UP: //两列,返回3
                            return 3;

                        default:
                            return 6;
                    }
                }
            });
        }
    }

    @Override
    public int getItemViewType(int position) {
        if (position < 3) { //前三行显示 图片在左、文字在右布局
            return TYPE_ITEM_ONE_LEFT;

        } else if (position < 6) { //第 4、5、6 行显示 图片在上、文字在下布局
            return TYPE_ITEM_ONE_UP;

        } else { // 其他行显示 两列,图片在上、文字在下布局
            return TYPE_ITEM_TWO_UP;
        }
    }

    public static class MyViewHolder extends RecyclerView.ViewHolder {
        TextView mTextView;

        public MyViewHolder(View view) {
            super(view);
            mTextView = (TextView) view.findViewById(R.id.text);
        }
    }
}
/**
 * Created by zhangyb on 2017/7/3.
 */
public class MultiGridRecycleAdapter extends RecyclerView.Adapter<MultiGridRecycleAdapter.MyViewHolder> {

    public static final int TYPE_ITEM_ONE_LEFT = 0;
    public static final int TYPE_ITEM_ONE_UP = 1;
    public static final int TYPE_ITEM_TWO_UP = 2;

    private List<String> mDataList;
    private LayoutInflater mInflater;

    public MultiGridRecycleAdapter(Context context, List<String> dataList) {
        mDataList = dataList;
        mInflater = LayoutInflater.from(context);
    }

    @Override
    public MultiGridRecycleAdapter.MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view;
        //根据 viewType 区分不同的 布局
        if (viewType == TYPE_ITEM_ONE_LEFT) {
            view = mInflater.inflate(R.layout.item_picture_left, parent, false);
        } else {
            view = mInflater.inflate(R.layout.item_picture_up, parent, false);
        }
        return new MyViewHolder(view);
    }

    @Override
    public void onBindViewHolder(MultiGridRecycleAdapter.MyViewHolder holder, int position) {
        holder.mTextView.setText(mDataList.get(position));
    }

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

    //此函数在调用 RecyclerView.setAdapter 时调用
    @Override
    public void onAttachedToRecyclerView(RecyclerView recyclerView) {
        super.onAttachedToRecyclerView(recyclerView);

        RecyclerView.LayoutManager manager = recyclerView.getLayoutManager();
        if (manager instanceof GridLayoutManager) {
            GridLayoutManager gridManager = ((GridLayoutManager) manager);

            gridManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
                @Override
                public int getSpanSize(int position) {
                    int type = getItemViewType(position);
                    switch (type) {
                        case TYPE_ITEM_ONE_LEFT:
                        case TYPE_ITEM_ONE_UP:
                            return 6;

                        case TYPE_ITEM_TWO_UP:
                            return 3;

                        default:
                            return 6;
                    }
                }
            });
        }
    }

    @Override
    public int getItemViewType(int position) {
        if (position < 3) { //前三行显示 图片在左、文字在右布局
            return TYPE_ITEM_ONE_LEFT;

        } else if (position < 6) { //第 4、5、6 行显示 图片在上、文字在下布局
            return TYPE_ITEM_ONE_UP;

        } else { // 其他行显示 两列,图片在上、文字在下布局
            return TYPE_ITEM_TWO_UP;
        }
    }

    public static class MyViewHolder extends RecyclerView.ViewHolder {
        TextView mTextView;

        public MyViewHolder(View view) {
            super(view);
            mTextView = (TextView) view.findViewById(R.id.text);
        }
    }
}

可以通过 getItemViewType 来设置不同的 item,并在 onCreateViewHolder 中加载不同的 layout,此方法还可以实现为界面添加 Header 和 Fooder:

Paste_Image.png Paste_Image.png

实现上述的效果,就可以用上面的方法。

假设我们现在已经完成了RecyclerView的编写,忽然有个需求,需要在列表上加个HeaderView,此时我们该怎么办呢?

打开我们的Adapter,然后按照我们上述的原理,添加特殊的ViewType,然后修改代码完成。

这是比较常规的做法了,但是有个问题是,如果需要添加viewType,那么可能我们的Adapter需要修改的幅度就比较大了,比如getItemType、getItemCount、onBindViewHolder、onCreateViewHolder等,几乎所有的方法都要进行改变。

这样来看,出错率是非常高的。

况且一个项目中可能多个RecyclerView都需要在其列表中添加headerView。

这种情况,请参考:http://blog.csdn.net/lmj623565791/article/details/51854533

参考

在网上找的感觉比较好的文章,和这个不相关:

上一篇下一篇

猜你喜欢

热点阅读