RecyclerView使用详解

RecyclerView是Android
5.x版本中新添加的一个全新控件,他比ListView,GridView更加的灵活,我们能够使用RecyclerView就完成ListView,GridView所做的工作,同时使用RecyclerView也能非常方便的实现瀑布流的效果。

一.竖屏ListView,横屏GridView效果

MainActivity代码:

public class MainActivity extends Activity implements MyRecyclerViewAdapter.OnItemClickListener{
private RecyclerView recyclerView;
private List<string> dataList;
private MyRecyclerViewAdapter adapter;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

Display disPlay = getWindowManager().getDefaultDisplay();
Point size = new Point();
disPlay.getSize(size);
int width = size.x;
int height = size.y;
System.out.println(width+","+height);

dataList = new ArrayList<string>();
initValues();

recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
if(width < height){
LinearLayoutManager llm = new LinearLayoutManager(this,LinearLayoutManager.VERTICAL, false);
initRecyclerView(llm,new DividerItemDecoration(this, LinearLayoutManager.VERTICAL));
}else if(width > height){
LinearLayoutManager llm = new GridLayoutManager(this, 4);
initRecyclerView(llm,new DividerGridItemDecoration(this));
}
recyclerView.setItemAnimator(new DefaultItemAnimator());

adapter.setOnItemClickListener(this);
}

private void initRecyclerView(LayoutManager llm,ItemDecoration itemDecoration) {
recyclerView.setLayoutManager(llm);
adapter = new MyRecyclerViewAdapter(this,dataList);
recyclerView.setAdapter(adapter);
recyclerView.addItemDecoration(itemDecoration);
}

private void initValues() {
for (int i = 0; i <= 50; i++) {
dataList.add("item"+i);
}
}

@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main, menu);
return true;
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}

@Override
public void onItemClick(View view, int position) {
Toast.makeText(MainActivity.this, "点击了"+position, Toast.LENGTH_SHORT).show();
Intent intent = new Intent(MainActivity.this,StaggeredGridLayoutActivity.class);
startActivity(intent);
}

@Override
public void onItemLongClick(View view, int position) {
Toast.makeText(MainActivity.this, "删除了"+position, Toast.LENGTH_SHORT).show();
adapter.removeData(position);
adapter.notifyItemRangeChanged(position, adapter.getItemCount());
}

}</string></string>

首先获取到手机屏幕的宽高,如果width<height,那就展示一个ListView,如果width>height,那就展示一个4列的GridView。

MyRecyclerViewAdapter代码:

class MyRecyclerViewAdapter extends
RecyclerView.Adapter<myrecyclerviewadapter myviewholder> {
private List<string> dataList;
private Context mContext;

public interface OnItemClickListener {
void onItemClick(View view, int position);

void onItemLongClick(View view, int position);
}

private OnItemClickListener mOnItemClickListener;

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

public MyRecyclerViewAdapter(Context mContext, List<string> dataList) {
this.mContext = mContext;
this.dataList = dataList;
}

class MyViewHolder extends ViewHolder {
TextView tv;
FrameLayout root_view;

public MyViewHolder(View view) {
super(view);
tv = (TextView) view.findViewById(R.id.tv);
root_view = (FrameLayout) view.findViewById(R.id.root_view);
}

}

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

@Override
public void onBindViewHolder(final MyViewHolder holder, final int position) {
holder.tv.setText(dataList.get(position));
if(null != mOnItemClickListener){
holder.root_view.setOnClickListener(new OnClickListener() {

@Override
public void onClick(View view) {
mOnItemClickListener.onItemClick(view, position);
}
});

holder.root_view.setOnLongClickListener(new OnLongClickListener() {

@Override
public boolean onLongClick(View view) {
mOnItemClickListener.onItemLongClick(view, position);
return true;
}
});
}
}

public void addData(int position)
{
dataList.add(position, "Insert One");
notifyItemInserted(position);
}

public void removeData(int position)
{
dataList.remove(position);
notifyItemRemoved(position);

}

@Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(mContext).inflate(
R.layout.recycler_view_item, parent, false);
MyViewHolder holder = new MyViewHolder(view);

return holder;
}
}
</string></string></myrecyclerviewadapter>

由于RecyclerView并没有像ListView一样给每个Item设置点击监听事件,所以我们需要自己实现对RecyclerView的点击监听。实现的方法可以是在我们的MyRecyclerViewAdapter中去为点击事件提供一个回调。

