Sometimes there is no next time, no time-outs, no second chances; sometimes it's now or never.java
对于注解来讲,其实就是用来打标记用的,在别的地方能够根据特殊的标记获得标记的东西(属性、类、方法等),对于这你们应该很是熟悉了,不少第三方框架使用也巨多,好比Dagger、ButterKnife等等,这些第三方注解框架,省略了findviewById(),不由要问,难道真的不须要这行findviewById()代码就能在xml中获得控件实例吗?答案很明确,确定不是如此。本篇文章就会给出答案,主要介绍自定义注解相关的知识,模仿实现findviewById功能。sql
在开始实现 findviewById 功能以前,仍是复习一下怎么自定义注解,以及注解有啥做用吧。bash
有这样一种场景:框架
主要工做是,要写一些注解类来标识SxtStudent类,而后经过注解把类中的信息跟表对应起来。好比上面SxtStudent类中,id为int类型,对应SQL中int(10);name为String类型,对应SQL中的varchar(10).。方式就是经过注解对类中的信息进行标注,写一个中间程序获取这个类中的注解信息,把注解解析出来,而后刚好能够做为SQL语句中拼接。ide
首先定义JavaBean类:工具
public class YdlStudent {
private int id;
private String studentName;
private int age;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getStudentName() {
return studentName;
}
public void setStudentName(String studentName) {
this.studentName = studentName;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
复制代码
而后定义类与表对应转换的注解:ui
/**
* 针对表的注解
* 表明类与表之间对应转换
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(value = {ElementType.TYPE})
public @interface YdlTable {
String value();
}
复制代码
由于表只须要一个表名便可,因此注解中只须要一个信息,这里使用String value();标识。this
这个时候就能够对类进行注解了,这里对类的注解能够经过反射技术获取注解信息,而后做为建立的表名称。spa
而后还能够对属性添加注解,定义与属性关联的注解类:3d
/**
* 针对属性的注解
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(value = {ElementType.FIELD})
public @interface YdlFiled {
String columName();//列名
String type();//数据类型
int length();//年龄
}
复制代码
这里注解里面的内容是根据Student类和SQL来写的,SQL中的列名称定义为String columName();SQL中的数据类型为String type();SQL中的数据类型占的长度定义为int length();
而后在YdlStudent类中对属性添加注解:
这里
@YdlFiled(columName = "id",type = "int",length = 10)
private int id;
理解为:YdlStudent中属性id对应表中的字段id这一列,sql中的类型为int,int长度为10。
@YdlFiled(columName = "sname",type = "vachar",length = 10)
private String studentName;
理解为:YdlStudent中属性studentName对应表中的字段sname这一列,sql中的类型为vachar,vachar长度为10。
这样标识之后,就能够写一个解析程序来解析YdlStudent类,获取注解相关信息来建立表了:
public class Main {
public static void main(String[] args) {
Class clazz = YdlStudent.class;
Annotation[] annotations = clazz.getAnnotations();
for (Annotation annotation : annotations) {
// 获取全部的【类】注解
System.out.println(annotation);
}
// 或得类的指定注解
YdlTable table = (YdlTable) clazz.getAnnotation(YdlTable.class);
// table.value()直接获取注解内容名称---@YdlTable(value = "tb_student")//能够经过反射读取这里,而后建立对应的表
System.out.println(table.value());
try {
// 根据名称获取属性对象
Field field = clazz.getDeclaredField("studentName");
// 获取该属性上面的注解
YdlFiled ydlFiled = field.getAnnotation(YdlFiled.class);
// 获取注解属性的内容。
System.out.println(ydlFiled.columName() + "---" + ydlFiled.type()
+ "---" + ydlFiled.length());
// 同理能够获取 YdlStudent 类中 id、age所对应的注解信息
} catch (Exception e) {
e.printStackTrace();
}
// 根据上方获取到的表名、属性(字段)信息等拼接Sql语句。
}
}
复制代码
这里分段来说:
Class clazz = YdlStudent.class;
Annotation[] annotations = clazz.getAnnotations();
for (Annotation annotation : annotations) {
// 获取全部的【类】注解
System.out.println(annotation);
}
复制代码
这里表示经过反射获取全部的类注解,因类注解只有一个:
因此打印结果为:@itydl03.YdlTable(value=tb_student)
// 或得类的指定注解
YdlTable table = (YdlTable) clazz.getAnnotation(YdlTable.class);
// table.value()直接获取注解内容名称---@YdlTable(value = "tb_student")//能够经过反射读取这里,而后建立对应的表
System.out.println(table.value());
复制代码
表示经过反射获取类的指定注解,clazz.getAnnotation(YdlTable.class);表示指定获取YdlTable类的注解。而后table.value()直接获取注解内容名称---@YdlTable(value = "tb_student")。获取的内容就是tb_student,打印结果:
tb_student
try {
// 根据名称获取属性对象
Field field = clazz.getDeclaredField("studentName");
// 获取该属性上面的注解
YdlFiled ydlFiled = field.getAnnotation(YdlFiled.class);
// 获取注解属性的内容。
System.out.println(ydlFiled.columName() + "---" + ydlFiled.type()
+ "---" + ydlFiled.length());
// 同理能够获取 YdlStudent 类中 id、age所对应的注解信息
} catch (Exception e) {
e.printStackTrace();
}
复制代码
clazz.getDeclaredField("studentName");表示根据名称获取属性对象,返回值获得一个Field field 表示属性 。 在YdlStudent类中 private String studentName; 属性对应的注解为
@YdlFiled(columName = "sname",type = "vachar",length = 10)
field.getAnnotation(YdlFiled.class);就表示获上边属性YdlFiled注解类中的@YdlFiled(columName = "sname",type = "vachar",length = 10)的全部注解信息;返回值为YdlFiled类型。他能够理解为columName = "sname",type = "vachar",length = 10的注解信息的javaBean。最后打印:
sname---vachar---10
最后再把全部log给出:
有了上边的基础回顾,下面就不写那么细致了。
首先定义一个注解类,ViewById:
@Target(value={ElementType.FIELD})//FIELD表示的是成员变量级别可使用该注解
@Retention(RetentionPolicy.RUNTIME)//RUNTIME级别能够被反射读取注解,都是运行时
public @interface ViewById {
int value();
}
复制代码
而后在MainActivity中使用该注解:
public class MainActivity extends AppCompatActivity {
@ViewById(R.id.tv)
private TextView mTextView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTextView.setText("自定义注解");
}
}
复制代码
看着好像挺熟悉的,跟使用第三方框架差很少,可是运行程序确定报错。这是由于MainActivity类跟自定义注解压根就没有什么链接关系。那么接下来就创建链接关系。而这个中介就是反射。
自定义ViewUtils类:
public class ViewUtils {
public static void inject(Activity activity) {
// 1.获取全部的属性
Field[] fields = activity.getClass().getDeclaredFields();
// 2.过滤关于 ViewById 属性
for (Field field : fields) {
//获取注解封装---ViewById类型的注解。至关于对ViewById所标识属性的封装类
ViewById viewById = field.getAnnotation(ViewById.class);
if(viewById != null){
// 3.findViewById
View view = activity.findViewById(viewById.value());
field.setAccessible(true);
try {
// 4.反射注入
// activity 属性所在类,view 表明的是属性的值
field.set(activity,view);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
}
复制代码
代码注释很是详细了,主要功能就是反射解析注解,而后在这里进行了findviewById操做获取View,最后再注入到属性对象里面field.set(activity,view);
此时须要在MainActivity中加入以下代码:
ViewUtils.inject(this);
复制代码
表示注入当前的MainActivity,只有注入了才能使用自定义的注解“框架”。
为了更直观,把MainActivity中代码稍微修改以下:
public class MainActivity extends AppCompatActivity {
//注解属性,解析类就是解析这里带注解的属性
@ViewById(R.id.tv)
private TextView mTextView;
@ViewById(R.id.tv2)
private TextView mTextView2;
//非注解属性
private int age;
private String name;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ViewUtils.inject(this);
mTextView.setText("自定义注解");
mTextView2.setText("我是SuperMan");
}
}
复制代码
其中private int age;和private String name;两个属性虽然在注解解析工具类会获取到,可是咱们已经对其作了过滤处理。此时运行程序:
到此为止,注解相关的知识就讲解完了。