MaterialDesign学习篇(八),掌握RecyclerView和SwipeRefreshLayout

RecyclerView介绍

RecyclerView是Android5.0添加的一个用于取代ListView的控件,它的灵活性比ListView和GridView更加优秀,ListView和GridView能够做到的,它都可以做到,可以说是ListView和GridView的升级版,但是它的使用又和ListView与GridView有些不同,下面将会对此进行介绍。

如何使用RecyclerView

先看下演示效果:

列表布局:

网格布局:

瀑布流布局:

可以看到一个RecyclerView就可以实现列表、网格和瀑布流的布局,并且还有纵向和水平两个方向的摆放,这些都是由LayoutManager(布局管理器)控制的,在使用RecyclerView之前,我们需要先了解LayoutManager

LayoutManager(布局管理器)介绍

RecyclerView的使用和ListView大同小异,也是需要设置Adapter(适配器),但是在设置适配器之前,需要先设置LayoutManager(布局管理器),LayoutManager用来确定每一个item如何进行排列摆放,何时展示和隐藏。回收或重用一个View的时候,LayoutManager会向适配器请求新的数据来替换旧的数据,这种机制避免了创建过多的View和频繁的调用findViewById方法(与ListView原理类似)。

RecyclerView的三种LayoutManager(布局管理器)

  • LinearLayoutManager(线性布局管理器)

  • GridLayoutManager(网格布局管理器)

  • StaggeredGridLayoutManager(瀑布流布局管理器)

以上三种布局管理器均可以设置垂直和水平两个方向,效果对应以上三张图,接下来开始介绍RecyclerView的使用。

导入依赖

compile 'com.android.support:recyclerview-v7:26.0.0-alpha1'

布局文件中引用RecyclerView

<?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="match_parent"
    android:orientation="vertical">

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

</LinearLayout>

java文件中,找到对应的RecyclerView,对其设置布局管理器和设置Adapter

mRv = (RecyclerView) findViewById(R.id.rv);

MyListAdapter adapter = new MyListAdapter(this, mDatas);
LinearLayoutManager layoutManger = new LinearLayoutManager(this, LinearLayout.VERTICAL, false);
mRv.setLayoutManager(layoutManger);
mRv.setAdapter(adapter);

LinearLayoutManager的使用

一般使用LinearLayoutManager的这两个构造方法:

单个参数的构造方法,传入的是上下文,方向是默认的垂直方向:

new LinearLayoutManager(Context context)

三个参数的构造方法,分别传入上下文,方向,第三个参数是布尔类型的值,表示是否反转,不反转的垂直布局:数据从上到下加载,加载新数据,新数据在底部,滑动从下往上。

new LinearLayoutManager( Context context, int orientation, boolean reverseLayout)

这里我们使用第二个构造方法,第三个参数传入false,不反转,第二个参数根据选择的方向传入LinearLayout.VERTICAL或 LinearLayout.HORIZONTAL。

当点击切换成水平方向的列表布局时,调用以下代码:

MyListAdapter adapter = new MyListAdapter(this, mDatas);
LinearLayoutManager layoutManager = new LinearLayoutManager(this, LinearLayout.HORIZONTAL, false);
mRv.setLayoutManager(layoutManager);
mRv.setAdapter(adapter);

改变了LinearLayoutManager的方向,变成横向列表展示。

GridLayoutManager的使用

一般使用GridLayoutManager的这两个构造方法:

两个参数的构造方法,第一个参数是上下文,第二个参数设置显示的列数,方向默认为垂直方向:

new GridLayoutManager(Context context, int spanCount)

四个参数的构造方法,第一个参数是上下文,第二个参数设置显示的列数,第三个参数为方向,第四个参数为是否反转:

new GridLayoutManager( Context context, int spanCount, int orientation, boolean reverseLayout)

纵向方向网格布局是设置以下代码:

GridLayoutManager layoutManager = new GridLayoutManager(this,2, GridLayout.VERTICAL, false);
mRv.setLayoutManager(layoutManager);
MyListAdapter adapter = new MyListAdapter(this, mDatas);
mRv.setAdapter(adapter);

当点击切换成水平方向的网格布局时:

GridLayoutManager layoutManager = new GridLayoutManager(this,2,GridLayout.HORIZONTAL, false);
mRv.setLayoutManager(layoutManager);
MyListAdapter adapter = new MyListAdapter(this, mDatas);
mRv.setAdapter(adapter);

StaggeredGridLayoutManager的使用

两个参数的构造方法,第一个参数是显示的列数,第二个参数是方向:

new StaggeredGridLayoutManager(int spanCount, int orientation);

纵向瀑布流布局是设置以下代码:

StaggeredGridLayoutManager layoutManager = new StaggeredGridLayoutManager(2,StaggeredGridLayoutManager.VERTICAL);
mRv.setLayoutManager(layoutManager);
MyStaggerAdapter adapter = new MyStaggerAdapter(this, mStaggerDatas);
mRv.setAdapter(adapter);

当点击切换成水平方向的瀑布流布局时:

StaggeredGridLayoutManager layoutManager = new StaggeredGridLayoutManager(2,StaggeredGridLayoutManager.HORIZONTAL);
mRv.setLayoutManager(layoutManager);
MyStaggerAdapter adapter = new MyStaggerAdapter(this, mStaggerDatas);
mRv.setAdapter(adapter);

RecyclerView的Adapter

public class MyListAdapter extends RecyclerView.Adapter<MyListAdapter.MyHolder> {

private List<DataBean> list;
private Context context;

public MyListAdapter(Context context, List<DataBean> list) {
    this.list = list;
    this.context = context;
}

/**创建条目布局*/
@Override
public MyHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    View view = View.inflate(context, R.layout.item_list, null);
    return new MyHolder(view);
}

/**绑定数据*/
@Override
public void onBindViewHolder(MyHolder myHolder, int position) {
    myHolder.setDataAndRefreshUI(list.get(position));
}

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

public class MyHolder extends RecyclerView.ViewHolder{
    private ImageView mIv;
    private TextView mTv;

    public MyHolder(View itemView) {
        super(itemView);
        mIv = (ImageView) itemView.findViewById(R.id.iv_icon);
        mTv = (TextView) itemView.findViewById(R.id.tv_name);
    }

    public void setDataAndRefreshUI(DataBean dataBean){
         mIv.setImageResource(dataBean.iconId);
         mTv.setText(dataBean.content);
    }
}

}

定义MyListAdapter继承RecyclerView.Adapter<VH extends ViewHolder>,泛型中需要传入RecyclerView.ViewHolder的子类,我们定义了MyHolder继承RecyclerView.ViewHolder,在ViewHolder中,构造函数需要传入View对象,即子条目的根View,接着我们做了子条目view各个控件的初始化操作和定义了setDataAndRefreshUI()方法,需要传入对应的bean类,并为相应的控件设置数据。

在Adapter需要重写onCreateViewHolder()返回对应的ViewHolder对象,还需要重写onBindViewHolder()方法,绑定数据,通过调用MyHolder中的setDataAndRefreshUI()方法,传入对应position的bean类;重写getItemCount()方法,返回对应条目的个数,这里返回集合的大小。

从Adapter的写法和思想可以看出和ListView的Adapter很相似,定义一个Adapter也不是一件很难的事情。

Item的点击事件

由于RecyclerView不再负责Item视图的布局及显示,所以RecyclerView也没有为Item开放OnItemClick等点击事件,这就需要开发者自己实现,在这里我们可以模拟ListView的setOnItemClickListener(),在Adapter中创建setOnItemClickListener(),并创建一个OnItemClickListener接口,用于回调ViewHolder的根View的点击事件。

一、创建子条目点击回调的接口:

  public interface MyItemClickListener {
    public void onItemClick(View view,int postion);
  }

二、创建接口成员变量:

 private MyItemClickListener mOnItemClickListener;

三、创建对应set方法:

 public void setOnItemClickListener(MyItemClickListener listener){
    this.mOnItemClickListener = listener;
}

四、在对应ViewHolder根View点击事件中回调:

public MyHolder(View itemView) {
    super(itemView);
    mIv = (ImageView) itemView.findViewById(R.id.iv_icon);
    mTv = (TextView) itemView.findViewById(R.id.tv_name);
    itemView.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            if (mOnItemClickListener != null){
                mOnItemClickListener.onItemClick(v,getPosition());
            }
        }
    });
}

五、在代码中,调用Adapter的setOnItemClickListener()获得子条目点击的回调:

private void initListener() {
    mListAdapter.setOnItemClickListener(new MyListAdapter.MyItemClickListener() {
        @Override
        public void onItemClick(View view, int postion) {
            Toast.makeText(RecyclerViewActivity.this, mDatas.get(postion).content, Toast.LENGTH_SHORT).show();
        }
    });
}

看下效果:

可以看到,子条目的点击事件已经可以像ListView一样,通过设置回调,在回调中做相应的处理就完成了,同理,子条目长按的事件也可以通过像上面的操作实现,在这里就不再做演示了,这里只是简单介绍下思想,有兴趣的话你可以尝试去实现下。

这里向大家推荐下一个很好用的开源框架,RecyclerView万能适配器,它可以省去编写Adapter的大部分代码,使用起来非常方便,特别是多Item布局使用,只需要简单的操作就可以实现,它的github地址是

BaseRecyclerViewAdapterHelper

RecyclerView的分割线

使用ListView的时候,当我们想添加条目之间的分割线时,我们只需要在ListView中配置divider,简单的两行配置:

android:divider="#fffff"  分割线颜色  
android:dividerHeight="1px"  分割线高度 

但是RecyclerView却没有提供这些配置,而是提供了方法

recyclerView.addItemDecoration()

需要自己定制ItemDecoration,有别于ListView,ItemDecoration这种可插拔设计不仅好用,而且功能强大,ItemDecoration中主要有以下三个方法:

public void onDraw(Canvas c, RecyclerView parent, State state) //可以实现类似绘制背景的效果,内容在上面
public void onDrawOver(Canvas c, RecyclerView parent, State state) //可以绘制在内容的上面,覆盖内容
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, State state)//可以实现类似padding的效果

绘制分割线

要实现分割线效果需要重写onDraw()和getItemOffsets()这两个方法:

public class MyDividerItemDecoration extends RecyclerView.ItemDecoration{
    private Paint mPaint;
    private int mDividerHeight;

    public MyDividerItemDecoration(Context context, int dividerHeight, int dividerColor){
        mDividerHeight = dividerHeight;
        mPaint = new Paint();
        mPaint.setColor(dividerColor);
    }

    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
        super.getItemOffsets(outRect, view, parent, state);
        outRect.bottom = mDividerHeight;//矩形的底部赋值分割线的高度
    }

    @Override
    public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
        int childCount = parent.getChildCount();//获取到子View的个数
        int left = parent.getPaddingLeft();
        int right = parent.getWidth() - parent.getPaddingRight();

        for (int i = 0; i < childCount - 1; i++) {
            View view = parent.getChildAt(i);
            float top = view.getBottom();
            float bottom = view.getBottom() + mDividerHeight;//子View底部添加分割线的高度
            c.drawRect(left, top, right, bottom, mPaint);//绘制
        }
    }
}

这里简单实现了绘制分割线,其中,分割线的高度和颜色可以通过构造方法传入,代码中使用:

mRv.addItemDecoration(new MyDividerItemDecoration(this,1,getResources().getColor(R.color.divider)));

看下未添加分割线和添加分割线的效果

这里只是简单教了如何绘制分割线,对于条目之间美化和修饰,可以通过ItemDecoration实现,这些需要去学习和探索。

SwipeRefreshLayout介绍

SwipeRefreshLayout是Google提供的一个官方的下拉刷新控件,该控件和以往的下拉刷新控件不同,第一次看到的时候觉得让人耳目一新,现在大部分的App都使用SwipeRefreshLayout作为下拉刷新控件,因为它不仅看起来美观,使用起来也是相当方便。

SwipeRefreshLayout在support v4包中,要想使用它,需要导入v4包的依赖,AS创建项目的时候默认有添加v4包的依赖,所以我们可以直接使用SwipeRefreshLayout。

布局文件中,使用SwipeRefreshLayout

<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.SwipeRefreshLayout
    android:id="@+id/srl"
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
>

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

</android.support.v4.widget.SwipeRefreshLayout>

和ScrollView的使用相似,SwipeRefreshLayout只能有一个直接子View,如果使用其他布局,比如不是SwipeRefreshLayout + RecyclerView,那么可以使用一个ViewGroup将布局好的控件包裹起来,最外层再用SwipeRefreshLayout包裹即可。

添加下拉刷新监听

mSrlRoot = (SwipeRefreshLayout) findViewById(R.id.srl);
mSrlRoot.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
        @Override
        public void onRefresh() {
            mHandler.postDelayed(new Runnable() {
                @Override
                public void run() {
                    mSrlRoot.setRefreshing(false);//收起下拉刷新
                    Toast.makeText(RecyclerViewActivity1.this, "刷新完毕", Toast.LENGTH_SHORT).show();
                }
            },2000);
        }
    });

效果如下:

可以看到,当RecyclerView处于顶部时,向下拉动,会出现一个小圆圈在转动,这就是SwipeRefreshLayout控件的样子,我们在下拉刷新的回调中,简单模拟了刷新的过程,延时2秒后收起下拉刷新,并弹出吐司,显示“刷新完毕”,可以看到SwipeRefreshLayou的出现、加载中和消失动画看起来也是很不错的。

修改SwipeRefreshLayout显示的颜色

上图我们看到了SwipeRefreshLayout的样子,是一个小圆圈,Google为我们提供了可定制化修改的属性,比如修改它的颜色:

 mSrlRoot.setColorSchemeColors(Color.RED);

上面代码设置它的颜色为红色,看下效果:

可以看到,SwipeRefreshLayout的小圈圈变成了红色。SwipeRefreshLayout设置颜色的方法setColorSchemeColors(int...colors),参数是可变参数,接收一个或多个颜色,如果我们传入多个颜色,将会是什么效果呢?

mSrlRoot.setColorSchemeColors(Color.RED,Color.GREEN,Color.BLUE);

这里我们设置了红绿蓝三个颜色,效果如下:

可以看到,加载中时,小圈圈交替变换红绿蓝这三种颜色,变得多姿多彩。

好了,本篇介绍RecyclerView和SwipeRefreshLayout到此就结束了,需要源码的可以查看:

https://github.com/chaychan/MaterialDesignExercise

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,542评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,596评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,021评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,682评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,792评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,985评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,107评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,845评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,299评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,612评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,747评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,441评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,072评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,828评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,069评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,545评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,658评论 2 350

推荐阅读更多精彩内容

  • 用两张图告诉你,为什么你的 App 会卡顿? - Android - 掘金 Cover 有什么料? 从这篇文章中你...
    hw1212阅读 12,704评论 2 59
  • 目录介绍 1.RecycleView的结构 2.Adapter2.1 RecyclerView.Adapter扮演...
    杨充211阅读 3,068评论 3 17
  • 1、概述 Android文档中是这么定义RecyclerView的:*A RecyclerView is a fl...
    高丕基阅读 1,423评论 2 36
  • 我在门外 走向你 你躲进门里 我抬起手 停顿 然后放弃 原谅我没有敲门 原谅我已经离去 我在门里 躲着你 等你找我...
    豹先森阅读 212评论 0 0
  • 这周是国庆节放假,周报就是碎碎念了。 工作 10月1日开始放假,2日处理了一点现场项目的故障,就没有其他工作了。 ...
    青梅煮酒2022阅读 237评论 0 0