DividerItemDecoration代码(为ListView添加分割线):

public class DividerItemDecoration extends ItemDecoration {
private static final int[] ATTRS = new int[]{
android.R.attr.listDivider
};

public static final int HORIZONTAL_LIST = LinearLayoutManager.HORIZONTAL;
public static final int VERTICAL_LIST = LinearLayoutManager.VERTICAL;

private Drawable mDivider;
private int mOrientation;

public DividerItemDecoration(Context context,int orientation){
final TypedArray typedArray = context.obtainStyledAttributes(ATTRS);
mDivider = typedArray.getDrawable(0);
typedArray.recycle();
setOrientation(orientation);
}

public void setOrientation(int orientation) {
if(orientation != HORIZONTAL_LIST && orientation != VERTICAL_LIST){
try {
throw new InterruptedIOException("invalid orientation");
} catch (InterruptedIOException e) {
e.printStackTrace();
}
}
mOrientation = orientation;
}

@Override
public void onDraw(Canvas c, RecyclerView parent, State state) {
if(mOrientation == VERTICAL_LIST){
drawVertical(c,parent);
}else{
drawHorizontal(c,parent);
}
}

private void drawHorizontal(Canvas c, RecyclerView parent) {
int top = parent.getPaddingTop();
int bottom = parent.getHeight()-parent.getPaddingBottom();

final int childCount = parent.getChildCount();
for(int i =0;i<childCount;i++){
final View child = parent.getChildAt(i);
final RecyclerView.LayoutParams params = (LayoutParams) child
.getLayoutParams();
final int left = child.getRight()+params.rightMargin;
final int right = left+mDivider.getIntrinsicHeight();
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);

}
}

private void drawVertical(Canvas c, RecyclerView parent) {
int left = parent.getPaddingLeft();
int right = parent.getWidth()-parent.getPaddingRight();

final int childCount = parent.getChildCount();
for(int i =0;i<childCount;i++){
final View child = parent.getChildAt(i);
final RecyclerView.LayoutParams params = (LayoutParams) child
.getLayoutParams();
final int top = child.getBottom()+params.bottomMargin;
final int bottom = top+mDivider.getIntrinsicHeight();
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);

}
}

@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
State state) {
if(mOrientation == VERTICAL_LIST){
outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
}else{
outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);
}
}
}

DividerGridItemDecoration代码(为GridView添加分割线):

public class DividerGridItemDecoration extends ItemDecoration {
private static final int[] ATTRS = new int[] { android.R.attr.listDivider };
private Drawable mDivider;

public DividerGridItemDecoration(Context context) {
final TypedArray typedArray = context.obtainStyledAttributes(ATTRS);
mDivider = typedArray.getDrawable(0);
typedArray.recycle();
}

@Override
public void onDraw(Canvas c, RecyclerView parent, State state) {
drawHorizontal(c, parent);
drawVertical(c, parent);

}

private void drawVertical(Canvas c, RecyclerView parent) {
final int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = parent.getChildAt(i);
RecyclerView.LayoutParams params = (LayoutParams) child
.getLayoutParams();
final int top = child.getTop() - params.topMargin;
final int bottom = child.getBottom() + params.bottomMargin;
final int left = child.getRight() + params.rightMargin;
final int right = left + mDivider.getIntrinsicWidth();
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
}

private void drawHorizontal(Canvas c, RecyclerView parent) {
final int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = parent.getChildAt(i);
RecyclerView.LayoutParams params = (LayoutParams) child
.getLayoutParams();
final int left = child.getLeft() - params.leftMargin;
final int right = child.getRight() + params.rightMargin
+ mDivider.getIntrinsicWidth();
final int top = child.getBottom() + params.bottomMargin;
final int bottom = top + mDivider.getIntrinsicHeight();
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
}

private int getSpanCount(RecyclerView parent) {
int spanCount = -1;
LayoutManager layoutManager = parent.getLayoutManager();
if (layoutManager instanceof GridLayoutManager) {
spanCount = ((GridLayoutManager) layoutManager).getSpanCount();
} else if (layoutManager instanceof StaggeredGridLayoutManager) {
spanCount = ((StaggeredGridLayoutManager) layoutManager)
.getSpanCount();
}
return spanCount;
}

private boolean isLastColum(RecyclerView parent, int pos, int spanCount,
int childCount) {
LayoutManager layoutManager = parent.getLayoutManager();
if (layoutManager instanceof GridLayoutManager) {
if ((pos + 1) % spanCount == 0)
{
return true;
}
} else if (layoutManager instanceof StaggeredGridLayoutManager) {
int orientation = ((StaggeredGridLayoutManager) layoutManager)
.getOrientation();
if (orientation == StaggeredGridLayoutManager.VERTICAL) {
if ((pos + 1) % spanCount == 0)
{
return true;
}
} else {
childCount = childCount - childCount % spanCount;
if (pos >= childCount)
return true;
}
}
return false;
}

private boolean isLastRaw(RecyclerView parent, int pos, int spanCount,
int childCount) {
LayoutManager layoutManager = parent.getLayoutManager();
if (layoutManager instanceof GridLayoutManager) {
childCount = childCount - childCount % spanCount;
if (pos >= childCount)
return true;
} else if (layoutManager instanceof StaggeredGridLayoutManager) {
int orientation = ((StaggeredGridLayoutManager) layoutManager)
.getOrientation();
if (orientation == StaggeredGridLayoutManager.VERTICAL) {
childCount = childCount - childCount % spanCount;
if (pos >= childCount)
return true;
} else
{
if ((pos + 1) % spanCount == 0) {
return true;
}
}
}
return false;
}

@Override
public void getItemOffsets(Rect outRect, int itemPosition,
RecyclerView parent) {
int spanCount = getSpanCount(parent);
int childCount = parent.getAdapter().getItemCount();
if (isLastRaw(parent, itemPosition, spanCount, childCount))
{
outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);
} else if (isLastColum(parent, itemPosition, spanCount, childCount))
{
outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
} else {
outRect.set(0, 0, mDivider.getIntrinsicWidth(),
mDivider.getIntrinsicHeight());
}
}

}

RecyclerView本身并不支持divider属性,所以如果我们不做定制,RecyclerView的每个Item之间是不会有分割线的,但是我们可以通过addItemDecoration()来定制自己的分割线,addItemDecoration()接收的参数是个ItemDecoration对象,我们可以自定义一个DividerItemDecoration类继承自ItemDecoration,在这个类中完成分割线的定制。

二.实现瀑布流效果

StaggeredGridActivity代码:

public class StaggeredGridActivity extends Activity implements StaggeredGridAdapter.OnItemClickListener{
private RecyclerView recyclerView;
private List<string> dataList;
private StaggeredGridAdapter adapter;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
dataList = new ArrayList<string>();
initValues();
adapter = new StaggeredGridAdapter(this, dataList);
recyclerView.setLayoutManager(new StaggeredGridLayoutManager(3,
StaggeredGridLayoutManager.VERTICAL));
recyclerView.setItemAnimator(new DefaultItemAnimator());
recyclerView.setAdapter(adapter);
adapter.setOnItemClickListener(this);

}

private void initValues() {
for (int i = 0; i < 50; i++) {
dataList.add("item"+i);
}
}

@Override
public void onItemClick(View view, int position) {
Toast.makeText(StaggeredGridLayoutActivity.this, "点击了"+position, Toast.LENGTH_SHORT).show();
}

@Override
public void onItemLongClick(View view, int position) {
Toast.makeText(StaggeredGridLayoutActivity.this, "删除了"+position, Toast.LENGTH_SHORT).show();
adapter.removeItem(position);
adapter.notifyItemRangeChanged(position, adapter.getItemCount());
}
}
</string></string>

StaggeredGridAdapter代码:

