Android 自定义注解框架

时间:2021-03-18 20:40:48

前言

在我们的项目中,我们几乎天天和一些固定的代码打交道,比如在Activity中你要写findViewById(int)方法来找到控件,然而这样子的代码对于一个稍微有点资格的程序员来说,都是毫无营养的,你根本学不到任何的东西,但是你却必须写。这也就是注解框架的出现,极大的简化了程序员的工作,并且让代码简洁。也许你早就使用过了注解的框架,那么你会自己会写么?好了,今天就让大家来完成一个注解的框架

阅读的你需要掌握的知识

1.Java反射的知识

2.Java注解的知识


普通的写法

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"
tools:context="${relativePackage}.${activityClass}" >

<Button
android:id="@+id/bt"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="点我" />

</RelativeLayout>

Activity中的代码
public class MainActivity extends Activity implements OnClickListener {

/**
* 按钮
*/
private Button bt = null;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 找到按钮
bt = (Button) findViewById(R.id.bt);
// 设置点击事件
bt.setOnClickListener(this);
}

@Override
public void onClick(View v) {
Toast.makeText(this, "点我了", Toast.LENGTH_LONG).show();
}

}

也是很简单的,然后是效果图 Android 自定义注解框架

上面就是我们平常的写法,那么我们来看看用了注解框架的写法,先体验,再动手写注解框架

用了注解框架的写法

public class MainActivity extends Activity {

/**
* 按钮
*/
@Injection(value = R.id.bt,click = "clickView")
private Button bt = null;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//让注解起作用
ViewInjectionUtil.injectView(this);
}

public void clickView() {
Toast.makeText(this, "点我了,用了注解框架", Toast.LENGTH_LONG).show();
}

}


就这么少代码?是的,你没有看错,就这么点代码,和刚刚的普通的写法的效果是一模一样的
可以注意到Activity实现的点击事件的接口不见了,寻找控件和设置监听的代码都不见了,当一个项目足够大了,是不是很省下很多的代码呢? 只是多了一句让注解起作用的代码,看下用了注解框架的效果 Android 自定义注解框架

可以看到,程序是没有问题的,那么让我们来实现它吧!

思路

1.首先明确一点,框架一定是帮你做了findViewById(int)和setOnClickListener(View.OnCLickListener)的这些操作,否则代码是不可能正常运行的 2.findViewById(int)方法需要的是控件的id,从框架的使用上看,控件的id就是利用注解告诉了框架
@Injection(value = R.id.bt,click = "clickView")
此处的R.id.bt就是value的值,所以可以知道就是这里告诉框架的,而我们的点击事件起作用了,这里告诉了框架需要调用的方法的名称所以我们的一个和普通的方法可以被调用

思路的总结

所以注解就是一个信息,它会被框架读取并且框架会帮你做一些繁琐的工作,从而让你的代码编辑简洁,所以用了注解之后,比如让框架来读取,所以这也就是为什么会有一句很陌生的语句:
//让注解起作用
ViewInjectionUtil.injectView(this);
这句代码就是框架读取了注解中的信息并且帮你找到了控件,并且注册相应的事件

开工写代码

首先定义创建一个注解

就是上述使用的那个注解:@Injection,你可以理解为创建一个类差不多的概念,但是两者是不同的,自己要清楚哦
/**
* 运行时期有效,和针对的是字段
*
* @author xiaojinzi
*
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Injection {

/**
* 字段对应布局文件的id
*
* @return
*/
int value();

/**
* 点击事件
* @return
*/
String click() default "";

/**
* 长按的点击事件
* @return
*/
String longClick() default "";

}
这里对代码做一下解释@Target(ElementType.FIELD)表示我们声明的注解是作用到字段上的,你肯定见过作用在方法上的注解,比如:@Override  这个不陌生吧? @Retention(RetentionPolicy.RUNTIME)这个表示我们声明的注解是运行的时候有效的,还有其他的情况,这里不做深入
好了,我们的注解就写好了,注解中有三个属性可以用,分别是: 1.value        int类型     表示控件的id 2.click         String类型    点击事件的方法名称 3.longClick  String类型    长按事件的方法名称 好了,我们的注解就写完了,你可以在任何一个字段上面使用这个注解了,都是不会报错的,但是还没有实际的作用

编写核心代码

首先创建一个类,用来让注解起作用,也就是之前说过的,去读取注解中的信息,然后完成繁琐的操作 我就起一个名字为:ViewInjectionUtil,你可以自己起一个其他的名称 框架帮你完成findViewById(int)的操作那么是不是你需要把当前的Activity对象传给框架呢?
/**
* 对activity中的字段进行注入
*
* @param act
*/
public static void injectView(Activity act) {
// 获取这个activity中的所有字段
Field[] fields = act.getClass().getDeclaredFields();

for (int i = 0; i < fields.length; i++) {
// 循环拿到每一个字段
Field field = fields[i];
if (field.isAnnotationPresent(Injection.class)) { // 如果这个字段有注入的注解
// 获取注解对象
Injection injection = field.getAnnotation(Injection.class);
int value = injection.value();
field.setAccessible(true); // 即使私有的也可以设置数据
Object view = null;
try {
view = act.findViewById(value);
// 设置字段的属性
field.set(act, view);
} catch (Exception e) {
e.printStackTrace();
L.s(TAG, "注入属性失败:" + field.getClass().getName() + ":" + field.getName());
}

try {
if (view instanceof View) {
View v = (View) view;
// 获取点击事件的触发的方法名称
String methodName = injection.click();
EventListener eventListener = null;
// 如果不是空字符串
if (!"".equals(methodName)) {
eventListener = new EventListener(act);
// 设置点击事件
v.setOnClickListener(eventListener);
eventListener.setClickMethodName(methodName);
}
methodName = injection.longClick();
if (!"".equals(methodName)) {
if (eventListener == null) {
eventListener = new EventListener(act);
}
// 设置点击事件
v.setOnLongClickListener(eventListener);
eventListener.setLongClickMethodName(methodName);
}
}
} catch (Exception e) {
e.printStackTrace();
}

}
}
}

