1. 引言:

RecyclerView侧重的是布局的灵活性,虽说可以替代ListView但是连基本的点击事件都没有,这篇文章就来详细讲解如何为RecyclerView的item添加点击事件,顺便复习一下观察者模式。

2. 最终目的

模拟ListView的setOnItemClickListener()方法,调用者只须调用类似于setOnItemClickListener的东西就能获得被点击item的相关数据。

 

3. 原理

为RecyclerView的每个子item设置setOnClickListener,然后在onClick中再调用一次对外封装的接口,将这个事件传递给外面的调用者。而"为RecyclerView的每个子item设置setOnClickListener"在Adapter中设置。其实直接在onClick中也能完全处理item的点击事件,但是这样会破坏代码的逻辑。

4. 具体步骤如下:

在自定义MyAdapter之中(继承自RecyclerView.Adapter)

(1)在MyAdapter中定义如下接口,模拟ListView的OnItemClickListener:

 //define interface
public static interface OnRecyclerViewItemClickListener {
void onItemClick(View view , String data);
}

(2)声明一个这个接口的变量

private OnRecyclerViewItemClickListener mOnItemClickListener = null;

(3)在onCreateViewHolder()中为每个item添加点击事件

 @Override
public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.item, viewGroup, false);
ViewHolder vh = new ViewHolder(view);
//将创建的View注册点击事件
view.setOnClickListener(this);
return vh;
}

(4)将点击事件转移给外面的调用者:

 @Override
public void onClick(View v) {
if (mOnItemClickListener != null) {
//注意这里使用getTag方法获取数据
mOnItemClickListener.onItemClick(v,(String)v.getTag());
}
}

(5)注意上面调用接口的onItemClick()中的v.getTag()方法,这需要在onBindViewHolder()方法中设置和item相关的数据

@Override
public void onBindViewHolder(ViewHolder viewHolder, int position) {
viewHolder.mTextView.setText(datas[position]);
//将数据保存在itemView的Tag中,以便点击时进行获取
viewHolder.itemView.setTag(datas[position]);
}

(6)最后暴露给外面的调用者,定义一个设置Listener的方法():

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

 以上所有步骤都发生在自定义的adapter中,典型的观察者模式,有点绕的地方在于,这里涉及到两个观察者模式的使用,view的setOnClickListener本来就是观察者模式,我们将这个观察者模式的事件监听传递给了我们自己的观察者模式。

(7)接下来当然是在Activity中使用,如下:

 mRecyclerView = (RecyclerView)findViewById(R.id.my_recycler_view);
//创建默认的线性LayoutManager
mLayoutManager = new LinearLayoutManager(this);
mRecyclerView.setLayoutManager(mLayoutManager);
//如果可以确定每个item的高度是固定的,设置这个选项可以提高性能
mRecyclerView.setHasFixedSize(true);
//创建并设置Adapter
mAdapter = new MyAdapter(data);
mRecyclerView.setAdapter(mAdapter);
mAdapter.setOnItemClickListener(new OnRecyclerViewItemClickListener(){
@Override
public void onItemClick(View view , String data){
Toast.makeText(MainActivity.this, data, 600).show();
}
});

5. 案例演示:(结合上面的步骤理解)

(1)使用Eclipse创建一个工程,如下:

同时注意API要使用API21

(2)首先我们来到主布局activity_main.xml,如下:

 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent" > <android.support.v7.widget.RecyclerView
android:id="@+id/recyclerview"
android:layout_width="match_parent"
android:layout_height="match_parent"
/> </RelativeLayout>

(3)接下来,我们来到RecyclerView的item布局item.xml,如下:

 <?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="50dip" > <TextView
android:id="@+id/text"
android:text="默认"
android:layout_marginTop="5dp"
android:gravity="center_horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="20sp"
android:textColor="@android:color/holo_red_dark"/> </RelativeLayout>

(4)接下来我们自定义MyAdapter,如下:

 package com.himi.recyclerviewdemo;

 import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView; public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder>
implements View.OnClickListener { private String[] datas; public MyAdapter(String[] datas) {
this.datas = datas; } /**
* 1.定义接口
*/
public static interface OnRecyclerViewItemClickListener { void onItemClick(View view, String data); } /**
* 3.在onCreateViewHolder()中为每个item添加点击事件
*/
@Override
public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
View view = LayoutInflater.from(viewGroup.getContext())
.inflate(R.layout.item, viewGroup, false); ViewHolder vh = new ViewHolder(view);
// 将创建的View注册点击事件
view.setOnClickListener(this); return vh;
} /**
* 5.注意上面调用接口的onItemClick()中的v.getTag()方法,
* 这需要在onBindViewHolder()方法中设置和item相关的数据
*/
@Override
public void onBindViewHolder(ViewHolder viewHolder, int position) { viewHolder.mTextView.setText(datas[position]); // 将数据保存在itemView的Tag中,以便点击时进行获取
viewHolder.itemView.setTag(datas[position]);
} /**
* 4.将点击事件转移给外面的调用者
*/
public void onClick(View v) {
if (mOnItemClickListener != null) {
// 注意这里使用getTag方法获取数据
mOnItemClickListener.onItemClick(v, (String) v.getTag());
}
} /**
* 2.声明一个这个接口的变量
*/
private OnRecyclerViewItemClickListener mOnItemClickListener = null; /**
*6.最后暴露给外面的调用者,定义一个设置Listener的方法()
*/ public void setOnItemClickListener(OnRecyclerViewItemClickListener listener) {
this.mOnItemClickListener = listener;
} // 获取数据的数量
@Override
public int getItemCount() {
return datas.length;
} // 自定义的ViewHolder,持有每个Item的的所有界面元素
public static class ViewHolder extends RecyclerView.ViewHolder {
public TextView mTextView; public ViewHolder(View view) {
super(view);
mTextView = (TextView) view.findViewById(R.id.text);
}
} }

(5)接下来来到MainActivity,如下:

 package com.himi.recyclerviewdemo;

 import com.himi.recyclerviewdemo.MyAdapter.OnRecyclerViewItemClickListener;

 import android.app.Activity;
import android.os.Bundle;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.widget.Toast; public class MainActivity extends Activity { private RecyclerView mRecyclerView;
private LinearLayoutManager mLayoutManager;
private MyAdapter mAdapter;
private String[] data = new String[] {
"刘德华", "周杰伦", "梁朝伟", "郭富城", "黎明", "张学友",
"成龙", "午马", "洪金宝", "林正英", "元彪", "林志颖",
"吴奇隆", "苏有朋", "赵薇", "陈坤", "周润发", "范冰冰",
"贾静雯", "周星驰"
}; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mRecyclerView = (RecyclerView) findViewById(R.id.recyclerview); // 创建默认的线性LayoutManager
mLayoutManager = new LinearLayoutManager(this);
mRecyclerView.setLayoutManager(mLayoutManager); // 如果可以确定每个item的高度是固定的,设置这个选项可以提高性能
mRecyclerView.setHasFixedSize(true); //添加item间的分割线
mRecyclerView.addItemDecoration(new RecycleViewDivider(this, LinearLayoutManager.HORIZONTAL)); // 创建并设置Adapter
mAdapter = new MyAdapter(data);
mRecyclerView.setAdapter(mAdapter);
mAdapter.setOnItemClickListener(new OnRecyclerViewItemClickListener() {
@Override
public void onItemClick(View view, String data) {
Toast.makeText(MainActivity.this, data, 1).show();
}
});
} }

这里使用到的自定义分割线类RecycleViewDivider是别人写的,我直接拿来用了,在此我表示感谢,如下:

 package com.himi.recyclerviewdemo;

 import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.support.v4.content.ContextCompat;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.RecyclerView.ItemDecoration;
import android.view.View; public class RecycleViewDivider extends ItemDecoration {
private Paint mPaint;
private Drawable mDivider;
private int mDividerHeight = 2;//分割线高度,默认为1px
private int mOrientation;//列表的方向:LinearLayoutManager.VERTICAL或LinearLayoutManager.HORIZONTAL
private static final int[] ATTRS = new int[]{android.R.attr.listDivider}; /**
* 默认分割线:高度为2px,颜色为灰色
*
* @param context
* @param orientation 列表方向
*/
public RecycleViewDivider(Context context, int orientation) {
if (orientation != LinearLayoutManager.VERTICAL && orientation != LinearLayoutManager.HORIZONTAL) {
throw new IllegalArgumentException("请输入正确的参数!");
}
mOrientation = orientation; final TypedArray a = context.obtainStyledAttributes(ATTRS);
mDivider = a.getDrawable(0);
a.recycle();
} /**
* 自定义分割线
*
* @param context
* @param orientation 列表方向
* @param drawableId 分割线图片
*/
public RecycleViewDivider(Context context, int orientation, int drawableId) {
this(context, orientation);
mDivider = ContextCompat.getDrawable(context, drawableId);
mDividerHeight = mDivider.getIntrinsicHeight();
} /**
* 自定义分割线
*
* @param context
* @param orientation 列表方向
* @param dividerHeight 分割线高度
* @param dividerColor 分割线颜色
*/
public RecycleViewDivider(Context context, int orientation, int dividerHeight, int dividerColor) {
this(context, orientation);
mDividerHeight = dividerHeight;
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(dividerColor);
mPaint.setStyle(Paint.Style.FILL);
} //获取分割线尺寸
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
outRect.set(0, 0, 0, mDividerHeight);
} //绘制分割线
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDraw(c, parent, state);
if (mOrientation == LinearLayoutManager.VERTICAL) {
drawVertical(c, parent);
} else {
drawHorizontal(c, parent);
}
} //绘制横向 item 分割线
private void drawHorizontal(Canvas canvas, RecyclerView parent) {
final int left = parent.getPaddingLeft();
final int right = parent.getMeasuredWidth() - parent.getPaddingRight();
final int childSize = parent.getChildCount();
for (int i = 0; i < childSize; i++) {
final View child = parent.getChildAt(i);
RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) child.getLayoutParams();
final int top = child.getBottom() + layoutParams.bottomMargin;
final int bottom = top + mDividerHeight;
if (mDivider != null) {
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(canvas);
}
if (mPaint != null) {
canvas.drawRect(left, top, right, bottom, mPaint);
}
}
} //绘制纵向 item 分割线
private void drawVertical(Canvas canvas, RecyclerView parent) {
final int top = parent.getPaddingTop();
final int bottom = parent.getMeasuredHeight() - parent.getPaddingBottom();
final int childSize = parent.getChildCount();
for (int i = 0; i < childSize; i++) {
final View child = parent.getChildAt(i);
RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) child.getLayoutParams();
final int left = child.getRight() + layoutParams.rightMargin;
final int right = left + mDividerHeight;
if (mDivider != null) {
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(canvas);
}
if (mPaint != null) {
canvas.drawRect(left, top, right, bottom, mPaint);
}
}
}
}

(6)部署程序到手机上,如下:

6. 总结:

在ListView中我们是调用ListView的setOnItemClickListener:

 mListView.setOnItemClickListener(new OnItemClickListener() {
public void onItemClick(AdapterView<?> parent, View v, int position, long id) { ... }
});

而在我们这里是调用mAdapter的setOnItemClickListener。且回调方法public void onItemClick()的参数也不一致,ListView中有被点击item的position参数,而我们这里直接是被点击item的相关数据(这里只是一个字符串)。

 

