Android RecyclerView从入门到玩坏

作者: sean_depp | 来源:发表于2018-08-18 12:14 被阅读14次

    目录

    • 前言
    • 基础使用
    • 分隔线
    • 点击监听
    • 搭配CardView
    • 更丰富的条目
    • 增删条目
    • 快速添加视图
    • 让RecyclerView支持复杂视图
    • 最后

    前言

    RecyclerView在Android界面开发当中是很重要的, 那掌握它也是很必要的. 但是有些时候会觉得它很厚重, 这里就从RecyclerView的基础一直说到扩展, 让你把RecyclerView学薄了.

    RecyclerView官方文档也是非常厚重.

    这篇文章融合了自己原来的多篇文章, 并进行了修正和改进, 而且添加了很多很有趣的内容.
    本文需要20分钟以上的阅读时间, 请合理安排.
    多图预警, 转载请注明出处!


    基础使用

    要使用RecyclerView在Android Studio 2.x(以下简称AS), 要这样:

    compile 'com.android.support:cardview-v7:25.3.1'
    compile 'com.android.support:recyclerview-v7:25.3.1'
    

    到了AS 3.x, 要这样:

    implementation 'com.android.support:cardview-v7:26.1.0'
    implementation 'com.android.support:recyclerview-v7:26.1.0'
    

    之后在布局文件中写入如下代码就引入了RecyclerView了.

    <android.support.v7.widget.RecyclerView
        android:id="@+id/rv_main"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scrollbars="vertical" />
    

    接下来说说介绍些各种布局. 可以看RecyclerView.LayoutManager官方文档.

    布局类 效果
    LinearLayoutManager 以垂直或水平滚动列表方式显示项目
    GridLayoutManager 在网格中显示项目
    StaggeredGridLayoutManager 在分散对齐网格中显示项目
    mRvMain = (RecyclerView) findViewById(R.id.rv_main);
    
    // 设置布局
    LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
    mRvMain.setLayoutManager(linearLayoutManager);
    

    最关键的还是适配器的撰写. 但是理解起来不是很难, 你只要将ListView的适配器写法带入理解就好. 这里把全部代码贴出来, 因为后面要在这个基础上不断扩充.

    public class MyRVAdapter2 extends RecyclerView.Adapter<MyRVAdapter2.MyTVHolder> {
    
        private final LayoutInflater mLayoutInflater;
        private final Context mContext;
        private final ArrayList<String> mData;
    
        public MyRVAdapter2(Context context) {
            mLayoutInflater = LayoutInflater.from(context);
            mContext = context;
    
            mData = new ArrayList<>();
            for (int i = 0; i < 40; i++) {
                mData.add("hello " + i);
            }
        }
    
        @Override
        public MyRVAdapter2.MyTVHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            return new MyRVAdapter2.MyTVHolder(mLayoutInflater.inflate(R.layout.rv_txt_item, parent, false));
        }
    
        @Override
        public void onBindViewHolder(final MyRVAdapter2.MyTVHolder holder, int pos) {
            holder.mTextView.setText(mData.get(pos));
        }
    
        @Override
        public int getItemCount() {
            return mData == null ? 0 : mData.size();
        }
    
        class MyTVHolder extends RecyclerView.ViewHolder {
            TextView mTextView;
    
            MyTVHolder(View itemView) {
                super(itemView);
                mTextView = (TextView) itemView.findViewById(R.id.tv_txt);
            }
        }
    }
    

    然后写个最基础的TextView条目. 让它跑起来看看效果.

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">
    
        <TextView
            android:id="@+id/tv_txt"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="center_horizontal"
            android:padding="@dimen/eight_dp"
            android:text="@string/tmp"
            android:textSize="@dimen/thirty_sp" />
    </LinearLayout>
    
    基础

    分隔线

    前面的部分已经是基础的RecyclerView使用了. 那比起ListView是不是没有了分隔线. 这里上一个简单好用的开源库RecyclerView-FlexibleDivider.

    引入:

    implementation 'com.yqritc:recyclerview-flexibledivider:1.4.0'
    

    使用:

    mRvMain.addItemDecoration(
            new HorizontalDividerItemDecoration.Builder(this).build());
    

    看效果就达到了吧.

    分隔线

    觉得不好看, 还可以自定义, 更多写法可以参见文档内容.

    mRvMain.addItemDecoration(
            new HorizontalDividerItemDecoration.Builder(this)
                    .color(Color.BLUE)
                    .sizeResId(R.dimen.two_dp)
                    .marginResId(R.dimen.eight_dp, R.dimen.eight_dp)
                    .build());
    
    自定义分隔线

    而且而且, 竖着的分隔线也大丈夫哦.

    GridLayoutManager gridLayoutManager
            = new GridLayoutManager(this, 2);
    mRvMain.setLayoutManager(gridLayoutManager);
    
    mRvMain.addItemDecoration(
            new VerticalDividerItemDecoration.Builder(this).build());
    
    竖线

    点击监听

    再回忆一下在天国的ListView, 还有item的点击吧, 这个也要自己写.

    适配器中:

    public interface OnItemClickListener {
        void onItemClick(View view, int position);
    
        void onItemLongClick(View view, int position);
    }
    
    private MyRVAdapter2.OnItemClickListener mOnItemClickListener;
    
    public void setOnItemClickListener(MyRVAdapter2.OnItemClickListener mOnItemClickListener) {
        this.mOnItemClickListener = mOnItemClickListener;
    }
    

    onBindViewHolder中设置点击监听.

    @Override
    public void onBindViewHolder(final MyRVAdapter2.MyTVHolder holder, int pos) {
        holder.mTextView.setText(mData.get(pos));
    
        if (mOnItemClickListener != null) {
            holder.itemView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    int pos = holder.getLayoutPosition();
                    mOnItemClickListener.onItemClick(holder.itemView, pos);
                }
            });
            holder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
                @Override
                public boolean onLongClick(View v) {
                    int pos = holder.getLayoutPosition();
                    mOnItemClickListener.onItemLongClick(holder.itemView, pos);
                    return false;
                }
            });
        }
    }
    

    使用监听:

    mAdapter.setOnItemClickListener(new MyRVAdapter2.OnItemClickListener() {
        @Override
        public void onItemClick(View view, int position) {
            Toast.makeText(UIUtil.getContext(), "click" + position, Toast.LENGTH_SHORT).show();
        }
    
        @Override
        public void onItemLongClick(View view, int position) {
            Toast.makeText(UIUtil.getContext(), "long click" + position, Toast.LENGTH_SHORT).show();
        }
    });
    
    点击

    搭配CardView

    是不是这个点击看着没啥感觉, 没事, 我们换上CardView再来一次.

    布局文件:

    <?xml version="1.0" encoding="utf-8"?>
    <android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:card_view="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="@dimen/eight_dp"
        android:foreground="@drawable/card_foreground"
        card_view:cardCornerRadius="@dimen/four_dp">
    
        <TextView
            android:id="@+id/tv_txt"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="center_horizontal"
            android:padding="@dimen/eight_dp"
            android:text="@string/tmp"
            android:textSize="@dimen/thirty_sp" />
    </android.support.v7.widget.CardView>
    
    cardview

    给CardView加上水波纹点击特效:

    <?xml version="1.0" encoding="utf-8"?>
    <ripple xmlns:android="http://schemas.android.com/apk/res/android"
        android:color="@color/colorPrimary" />
    
    波纹点击

    在老版本就只能用选择器了, 其实效果也还好:

    <?xml version="1.0" encoding="utf-8"?>
    <inset xmlns:android="http://schemas.android.com/apk/res/android"
        android:drawable="@drawable/card_foreground_selector"
        android:insetBottom="@dimen/four_dp"
        android:insetLeft="@dimen/three_dp"
        android:insetRight="@dimen/three_dp"
        android:insetTop="@dimen/four_dp" />
    
    <selector xmlns:android="http://schemas.android.com/apk/res/android">
        <item android:state_pressed="true">
            <shape android:shape="rectangle">
                <solid android:color="@color/colorPrimaryTran" />
                <corners android:radius="@dimen/four_dp" />
            </shape>
        </item>
        <item android:state_enabled="true" android:state_focused="true">
            <shape android:shape="rectangle">
                <solid android:color="#0f000000" />
                <corners android:radius="@dimen/four_dp" />
            </shape>
        </item>
    </selector>
    
    低版本兼容

    更丰富的条目

    大家应该都知道TextView可以设置图标吧, 这里来看下效果图, 顺带感受下android界面设计语言的变化.

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">
    
        <TextView
            android:id="@+id/tv_txt"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:drawableLeft="@mipmap/ic_launcher"
            android:drawablePadding="@dimen/sixteen_dp"
            android:drawableStart="@mipmap/ic_launcher"
            android:gravity="center_vertical"
            android:padding="@dimen/eight_dp"
            android:text="@string/tmp"
            android:textSize="@dimen/thirty_sp" />
    </LinearLayout>
    
    4.x 8.x

    让GridLayoutManager展示不同宽度的条目

    方的是4.x上的, 圆的是8.x上的, 可以看到, 变化还是很大的. 我们回正题. GridLayoutManager布局是可以设置宽度的, 不一定都是一样大的, 来看下实现.

    // 指定item宽度
    gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
        @Override
        public int getSpanSize(int position) {
            if (position == 0
                    || position == (mAdapter.getItemCount() - 1) / 2
                    || position == (mAdapter.getItemCount() - 1)) {
                return gridLayoutManager.getSpanCount();
            } else {
                return 1;
            }
        }
    });
    

    来看效果图, 发现我们的分隔线崩了是吧, 如果真想用这个分隔线也还是要自己动手修补修补, 改动改动, 开源库再棒也猜不到你的项目需求呀.

    分隔线异常 设置宽度

    当然了, 我还是很喜欢这个分隔线的, 我们来看看横着滚动的效果.

    布局文件要改动:

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:orientation="horizontal">
    
        <TextView
            android:id="@+id/tv_txt"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:gravity="center_vertical"
            android:text="@string/tmp"
            android:textSize="@dimen/thirty_sp" />
    </LinearLayout>
    
    gridLayoutManager.setOrientation(GridLayoutManager.HORIZONTAL);
    
    横滑

    展示不同布局

    之前变化宽度其实还是相同条目, 现在要展示不同条目:

    写一个图的条目:

    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:padding="@dimen/eight_dp">
    
        <ImageView
            android:id="@+id/iv_img"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerInParent="true"
            android:src="@mipmap/ic_launcher" />
    
    </RelativeLayout>
    
    public enum ITEM_TYPE {
        ITEM_TYPE_IMAGE,
        ITEM_TYPE_TEXT
    }
    

    这里多了判断条目类型, 还要注意返回值的变化, 用了更基类的RecyclerView.ViewHolder.

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        if (viewType == ITEM_TYPE.ITEM_TYPE_IMAGE.ordinal()) {
            return new MyRVAdapter2.MyIVHolder(mLayoutInflater.inflate(R.layout.rv_img_item, parent, false));
        } else {
            return new MyRVAdapter2.MyTVHolder(mLayoutInflater.inflate(R.layout.rv_txt_item, parent, false));
        }
    }
    

    类继承上面也要变成RecyclerView.ViewHolder, 这些都是要对应的.

    extends RecyclerView.Adapter<RecyclerView.ViewHolder>
    

    当然了, holder也是不能少的.

    public class MyIVHolder extends RecyclerView.ViewHolder {
        ImageView mImageView;
    
        MyIVHolder(View view) {
            super(view);
            mImageView = (ImageView) view.findViewById(R.id.iv_img);
        }
    }
    
    @Override
    public void onBindViewHolder(final RecyclerView.ViewHolder holder, int pos) {
        if (holder instanceof MyRVAdapter2.MyTVHolder) {
            ((MyRVAdapter2.MyTVHolder) holder).mTextView.setText(mData.get(pos));
        } else if (holder instanceof MyRVAdapter2.MyIVHolder) {
            ((MyRVAdapter2.MyIVHolder) holder).mImageView.setImageDrawable(UIUtil.getDrawable(R.mipmap.ic_launcher));
        }
    
        // 点击监听
        ...
    }
    

    顺带的, 我们把之前放宽的条目变成不同的视图, 也就是对应起来:

    @Override
    public int getItemViewType(int position) {
        if (position == 0
                || position == (getItemCount() - 1) / 2
                || position == (getItemCount() - 1)) {
            return ITEM_TYPE.ITEM_TYPE_IMAGE.ordinal();
        } else {
            return ITEM_TYPE.ITEM_TYPE_TEXT.ordinal();
        }
    }
    

    看看效果:

    不同布局

    它还能继续地复杂, 试试瀑布流StaggeredGridLayoutManager:

    <?xml version="1.0" encoding="utf-8"?>
    <android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:card_view="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="@dimen/eight_dp"
        card_view:cardCornerRadius="@dimen/four_dp">
    
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical">
    
            <ImageView
                android:id="@+id/iv_img"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center_horizontal"
                android:src="@mipmap/ic_launcher" />
    
            <TextView
                android:id="@+id/tv_txt"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center_horizontal"
                android:text="@string/tmp"
                android:textSize="@dimen/thirty_sp" />
        </LinearLayout>
    </android.support.v7.widget.CardView>
    
    StaggeredGridLayoutManager staggeredGridLayoutManager
            = new StaggeredGridLayoutManager(3, StaggeredGridLayoutManager.VERTICAL);
    mRvMain.setLayoutManager(staggeredGridLayoutManager);
    
    瀑布流

    分割线又崩了, 嘿嘿, 其实用上了CardView, 分割线没什么必要再用了.

    分隔线异常

    增删条目

    现在适配器中添加增删方法:

    public void addData(int position) {
        mData.add(position, "hello x");
        notifyItemInserted(position);
    }
    
    public void removeData(int position) {
        mData.remove(position);
        notifyItemRemoved(position);
    }
    

    再写入点击事件中, 点击增加, 长按删除:

    mAdapter.setOnItemClickListener(new MyRVAdapter2.OnItemClickListener() {
        @Override
        public void onItemClick(View view, int position) {
            mAdapter.addData(position);
        }
    
        @Override
        public void onItemLongClick(View view, int position) {
            mAdapter.removeData(position);
        }
    });
    
    增删条目

    增删条目开源库

    这里再上一个开源库recyclerview-animators, 可以修改增删动画, 种类也很丰富, 还能在它基础上自定义:

    分类 动画类名
    Cool LandingAnimator
    Scale ScaleInAnimator, ScaleInTopAnimator, ScaleInBottomAnimator, ScaleInLeftAnimator, ScaleInRightAnimator
    Fade FadeInAnimator, FadeInDownAnimator, FadeInUpAnimator, FadeInLeftAnimator, FadeInRightAnimator
    Flip FlipInTopXAnimator, FlipInBottomXAnimator, FlipInLeftYAnimator, FlipInRightYAnimator
    Slide SlideInLeftAnimator, SlideInRightAnimator, OvershootInLeftAnimator, OvershootInRightAnimator, SlideInUpAnimator, SlideInDownAnimator

    引入:

    implementation 'jp.wasabeef:recyclerview-animators:2.3.0'
    

    使用:

    mRvMain.setItemAnimator(new SlideInLeftAnimator());
    

    这里给大家展示两种效果, 其它的自己尝试吧.

    增删动画
    mRvMain.setItemAnimator(new LandingAnimator());
    
    增删动画

    快速添加视图

    还有像Header, Foot这样的视图, 自己写也还是要费些功夫的, 这里推荐Android大神的库baseAdapter

    引入:

    implementation 'com.zhy:base-rvadapter:3.0.3'
    

    添加头尾视图

    HeaderAndFooterWrapper mHeaderAndFooterWrapper = new HeaderAndFooterWrapper(mAdapter);
    
    TextView t1 = new TextView(this);
    t1.setText("Header 1");
    t1.setTextSize(30);
    TextView t2 = new TextView(this);
    t2.setText("Foot 1");
    t2.setTextSize(30);
    
    mHeaderAndFooterWrapper.addHeaderView(t1);
    mHeaderAndFooterWrapper.addFootView(t2);
    
    mRvMain.setAdapter(mHeaderAndFooterWrapper);
    
    头尾

    添加更多视图

    LoadMoreWrapper mLoadMoreWrapper = new LoadMoreWrapper(mAdapter);
    mLoadMoreWrapper.setLoadMoreView(R.layout.rv_cv_img_txt_item);
    mLoadMoreWrapper.setOnLoadMoreListener(new LoadMoreWrapper.OnLoadMoreListener() {
        @Override
        public void onLoadMoreRequested() {
        }
    });
    
    mRvMain.setAdapter(mLoadMoreWrapper);
    
    更多

    是不是感觉特别爽, 那看看更爽的, 在不写适配器的情况下快速添加条目:

    final ArrayList<String> mData = new ArrayList<>();
    for (int i = 0; i < 40; i++) {
        mData.add("hello " + i);
    }
    
    mRvMain.setAdapter(new CommonAdapter<String>(this, R.layout.rv_cv_txt_item, mData) {
        @Override
        protected void convert(ViewHolder holder, String s, int position) {
            holder.setText(R.id.tv_txt, mData.get(position));
    
        }
    });
    
    快速添加条目

    是不是感觉省了一万个小时呢.


    让RecyclerView支持复杂视图

    每次加入新的视图都要对适配器进行比较大程度的改动, 这样是很容易出错的. 这里引入一个非常棒的开源库-AdapterDelegates, 降低下代码耦合性.

    引入:

    implementation 'com.hannesdorfmann:adapterdelegates3:3.0.1'
    

    先不说使用细节, 来看看实现后想加入不同视图有多简单吧:

    ArrayList<Base> data = new ArrayList<>();
    for (int i = 0; i < 10; i++) {
        data.add(new B("b " + i));
    }
    for (int i = 0; i < 10; i++) {
        data.add(new A("a " + i));
    }
    BaseAdapter animalAdapter = new BaseAdapter(this, data);
    mRvMain.setAdapter(animalAdapter);
    
    添加复杂条目

    是不是惊了, 也就是说, 你只要实现了A, B这些视图类, 直接新建放入数组就完事了.

    需要写基础适配器:

    public class BaseAdapter extends RecyclerView.Adapter {
    
        private AdapterDelegatesManager<List<Base>> delegatesManager;
        private List<Base> items;
    
        public BaseAdapter(Activity activity, List<Base> items) {
            this.items = items;
    
            delegatesManager = new AdapterDelegatesManager<>();
    
            delegatesManager.addDelegate(new AAdapterDelegate(activity))
                    .addDelegate(new BAdapterDelegate(activity));
        }
    
        @Override
        public int getItemViewType(int position) {
            return delegatesManager.getItemViewType(items, position);
        }
    
        @Override
        public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            return delegatesManager.onCreateViewHolder(parent, viewType);
        }
    
        @Override
        public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
            delegatesManager.onBindViewHolder(items, position, holder);
        }
    
        @Override
        public int getItemCount() {
            return items.size();
        }
    }
    

    需要对每个类进行进行具体设置, 这里以A为例.

    public class AAdapterDelegate extends AdapterDelegate<List<Base>> {
    
        private LayoutInflater inflater;
    
        public AAdapterDelegate(Activity activity) {
            inflater = activity.getLayoutInflater();
        }
    
        @Override
        public boolean isForViewType(@NonNull List<Base> items, int position) {
            return items.get(position) instanceof A;
        }
    
        @NonNull
        @Override
        public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent) {
            return new CatViewHolder(inflater.inflate(R.layout.rv_cv_img_txt_item, parent, false));
        }
    
        @Override
        public void onBindViewHolder(@NonNull List<Base> items, int position,
                                     @NonNull RecyclerView.ViewHolder holder, @Nullable List<Object> payloads) {
    
            CatViewHolder vh = (CatViewHolder) holder;
            A cat = (A) items.get(position);
    
            vh.name.setText(cat.getName());
        }
    
        static class CatViewHolder extends RecyclerView.ViewHolder {
    
            public TextView name;
            public ImageView img;
    
            public CatViewHolder(View itemView) {
                super(itemView);
                name = (TextView) itemView.findViewById(R.id.tv_txt);
                img = (ImageView) itemView.findViewById(R.id.iv_img);
            }
        }
    }
    

    最后

    看完这篇应该是对RecyclerView有个大体认识了, 多练习练习就会得心应手起来了. 那还是有一点, 就像分隔线库的几次不理想表现, 具体项目要求还是要具体对待, 开源库也不是万能的. 最近不是又有什么开源项目套壳事件了嘛, 别人一开源就说自己有自主产权了真的好吗? 喜欢记得点赞或者关注我哦, 有意见或者建议评论区见~


    相关文章

      网友评论

        本文标题:Android RecyclerView从入门到玩坏

        本文链接:https://www.haomeiwen.com/subject/kptxiftx.html