/**
* Created by cxj on 2016/1/21.
*
* @author 小金子
*/
public class EventListener implements View.OnClickListener, View.OnLongClickListener {

/**
* 类的标识
*/
private String tag = "EventListener";

/**
* 设置是否有日志的输出
*/
private boolean isLog = false;

/**
* 反射中要被调用方法的对象,通过构造方法进来
*/
private Object receiver = null;

/**
* 点击事件的方法名字
*/
private String clickMethodName = "";

/**
* 长按事件的方法的名字
*/
private String longClickMethodName = "";

/**
* 设置点击事件的方法名字
*
* @param clickMethodName
*/
public void setClickMethodName(String clickMethodName) {
this.clickMethodName = clickMethodName;
}

/**
* 设置长按的点击事件的方法的名字
*
* @param longClickMethodName
*/
public void setLongClickMethodName(String longClickMethodName) {
this.longClickMethodName = longClickMethodName;
}

/**
* 构造函数
*
* @param receiver
* 控件所在的activity或者Fragment
*/
public EventListener(Object receiver) {
this.receiver = receiver;
}

@Override
public void onClick(View v) {
Method method = null;
try {
method = receiver.getClass().getMethod(clickMethodName);
if (method != null) {
// 调用该方法
method.invoke(receiver);
}
} catch (Exception e) {
if (isLog)
L.s(tag, "寻找无参数列表的方法:" + clickMethodName + "失败");
}
try {
if (method == null) {
method = receiver.getClass().getMethod(clickMethodName, View.class);
if (method != null) {
method.invoke(receiver, v);
}
}
} catch (Exception e) {
if (isLog)
L.s(tag, "寻找带有View类型参数的方法:" + clickMethodName + "失败");
}
}

@Override
public boolean onLongClick(View v) {
Method method = null;
try {
method = receiver.getClass().getMethod(longClickMethodName);
if (method != null) {
// 调用该方法
method.invoke(receiver);
}
} catch (Exception e) {
if (isLog)
L.s(tag, "寻找无参数列表的方法:" + longClickMethodName + "失败");
}
try {
if (method == null) {
method = receiver.getClass().getMethod(longClickMethodName, View.class);
if (method != null) {
method.invoke(receiver, v);
}
}
} catch (Exception e) {
if (isLog)
L.s(tag, "寻找带有View类型参数的方法:" + longClickMethodName + "失败");
}
return true;
}

}

类EventListener:这个类主要是处理View的事件的,比如点击事件,长按事件,类实现了这些接口
创建该类的时候需要传入Activity对象或者Fragment对象,比如点击事件触发的时候该类中实现的方法onClick(View)方法被调用,但是并不是用户指定的方法名的那个方法,所以这里需要有注解中的click字符串信息,也就是Activity需要调用的方法的方法名,有了方法名和Activity,就可以利用反射从activity中调用指定方法名的方法,从而达到我们开始的时候演示的时候那样子,点击按钮的时候,会调用activity中的bt字段上的注解中的click写的那个方法名对应的方法


这里对方法injectView(Activity act)中的代码做一个详细的解释: 1.利用反射获取到activity中所有的字段 2.循环所有的字段,筛选出有Injection注解的字段 3.读取字段中的注解中的信息:         a)拿到控件的id信息,调用act中的findViewById(int)找到控件,利用反射赋值给字段,这个过程等同于普通代码:            bt = (Button)findViewById(R.id.bt);         b)拿到点击事件和长按事件触发的时候调用的方法的方法名,然后创建EventListener对象,让这个对象保存点击事件的方法名称和长按事件的方法名称和activity的引用,并且给View对象注册相应的事件,EventListener中已经实现了对应的接口,所以注册的时候,传入的对象就是EventListener的对象,后续事件生效的时候就是上述类EventListener的功能了
到这里其实我们的代码就写完了,其实你懂反射的知识的话,这些代码都是不难的,看起来会很快,当然了,如果你不懂反射的知识,这里建议你去学习一下,作为一个JavaCoder,我觉得反射这么棒的内容,你不应该错过!因为在Java的世界中到处都是它的影子!

总结

博主只介绍了点击事件和长按事件,其实你可以照猫画虎的加入其他更多的事件,让你的框架支持更多的操作,也更强大 另外,本篇博客其实给大家一个思路,我相信你们能写出更好的小框架的! 只为了让编码变的更加轻松!

小框架下载

注解框架下载地址:https://github.com/xiaojinzi123/xiaojinzi-openSource-viewAnnotation