Android UI RecycleView单条目点击,长按事件

RecycleView的灵活性是无须质疑的,接下来我们自己实现标题所示功能

直接上代码+ 注释,相信大家会比较容易吸收

  1. 先贴出实际代码中使用方法
// 使用注解,加载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;
    }

}
  1. BaseActivity.java
public class BaseActivity extends AppCompatActivity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // 帮助子类进行,布局、控件、事件的注入
        InjectManager.inject(this);
    }
}
  1. 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 + '\'' +
                '}';
    }
}
  1. 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实现标题所示功能)

按钮实现长按短按Android_按钮实现长按短按Android

  1. 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);
    }
}
  1. 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;
            }
        });
    }
}
  1. 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;
    }

}
  1. 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();
            }
        }
    }

}
  1. 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);
    }
}

下面是用到自定义注解的几个注解类

  1. ContentView.java
// 该注解作用于类,接口或者枚举类型上
@Target(ElementType.TYPE)
// 注解会在class字节码文件中存在,jvm加载时可以通过反射获取到该注解的内容
@Retention(RetentionPolicy.RUNTIME)
public @interface ContentView {

    // int类型布局
    int value();

}
  1. EventBase.java
@Target(ElementType.ANNOTATION_TYPE) // 放在注解的上面
@Retention(RetentionPolicy.RUNTIME)
public @interface EventBase {

    // 事件的三个成员
    // 1、set方法名
    String listenerSetter();

    // 2、监听的对象
    Class<?> listenerType();

    // 3、回调方法
    String callBackListener();
}
  1. InjectView.java
@Target(ElementType.FIELD) // 属性上
@Retention(RetentionPolicy.RUNTIME)
public @interface InjectView {

    int value();
}
  1. OnClick.java
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@EventBase(listenerSetter = "setOnClickListener", listenerType = View.OnClickListener.class, callBackListener = "onClick")
public @interface OnClick {

    int[] value();
}
  1. OnItemClick.java
@Target(ElementType.METHOD) // 该注解作用于在方法
@Retention(RetentionPolicy.RUNTIME) // jvm运行时通过反射获取到该注解的内容
@EventBase(listenerSetter = "setOnItemClickListener", listenerType = RView.OnItemClickListener.class, callBackListener = "onItemClick")
public @interface OnItemClick {

    int[] value();
}
  1. OnItemLongClick.java
@Target(ElementType.METHOD) // 该注解作用于在方法
@Retention(RetentionPolicy.RUNTIME) // jvm运行时通过反射获取到该注解的内容
@EventBase(listenerSetter = "setOnItemLongClickListener", listenerType = RView.OnItemLongClickListener.class, callBackListener = "onItemLongClick")
public @interface OnItemLongClick {

    int[] value(); // 数组形式,多id多控件共用某点击方法
}
  1. OnLongClick.java
@Target(ElementType.METHOD) // 该注解作用于在方法
@Retention(RetentionPolicy.RUNTIME) // jvm运行时通过反射获取到该注解的内容
@EventBase(listenerSetter = "setOnLongClickListener", listenerType = View.OnLongClickListener.class, callBackListener = "onLongClick")
public @interface OnLongClick {

    int[] value(); // 数组形式,多id多控件共用某点击方法
}
  1. ViewInject.java
@Target(ElementType.FIELD) // 该注解作用于属性、枚举的常量
@Retention(RetentionPolicy.RUNTIME) // jvm运行时通过反射获取到该注解的内容
public @interface ViewInject {

    int value();
}