您的位置:首页 > 移动开发 > Android开发

[Android开发]从Android官方Demo谈RecyclerView的用法

2016-11-06 16:40 405 查看
RecyclerView是Android5.0中出现的新控件,官方API解释就一句话:

A flexible view for providing a limited window into a large data set

整体架构如下图:



RecyclerView的灵活性体现在6个方面:

可以控制显示方式,包括三个内置的不觉管理器,也可以定制

LinearLayoutManager 以垂直或水平滚动列表方式显示项目

GridLayoutManager 在网格中显示项目

StaggeredGridLayoutManager 瀑布了流中显示项目

默认情况下显示增加删除的动画,也扩RecyclerView.ItemAnimator定制

默认情况无分割线,可以扩展ItemDecoration定制

参考资料

官方API

官方Training:创建列表与卡片

深入理解 RecyclerView 系列之一:ItemDecoration

Android RecyclerView 使用完全解析 体验艺术般的控件

官方Demo效果

官方提供了一个Demo(github地址)的运行效果是这样的:



代码比较简单,重要的内容包括RecyclerView的初始化和其对应的Adapter的构造。

引申需求

设置分割线

分割线官方并没有提供默认的类型,默认也并没有分隔线。要提供分隔线必须自己实现RecyclerView.ItemDecoration。

/**
* An ItemDecoration allows the application to add a special drawing and layout offset
* to specific item views from the adapter's data set. This can be useful for drawing dividers
* between items, highlights, visual grouping boundaries and more.
*
* <p>All ItemDecorations are drawn in the order they were added, before the item
* views (in {@link ItemDecoration#onDraw(Canvas, RecyclerView, RecyclerView.State) onDraw()}
* and after the items (in {@link ItemDecoration#onDrawOver(Canvas, RecyclerView,
* RecyclerView.State)}.</p>
*/
public static abstract class ItemDecoration {
/**
* Draw any appropriate decorations into the Canvas supplied to the RecyclerView.
* Any content drawn by this method will be drawn before the item views are drawn,
* and will thus appear underneath the views.
*
* @param c Canvas to draw into
* @param parent RecyclerView this ItemDecoration is drawing into
* @param state The current state of RecyclerView
*/
public void onDraw(Canvas c, RecyclerView parent, State state) {
onDraw(c, parent);
}

/**
* @deprecated
* Override {@link #onDraw(Canvas, RecyclerView, RecyclerView.State)}
*/
@Deprecated
public void onDraw(Canvas c, RecyclerView parent) {
}

/**
* Draw any appropriate decorations into the Canvas supplied to the RecyclerView.
* Any content drawn by this method will be drawn after the item views are drawn
* and will thus appear over the views.
*
* @param c Canvas to draw into
* @param parent RecyclerView this ItemDecoration is drawing into
* @param state The current state of RecyclerView.
*/
public void onDrawOver(Canvas c, RecyclerView parent, State state) {
onDrawOver(c, parent);
}

/**
* @deprecated
* Override {@link #onDrawOver(Canvas, RecyclerView, RecyclerView.State)}
*/
@Deprecated
public void onDrawOver(Canvas c, RecyclerView parent) {
}

/**
* @deprecated
* Use {@link #getItemOffsets(Rect, View, RecyclerView, State)}
*/
@Deprecated
public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent) {
outRect.set(0, 0, 0, 0);
}

/**
* Retrieve any offsets for the given item. Each field of <code>outRect</code> specifies
* the number of pixels that the item view should be inset by, similar to padding or margin.
* The default implementation sets the bounds of outRect to 0 and returns.
*
* <p>
* If this ItemDecoration does not affect the positioning of item views, it should set
* all four fields of <code>outRect</code> (left, top, right, bottom) to zero
* before returning.
*
* <p>
* If you need to access Adapter for additional data, you can call
* {@link RecyclerView#getChildAdapterPosition(View)} to get the adapter position of the
* View.
*
* @param outRect Rect to receive the output.
* @param view    The child view to decorate
* @param parent  RecyclerView this ItemDecoration is decorating
* @param state   The current state of RecyclerView.
*/
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, State state) {
getItemOffsets(outRect, ((LayoutParams) view.getLayoutParams()).getViewLayoutPosition(),
parent);
}
}


这里参考深入理解 RecyclerView 系列之一:ItemDecoration(http://www.tuicool.com/articles/fIbuYfI)来实现该接口。

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.View;

/**
* 参考:https://android.googlesource.com/platform/development/+/master/samples/Support7Demos/src
* /com/example/android/supportv7/widget/decorator/DividerItemDecoration.java#101
*/
public class DividerItemDecoration extends RecyclerView.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 a = context.obtainStyledAttributes(ATTRS);
mDivider = a.getDrawable(0);
a.recycle();
setOrientation(orientation);
}

public void setOrientation(int orientation) {
if (orientation != HORIZONTAL_LIST && orientation != VERTICAL_LIST) {
throw new IllegalArgumentException("invalid orientation");
}
mOrientation = orientation;
}

@Override
public void onDraw(Canvas c, RecyclerView parent) {

if (mOrientation == VERTICAL_LIST) {
drawVertical(c, parent);
} else {
drawHorizontal(c, parent);
}

}

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

final int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = parent.getChildAt(i);
android.support.v7.widget.RecyclerView v = new android.support.v7.widget.RecyclerView(parent.getContext());
final RecyclerView.LayoutParams params = (RecyclerView.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);
}
}

public void drawHorizontal(Canvas c, RecyclerView parent) {
final int top = parent.getPaddingTop();
final 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 = (RecyclerView.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);
}
}
/**
* outRect是用来设置left、top、right、bottom的padding值的
* */
@Override
public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent) {
if (mOrientation == VERTICAL_LIST) {
outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
} else {
outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);
}
}
}


点击事件

RecyclerView本身不提供单击和长按事件,需要自己实现。分为几个步骤。

第一步,需要在CustomAdapter中自己定义回调接口。

/**
* 点击事件接口
* */
public interface OnItemClickListener{
void onItemClick(View view,int position);
void onItemLongClick(View view,int position);
}
private OnItemClickListener onItemClickListener;

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


第二步,在CustomAdapter中的onBindViewHolder(ViewHolder viewHolder, final int position)中调用接口函数。

@Override
public void onBindViewHolder(ViewHolder viewHolder, final int position) {
Log.d(TAG, "Element " + position + " set.");

// Get element from your dataset at this position and replace the contents of the view
// with that element
viewHolder.getTextView().setText(mDataSet[position]);
viewHolder.setColor(position);
if (onItemClickListener!=null){
//可以获得每个item的包装类itemView
viewHolder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
onItemClickListener.onItemClick(v,position);
}
});
viewHolder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
onItemClickListener.onItemLongClick(v,position);
return true;
}
});
}
}


第三步,在CustomAdapter初始化的地方传入该接口实例。

mAdapter.setOnItemClickListener(new CustomAdapter.OnItemClickListener() {
@Override
public void onItemClick(View view, int position) {
××××单击事件××××××
}

@Override
public void onItemLongClick(View view, int position) {
××××长按事件××××××
}
});


多选模式

多选模式可以采用ActionMode来进行UI设计,本质上是通过长按进入ActionMode模式,可以点击按钮取消多选模式。

这里配合上面的点击事件,仅仅模拟了多选的操作,而没有添加ActionMode。

mAdapter.setOnItemClickListener(new CustomAdapter.OnItemClickListener() {
@Override
public void onItemClick(View view, int position) {
if (isOnLonCliked){
addOrRemove(position);
Log.i(TAG, position+"OnLonCliked");
}else {
Toast.makeText(getActivity(),position+" clicked",Toast.LENGTH_LONG).show();
}
}
@Override
public void onItemLongClick(View view, int position) {
isOnLonCliked=true;
Log.i(TAG, position+"OnLonCliked");
}
});


其中,addOrRemove(int position)多选的逻辑。

**
* 模拟多选情况
* */
private void  addOrRemove(int position){
if(mAdapter.positionSet.contains(position)){
mAdapter.positionSet.remove(position);
}else {
mAdapter.positionSet.add(position);
}
if (mAdapter.positionSet.size()==0){
isOnLonCliked=false;
}
mAdapter.notifyDataSetChanged();
}


CustomAdapter中有一个集合在记录多选模式下已经点选的位置,点击时判断该集合是否包含了该位置,如果已经包含,就取消颜色,否则改变颜色。

/**
* Provide views to RecyclerView with data from mDataSet.
*/

××××××省略无关代码××××××××××××××

public static Set<Integer> positionSet = new HashSet<>();

// BEGIN_INCLUDE(recyclerViewSampleViewHolder)
/**
* Provide a reference to the type of views that you are using (custom ViewHolder)
*/
public static class ViewHolder extends RecyclerView.ViewHolder {
public final TextView textView;

public ViewHolder(View v) {
super(v);
// Define click listener for the ViewHolder's View.
v.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.d(TAG, "Element " + getAdapterPosition() + " clicked.");
}
});
textView = (TextView) v.findViewById(R.id.textView);
Log.d(TAG, "ViewHolder ");
}

public TextView getTextView() {
return textView;
}
public void setColor(int position){
if (positionSet.contains(position)){
textView.setTextColor(Color.GREEN);
}else {
textView.setTextColor(Color.BLACK);
}

}
}
××××××省略无关代码××××××××××××××


最后达成的效果是

长按RecyclerView中的某一项,会进入到多选模式

如果点击的项已经在CustomAdapter中的集合中,则去除这些项,如果集合清空,则退出多选模式

多选模式下再点击某些项,这些项会记录到CustomAdapter中的集合中



GitHub地址

本文的工程地址为:

https://github.com/caoyanfeng/GoogleSamples

如果觉得写得不错,请给我的GitHub点star哦~
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: