Android RecyclerView 在去年的Google I/O大会上就推出来了,以前经常使用的ListView 继承的是AbsListView,而RecyclerView则直接继承 ViewGroup,并实现了ScrollingView 和 NestedScrollingChild接口,RecyclerView相比ListView,是一次彻底的改变,RecyclerView 比ListView更加强大灵活。
DEMO实现功能:
- RecyclerView的点击事件: Item及item中的子View添加点击事件RecyclerView Item之间添加分隔线:垂直与水平方向
RecyclerView 单个与多个Item的添加与删除
RecyclerView Item添加与删除动画效果RecyclerView滚动状态监听
LayoutManager的使用DEMO效果图:
RecyclerView的相关的LayoutManager ItemDecoration 和 ItemAnimator
- LayoutManager:这个是为RecyclerView设置布局管理器的,决定RecyclerView的显示风格,它有两个直接子类:LinearLayoutManager 和 StaggeredGridLayoutManager,还有一个间接子类GridLayoutManager,GridLayoutManager继承LinearLayoutManager 。线性布局管理器 LinearLayoutManager 的布局像ListView显示多列,可以直接设置其布局方向(垂直或水平),网格布局管理器 GridLayoutManager像Gridview那样显示多行多列,而StaggeredGridLayoutManager则可以实现流式布局。
- ItemDecoration:在Item之间设置分隔线和偏移,提供了三个方法:getItemOffsets,onDraw,onDrawOver。ItemDecoration的绘制是有一定的顺序的,onDraw的绘制在Item视图绘制之前,onDrawOver 的绘制在Item视图绘制之后,并将其绘制的显示在视图之上,getItemOffsets为Item设置偏移量。如果你只是想实现简单的Item之间的分割线的话,可以直接在item的XML文件中直接定义就可以了。
- ItemAnimator:用来设置item的添加或者删除的动画风格,默认的动画是DefaultItemAnimator,也可以自己设置,继承RecyclerView.ItemAnimator,然后重写animateAdd,animateMove等方法就可以了。 来看看一个简单的例子: XML中添加RecyclerView:
1.
<android.support.v7.widget.RecyclerView
2.
android:id=
"@+id/recyclerview"
3.
android:layout_width=
"match_parent"
4.
android:layout_height=
"0dp"
5.
android:layout_weight=
"1"
6.
android:scrollbars=
"vertical"
/>
01.
// 如果布局大小一致有利于优化
02.
recyclerView.setHasFixedSize(
true
);
03.
// 创建一个线性布局管理器
04.
LinearLayoutManager layoutManager =
new
LinearLayoutManager(
this
);
05.
layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
06.
// 设置布局管理器
07.
recyclerView.setLayoutManager(layoutManager);
08.
// 创建数据集
09.
List<User> listData =
new
ArrayList<User>();
10.
for
(
int
i =
0
; i <
20
; ++i) {
11.
User uBean =
new
User();
12.
uBean.setUsername(
"我是Item"
+ i);
13.
listData.add(uBean);
14.
}
15.
16.
// 创建Adapter,并指定数据集
17.
MyAdapter adapter =
new
MyAdapter(context, listData);
18.
// 设置Adapter
19.
recyclerView.setAdapter(adapter);
01.
public
class
MyAdapter
extends
RecyclerView.Adapter<MyAdapter.MViewHolder> {
02.
03.
private
Context context;
04.
private
List<User> listData;
05.
06.
public
MyAdapter(Context context, List<User> mList) {
07.
super
();
08.
this
.context = context;
09.
this
.listData = mList;
10.
}
11.
12.
@Override
13.
public
int
getItemCount() {
14.
// TODO Auto-generated method stub
15.
return
listData.size();
16.
}
17.
18.
@Override
19.
public
MViewHolder onCreateViewHolder(ViewGroup viewGroup,
int
arg1) {
20.
21.
View view = View.inflate(viewGroup.getContext(),
22.
R.layout.item_user_friend_nod,
null
);
23.
// 创建一个ViewHolder
24.
MViewHolder holder =
new
MViewHolder(view);
25.
return
holder;
26.
}
27.
28.
@Override
29.
public
void
onBindViewHolder(MViewHolder mViewHolder,
int
arg1) {
30.
31.
mViewHolder.mTextView.setText(listData.get(arg1).getUsername());
32.
mViewHolder.image.setBackgroundResource(R.drawable.head);
33.
34.
}
35.
36.
public
class
MViewHolder
extends
RecyclerView.ViewHolder {
37.
public
TextView mTextView;
38.
public
ImageView image;
39.
40.
public
MViewHolder(View view) {
41.
super
(view);
42.
this
.mTextView = (TextView) view.findViewById(R.id.tv_friend_name);
43.
this
.image = (ImageView) itemView.findViewById(R.id.img_friend_avatar);
44.
45.
}
46.
}
47.
48.
}
为RecyclerView的Item及item中的子View添加点击事件
RecyclerView并没有像ListView那样提供OnItemClickListener和OnLongClickListener的回调,为了给RecyclerView添加Onclick监听,需要自己去实现其Onclick监听方法再对外公开。
首先,定义一个接口,并在里面声明3个监听回调函数,分别是Item普通点击监听,Item长按监听和Item内部View点击监听。01.
/**
02.
* item点击回调接口
03.
*
04.
* @author wen_er
05.
*
06.
*/
07.
public
interface
ItemClickListener {
08.
09.
/**
10.
* Item 普通点击
11.
*/
12.
13.
public
void
onItemClick(View view,
int
postion);
14.
15.
/**
16.
* Item 长按
17.
*/
18.
19.
public
void
onItemLongClick(View view,
int
postion);
20.
21.
/**
22.
* Item 内部View点击
23.
*/
24.
25.
public
void
onItemSubViewClick(View view,
int
postion);
26.
}
01.
public
class
MyAdapter
extends
RecyclerView.Adapter<MyAdapter.MViewHolder> {
02.
03.
private
Context context;
04.
private
List<User> listData;
05.
private
ItemClickListener mItemClickListener;
06.
07.
public
MyAdapter(Context context, List<User> mList) {
08.
super
();
09.
this
.context = context;
10.
this
.listData = mList;
11.
}
12.
13.
public
void
setItemClickListener(ItemClickListener mItemClickListener) {
14.
15.
this
.mItemClickListener = mItemClickListener;
16.
}
17.
18.
@Override
19.
public
int
getItemCount() {
20.
// TODO Auto-generated method stub
21.
return
listData.size();
22.
}
23.
24.
@Override
25.
public
MViewHolder onCreateViewHolder(ViewGroup viewGroup,
int
arg1) {
26.
27.
View view = View.inflate(viewGroup.getContext(),
28.
R.layout.item_user_friend_nod,
null
);
29.
// 创建一个ViewHolder
30.
MViewHolder holder =
new
MViewHolder(view);
31.
return
holder;
32.
}
33.
34.
@Override
35.
public
void
onBindViewHolder(
final
MViewHolder mViewHolder,
36.
final
int
postion) {
37.
38.
mViewHolder.mTextView.setText(listData.get(postion).getUsername());
39.
mViewHolder.image.setBackgroundResource(R.drawable.head);
40.
// 为image添加监听回调
41.
mViewHolder.image.setOnClickListener(
new
OnClickListener() {
42.
43.
@Override
44.
public
void
onClick(View v) {
45.
if
(
null
!= mItemClickListener) {
46.
mItemClickListener.onItemSubViewClick(mViewHolder.image,
47.
postion);
48.
}
49.
50.
}
51.
52.
});
53.
54.
}
55.
56.
public
class
MViewHolder
extends
RecyclerView.ViewHolder {
57.
public
TextView mTextView;
58.
public
ImageView image;
59.
60.
public
MViewHolder(
final
View view) {
61.
super
(view);
62.
this
.mTextView = (TextView) view.findViewById(R.id.tv_friend_name);
63.
this
.image = (ImageView) itemView.findViewById(R.id.img_friend_avatar);
64.
//为item添加普通点击回调
65.
view.setOnClickListener(
new
OnClickListener() {
66.
67.
@Override
68.
public
void
onClick(View v) {
69.
70.
if
(
null
!= mItemClickListener) {
71.
mItemClickListener.onItemClick(view, getPosition());
72.
}
73.
74.
}
75.
});
76.
77.
//为item添加长按回调
78.
view.setOnLongClickListener(
new
OnLongClickListener() {
79.
80.
@Override
81.
public
boolean
onLongClick(View v) {
82.
if
(
null
!= mItemClickListener) {
83.
mItemClickListener.onItemLongClick(view, getPosition());
84.
}
85.
return
true
;
86.
}
87.
});
88.
89.
}
90.
}
91.
92.
}
01.
//为Item具体实例点击3种事件
02.
adapter.setItemClickListener(
new
ItemClickListener() {
03.
04.
@Override
05.
public
void
onItemSubViewClick(View view,
int
postion) {
06.
T.showShort(context,
"亲,你点击了Image"
+postion);
07.
08.
}
09.
10.
@Override
11.
public
void
onItemLongClick(View view,
int
postion) {
12.
T.showShort(context,
"亲,你长按了Item"
+postion);
13.
14.
}
15.
16.
@Override
17.
public
void
onItemClick(View view,
int
postion) {
18.
T.showShort(context,
"亲,你点击了Item"
+postion);
19.
20.
}
21.
});
为Item之间添加分隔线
如果想要给RecyclerView的Item之间添加分隔线,可以使用addItemDecoration,但如果想图方便,就直接在Item对应的XML中定义就可以了,比如说,像这样(下面的例子只适合Item垂直布局)1.
01.
<RelativeLayout xmlns:android=
"http://schemas.android.com/apk/res/android"
02.
android:layout_width=
"match_parent"
03.
android:layout_height=
"match_parent"
>
04.
05.
<RelativeLayout
06.
android:layout_width=
"match_parent"
07.
android:layout_height=
"wrap_content"
08.
android:background=
"@drawable/selector_item_action"
>
09.
10.
<TextView
11.
android:id=
"@+id/tv_friend_name"
12.
android:layout_width=
"wrap_content"
13.
android:layout_height=
"wrap_content"
14.
android:layout_centerVertical=
"true"
15.
android:layout_marginLeft=
"20dp"
16.
android:layout_toRightOf=
"@+id/img_friend_avatar"
17.
android:text=
"test"
18.
android:textSize=
"18sp"
/>
19.
20.
<ImageView
21.
android:id=
"@+id/img_friend_avatar"
22.
android:layout_width=
"50dp"
23.
android:layout_height=
"50dp"
24.
android:layout_alignParentLeft=
"true"
25.
android:layout_marginBottom=
"8dip"
26.
android:layout_marginLeft=
"8dip"
27.
android:layout_marginTop=
"8dip"
28.
android:background=
"@drawable/ic_launcher"
/>
29.
</RelativeLayout>
30.
31.
<!-- 可以添加以下代码为Item之间设置分隔线 -->
32.
<View
33.
android:layout_width=
"match_parent"
34.
android:layout_height=
"1dp"
35.
android:layout_alignParentBottom=
"true"
36.
android:background=
"@drawable/divider_horizontal_line"
/>
37.
38.
39.
</RelativeLayout>
01.
<pre name=
"code"
class
=
"java"
>
public
class
ItemDecorationDivider
extends
ItemDecoration {
02.
03.
private
Drawable mDivider;
04.
private
int
mOritation;
05.
06.
public
ItemDecorationDivider(Context context,
int
resId,
int
oritation) {
07.
08.
mDivider = context.getResources().getDrawable(resId);
09.
this
.mOritation = oritation;
10.
Log.i(
"ItemDecorationDivider"
,
"mOritation="
+ mOritation);
11.
12.
}
13.
14.
@Override
15.
public
void
onDrawOver(Canvas c, RecyclerView parent) {
16.
17.
if
(mOritation == LinearLayoutManager.VERTICAL) {
18.
final
int
left = parent.getPaddingLeft();
19.
final
int
right = parent.getWidth() - parent.getPaddingRight();
20.
21.
final
int
childCount = parent.getChildCount();
22.
for
(
int
i =
0
; i < childCount; i++) {
23.
final
View child = parent.getChildAt(i);
24.
final
RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
25.
.getLayoutParams();
26.
27.
final
int
top = child.getBottom() + params.bottomMargin;
28.
final
int
bottom = top + mDivider.getIntrinsicHeight();
29.
mDivider.setBounds(left, top, right, bottom);
30.
mDivider.draw(c);
31.
}
32.
}
else
if
(mOritation == LinearLayoutManager.HORIZONTAL) {
33.
34.
final
int
top = parent.getPaddingTop();
35.
// final int bottom = parent.getHeight() -
36.
// parent.getPaddingBottom();
37.
38.
final
int
childCount = parent.getChildCount();
39.
for
(
int
i =
0
; i < childCount; i++) {
40.
final
View child = parent.getChildAt(i);
41.
final
RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
42.
.getLayoutParams();
43.
final
int
left = child.getRight() + params.rightMargin;
44.
final
int
right = left + mDivider.getIntrinsicHeight();
45.
46.
final
int
bottom = child.getBottom();
47.
mDivider.setBounds(left, top, right, bottom);
48.
mDivider.draw(c);
49.
}
50.
}
51.
52.
}
53.
54.
@Override
55.
public
void
getItemOffsets(Rect outRect,
int
position,
56.
RecyclerView parent) {
57.
if
(mOritation == LinearLayoutManager.VERTICAL) {
58.
outRect.set(
0
,
0
,
0
, mDivider.getIntrinsicWidth());
59.
// outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
60.
}
else
if
(mOritation == LinearLayoutManager.HORIZONTAL) {
61.
// outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);
62.
outRect.set(
0
,
0
,
0
, mDivider.getIntrinsicHeight());
63.
}
64.
65.
}
66.
}
1.
1.
1.
recyclerView.addItemDecoration(
new
ItemDecorationDivider(context,
2.
R.drawable.item_divider, LinearLayoutManager.VERTICAL));
1.
<?xml version=
"1.0"
encoding=
"utf-8"
?>
2.
<shape xmlns:android=
"http://schemas.android.com/apk/res/android"
3.
android:shape=
"rectangle"
>
4.
<solid android:color=
"#CCCCCC"
/>
5.
<size android:height=
"1dp"
/>
6.
</shape>
RecyclerView Item的添加与删除
Adapter提供的的几个常用方法:- notifyItemChanged(int position) //通知位置position的Item的数据改变
-
- notifyItemInserted(int)//通知位置position的Item的数据插入
- notifyItemRemoved(int)//通知位置position的Item的数据移除
- notifyItemRangeChanged(int positionStart, int itemCount) //通知从位置positionStart开始,有itemCount个Item的数据发生改变
- notifyItemRangeInserted(int positionStart, int itemCount) //通知从位置positionStart开始,有itemCount个Item的数据插入
- notifyItemRangeRemoved(int positionStart, int itemCount)//通知从位置positionStart开始,有itemCount个Item的数据移除 主要是使用Adapter提供的notifyItemInserted(position)和notifyItemRemoved(position)方法,告知数据改变,如果删除或者添加Item都是从Position为0的位置开始,加上notifyDataSetChanged()刷新一下UI。 注意:使用notifyDataSetChanged()不会触发Item的动画效果。
01.
</pre><pre name=
"code"
class
=
"java"
>
/**
02.
* TODO<添加数据,指定其位置>
03.
*/
04.
05.
public
void
addData(User info,
int
position) {
06.
listData.add(position, info);
07.
notifyItemInserted(position);
08.
// notifyDataSetChanged(); //不会触发Item的动画效果,告知数据改变,刷新UI
09.
10.
}
11.
12.
/**
13.
* TODO<添加数据到最后面添加>
14.
*/
15.
16.
public
void
addData(User info) {
17.
// listData.add(position, info);
18.
// notifyItemInserted(position);
19.
listData.add(info);
20.
notifyDataSetChanged();
21.
}
22.
23.
/**
24.
* TODO<删除数据,指定其位置>
25.
*/
26.
public
void
daleteData(
int
position) {
27.
listData.remove(position);
28.
notifyItemRemoved(position);
29.
30.
}
31.
32.
/**
33.
* TODO<某一位置开始,有itemCount个Item的数据删除>
34.
*/
35.
public
void
itemRangeRemoved(
int
positionStart,
int
itemCount) {
36.
for
(
int
i = positionStart; i < itemCount; i++) {
37.
listData.remove(positionStart);
38.
}
39.
notifyItemRangeRemoved(positionStart, itemCount);
40.
// notifyDataSetChanged(); //不会触发Item的动画效果,告知数据改变,刷新UI
41.
}
42.
43.
/**
44.
* TODO<某一位置开始,有itemCount个Item的数据插入>
45.
*/
46.
public
void
itemRangeInserted(User info,
int
positionStart,
int
itemCount) {
47.
for
(
int
i = positionStart; i < itemCount; i++) {
48.
listData.add(i, info);
49.
}
50.
notifyItemRangeInserted(positionStart, itemCount);
51.
// notifyDataSetChanged();
52.
}
1.
</pre><pre>
01.
case
R.id.btn3:
02.
User uBean =
new
User();
03.
uBean.setUsername(
"我是增加的Item"
);
04.
adapter.addData(uBean,
0
);
// 添加到第一个
05.
break
;
06.
case
R.id.btn4:
07.
adapter.daleteData(
0
);
// 删除第一个
08.
break
;
09.
case
R.id.btn5:
10.
User uBean1 =
new
User();
11.
uBean1.setUsername(
"我是连续添加的Item"
);
12.
adapter.itemRangeInserted(uBean1,
0
,
5
);
13.
break
;
14.
case
R.id.btn6:
15.
adapter.itemRangeRemoved(
0
,
5
);
16.
break
;
为RecyclerView的Item添加动画
在在ListView中,给item添加动画的常用方式是,使用LayoutAnimationController为ViewGroup添加动画,在RecyclerView中,则使用RecyclerView提供的setItemAnimator()方法1.
// 使用RecyclerView提供的默认的动画效果
2.
recyclerView.setItemAnimator(
new
DefaultItemAnimator());
这就是我们在效果图看到的动画效果,如果想要其它的动画效果,参见GitHub:https://github.com/gabrielemariotti/RecyclerViewItemAnimators 目前提供了:ScaleInOutItemAnimator,SlideInOutBottomItemAnimator,SlideInOutLeftItemAnimator,SlideInOutRightItemAnimator,SlideInOutTopItemAnimator,SlideScaleInOutRightItemAnimator几种动画效果,使用方法相似。RecyclerView滚动状态监听
RecyclerView提供了setOnScrollListener方法,以便监听屏幕滚动状态。01.
recyclerView.setOnScrollListener(
new
RecyclerView.OnScrollListener() {
02.
@Override
03.
public
void
onScrollStateChanged(RecyclerView recyclerView,
04.
int
scrollState) {
05.
updateState(scrollState);
06.
}
07.
08.
@Override
09.
public
void
onScrolled(RecyclerView recyclerView,
int
i,
int
i2) {
10.
11.
String s =
"可见Item数量:"
+ layoutManager.getChildCount()+"
12.
"
13.
+
"可见Item第一个Position:"
14.
+ layoutManager.findFirstVisibleItemPosition()+"
15.
"
16.
+
"可见Item最后一个Position:"
17.
+ layoutManager.findLastVisibleItemPosition();
18.
tv.setText(s);
19.
}
20.
});
01.
private
void
updateState(
int
scrollState) {
02.
String stateName =
"Undefined"
;
03.
switch
(scrollState) {
04.
case
SCROLL_STATE_IDLE:
05.
stateName =
"Idle"
;
06.
break
;
07.
08.
case
SCROLL_STATE_DRAGGING:
09.
stateName =
"Dragging"
;
10.
break
;
11.
12.
case
SCROLL_STATE_SETTLING:
13.
stateName =
"Flinging"
;
14.
break
;
15.
}
16.
17.
tv_state.setText(
"滑动状态:"
+ stateName);
18.
}
最后看看LayoutManager,前面说过,LayoutManager是为RecyclerView设置布局管理器的,决定RecyclerView的显示风格。LinearLayoutManager
前面的代码中,使用 LinearLayoutManager layoutManager = new LinearLayoutManager(this)和 layoutManager.setOrientation(LinearLayoutManager.VERTICAL)创建一个线性布局管理器和设置其布局方向 还可以使用直接以下使用构造方法传入布局方向: LinearLayoutManager(Context context, int orientation, boolean reverseLayout) 第二个参数为布局方向:LinearLayoutManager.HORIZONTAL或者LinearLayoutManager.VERTICAL 第三个参数:true或者false,决定布局是否反向GridLayoutManager
这是类似GridView的网格布局,三个构造函数:
GridLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) //可以直接在XMl中设置RecyclerView 属性"layoutManager". GridLayoutManager(Context context, int spanCount) //spanCount为列数,默认方向vertical
GridLayoutManager(Context context, int spanCount, int orientation, boolean reverseLayout)//spanCount为列数,orientation为布局方向,reverseLayout决定布局是否反向。
1.
gridLayoutManager =
new
GridLayoutManager(
this
,
3
,
2.
GridLayoutManager.VERTICAL,
false
);
3.
// 设置布局管理器
4.
recyclerView.setLayoutManager(gridLayoutManager);
StaggeredGridLayoutManager
流式布局,两个构造函数:
StaggeredGridLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)
StaggeredGridLayoutManager(int spanCount, int orientation) //spanCount为列数,orientation为布局方向1.
StaggeredGridLayoutManager =
new
StaggeredGridLayoutManager(
2
,
2.
StaggeredGridLayoutManager.VERTICAL);
3.
// 设置布局管理器
4.
recyclerView.setLayoutManager(StaggeredGridLayoutManager);
LinearLayoutManager,GridLayoutManager和StaggeredGridLayoutManager使用方法都类似,直接作为参数传给setLayoutManager就可以了。
关于RecyclerView更深入的用法在后面的博文中介绍。
- .新的思路:notifyItemChanged
RecyclerView不像ListView,只有一个更新notifyDataSetChanged,它不仅保留了ListView的更新特点,还针对“增加,删除,更新”操作专门进行更新,可以只更新一个item,也可以更新一部分item,所以,用起来效率更高。因此,RecyclerView的局部刷新,就可以通过修改数据源的方式,调用notifyItemChanged(position)即可。
优化
虽然只更新单个item,不会造成闪烁,但是,如果单个item都很复杂,比如,item中需要从网络上加载图片等等。为了避免多次刷新照成的闪烁,我们可以在加载的时候,为ImageView设置一个Tag,比如imageView.setTag(image_url),下一次再加载之前,首先获取Tag,比如imageUrl = imageView.getTag(),如果此时的地址和之前的地址一样,我们就不需要加载了,如果不一样,再加载。