自定义注解并进行动态解析

尊重他人的劳动成果,转载请标明出处:http://blog.csdn.net/gengqiquan/article/details/70230597, 本文出自:【gengqiquan的博客】

前两篇博客我们唠了Java支持基本的注解以及Android Support Annotations库提供的静态检查类型的注解
今天我们来唠唠怎么自定义动态注解,并且实现一个控件自动绑定功能(老版本butterknife,新版本改成利用IDE插件预先编译了)以及通过注解设置activity主布局,通过这两个小例子来学习如何自定义自己的动态注解

首先我们定义一个注解。还叫BindView吧

/**
 * Created by gengqiquan on 2017/4/18.
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.CLASS)
@Documented
public @interface BindView {
    @IdRes
    int value();
}

和butterknife的一样,类文件中有效,在默认方法上加上 @IdRes,防止传入非资源ID的int值。

提供一个注入器,这里我们叫MagicHand

既然是动态解析注解,那我们就需要用到反射了,通过class的

target.getDeclaredFields()

取到类下面的所有成员

然后遍历成员集合,看是否有标记了@BindView注解的成员

for (Field f : fields) {
 BindView bind = f.getAnnotation(BindView.class);
 }

若能取到注解则表明这个成员是被@BindView注解了的,通过注解的默认方法

bind.value()

取出它的值也就是资源ID

然后通过activity的

activity.getWindow().getDecorView()

方法取到顶层view,这样我们就可以通过view的

findViewById()

方法来解析ID资源对应的控件了

完整类以及方法

public class MagicHand {
    public static void inject(Activity activity) {
        Class target = activity.getClass();
        Field[] fields = target.getDeclaredFields();

        View decorView = activity.getWindow().getDecorView();
        for (Field f : fields) {
            BindView bind = f.getAnnotation(BindView.class);
            if (bind != null) {
                f.setAccessible(true);
                try {
                    f.set(activity, decorView.findViewById(bind.value()));
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }


            }
        }
    }
}

切记调用field的set方法之前一定要调用f.setAccessible(true);

然后我们写一个MainActivity,在TextView上打上我们自定义的注解

在oncreate里动态注入注解,最后调用textView.setText(“你好”)看是否能正常赋值显示

public class MainActivity extends AppCompatActivity {
    @BindView(R.id.name)
    TextView textView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        MagicHand.inject(this);
        textView.setText("你好");
    }
}

布局文件


<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:text="Hello World!" />

RelativeLayout>

运行看下显示效果
自定义注解并进行动态解析_第1张图片
OK,上面就是通过注解动态绑定布局的实现了

下面我们再来看看怎么通过注解设置activity主布局

定义一个布局注解

/**
 * Created by gengqiquan on 2017/4/18.
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
@Documented
public @interface LayoutID {
    @LayoutRes
    int value();
}

因为我们这个注解只作用于类,所以域我们选择了ElementType.TYPE
同时通过@LayoutRes注解限制了必须传入一个layout的资源ID

我们改变下之前的inject方法。添加一个bindContentView方法

public class MagicHand {
    public static void inject(Activity activity) {
        bindContentView(activity);
        bindViewAndID(activity);
    }

    private static void bindContentView(Activity activity) {
        Class target = activity.getClass();
        LayoutID layoutID = (LayoutID) target.getAnnotation(LayoutID.class);
        if (layoutID != null) {
            activity.setContentView(layoutID.value());
        }
    }

    private static void bindViewAndID(Activity activity) {
        Class target = activity.getClass();
        Field[] fields = target.getDeclaredFields();

        View decorView = activity.getWindow().getDecorView();
        for (Field f : fields) {
            //获取字段中包含fieldMeta的注解
            BindView bind = f.getAnnotation(BindView.class);
            if (bind != null) {
                f.setAccessible(true);
                try {
                    f.set(activity, decorView.findViewById(bind.value()));
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }


            }
        }
    }
}

然后在MainActivity上打上@LayoutID注解,同时去掉 setContentView(R.layout.activity_main);这一句

@LayoutID(R.layout.activity_main)
public class MainActivity extends AppCompatActivity {
    @BindView(R.id.name)
    TextView textView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        MagicHand.inject(this);
        textView.setText("你好");
    }
}

沿用之前的布局
再次运行
自定义注解并进行动态解析_第2张图片
发现界面显示正常。说明我们布局注入成功了

以上就是自定义注解动态解析了。由于我们是在运行时通过反射解析的。所以必然会消耗性能。后面我们再讲如何通过IDE插件进行预编译解析。

完整的示例项目GitHub地址:https://github.com/gengqiquan/MagicHand

有什么建议的可以留言喔

如果我的博客对您有帮助,请留言鼓励下或者点个赞吧!

我建了一个QQ群(群号:121606151),用于大家讨论交流Android技术问题,有兴趣的可以加下,大家一起进步。

你可能感兴趣的:(android)