您的位置:首页 > 其它

利用自定义注解实现安卓中视图的绑定

2016-01-08 14:50 369 查看
先简单说一下如何定义一个注解

定义一个注解就和定义一个接口很像,一样需要提供抽象方法。与接口不同之处有3个:

1. 需要提供元注解

2. 关键字使用@interface

3. 抽象方法可以有一个默认的返回值

看一个例子:

@Rentention(RententionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface InjectView{
intvalue() default 0 ;
}


@Rentention(RententionPolicy RUNTIME)与@Target(ElementTypeMETHOD)就是元注解,也就是用来描述注解的注解。Rentention用来描述该注解信息会被保留到何时,RUNTIME表明该该注解在程序运行期都会被保留着,在运行期被保留就意味着我们可以使用运行期的反射机制来读取注解内容。Target用来描述该注解应用的位置,FIELD意味着该注解是应用于属性上的,Target可供选择的值包括:

PACKAGE                   包
TYPE                      类
CONSTRUCTOR               构造器
FIELD                     属性,包括enum
METHOD                    方法
LOCAL_VARIABLE            局部变量
PARAMETER                 参数


定义注解时用@interface关键字,使用注解时@注解的名称即可,例如在某个属性上要使用刚才定义的InjectView,就写@InjectView

注解中定义了一个方法value,它定义了使用该注解时可以使用的注解元素。并通过这个方法将同名的注解元素的值取出。比如这个value方法就是我为Injectview注解定义了一个元素value,在使用注解时为value元素赋值@InjectView(value = R.id.textView1),当解析注解的时候就可以通过value()方法就可以取到R.id.textView1这个int数值。value后面写了default 0,意味着使用InjectView注解但是没有提供value元素值,那么调用value方法的时候返回值就是0。原则上,注解中定义的方法(注解元素),要么提供默认值,要么就需要在使用注解的时候以“元素=
值”的方式传入,而value方法(注解元素)是一个特殊的方法名(注解元素名),如果在使用注解时仅仅为value赋值,那么value可以省略,也就是说@InjectView(value = R.id.textView1)与@InjectView(R.id.textView1)是等价的。另外,注解中方法的返回值是有限定的,它只能返回:

基本类型(而且在注解定义的内部,不允许使用包装类)
String
Class
枚举
注解

注解中的方法是可以返回另一个注解的,这意味着注解允许嵌套

例如:

public @interface InjectView{
int value()default R.id.textView1;
}
public @interface InjectLayout{
intvalue() default R.layout.activity_main;
InjectViewview() default @InjectView
}


在InjectLayout注解中的view方法就是返回一个InJectView注解。

注解间虽然可以通过方法返回值的方式进行嵌套,但是注解间是不存在继承或者实现关系的。

当我们定义好了注解,并且让注解保留到程序运行期,这样我们就可以通过反射去获取注解,并对注解的元素值进行利用。

注解的解析需要我们来提供一个解析类,根据注解的Target进行相应的反射操作。例如:

public static void bind(Activity activity){
try {
Class<?> clazz = activity.getClass();
Field[] fileds = clazz.getDeclaredFields();
for (Field field : fileds) {
field.setAccessible(true);
InjtectView inject = field.getAnnotation(InjtectView.class);
if(inject!=null){
int id = inject.value();
field.set(activity, activity.findViewById(id));
}
}
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}


MyAnnotation的bind方法接收一个传入的Activity对象,通过反射获得该Activity中的所有属性,接下来看哪个属性上面使用了InjectView注解,如果使用了InjectView注解,就把注解中value元素的值用value方法取出来,本例中,这个值就是一个资源id值。获得资源id值后,就去获取该资源id对应的视图组件,并将这个视图组件“赋值”给属性。需要注意的是,因为是使用findViewById的方式获取视图,所以,bind方法的调用要写在setContentView方法的后面才可以,具体代码如下:

布局文件activity_main.xml:

<RelativeLayoutxmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<TextView
android:id="@+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/hello_world" />
</RelativeLayout>


MainActivity:

public class MainActivity extends Activity{

@InjtectView
TextViewtextView;

@Override
protectedvoid onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
MyAnnotation.bind(this);
textView.setText("你好,注解!");
}

}


InjectView的value的默认值就是R.id.textView1,所以这里不需要为InjectView的注解元素赋值。

更进一步,我们可以为MainActivity添加上@InjectLayout注解,来通过反射调用MainActivity的setContentView方法。

@InjectLayout(R.layout.activity_main)
public class MainActivity extends Activity{
TextViewtextView;
@Override
protectedvoid onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
MyAnnotation.bind(this);
textView.setText("你好,注解!");
}
}


此时MainActivity添加了InjectLayout注解,value注解元素的值为R.layout.activtiy_main。撤掉了textView的InjectView注解和onCreate里面的setContentView方法。所以,如果不希望textView.setText("你好,注解!")报空指针错误,我们就需要改写一下MyAnnotation的bind方法,让它通过解析InjectLayout来调用setContentView方法同时为textView赋值。

public staticvoid bind(Activity act){
InjectLayoutlayout = act.getClass().getAnnotation(InjectLayout.class);
try{
if(layout!=null){
intresId = layout.value();
Methodm = act.getClass().getMethod("setContentView", int.class);
m.invoke(act,resId);
intid = layout.view().value();
Field[]ff = act.getClass().getDeclaredFields();
for(Field field : ff) {
field.setAccessible(true);
if(field.getName().equals("textView")){
field.set(act,act.findViewById(id));
}
}
}
parse(act);
}catch (Exception e) {
e.printStackTrace();
}
}


通过反射,如果发现一个Activity对象上使用了InjectLayout注解,就通过发射调用这个类的setContentView方法,并且方法的参数值为InjectLayout注解value注解元素的值。接下来再去找一找这个Activity对象中是否有名为textView的属性,如果有,就是用InjectView注解中value注解元素的默认值来作为它的资源id。这些都做完了之后,再去执行一个parse方法,parse方法就是原先写在bind方法里面遍历Activity对象中是否有使用了InjectView注解的方法:

private static void parse(Activityactivity) throws Exception{
Class<?>clazz = activity.getClass();
Field[]fileds = clazz.getDeclaredFields();
for(Field field : fileds) {
field.setAccessible(true);
InjtectViewinject = field.getAnnotation(InjtectView.class);
if(inject!=null){
field.set(activity,activity.findViewById(inject.value()));
}
}
}


再次基础上可以再进一步,就是在项目中如果有BaseActivity这样的基类,可以将bind方法的调用放到基类的onCreate方法中进行调用。

BaseActivity:

public class BaseActivity extends Activity{
@Override
protectedvoid onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
MyAnnotation.bind(this);
}
}


这样MainActivity只需要继承BaseActivity就可以

@InjectLayout(R.layout.activity_main)
public class MainActivity extendsBaseActivity {
TextViewtextView;
@Override
protectedvoid onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
textView.setText("你好,注解!");
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: