Java基础回归之注解Annotation【低仿ButterKnife实战篇】
前言
书接上回,上回说到库里对战湖人三分10投0中,真真气煞我库也,这下把气全撒在鹈鹕身上,一口气轰下破纪录的13记三分。 上回说到Java基础回归之注解Annotation【基础篇】,这回我们来真刀真枪实战。相信很多做安卓的同学都用过至少听过ButterKnife,没错,就是大神JakeWharton的黄油刀。本篇博文将结合注解和反射,实现类似Jake Wharton大神的ButterKnife注入框架,需要说明的是ButterKnife实现方式和我们的实现方式是有区别的,它里面是用到了APT技术,他是基于编译期的注入,所以效率比我们用反射高,本文是基于运行期的。但是这并不妨碍我们造轮子。
ps:对反射不熟悉可以看博主另外一篇博文:Java基础回归之反射Reflection,本文不讲解ButterKnife的用法。
取名
既然是低仿大名鼎鼎ButterKnife(黄油刀),那我们项目的名字也要低仿,就叫Shaver(剃须刀)吧..
Shaver的功能点
- @Bind(R.id.btn1):用于注入view,代替繁琐的findViewById操作
- @ContentView(R.layout.activity_main):用于代替注入contentView
- @StringRes(R.string.string_shaver):用于注入String资源文件
- @OnClick({R.id.btn1,R.id.btn2}):用于绑定view的监听事件
目标效果
![](http://upload-images.jianshu.io/upload_images/2954781-fe7e1a2081045f5d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
开始造轮子
我先帮大家捋一遍思路,其实说白了就是编写上述4个注解类,然后编写一个处理这四种注解的核心类。例如要处理绑定view的@Bind注解,我们需要将activity传入到核心类中,核心类反射获取到标注有@Bind注解的成员变量field,然后获取该注解的value,即view的id,最后将id利用activity.findViewById(value)获取到view,然后反射将获取到的view赋值给改成员变量filed,这样我们就成功将view注入进去了,其他三个注解同理,下面show you the code,代码注释很详细,请仔细看。
首先理所应当,我们编写4个注解类:
@Bind注解类
package com.youzhi.shaver.core; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * 代替findViewById的注解 */ @Target(ElementType.FIELD)//标注目标为成员变量 @Retention(RetentionPolicy.RUNTIME)//生命周期为运行时 public @interface Bind { int value();//用于保存控件id }
@ContentView注解类
package com.youzhi.shaver.core;import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * 代替setContentView的注解 */ @Target(ElementType.TYPE)//标注目标为类前 @Retention(RetentionPolicy.RUNTIME)//生命周期为运行时 public @interface ContentView { int value();//用于保存layoutId }
- @StringRes注解类
package com.youzhi.shaver.core;import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * 代替getResource().getString(R.string.xx) */ @Retention(RetentionPolicy.RUNTIME)//生命周期为运行时 @Target(ElementType.FIELD)//标注目标为成员变量 public @interface StringRes { int value();//用于保存string的id }
- OnClick注解类
package com.youzhi.shaver.core;import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * 代替setOnClickListener的注解 */ @Retention(RetentionPolicy.RUNTIME)//生命周期为运行时 @Target(ElementType.METHOD)//标注目标为方法上 public @interface OnClick { int[] value();//用于保存控件id集 }
接下来是最核心的处理类,所有逻辑都在这个处理类上面。(ps:可以优化,例如用Map将view缓存起来...这里留给大家)
package com.youzhi.shaver.core; import android.app.Activity; import android.view.View; import java.lang.reflect.Field; import java.lang.reflect.Method; /** * 注入接口 */ public class Shaver {public static void bind(Activity activity) {try {bindContentView(activity);bindStringRes(activity);bindViews(activity);bindClicks(activity);} catch (Exception e) {e.printStackTrace();}}/*** 注入contentView* @param activity*/private static void bindContentView(Activity activity) {Class<? extends Activity> aClass = activity.getClass();ContentView annotation = aClass.getAnnotation(ContentView.class);if(null != annotation){int layoutId = annotation.value();//获得注解上的layoutId值activity.setContentView(layoutId);//将layoutId设置给activity//activity.setContentView(layoutId)也可用反射实现,但效率低且麻烦,所以直接使用上面setContentView(layoutId)方法,此处只是顺便说一下反射调方法 //aClass.getMethod("setContentView",int.class).invoke(activity,layoutId); }}/*** 注入String资源* @param activity*/private static void bindStringRes(Activity activity) throws IllegalAccessException {Class<? extends Activity> aClass = activity.getClass();Field[] fields = aClass.getDeclaredFields();//遍历成员变量取出被StringRes注解的fieldfor (Field field : fields) {if(field.isAnnotationPresent(StringRes.class)){StringRes annotation = field.getAnnotation(StringRes.class);int stringId = annotation.value();//string资源文件idString stringValue = activity.getString(stringId);//获取到相应资源文件string值//反射赋值field.setAccessible(true);//破封装field.set(activity,stringValue);}}}/*** 注入view* @param activity* @throws IllegalAccessException*/private static void bindViews(Activity activity) throws IllegalAccessException/*, NoSuchMethodException, InvocationTargetException */{//反射拿到@Bind注解的成员变量Class<? extends Activity> aClass = activity.getClass();Field[] fields = aClass.getDeclaredFields();//拿到所有成员变量for (Field field : fields) {//遍历成员变量,判断成员变量上是否有@Bind注解if (field.isAnnotationPresent(Bind.class)) {//如果有,拿出注解的value值,即控件idBind bind = field.getAnnotation(Bind.class);int viewId = bind.value();View view = activity.findViewById(viewId);//获取到view对象//activity.findViewById(viewId)也可用反射实现,但效率低且麻烦,所以直接使用上面find方法,此处只是顺便说一下反射调方法//View view= (View) aClass.getMethod("findViewById",int.class).invoke(activity,viewId);field.setAccessible(true);//破封装field.set(activity, view);//将view设置给该成员变量}}}/*** 绑定监听事件* @param activity*/private static void bindClicks(final Activity activity) {Class<? extends Activity> aClass = activity.getClass();Method[] declaredMethods = aClass.getDeclaredMethods();//反射获取方法//遍历方法,判断方法上是否有@OnClick注解for (final Method method : declaredMethods) {if(method.isAnnotationPresent(OnClick.class)){OnClick annotation = method.getAnnotation(OnClick.class);int[] viewIds = annotation.value();//拿到该方法上注解的view的id集for (int viewId : viewIds) {final View view = activity.findViewById(viewId);if(null != view){view.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {try {method.setAccessible(true);//破封装method.invoke(activity,view);//调起该带有@OnClick注解方法} catch (Exception e) {e.printStackTrace();}}});}}}}} }
我们看到,Shaver类里面定义了一个入口方法bind(Activity activity) ,然后bind方法里面我们调用了4个方法,这4个方法分别是处理4个注解逻辑,其思路上面已经说了,就是通过反射扫描带有这4个注解的编程元素(类/方法/成员变量),然后获取注解上的value,拿到这些id后,我们就可以做很多事了。代码本身不复杂,这里就不多说了。
Shaver使用
package com.youzhi.shaver; //省略各种导包 @ContentView(R.layout.activity_main) public class MainActivity extends AppCompatActivity {@Bind(R.id.btn1)Button btn1;@Bind(R.id.btn2)Button btn2;@Bind(R.id.tv1)TextView tv1;@Bind(R.id.et1)EditText et1;@StringRes(R.string.string_shaver)String stringRes;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);Shaver.bind(this);Log.e("MainActivity", btn1.getText().toString() + " , " + btn2.getText().toString() + " , " + tv1.getText().toString() + " , " + et1.getText().toString());tv1.setText(stringRes);}@OnClick({R.id.btn1,R.id.btn2})public void onClicks(View view){switch (view.getId()){case R.id.btn1:Toast.makeText(this, "点击了btn1", Toast.LENGTH_SHORT).show();break;case R.id.btn2:Toast.makeText(this, "点击了btn2", Toast.LENGTH_SHORT).show();break;}} }
然后我们在String文件中有一个string_shaver,用于例子中注入@StringRes(R.string.string_shaver)
![](http://upload-images.jianshu.io/upload_images/2954781-5ae6d4661c75de76.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
布局文件:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:id="@+id/activity_main"android:layout_width="match_parent"android:orientation="vertical"android:gravity="center_horizontal"android:layout_height="match_parent"><Buttonandroid:id="@+id/btn1"android:layout_width="150dp"android:background="#33ff00ff"android:layout_marginBottom="10dp"android:text="按钮1"android:layout_height="30dp"/><Buttonandroid:id="@+id/btn2"android:text="按钮2"android:layout_width="150dp"android:background="#33ffff00"android:layout_marginBottom="10dp"android:layout_height="30dp"/><TextViewandroid:id="@+id/tv1"android:layout_width="150dp"android:background="#4433bb00"android:layout_marginBottom="10dp"android:gravity="center"android:text="文本1"android:layout_height="30dp"/><EditTextandroid:id="@+id/et1"android:layout_width="150dp"android:background="#443300ff"android:layout_marginBottom="10dp"android:gravity="center"android:text="输入文本框1"android:layout_height="30dp"/></LinearLayout>
- 运行结果及打印的log
![](http://upload-images.jianshu.io/upload_images/2954781-355328db6f3d2a5d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![](http://upload-images.jianshu.io/upload_images/2954781-684127dd3e7ceeb8.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
我们成功获取到各个view的文本,以及设置上点击事件,说明我们的Shaver起作用了!It works!
这里需要注意打印出来的文本框为什么运行结果是“我是字符串资源”而log是“文本1”?大家看仔细点,我们布局文件里面text是“文本1”,然后我们在MainActivity里面打印出来后,我们重新set了一次,改成string文件里的值了。
The End
我们的低仿ButterKnife到此结束,相信讲解的已经够仔细了,到这里我相信现在对反射及注解有更深入的理解和巩固。
最后,转载请注明出处。
原文链接:http://www.jianshu.com/p/77a8181eda7a
Java基础回归之注解Annotation【低仿ButterKnife实战篇】相关推荐
- Java基础教程:注解
Java基础教程:注解 本篇文章参考的相关资料链接: 维基百科:https://zh.wikipedia.org/wiki/Java%E6%B3%A8%E8%A7%A3 注解基础与高级应用:http: ...
- java基础知识之注解、反射(一)
前言 此博客为供个人学习总结备用,如有错误请指正. 正文 Annotation(注解) java自带的注解包含以下三个: 1.@Override 注解表示重写父类的方法 2.@Deprecated 声 ...
- java参数注解pam_吃透Java基础十:注解
一.什么是注解 官方定义: 注解是一系列元数据,它提供数据用来解释程序代码,但是注解并非是所解释的代码本身的一部分.注解对于代码的运行效果没有直接影响. 注解有许多用处,主要如下: 提供信息给编译器: ...
- java基础-枚举和注解
枚举类的使用 如何自定义枚举类 如何使用关键字enum定义枚举类 Enum类的主要方法 实现接口的枚举类 类的对象只有有限个,确定的.举例如下: 星期:Monday(星期一)........Sunda ...
- java中override快捷键_【基础回溯1】面试又被 Java 基础难住了?推荐你看看这篇文章。...
本文已经收录自 https://github.com/Snailclimb/JavaGuide (59k+ Star):[Java学习+面试指南] 一份涵盖大部分Java程序员所需要掌握的核心知识. ...
- java基础入门答案谭晓芳,原理+实战讲解
One:JVM实践思维图(完整版) Two: 走近Java 概述+ Java技术体系+Java发展史+Java虚拟机家族:(Sun Classic/Exact VM.HotSpot VM.Mobile ...
- Java基础面试题精选汇总,备战面试突破篇--直击offer
前言 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语法,集合的语法,io 的语法,虚拟机方面的语法. 在开篇之前我们思考下,如果我们是面试官会问些什么问题呢 ...
- Java基础(二):集合、IO流(Zip压缩输入/输出流等)、File文件类、反射、枚举
Java基础(一):编译和解释.数据类型.变量作用域.String常用方法.数组.面向对象.异常 Java基础(二):集合.IO流(Zip压缩输入/输出流等).File文件类.反射.枚举 Java异常 ...
- JAVA基础之变量(数据类型及其转换)
变量 变量的概念: 内存中的一个存储区域: 该区域的数据可以在同一类型范围内不断变化: 变量是程序中最基本的存储单元.包含变量类型.变量名和存储的值. 变量的作用: 用于在内存中保存数据. 使用变量注 ...
最新文章
- Mac上更新Ruby
- mysql providername,c#访问各数据库的providerName各驱动
- yarn container写token目录选择bug
- 【Canal源码分析】Sink及Store工作过程
- linux配ipv6 ipv4 双栈,RouterOS配置原生IPv6(电信IPv4/IPv6双栈)
- 一条语句复制整个目录《转》
- 15crmo焊接后多长时间探伤_焊工必看:掌握钢结构焊接最重要的10个知识,不愁拿不到高工资!...
- 这 4 款电脑记事本软件,得试试
- SQL如何本地数据库连接服务器的数据库
- newifi虚拟服务器,简单几个步骤,newifi mini变身网络打印服务器,轻松省下100+-win7默认网关不可用...
- [摘录]第二部分 战略决策(1)
- (轉貼) 太空探索/液態水存在?火星南極有廣大冰層 可能有生命 (News)
- Python 算法:线性回归及相关公式推导
- 两年数据对比柱形图_2018年、2019年的数据对比图!想学习这种对比图的做法!安排...
- 用android写的微信闲聊机器人
- 【UGUI】横向与纵向布局组件
- Android打印小票速度太慢,解决打印PDF打印机打印速度慢的问题(适用所有打印机)...
- python成功解决'\xbe\xfc\xca\xc2'类型字符数据的正常输出问题
- 完美解决无Internet但能正常上网的问题
- Java语言中的输入方法
热门文章
- Python中拼音库PyPinyin
- 国之殤! 哀悼汶川大地震! 表达我的哀思!
- 简单代码实现100内阿拉伯数字转英文
- android开发 硬件加速,Android开发的硬件加速
- 网易微专业web前端开发课程|价值1648元
- c语言消隐的作用是什么,【C语言程序设计最终版材料】
- 2022建筑焊工(建筑特殊工种)考试题库及在线模拟考试
- java git打包iOS_懒人做iOS系列之jenkins+git+fir打包(xCode9)
- 如何使用 ffmpeg替代方式将webm转换为常用mp3格式
- 【概率论与数理统计】猴博士 笔记 p26-28 F、f的性质、一、二维连续型求期望、方差