相信大家都使用过ButterKnife这个专注于Android的view注入的框架,这个框架用起来是非常的省心省力,去除了大量的"findViewById(resId);"操作,令我们写的代码简化了许多。
其实我们也可以自己动手封装一个相似功能的简易框架,来实现view的绑定和点击事件的处理。
首先我们需要运用到注解和反射的相关知识。
1、定义一个自定义注解
认识一下几个用到的注解:
- @Target - 标记这个注解应该是哪种 Java 成员。
- @Retention - 标识这个注解怎么保存,是只在代码中,还是编入class文件中,或者是在运行时可以通过反射访问。
@Target(ElementType.FIELD) //用于限制当前自定义注解的对象
//@Retention(RetentionPolicy.SOURCE)//只会在源码时期出现,当编译成字节码文件的时候就消失
//@Retention(RetentionPolicy.CLASS) //会加载到字节码文件中,但是当虚拟器加载该字节码文件时,注解信息会被清楚
@Retention(RetentionPolicy.RUNTIME) //一直会被保留到被加载到虚拟机中
public @interface ViewInject {
//int id(); 这个地方抽象方法名的话我们可以自定义,但是如果我们自定义的话,就要在使用的时候多一些繁琐的步骤,
// 比如: 我们采用int id(); 我们就必须在activity中使用绑定时@ViewInject(id = R.id.iv_1)
int value();//这里我们采用特殊的抽象方法名【 value]
}
好了,这个时候我们就完成了一个注解类,很简单就写了一个特殊的抽象方法。
2.写一个工具类来对Activity做一个事件绑定
public class ViewUtils {
public static void inject(Activity activity) {
//绑定activity
bindView(activity);
}
}
在这里我们接受activity的参数,对他进行一些列的反射操作。
3.绑定view
private static void bindView(Activity activity) {
/**
* 1、首先获取到Activity的字节码文件
*/
Class<?> clazz = activity.getClass();
Field[] declaredFields = clazz.getDeclaredFields();
/**
* 2、遍历所有得到的字段,找出我们所需要的字段即可,即我们添加了自定义注解的字段
*/
for (Field field : declaredFields) {
//3、获取自定义字段上面的注解
ViewInject viewInject = field.getAnnotation(ViewInject.class);
if (viewInject != null) {
/**
* 4、获得该注解的值
*/
int resId = viewInject.value();//本质上是资源文件的id
/**
* 5、调用activity的findViewById方法, 将控件id传入,进行一个绑定,拿到该资源id的view
*/
View viewById = activity.findViewById(resId);
/**
* 6、将当前的View设置给对应的field
*/
field.setAccessible(true);
try {
field.set(activity, viewById);//参数1:object,参数2:value ,该方法为:给某某对象设置某个值,这里我们给activity设置好view对象
} catch (IllegalAccessException e) {
e.printStackTrace();
}
} else {
//该Activity上的字段无我们的自定义注解
}
}
}
这个地方我们可以简单梳理一下流程:1、拿到activity的字节码文件;2、遍历所有的字段,找到哪些字段是被我们添加了注解的;3、拿到我们的自定义注解;4、获取注解上的值(就是刚才定义的那个特殊的抽象方法value()返回的值,这个值就是资源的Id值)5、使用findviewById方法拿到view;6、将当前的View设置给对应的field。此时,view就绑定好。
4.使用注解在Activity中对View进行绑定
@ViewInject(R.id.tv_1)
private TextView tv_1;
@ViewInject(R.id.tv_2)
private TextView tv2;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ViewUtils.inject(this);
//可以做一下测试看是否可以拿到值检测是否注入成功
// Log.d("tv2_",tv2.getText()+"-----------");
// Log.d("tv1_",tv_1.getText()+"-----------");
}
至此,view的绑定功能就完成了,接下来我们继续实现对点击事件的绑定。
5.绑定点击事件
与view的绑定流程几乎是一致的,首先也是定义一个自定义注解:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface OnClick {
int value();
}
然后接受Activity对其进行一系列反射。
private static void bindMethod(final Activity activity) {
/**
* 1、获取字节码文件
*/
Class<?> clazz = activity.getClass();
/**
* 2、获取所有的方法
*/
Method[] declaredMethods = clazz.getDeclaredMethods();
/**
* 3、遍历所有的方法
*/
for (final Method method : declaredMethods) {
OnClick onClick = method.getAnnotation(OnClick.class);
/**
* 4、找到有我们自定义注解的方法
*/
if (onClick != null) {
int resId = onClick.value();
/**
* 5、拿到View
*/
final View viewById = activity.findViewById(resId);
viewById.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
method.setAccessible(true);
try {
/**
* 6、方法反射
*/
method.invoke(activity, viewById);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
});
} else {
//若没有注解,则不用做任何处理
}
}
}
好了,接下来就是在Activity中做一下测试:
@OnClick(R.id.btn_test)
private void useToast(View view){
Toast.makeText(this, "tv_1"+String.valueOf(tv_1.getText())+"tv_2"+String.valueOf(tv2.getText()), Toast.LENGTH_SHORT).show();
}
6、总结
用的知识点基本上就是注解和反射了,简简单单就可以把大量的“findViewByid”给省略了,这样也许会产生影响效率的问题,但是也可以忽略了,最后你也可以把功能单独抽取一下封装成jar包,就可以运用到自己的项目中了。