Android 高级UI设计笔记20:RecyclerView 的详解之RecyclerView添加Item点击事件的更多相关文章

  1. Android 高级UI设计笔记03:使用ListView实现左右滑动删除Item

    1. 这里就是实现一个很简单的功能,使用ListView实现左右滑动删除Item: (1)当我们在ListView的某个Item,向左滑动显示一个删除按钮,用户点击按钮,即可以删除该项item,并且有 ...

  2. Android 高级UI设计笔记07:RecyclerView 的详解

    1. 使用RecyclerView       在 Android 应用程序中列表是一个非常重要的控件,适用场合非常多,如新闻列表.应用列表.消息列表等等,但是从Android 一出生到现在并没有非常 ...

  3. Android 高级UI设计笔记06:仿微信图片选择器(转载)

    仿微信图片选择器: 一.项目整体分析: 1. Android加载图片的3个目标: (1)尽可能的去避免内存溢出. a. 根据图片的显示大小去压缩图片 b. 使用缓存对我们图片进行管理(LruCache ...

  4. Android 高级UI设计笔记21:Android SegmentView(分段选择控件)

    1. 分段控制(SegmentView) 首先我们先看看什么是SegmentView的效果,如下: 分段控制这个View控件是ios7的分段控制,和QQ消息页面顶部的效果一样,android没有这个控 ...

  5. Android 高级UI设计笔记09:Android如何实现无限滚动列表

    ListView和GridView已经成为原生的Android应用实现中两个最流行的设计模式.目前,这些模式被大量的开发者使用,主要是因为他们是简单而直接的实现,同时他们提供了一个良好,整洁的用户体验 ...

  6. Android 高级UI设计笔记09:Android实现无限滚动列表

    1. 无限滚动列表应用场景: ListView和GridView已经成为原生的Android应用实现中两个最流行的设计模式.目前,这些模式被大量的开发者使用,主要是因为他们是简单而直接的实现,同时他们 ...

  7. Android 高级UI设计笔记01:使用ExpandableListView组件(ListView的扩展)

    1.ExpandableListView是一个用来显示二级节点的ListView. 比如如下效果的界面: 2.使用ExpandableListView步骤 (1)要给ExpandableListVie ...

  8. Android 高级UI设计笔记22:Android 指示引导页(带圆点)

    1. 引导页: 我们在安装某个软件首次运行时,大部分都会有一个引导页的提示,介绍软件新功能的加入或者使用说明等,支持滑动且下面会有几个圆点,显示共有多少页和当前图片的位置,类似如下效果: 2. 引导页 ...

  9. Android 高级UI设计笔记19:PopupWindow使用详解

    1. PopupWindow使用 PopupWindow这个类用来实现一个弹出框,可以使用任意布局的View作为其内容,这个弹出框是悬浮在当前activity之上的. 2. PopupWindow使用 ...

随机推荐

  1. Configure the Struts Tag Libraries

    In Struts framework, you always need to configure the Struts tag libraries in order to access it in ...

  2. stm32 usb数据接收与数据发送程序流程分析

    http://blog.csdn.net/u011318735/article/details/17424349 既然学习了USB,那就必须的搞懂USB设备与USB主机数据是怎么通讯的.这里主要讲设备 ...

  3. 实现jsp网页设为首页功能

    var url = location.href; var browser_name = navigator.userAgent; if(browser_name.indexOf('Chrome')!= ...

  4. MVC路由中routes.IgnoreRoute("{resource}.axd/{*pathInfo}") 到底什么意思!

    转自:http://blog.csdn.net/lvjin110/article/details/24638913 参考(1) http://www.cnblogs.com/flyfish2012/a ...

  5. [转载]SQL Server内核架构剖析

    原文链接:http://www.sqlserver.com.cn 我们做管理软件的,主要核心就在数据存储管理上.所以数据库设计是我们的重中之重.为了让我们的管理软件能够稳定.可扩展.性能优秀.可跟踪排 ...

  6. Spring MVC防止数据重复提交

    现实开发中表单重复提交的例子很多,就包括手上这个门户的项目也有这种应用场景,用的次数多,但是总结,这还是第一次. 一.基本原理 使用token,给所有的url加一个拦截器,在拦截器里面用java的UU ...

  7. SiteMesh学习入门

    http://www.java3z.com/cwbwebhome/article/article2/2962.html?id=1668 demo下载 简介:     sitemesh应用Decorat ...

  8. C++学习笔记之迭代器

    模板是的算法独立于存储的数据类型,而迭代器使算法独立于使用的容器类型.理解迭代器是理解STL的关键. 迭代器应该具备的特征: (1)应该能够对迭代器进行解除引用的操作,以便能够访问它引用的值.即如果P ...

  9. 安装linux系统并配置那点事

    安装完成后,将看到如下控制台: 输入以上安装信息中所填写的用户名(user),随后输入密码(user),即可登录 Ubuntu. 随时可使用 Ctrl + Alt 快捷键可离开虚拟机. 3 配置 Ub ...

  10. [Angular 2] Angular 2 Smart Components vs Presentation Components

    Both Smart Components and Presentation Components receive data from Services in entirely different w ...