Xutils是一个很牛的框架,相信很多人都有用过,今天主要介绍的是Xutils的四大功能框架之一的ViewUtils,在这里我们会自己写一些注解,来实现与ViewUtils同样的功能,主要运用到的知识无非就是两种:注解和反射。
先来说一下注解,很多人都有看到过注解,比如最常见的@Override,但是并不知道这些注解的作用是什么,怎么来自己写一个注解,点进去@Override,看到如下的代码片段:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
没错,我们看到了@interface这个字段,这就是注解的修饰词,然后方法体里面是空的,这个先放一放,文章后面会讲到为什么是空的。另外在上面还有两个注解,是什么意思呢?下面我们就来说说:
1.@Target:这个注解有个参数:ElementType.XXX,这个参数的作用就是这个注解的用处、用在什么上
CONSTRUCTOR:用于描述构造器
FIELD:用于描述域,也可以理解成成员变量
LOCAL_VARIABLE:用于描述局部变量
METHOD:用于描述方法
PACKAGE:用于描述包
PARAMETER:用于描述参数
TYPE:用于描述类、接口(包括注解类型) 或enum声明
2.@Retention:同样有一个参数RetentionPolicy.XXX
SOURCE:在源文件中有效(即源文件保留)
CLASS:在class文件中有效(即class保留)
RUNTIME:在运行时有效(即运行时保留)
有些人看到这里就不明白了,这个参数是什么意思?举个例子,@Override用的是SOURCE,也就是说,@Override的作用仅仅是告诉你,这是方法重写,而不需要出现在class里,因为即使class文件里没有这个注解也可以正常运行程序,而稍后我们要写的用注解初始化,咋需要用RUNTIME,因为需要把注解编译到.class文件里,在程序运行时需要用注解来初始化控件。
接下来看代码,最简单的布局文件:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
android:orientation="vertical"
tools:context=".ui.MainActivity">
<TextView
android:id="@+id/tv_main"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="我是一个TextView"
android:textColor="#ff0000" />
<Button
android:id="@+id/btn_login"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="登录" />
<Button
android:id="@+id/btn_logoff"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="注销" />
</LinearLayout>
不多说,继续看下面,写一个注解实现setContentView的功能 名为 InjectContentView:
@Target(ElementType.TYPE)//因为要在类上使用看上面代码,方法体里面有一个int型的参数,因为布局文件R.layout.XXX就是int型,之前的@Override为什么方法体里面是空的呢?没错,因为不需要参数!接下来看MainActivity代码:
@Retention(RetentionPolicy.RUNTIME)//之前已经说过,要编译到.class文件里
public @interface InjectContentView {
int value(); //声明一个int参数
//int value() default 0;//也可以设一个默认值
}
@InjectContentView(R.layout.activity_main) //这里就是之前写的注解,参数是布局文件
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
InjectUtils.InjectAll(this);//这行代码什么意思呢?用过xUtils的人都知道,也需要有这么一行代码
}
}
InjectUtils.InjectAll(this)这行代码的作用就是将activity传进去,然后在里面实现对注解的解析与运用,代码如下:
public class InjectUtils {
public static void InjectAll(Activity activity) {
InjectUtils.InjectContentView(activity);
InjectUtils.InjectViews(activity);
InjectUtils.OnClick(activity);
}
private static void OnClick(Activity activity) { //点击事件的注解处理
}
public static void InjectContentView(Activity activity) { //setContentView的注解处理
}
public static void InjectViews(Activity activity) { //findViewById的注解处理
}
}
很容易理解吧,注解写的很清楚。首先看InjectContentView的代码:
public static void InjectContentView(Activity activity) {好了,代码就这么多,接下来运行一下!
//获取MainActivity类
Class<? extends Activity> clazz = activity.getClass();
//获取MainActivity类上的注解,传入的是InjectContentView.class,注意返回的是InjectContentView
InjectContentView contentView = clazz.getAnnotation(InjectContentView.class);
//获取注解里的参数,也就是那个int的布局文件(R.layout.XXX)
int value = contentView.value();
try {
//通过反射获取Activity里的setContentView方法,参数是int型的布局文件id
Method method = clazz.getMethod("setContentView", int.class);
//调用反射获取到的方法,value为上面通过注解获取到的layoutID
method.invoke(activity, value);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
呃....平板截图,貌似有点大!先不管,小憩一下,接着看后面的代码,用注解来实现findViewBiID的功能,首先创建一个注解InjectViews:
@Target(ElementType.FIELD) //需要的对应不再是类,而是成员变量
@Retention(RetentionPolicy.RUNTIME)
public @interface InjectViews {
int value() default 0;
}
代码很简单,接着看 InjectUtils 的 InjectViews 方法:
public static void InjectViews(Activity activity) {
//获取MainActivity类
Class<? extends Activity> clazz = activity.getClass();
//获取全部的类中生命的的成员变量
Field[] declaredFields = clazz.getDeclaredFields();
for (Field declaredField : declaredFields) {
//获取对应的注解,传入的是InjectViews.class,注意返回的是InjectViews
InjectViews annotation = declaredField.getAnnotation(InjectViews.class);
//这里要判断下注解是否为null,因为比如声明一个 public int i 时,是没有注解的
if (annotation != null) {
//当注解不为null,获取其参数
int value = annotation.value();
try {
//通过反射获取findViewById方法
Method method = clazz.getMethod("findViewById", int.class);
//调用方法
Object view = method.invoke(activity, value);
//这里要注意,类中的成员变量为private,故必须进行此操作,否则无法给控件赋值(即初始化的捆绑)
declaredField.setAccessible(true);
//将初始化后的控件赋值到MainActivity里的对应控件上
declaredField.set(activity, view);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
代码相对之前较多,但是很容易理解,注解很详细,看一下大概就明白了,接下来在MainActivity 的代码:
@InjectContentView(R.layout.activity_main)接下来运行一下:
public class MainActivity extends Activity {
@InjectViews(R.id.tv_main)
private TextView tv_main;
@InjectViews(R.id.btn_login)
private Button btn_login;
@InjectViews(R.id.btn_logoff)
private Button btn_logoff;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//这行代码,别忘了
InjectUtils.InjectAll(this);
tv_main.setText("我是一个改变了文字的TextView");
btn_login.setText("登录再登录");
btn_logoff.setText("注销在注销");
}
}
好了,没有报空指针,并且在UI上也做出了相应的改变,很成功!
最后就是点击事件了,这才是最精彩的!!!接下来写的比较多,所以打算在下一篇博客里接着写!! 朋友们,喜欢的留下言吧~~~~