public class StaggeredGridAdapter extends
RecyclerView.Adapter<staggeredgridadapter myviewholder> {
private List<string> dataList;
private List<integer> mHeights;
private Context mContext;

public interface OnItemClickListener{
void onItemClick(View view,int position);
void onItemLongClick(View view,int position);
}

private OnItemClickListener mOnItemClickListener;

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

public StaggeredGridAdapter(Context mContext, List<string> dataList) {
this.dataList = dataList;
this.mContext = mContext;

mHeights = new ArrayList<integer>();
for(int i = 0;i<dataList.size();i++){
mHeights.add((int)(100+Math.random()*300));
}
}

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

@Override
public void onBindViewHolder(final MyViewHolder holder, final int position) {
LayoutParams params = (LayoutParams) holder.tv.getLayoutParams();
params.height = mHeights.get(position);
holder.tv.setLayoutParams(params);
holder.tv.setText(dataList.get(position));
if(mOnItemClickListener != null){
holder.root_view.setOnClickListener(new OnClickListener() {

@Override
public void onClick(View view) {
mOnItemClickListener.onItemClick(view, position);
}
});

holder.root_view.setOnLongClickListener(new OnLongClickListener() {

@Override
public boolean onLongClick(View view) {
mOnItemClickListener.onItemLongClick(view, position);
return true;
}
});
}
}

public void addItem(int position){
dataList.add(position, "Insert One");
mHeights.add( (int) (100 + Math.random() * 300));
notifyItemInserted(position);
}

public void removeItem(int position){
dataList.remove(position);
notifyItemRemoved(position);
}

@Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int arg1) {
View view = LayoutInflater.from(mContext).inflate(
R.layout.stagger_recycler_view_item, parent, false);
MyViewHolder holder = new MyViewHolder(view);
return holder;
}

class MyViewHolder extends ViewHolder {
TextView tv;
FrameLayout root_view;

public MyViewHolder(View view) {
super(view);
tv = (TextView) view.findViewById(R.id.tv);
root_view = (FrameLayout) view.findViewById(R.id.root_view);
}

}

}
</integer></string></integer></string></staggeredgridadapter>

其实瀑布流的实现和GridView的实现基本一致,只是瀑布流的每个Item的高不一样,所以这里在Adapter中动态的设置每个Item的高,在onBindViewHolder()中我们用到了LayoutParams方法,这里需要注意的是LayoutParams有很多不同的包,导包的时候一定要导对应的包。不然会报类型不匹配的错误。

三.添加、删除Item

1.删除:

notifyItemRemoved(int position)

2.添加:

notifyItemInserted(int position)

设置动画:

recyclerView.setItemAnimator(new DefaultItemAnimator())

四.点击监听(onClick,onLongClick)

如何实现RecyclerView的点击事件在上面的代码已经给出,上面的代码中如果是点击,则显示一条Toast,如果是长按则删除对应的Item。

点击事件处理中遇到的2个问题:

1.长按删除后也会显示非长按点击的Toast:

原因:一开始在setOnLongClickListener中返回了false,这样这个点击事件还会在向下传递,就会传递到setClickListener中,如果返回了true,则代表这次点击事件在setOnLongClickListener中就被消费了,不会再往下传递。(个人理解,有待验证...)可以参考下之前的文章

OnTouch事件ACTION_DOWN,ACTION_MOVE,ACTION_UP的事件拦截

解决:setOnLongClickListener中return
true。

2.长按删除后出现数据错乱:

原因:在StaggeredGridAdapter的removeItem()方法中,我们对Item的删除使用了notifyItemRemoved(int position)方法,这是RecyclerView为我们提供的带动画的删除方法,但是这个方法只是删除了界面上的Item,为保持数据的一致,我们还需要删除数据源中对应的数据,即dataList.remove(position)。如果对Item的删除仅仅这样处理的话,你会发现再去做删除操作时会发生数据的错乱,导致这个的原因是,我们在做完删除处理后并没有对RecyclerView进行刷新,所以会导致RecyclerView显示的数据与数据源不一致

解决:解决这个问题的办法就是在删除数据后我们就对RecyclerView刷新一次。RecyclerView和ListView一样,有notifyDataSetChanged()方法,但是使用这个方法进行刷新我们就无法看到删除动画了,原因是调用notifyDataSetChanged()刷新屏幕上显示的所有item的话,必然也会刷新当前正在执行动画的那个Item,这样导致的结果是前面的动画还没执行完,它马上又被刷新了,动画就看不见了。这里我们可以用notifyItemRangeChanged(int
position, int itemCount())方法,只需要从被删除的Item后面开始刷新就可以了,这样就保持了原有的删除动画。

时间: 2024-10-10 01:59:27

RecyclerView使用详解的相关文章

RecyclerView使用详解(三)

在上一篇(RecyclerView使用详解(二))文章中介绍了RecyclerView的多Item布局实现,接下来要来讲讲RecyclerView的Cursor实现,相较于之前的实现,Cursor有更多的使用场景,也更加的常用,特别是配合LoaderManager和CursorLoader进行数据的缓存及加载显示,基于此我们来重点看看RecyclerView的CursorAdapter具体要怎么实现. 一.CursorAdapter实现(配合LoaderManager和CursorLoader)

RecyclerView使用详解(二)

在上一篇(RecyclerView使用详解(一))文章中简单的介绍了RecyclerView的基本用法,接下来要来讲讲RecyclerView的更多用法,要实现不同的功能效果,大部分都还是在于RecyclerView的Adapter写法,所以我们着重来看看几种不同功能的Adapter写法. 一.多Item布局实现(MultipleItem) 如果之前你用过ListView实现过此功能,那么你一定对下面这两个方法并不陌生 @Override public int getItemViewType(i

RecyclerView使用详解(一)

一.前言 RecyclerView是谷歌V7包下新增的控件,用来替代ListView的使用,在RecyclerView标准化了ViewHolder类似于ListView中convertView用来做视图缓. 先来说说RecyclerView的有点就是,他可以通过设置LayoutManager来快速实现listview.gridview.瀑布流的效果,而且还可以设置横向和纵向显示,添加动画效果也非常简单(自带了ItemAnimation,可以设置加载和移除时的动画,方便做出各种动态浏览的效果),也

Android 中RecyclerView使用详解(一)

概述 针对RecyclerView,谷歌有一段介绍的话: RecyclerView is a more advanced and flexible version of ListView. This widget is a Container for large sets of views that can be recycled and scrolled very efficiently. Use the RecyclerView widget when you have lists with

ListView和RecyclerView复用详解

做一家受人尊敬的企业,做一位受人尊敬的老师-动脑学院 前言 我们每一个Android开发人员对ListView的使用肯定是很熟悉的,然而多少人能真正的懂ListView的缓存机制呢,说白了就是ListView为了提高效率,而内部实现的一种优化,牺牲一点内存.而这种优化就需要复用ItemView(也就是item对应的View).那么下面楼主来对ListView和RecyclerView的item复用问题做一个深入的讲解 首先来解答几个问题 1.ListView为什么会存在Item复用问题 答:Li

Android 高级UI设计笔记20:RecyclerView 的详解之RecyclerView添加Item点击事件

1. 引言: RecyclerView侧重的是布局的灵活性,虽说可以替代ListView但是连基本的点击事件都没有,这篇文章就来详细讲解如何为RecyclerView的item添加点击事件,顺便复习一下观察者模式. 2. 最终目的 模拟ListView的setOnItemClickListener()方法,调用者只须调用类似于setOnItemClickListener的东西就能获得被点击item的相关数据.   3. 原理 为RecyclerView的每个子item设置setOnClickLi

【Android】RecyclerView详解(一)

1.介绍 RecyclerView是比 ListView 更高级且更具灵活性的组件. 此组件是一个用于显示庞大数据集的容器,可通过保持有限数量的视图进行非常有效的滚动操作. 如果您有数据集合,其中的元素将因用户操作或网络事件而发生改变,请使用 RecyclerView 小组件. RecyclerView使用起来很方便因为它: 提供了一种插拔式的体验,高度的解耦,异常的灵活使用; 显示的样式更丰富包括水平,竖直,Grid,瀑布显示方式; 可以通过ItemDecoration自定义Item间的间隔;

android L新控件RecyclerView详解与DeMo

介绍 在谷歌的官网我们可以看到它是这样介绍的:RecyclerView is a more advanced and flexible version of ListView. This widget is a container for large sets of views that can be recycled and scrolled very efficiently. Use the RecyclerView widget when you have lists with eleme

Android群英传笔记——第十二章:Android5.X 新特性详解,Material Design UI的新体验

Android群英传笔记--第十二章:Android5.X 新特性详解,Material Design UI的新体验 第十一章为什么不写,因为我很早之前就已经写过了,有需要的可以去看 Android高效率编码-第三方SDK详解系列(二)--Bmob后端云开发,实现登录注册,更改资料,修改密码,邮箱验证,上传,下载,推送消息,缩略图加载等功能 这一章很多,但是很有趣,也是这书的最后一章知识点了,我现在还在考虑要不要写这个拼图和2048的案例,在此之前,我们先来玩玩Android5.X的新特性吧!