Android UI RecycleView单条目点击,长按事件
RecycleView的灵活性是无须质疑的,接下来我们自己实现标题所示功能
直接上代码+ 注释,相信大家会比较容易吸收
- 先贴出实际代码中使用方法
// 使用注解,加载rview_act布局
@ContentView(R.layout.rview_act)
public class RViewAct extends BaseActivity {
// 使用注解获取控件(类ButterKnife)
@InjectView(R.id.recyclerView)
private RView recyclerView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
initRView();
}
// 点击单个Item,会通过InjectManager的injectEvents方法,找到OnItemClick 注解的注解 EventBase
// 然后通过代理将 RViewAdapter中holder.getConvertView()设置的点击,长按监听回调到 RViewAct 中
// itemClick itemLongClick方法
// 实现RecycleView单条目点击回调
@OnItemClick(R.id.recyclerView)
public void itemClick(View view, UserInfo info, int position) {
Toast.makeText(this, "OnItemClick\n" + info.getPassword(), Toast.LENGTH_SHORT).show();
}
// 实现RecycleView单条目长按
@OnItemLongClick(R.id.recyclerView)
public boolean itemLongClick(View view, UserInfo info, int position) {
Toast.makeText(this, "OnItemLongClick\n" + info.getPassword(), Toast.LENGTH_SHORT).show();
return true;
}
private void initRView() {
LinearLayoutManager manager = new LinearLayoutManager(this);
manager.setOrientation(LinearLayoutManager.VERTICAL);
recyclerView.setLayoutManager(manager);
// 一个适配器,实现简单文本Item展示在RecycleView
SampleAdapter adapter = new SampleAdapter(initDatas());
recyclerView.setRViewAdapter(adapter);
recyclerView.setAdapter(adapter);
InjectManager.injectEvents(this);
}
// 模拟真实列表数据
private List<UserInfo> initDatas() {
List<UserInfo> datas = new ArrayList<>();
for (int i = 0; i < 49; i++) {
datas.add(new UserInfo("netease", "p" + i));
}
return datas;
}
}
- BaseActivity.java
public class BaseActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 帮助子类进行,布局、控件、事件的注入
InjectManager.inject(this);
}
}
- UserInfo.java
// 单Item对应实体对象
public class UserInfo {
private String name;
private String password;
public UserInfo(String name, String password) {
this.name = name;
this.password = password;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "UserInfo{" +
"name='" + name + '\'' +
", password='" + password + '\'' +
'}';
}
}
- SampleAdapter.java
public class SampleAdapter extends RViewAdapter<UserInfo> {
public SampleAdapter(List<UserInfo> datas) {
super(datas);
}
@Override
public int getLayoutId() {
return R.layout.adapter_item;
}
@Override
public void convert(RViewHolder holder, UserInfo info) {
TextView textView = holder.getView(R.id.item_tv);
textView.setText(info.toString());
}
}
这里引用 librview这个库(封装了RView实现标题所示功能)
- RView.java
public class RView extends RecyclerView {
private RViewAdapter adapter;
public RView(@NonNull Context context) {
super(context);
}
public RView(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public RView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
/**
* @param adapter 设置适配器
*/
public void setRViewAdapter(RViewAdapter adapter) {
this.adapter = adapter;
}
// 条目点击监听接口
public interface OnItemClickListener<T> {
// 点击事件监听
void onItemClick(View view, T entity, int position);
}
// 条目长按监听接口
public interface OnItemLongClickListener<T> {
// 长按事件监听
boolean onItemLongClick(View view, T entity, int position);
}
/** 设置点击监听属性 */
public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
adapter.setmOnItemClickListener(onItemClickListener);
}
/** 设置长按监听属性 */
public void setOnItemLongClickListener(OnItemLongClickListener onItemLongClickListener) {
adapter.setmOnItemLongClickListener(onItemLongClickListener);
}
}
- RViewAdapter.java
public abstract class RViewAdapter<T> extends RecyclerView.Adapter<RViewHolder> {
// 条目点击事件监听
private RView.OnItemClickListener<T> mOnItemClickListener;
// 条目长按事件监听
private RView.OnItemLongClickListener<T> mOnItemLongClickListener;
private List<T> datas;
public abstract void convert(RViewHolder holder, T t);
public RViewAdapter(List<T> datas) {
if (datas == null) {
this.datas = new ArrayList<>();
}
this.datas = datas;
}
public abstract int getLayoutId();
@NonNull
@Override
public RViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
int layoutId = getLayoutId();
RViewHolder holder = RViewHolder.createViewHolder(viewGroup.getContext(), viewGroup, layoutId);
setListener(holder);
return holder;
}
@Override
public void onBindViewHolder(@NonNull RViewHolder holder, int position) {
convert(holder, datas.get(position));
}
@Override
public int getItemCount() {
return datas.size();
}
void setmOnItemClickListener(RView.OnItemClickListener<T> mOnItemClickListener) {
this.mOnItemClickListener = mOnItemClickListener;
}
void setmOnItemLongClickListener(RView.OnItemLongClickListener<T> mOnItemLongClickListener) {
this.mOnItemLongClickListener = mOnItemLongClickListener;
}
private void setListener(final RViewHolder holder) {
if (holder == null) return;
holder.getConvertView().setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mOnItemClickListener != null) {
int position = holder.getAdapterPosition();
if (position != -1) { // 防止连续点击同一条目而且是删除操作
mOnItemClickListener.onItemClick(v, datas.get(position), position);
}
}
}
});
holder.getConvertView().setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
if (mOnItemLongClickListener != null) {
int position = holder.getAdapterPosition();
return mOnItemLongClickListener.onItemLongClick(v, datas.get(position), position);
}
return false;
}
});
}
}
- RViewHolder.java
public class RViewHolder extends RecyclerView.ViewHolder {
// 当前View
private View mConvertView;
// View控件集合
private SparseArray<View> mViews;
public RViewHolder(@NonNull View itemView) {
super(itemView);
mConvertView = itemView;
mViews = new SparseArray<>();
}
/**
* 通过viewId获取控件
*/
public <T extends View> T getView(int viewId) {
View view = mViews.get(viewId);
if (view == null) {
view = mConvertView.findViewById(viewId);
mViews.put(viewId, view);
}
return (T) view;
}
/**
* 创建RecycleViewViewHold对象
*
* @param context 上下文
* @param parent 父布局
* @param layoutId 布局ID
* @return RViewHolder
*/
static RViewHolder createViewHolder(Context context, ViewGroup parent, int layoutId) {
View itemView = LayoutInflater.from(context).inflate(layoutId, parent, false);
return new RViewHolder(itemView);
}
View getConvertView() {
return mConvertView;
}
}
- InjectManager.java
public class InjectManager {
public static void inject(Activity act) {
// 布局的注入
injectLayout(act);
// 控件的注入
injectViews(act);
// 事件的注入
injectEvents(act);
}
public static void injectEvents(Activity act) {
Class<? extends Activity> clazz = act.getClass();
// 获取类的所有方法
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods) {
// 获取每个方法的注解(多个控件id)
Annotation[] annotations = method.getAnnotations();
for (Annotation annotation : annotations) {
// 获取注解上的注解
Class<? extends Annotation> annotationType = annotation.annotationType();
if (annotationType != null) {
// 通过EventBase指定获取
EventBase eventBase = annotationType.getAnnotation(EventBase.class);
if (eventBase != null) { // 有些方法没有EventBase注解
// 事件3大成员
String listenerSetter = eventBase.listenerSetter();
Class<?> listenerType = eventBase.listenerType();
String callBackListener = eventBase.callBackListener();
try {
// 通过annotationType 获取 onClick 注解的value值
Method valueMethod = annotationType.getDeclaredMethod("value");
// 执行value方法获得注解的值
int[] viewIds = (int[]) valueMethod.invoke(annotation);
// 代理方式(3个成员组合)
// 拦截方法
// 得到监听的代理对象(新建代理单例、类的加载器,指定要代理的对象类的类型、class实例)
ListenerInvocationHandler handler = new ListenerInvocationHandler(act);
// 添加到拦截列表里面
handler.addMethod(callBackListener, method);
// 监听对象的代理对象
// ClassLoader loader:指定当前目标对象使用类加载器,获取加载器的方法是固定的
// Class<?>[] interfaces:目标对象实现的接口的类型,使用泛型方式确认类型
// InvocationHandler h:事件处理,执行目标对象的方法时,会触发事件处理器的方法
Object listener = Proxy.newProxyInstance(listenerType.getClassLoader(), new Class[]{listenerType}, handler);
// 遍历注解的值
for (int viewId : viewIds) {
// 获得当前activity的view(赋值)
View view = act.findViewById(viewId);
// 获取指定的方法
Method setter = view.getClass().getMethod(listenerSetter, listenerType);
// 执行方法
setter.invoke(view, listener);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
}
private static void injectViews(Activity act) {
Class<? extends Activity> clazz = act.getClass();
// 获取类的所有属性
Field[] fields = clazz.getDeclaredFields();
// 循环每个属性
for (Field field : fields) {
// 获得属性上的注解
InjectView injectView = field.getAnnotation(InjectView.class);
if (injectView != null) {
// 获取注解值
int viewId = injectView.value();
// 获取findViewById方法,并执行
try {
Method method = clazz.getMethod("findViewById", int.class);
// 设置访问权限private
field.setAccessible(true);
field.set(act, method.invoke(act, viewId));
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
}
}
private static void injectLayout(Activity act) {
Class<? extends Activity> clazz = act.getClass();
// 获取类的注解
ContentView contentView = clazz.getAnnotation(ContentView.class);
if (contentView != null) {
int layoutId = contentView.value();
// 获取Activity指定方法
try {
Method method = clazz.getMethod("setContentView", int.class);
method.invoke(act, layoutId);
// 另一种写法:
// act.setContentView(layoutId);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
}
}
- ListenerInvocationHandler.java
// 将回调的onClick方法拦截,执行我们自己自定义的方法(aop概念)
public class ListenerInvocationHandler implements InvocationHandler {
// 需要拦截的对象
private Object target;
// 需要拦截的对象键值对
private HashMap<String, Method> methodHashMap = new HashMap<>();
public ListenerInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (target != null) {
// 获取需要拦截的方法名
String methodName = method.getName(); // 假如是onClick
// 重新赋值,将拦截的方法换为show
method = methodHashMap.get(methodName); // 执行拦截的方法,show
if (method != null) {
return method.invoke(target, args);
}
}
return null;
}
/**
* 将需要拦截的方法添加
* @param methodName 需要拦截的方法,如:onClick()
* @param method 执行拦截后的方法,如:show()
*/
public void addMethod(String methodName, Method method) {
methodHashMap.put(methodName, method);
}
}
下面是用到自定义注解的几个注解类
- ContentView.java
// 该注解作用于类,接口或者枚举类型上
@Target(ElementType.TYPE)
// 注解会在class字节码文件中存在,jvm加载时可以通过反射获取到该注解的内容
@Retention(RetentionPolicy.RUNTIME)
public @interface ContentView {
// int类型布局
int value();
}
- EventBase.java
@Target(ElementType.ANNOTATION_TYPE) // 放在注解的上面
@Retention(RetentionPolicy.RUNTIME)
public @interface EventBase {
// 事件的三个成员
// 1、set方法名
String listenerSetter();
// 2、监听的对象
Class<?> listenerType();
// 3、回调方法
String callBackListener();
}
- InjectView.java
@Target(ElementType.FIELD) // 属性上
@Retention(RetentionPolicy.RUNTIME)
public @interface InjectView {
int value();
}
- OnClick.java
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@EventBase(listenerSetter = "setOnClickListener", listenerType = View.OnClickListener.class, callBackListener = "onClick")
public @interface OnClick {
int[] value();
}
- OnItemClick.java
@Target(ElementType.METHOD) // 该注解作用于在方法
@Retention(RetentionPolicy.RUNTIME) // jvm运行时通过反射获取到该注解的内容
@EventBase(listenerSetter = "setOnItemClickListener", listenerType = RView.OnItemClickListener.class, callBackListener = "onItemClick")
public @interface OnItemClick {
int[] value();
}
- OnItemLongClick.java
@Target(ElementType.METHOD) // 该注解作用于在方法
@Retention(RetentionPolicy.RUNTIME) // jvm运行时通过反射获取到该注解的内容
@EventBase(listenerSetter = "setOnItemLongClickListener", listenerType = RView.OnItemLongClickListener.class, callBackListener = "onItemLongClick")
public @interface OnItemLongClick {
int[] value(); // 数组形式,多id多控件共用某点击方法
}
- OnLongClick.java
@Target(ElementType.METHOD) // 该注解作用于在方法
@Retention(RetentionPolicy.RUNTIME) // jvm运行时通过反射获取到该注解的内容
@EventBase(listenerSetter = "setOnLongClickListener", listenerType = View.OnLongClickListener.class, callBackListener = "onLongClick")
public @interface OnLongClick {
int[] value(); // 数组形式,多id多控件共用某点击方法
}
- ViewInject.java
@Target(ElementType.FIELD) // 该注解作用于属性、枚举的常量
@Retention(RetentionPolicy.RUNTIME) // jvm运行时通过反射获取到该注解的内容
public @interface ViewInject {
int value();
}