RecycleView是Android 5.0,推出的一款替代Listview GridView的控件
A flexible view for providing a limited window into a large data set.
从定义可以看出,flexible(可扩展性)是RecyclerView的特点。
RecyclerView是support-v7包中的新组件,是一个强大的滑动组件,与经典的ListView相比,同样拥有item回收复用的功能,这一点从它的名字Recyclerview即回收view也可以看出。
RecyclerView的优点
RecyclerView并不会完全替代ListView(这点从ListView没有被标记为@Deprecated可以看出),两者的使用场景不一样。但是RecyclerView的出现会让很多开源项目被废弃,例如横向滚动的ListView, 横向滚动的GridView, 瀑布流控件,因为RecyclerView能够实现所有这些功能。
比如:有一个需求是屏幕竖着的时候的显示形式是ListView,屏幕横着的时候的显示形式是2列的GridView,此时如果用RecyclerView,则通过设置LayoutManager一行代码实现替换。
RecylerView相对于ListView的优点罗列如下:
- RecyclerView封装了viewholder的回收复用,也就是说RecyclerView标准化了ViewHolder,编写Adapter面向的是ViewHolder而不再是View了,复用的逻辑被封装了,写起来更加简单。
直接省去了listview中convertView.setTag(holder)和convertView.getTag()这些繁琐的步骤。 - 提供了一种插拔式的体验,高度的解耦,异常的灵活,针对一个Item的显示RecyclerView专门抽取出了相应的类,来控制Item的显示,使其的扩展性非常强。
- 设置布局管理器以控制Item的布局方式,横向、竖向以及瀑布流方式
例如:你想控制横向或者纵向滑动列表效果可以通过LinearLayoutManager这个类来进行控制(与GridView效果对应的是GridLayoutManager,与瀑布流对应的还StaggeredGridLayoutManager等)。也就是说RecyclerView不再拘泥于ListView的线性展示方式,它也可以实现GridView的效果等多种效果。 - 可设置Item的间隔样式(可绘制)
通过继承RecyclerView的ItemDecoration这个类,然后针对自己的业务需求去书写代码。 - 可以控制Item增删的动画,可以通过ItemAnimator这个类进行控制,当然针对增删的动画,RecyclerView有其自己默认的实现。
但是关于Item的点击和长按事件,需要用户自己去实现。
也就是说RecycleView标准化ViewHold,复用的逻辑标准化,设置的布局管理器的控制Item的布局方式,横线的,竖向以及是瀑布的方式。可设置Item的间隔样式,可控制Item的增删的动画。
基本使用
在build.gradle文件中引入该类。
compile 'com.android.support:recyclerview-v7:23.4.0'
布局
Activity布局文件activity_rv.xml
...
Item的布局文件item_1.xml
...
recyclerView = (RecyclerView) findViewById(R.id.recyclerView);
LinearLayoutManager layoutManager = new LinearLayoutManager(this ); //设置布局管理器 recyclerView.setLayoutManager(layoutManager); //设置为垂直布局,这也是默认的 layoutManager.setOrientation(OrientationHelper. VERTICAL); //设置Adapter
recyclerView.setAdapter(recycleAdapter); //设置分隔线
recyclerView.addItemDecoration( new DividerGridItemDecoration(this )); //设置增加或删除条目的动画 recyclerView.setItemAnimator( new DefaultItemAnimator());
在使用RecyclerView时候,必须指定一个适配器Adapter和一个布局管理器LayoutManager。适配器继承RecyclerView.Adapter
类,具体实现类似ListView的适配器,取决于数据信息以及展示的UI。布局管理器用于确定RecyclerView中Item的展示方式以及决定何时复用已经不可见的Item,避免重复创建以及执行高成本的findViewById()
方法。
创建适配器创建适配器
① 创建Adapter:创建一个继承RecyclerView.Adapter<VH>
的Adapter类(VH是ViewHolder的类名)
② 创建ViewHolder:在Adapter中创建一个继承RecyclerView.ViewHolder
的静态内部类,记为VH。ViewHolder的实现和ListView的ViewHolder实现几乎一样。
③ 在Adapter中实现3个方法:
- onCreateViewHolder()
这个方法主要生成为每个Item inflater出一个View,但是该方法返回的是一个ViewHolder。该方法把View直接封装在ViewHolder中,然后我们面向的是ViewHolder这个实例,当然这个ViewHolder需要我们自己去编写。
需要注意的是在onCreateViewHolder()
中,映射Layout必须为
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_1, parent, false);
而不能是:
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_1, null);
其功能是为每一个Item inflate出一个View,但是该方法返回的是一个ViewHold。
- onBindViewHolder()
这个方法主要用于适配渲染数据到View中。方法提供给你了一viewHolder而不是原来的convertView。 - getItemCount()
这个方法就类似于BaseAdapter的getCount方法了,即总共有多少个条目。
public class MyRecyclerViewAdapter extends RecyclerView.Adapter<MyRecyclerViewAdapter.ViewHolder> {
/**
* 相当于于getView方法中创建View和ViewHolder
* @param parent
* @param viewType
* @return
*/
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View itemView = View.inflate(context, R.layout.item_recyclerview,null);
return new ViewHolder(itemView);
}
/**相当于于getView绑定数据部分的代码
* 数据和View绑定
* @param holder
* @param position
*/
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
//根据位置得到对应的数据
String data = datas.get(position);
holder.tv_title.setText(data);
}
/**
* 得到总条数
* @return
*/
@Override
public int getItemCount() {
return datas.size();
}
class ViewHolder extends RecyclerView.ViewHolder {
private ImageView iv_icon;
private TextView tv_title;
public ViewHolder(View itemView) {
super(itemView);
iv_icon = (ImageView) itemView.findViewById(R.id.iv_icon);
tv_title = (TextView) itemView.findViewById(R.id.tv_title);
itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// Toast.makeText(context, "data=="+datas.get(getLayoutPosition()), Toast.LENGTH_SHORT).show();
if(onItemClickListener != null){
onItemClickListener.onItemClick(v,datas.get(getLayoutPosition()));
}
}
});
iv_icon.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(context, "我是图片=="+getLayoutPosition(), Toast.LENGTH_SHORT).show();
}
});
}
}
}
设置RecyclerView
创建完Adapter,接着对RecyclerView进行设置,一般来说,需要为RecyclerView进行四大设置,也就是后文说的四大组成:
- Layout Manager(必选)
- Adapter(必选)
- Item Decoration(可选,默认为空) 设置RecyclerView的分割线
- Item Animator(可选,默认为DefaultItemAnimator)设置删除添加的动画
如果要实现ListView的效果,只需要设置Adapter和Layout Manager,如下:
List<String> data = initData();
RecyclerView rv = (RecyclerView) findViewById(R.id.rv);
rv.setLayoutManager(new LinearLayoutManager(this));
rv.setAdapter(new NormalAdapter(data));
四大组成
RecyclerView的四大组成是:
- Layout Manager:Item的布局。
- Adapter:为Item提供数据。
- Item Decoration:Item之间的Divider。
- Item Animator:添加、删除Item动画。
Layout Manager布局管理器
在最开始就提到,RecyclerView 能够支持各种各样的布局效果,这是 ListView 所不具有的功能,那么这个功能如何实现的呢?其核心关键在于 RecyclerView.LayoutManager 类中。从前面的基础使用可以看到,RecyclerView 在使用过程中要比 ListView 多一个 setLayoutManager 步骤,这个 LayoutManager 就是用于控制我们 RecyclerView 最终的展示效果的。
LayoutManager负责RecyclerView的布局,其中包含了Item View的获取与回收。
RecyclerView提供了三种布局管理器:
- LinerLayoutManager 以垂直或者水平列表方式展示Item
- GridLayoutManager 以网格方式展示Item
- StaggeredGridLayoutManager 以瀑布流方式展示Item
如果你想用 RecyclerView 来实现自己自定义效果,则应该去继承实现自己的 LayoutManager,并重写相应的方法,而不应该想着去改写 RecyclerView。
LayoutManager 常见 API
关于 LayoutManager 的使用有下面一些常见的 API(有些在 LayoutManager 实现的子类中)
canScrollHorizontally();//能否横向滚动
canScrollVertically();//能否纵向滚动
scrollToPosition(int position);//滚动到指定位置
setOrientation(int orientation);//设置滚动的方向
getOrientation();//获取滚动方向
findViewByPosition(int position);//获取指定位置的Item View
findFirstCompletelyVisibleItemPosition();//获取第一个完全可见的Item位置
findFirstVisibleItemPosition();//获取第一个可见Item的位置
findLastCompletelyVisibleItemPosition();//获取最后一个完全可见的Item位置
findLastVisibleItemPosition();//获取最后一个可见Item的位置
上面仅仅是列出一些常用的 API 而已,更多的 API 可以查看官方文档,通常你想用 RecyclerView 实现某种效果,例如指定滚动到某个 Item 位置,但是你在 RecyclerView 中又找不到可以调用的 API 时,就可以跑到 LayoutManager 的文档去看看,基本都在那里。
另外还有一点关于瀑布流布局效果 StaggeredGridLayoutManager 想说的,看到网上有些文章写的示例代码,在设置了 StaggeredGridLayoutManager 后仍要去 Adapter 中动态设置 View 的高度,才能实现瀑布流,这种做法是完全错误的,之所以 StaggeredGridLayoutManager 的瀑布流效果出不来,基本是 item 布局的 xml 问题以及数据问题导致。如果要在 Adapter 中设置 View 的高度,则完全违背了 LayoutManager 的设计理念了。
Item Decoration间隔样式
RecyclerView通过addItemDecoration()
方法添加item之间的分割线。Android并没有提供实现好的Divider,因此任何分割线样式都需要自己实现。
在manifest配置文件中定义Android:theme
<activity android:name=".recyclervview.RecyclerViewActivity" android:theme="@style/listDividerTheme"/>
在styles.xml 定义
<!-- Application theme. -->
<style name="listDividerTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Customize your theme here. -->
<item name="android:listDivider">@drawable/divider_bg</item>
</style>
在drawable中定义.xml文件
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle" >
<gradient
android:centerColor="#ff00ff00"
android:endColor="#ff0000ff"
android:startColor="#ffff0000"
android:type="linear" />
<size android:height="2dp"/>
</shape>
//添加RecyclerView的分割线
recyclerview.addItemDecoration(new DividerListItemDecoration(RecyclerViewActivity.this,DividerListItemDecoration.VERTICAL_LIST));
Item Animator动画
RecyclerView能够通过mRecyclerView.setItemAnimator(ItemAnimator animator)
设置添加、删除、移动、改变的动画效果。
RecyclerView提供了默认的ItemAnimator实现类:DefaultItemAnimator。如果没有特殊的需求,默认使用这个动画即可。
// 设置Item添加和移除的动画
mRecyclerView.setItemAnimator(new DefaultItemAnimator());
下面就添加一下删除和添加Item的动作。在Adapter里面添加方法。
public void addNewItem() {
if(mData == null) {
mData = new ArrayList<>();
}
mData.add(0, "new Item");
更新数据集不是用adapter.notifyDataSetChanged()而是notifyItemInserted(position)与notifyItemRemoved(position) 否则没有动画效果。
notifyItemInserted(0);
}
public void deleteItem() {
if(mData == null || mData.isEmpty()) {
return;
}
mData.remove(0);
notifyItemRemoved(0);
}
添加事件的处理。
public void onClick(View v) {
int id = v.getId();
if(id == R.id.rv_add_item_btn) {
mAdapter.addNewItem();
// 由于Adapter内部是直接在首个Item位置做增加操作,增加完毕后列表移动到首个Item位置
mLayoutManager.scrollToPosition(0);
} else if(id == R.id.rv_del_item_btn){
mAdapter.deleteItem();
// 由于Adapter内部是直接在首个Item位置做删除操作,删除完毕后列表移动到首个Item位置
mLayoutManager.scrollToPosition(0);
}
}