Android 针对layout,view和监听的绑定注解
来源:互联网 发布:集美大学网络营业厅 编辑:程序博客网 时间:2024/05/22 06:56
前言
之前对注解着一块的知识一直很少使用,只知道基本概念,需要用反射操作,恰好最近的项目中有使用ButterKnife这种注解框架,感觉好用很多,当然,我这里写的跟ButterKnife不太一样,ButterKnife用的是编译时注解,我这里用的是运行时注解,不过学东西,总要一步一步来嘛,先可以应用上,后续再考虑性能问题。
注解
讲到注解,就不得不谈谈注解了,注解是什么,例如一个重写的方法,上面会声明@Override,那么我们就知道这是一个重写的方法了。
Java中有四个元注解,分别是:
@Target,@Retention,@Documented,@Inherited ,是Java 的api提供的,是专门用来定义注解的注解,其作用分别如下:
@Target 表示该注解用于什么地方,可能的值在枚举类 ElemenetType 中,包括:
ElemenetType.CONSTRUCTOR—————————-构造器声明
ElemenetType.FIELD ————————————–域声明(包括 enum 实例)
ElemenetType.LOCAL_VARIABLE————————- 局部变量声明
ElemenetType.METHOD ———————————-方法声明
ElemenetType.PACKAGE ——————————— 包声明
ElemenetType.PARAMETER ——————————参数声明
ElemenetType.TYPE————————————— 类,接口(包括注解类型)或enum声明
@Retention 表示在什么级别保存该注解信息。可选的参数值在枚举类型 RetentionPolicy 中,包括:
RetentionPolicy.SOURCE ———————————注解将被编译器丢弃
RetentionPolicy.CLASS ———————————–注解在class文件中可用,但会被VM丢弃
RetentionPolicy.RUNTIME VM——-将在运行期也保留注释,因此可以通过反射机制读取注解的信息。
@Documented 将此注解包含在 javadoc 中 ,它代表着此注解会被javadoc工具提取成文档。在doc文档中的内容会因为此注解的信息内容不同而不同。相当与@see,@param 等。
@Inherited 允许子类继承父类中的注解。
常用的注解一般会声明两个注解:
@Target和@Retention,来确定我们定义的注解的作用域和注解的作用时间,像之前的@Override我们点进注解中可以看到:
@Target(ElementType.METHOD)@Retention(RetentionPolicy.SOURCE)public @interface Override {}
作用域是针对方法,而@Retention这个注解是RetentionPolicy.SOURCE,也就是只是做个区分,最终会被编译器丢弃掉。
我们在点击进入ButterKnifer的任意一个注解库:
//// Source code recreated from a .class file by IntelliJ IDEA// (powered by Fernflower decompiler)//package butterknife;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;@Retention(RetentionPolicy.CLASS)@Target({ElementType.FIELD})public @interface InjectViews { int[] value();}
可以看到,这里的@Retention注解内容都是RetentionPolicy.CLASS,也就是会在生成的class文件中,android这边应该就是会编译成.dex文件,但最终还是会被虚拟机丢弃,这边在往深的我就不懂了,所以这里也就不搬门弄斧了。
我这里用到的@Retention注解内容是RetentionPolicy.RUNTIME ,也就是在运行时会保留。
对View的绑定
用到注解的话就要用到反射,所以大家还是需要对反射常用的类和方法有一定的了解。
我们平时写界面的时候,总是不断的,每个界面都要写上setContentView,findView等这些不断重复的代码,这些代码对我们的水平提升不大,但是我们还要反复的去敲,很浪费开发时间,所以,我们平时会用一些注解库等来解决这些问题,当然在kotlin里这种操作很简单(题外话),但是我们对这些原理还是要有一些了解,所以,为了避免这种重复的劳动力操作,我们还是要简化这种操作的。
我们参考ButterKnife的写法,在activity中也以这样的一种方式来写:
setContentView的注解
package com.example.panhao.retrofittest;import android.app.Activity;import android.os.Bundle;import android.support.annotation.Nullable;import android.util.Log;import android.view.View;import android.widget.Button;import android.widget.Switch;import static android.content.ContentValues.TAG;/** * Created by panhao on 17-6-17. */@ContentView(R.layout.activity_login)public class LoginActivity extends Activity { @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); InjectUtil.inject(this); }}
可以看到,在activity类外声明了一个注解,要想这个注解起作用,首先我们得声明一个注解:
package com.example.panhao.retrofittest;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;/** * Created by panhao on 17-6-17. */@Target(ElementType.TYPE)//在类的作用范围内@Retention(RetentionPolicy.RUNTIME)//运行时注解public @interface ContentView { int value();}
有了注解,当然要有注解的解释方法,我们在activity的onCreate方法里调用的InjectUtil的inject方法,这个方法里就对注解做了解析,代码如下:
public static void inject(Activity context) { injectLayout(context); } /** * 注入界面布局 * * @param context */ public static void injectLayout(Activity context) { Class<? extends Activity> clazz = context.getClass(); //拿到注解 ContentView contentView = clazz.getAnnotation(ContentView.class); if (contentView == null) { Log.i(TAG, "injectLayout: ======"); return; } int layoutId = contentView.value(); context.setContentView(layoutId); }
我们传入一个activity,通过Class的getAnnocation方法获得activity类上的注解,判断注解是否是我们需要的注解,是的话就找到注解传入的layoutId进行setContentView操作,我这里没有安装模拟器所以没有截图,真机上是可以绑定到view的大家可以试一下。
findView的注解
package com.example.panhao.retrofittest;import android.app.Activity;import android.os.Bundle;import android.support.annotation.Nullable;import android.util.Log;import android.view.View;import android.widget.Button;import android.widget.Switch;import static android.content.ContentValues.TAG;/** * Created by panhao on 17-6-17. */@ContentView(R.layout.activity_login)public class LoginActivity extends Activity { @InjectView(R.id.btn) Button btn; @InjectView(R.id.switches) Switch switches; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); InjectUtil.inject(this); btn.setText("哈哈哈哈哈哈哈哈"); switches.setChecked(true); }}
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <Button android:id="@+id/btn" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="測試注解" /> <Switch android:id="@+id/switches" android:layout_width="wrap_content" android:layout_height="wrap_content" /></LinearLayout>
在布局文件中声明一个button和一个switch,在activity中通过注解进行绑定,在onCreate时改变内容看是否完成了view的绑定,如果没完成肯定会报空指针。
package com.example.panhao.retrofittest;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;/** * Created by panhao on 17-6-17. */@Target(ElementType.FIELD)//字段中声明有效@Retention(RetentionPolicy.RUNTIME)//编译时注解public @interface InjectView { int value();}
/** * 注入界面内容的布局 * * @param context */ public static void injectView(Activity context) { Class<? extends Activity> clazz = context.getClass(); Field[] fields = clazz.getDeclaredFields(); for (Field field : fields) { InjectView injectView = field.getAnnotation(InjectView.class);//获取所有标明该注解的对象 //injectView注解不为空时 if (injectView != null) { int viewId = injectView.value(); View view = context.findViewById(viewId); try { field.set(context, view); } catch (IllegalAccessException e) { e.printStackTrace(); } } } }
这里是对activity里的字段进行注入,所以要获得activity里所有的字段进行遍历判断是否含有我们的注解,含有我们的注解的话就通过findView操作绑定view到activity中。
对事件监听的注入
android 中有很多种事件监听,所以这里如果只是对单个事件的监听绑定定还好处理,但是我们要针对不同事件的监听进行绑定,参考ButterKnife源码,要用到注解的注解,来标明一些信息:
package com.example.panhao.retrofittest;/** * Created by panhao on 17-6-17. */public @interface EventControl { /** * 设置事件监听的方法 * @return */ String listenerMethod(); /** * 设置事件回调的方法 * @return */ String callBack(); /** * 获取监听类型 * @return */ Class<?> getClazzType();}
package com.example.panhao.retrofittest;import android.app.Activity;import android.os.Bundle;import android.support.annotation.Nullable;import android.util.Log;import android.view.View;import android.widget.Button;import android.widget.Switch;import static android.content.ContentValues.TAG;/** * Created by panhao on 17-6-17. */@ContentView(R.layout.activity_login)public class LoginActivity extends Activity { @InjectView(R.id.btn) Button btn; @InjectView(R.id.switches) Switch switches; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); InjectUtil.inject(this); btn.setText("哈哈哈哈哈哈哈哈"); switches.setChecked(true); } @OnClick(R.id.btn) public void handler(View view) { Log.i(TAG, "onClick: 我被点击了哦~~~"); }}
package com.example.panhao.retrofittest;import android.view.View;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;/** * Created by panhao on 17-6-17. */@Target(ElementType.METHOD)//访问权限@Retention(RetentionPolicy.RUNTIME)//运行时执行@EventControl( listenerMethod = "setOnClickListener", callBack = "onClick", getClazzType = View.OnClickListener.class)public @interface OnClick { int[] value();//对一组数据添加监听}
在OnClick注解上声明了刚刚定义的对注解的注解,标明了它的监听方法,回调方法和监听类类型,同样我们也可以声明OnItemClick,OnLongClick等其它注解,这也是所以我们要加上注解的注解的原因。
private static void injectEvent(final Activity context) { Method[] methods = context.getClass().getDeclaredMethods(); for (Method method : methods) { //1.获取方法上的所有注解 Annotation[] annotations = method.getAnnotations(); //2判断注解是否是我们需要的注解:也就是注解上是否包含event for (Annotation annotation : annotations) { Class<? extends Annotation> annotationType = annotation.annotationType(); //获取注解上是否有我们约定好的注解 EventControl eventControl = annotationType.getAnnotation(EventControl.class); if (eventControl == null) { //说明不是我们想要的注解,跳出此次循环 continue; } //获取注解上声明好的内容 String listener = eventControl.listenerMethod(); String callback = eventControl.callBack();//要拦截的方法 Class<?> listenerType = eventControl.getClazzType(); final Map<String, Method> methodMap = new HashMap<>(); methodMap.put(callback, method); try { Method valueMethod = annotationType.getDeclaredMethod("value"); //第一个参数是接受者,也就是这里的注解对象,也就是调用annotation的value方法,如果后面有参数 //则在后面声明不定参数 int[] viewIds = (int[]) valueMethod.invoke(annotation); //遍历id找到view for (int viewId : viewIds) { final View view = context.findViewById(viewId); if (view != null) { //给view添加监听 if (view == null) { continue; } //获取设置监听的方法,但是不知道对什么设置监听:onClick,onItemClick... Method setListenerMethod = view.getClass().getMethod(listener, listenerType); //这里只有listener的类型 //根据对应类型生成对应的代理类对象 Object proxyInstance = Proxy.newProxyInstance(listenerType.getClassLoader(), new Class[]{listenerType}, new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (methodMap != null) { //获取对应方法在map中是否存在,这里的是我们的handle方法 Method m = methodMap.get(method.getName()); if (m != null) { //调用存入map中的方法,也就是我们在activity中声明监听注解的方法 return m.invoke(context, args); } } return method.invoke(proxy, args); } }); //给view设置监听 setListenerMethod.invoke(view, proxyInstance); } } } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } } } }
这里的逻辑处理就要麻烦一点了,但只要理清思路还是很容易理解的
1. 获取activity的所有方法 2. 获取每个方法上的所有注解 3. 遍历注解并判断注解上是否含有我们声明的注解的注解 4. 拿到注解的注解的基本信息 5. 拿到annotation上的value包含的所有id 6. 通过id获得view 7. 通过动态代理来生成需要的指定类型的代理对象,并拦截在注解的注解上声明好的方法,反射执行我们activity包含该注解的方法。 8. 调用setXXListener方法来给所有声明id的view添加该监听。
最后测试下对事件的绑定是否有效,效果如控制台打印所示
结语
结语这里本来像发一些感慨的,最近确实很忙,是我的事,不是我的事都往我身上推,我真的满烦的,不过想想也没什么,能力越大,责任越大,就算不能在当中学到什么,也能让我意识到我不是一无是处的存在,有的人说我太老实了,容易被别人占便宜,怎么说呢,我觉的这并不是什么老不老实的,只是做人的基本原则吧,答应别人的就好好做,可是有时候确实,一个人的力量真的很有限,还是希望大家不说多做什么,至少在团队中尽到自己的义务吧,如果都想着破罐子破摔,这个团队是很失败的,没有老实人该替你干活,你就算占了一时便宜,也并不能怎么样,你反而丧失了学习最好的时间,其实这反而是得不偿失的。好了,烦躁的胡乱bb了一堆,还是别胡思乱想了,洗洗睡了吧。
- Android 针对layout,view和监听的绑定注解
- 了解Android的view,viewgroup和layout
- Android自己动手做查找控件、绑定监听的注解框架
- Android通过注解初始化绑定View
- Android 注解,实现动态绑定view
- 【Android源码学习】View的layout和draw流程
- Android View绘制:measure,layout和draw
- Android 中 Checkbox和view的监听冲突问题
- Android View体系(八)从源码解析View的layout和draw流程
- Android中用注解和反射实现控件的绑定
- Android网络框架xUtils中的View的视图绑定注解操作
- Android 关于view layout的文章
- Android View中 layout 的使用
- 浅析Android View的Layout过程
- Android View的second layout pass
- Android View框架的layout机制
- Android应用程序窗口View的layout过程
- android view widget layout等的关系
- 很不错的关于人工智能框架的大致描述,可供开发选择
- javascript继承(三)混合继承
- VS2012的外部库zint的引入历程(上)
- 51nod 1639 绑鞋带(递推)
- mysql 5.7.18 解压版安装
- Android 针对layout,view和监听的绑定注解
- Hello World
- 一、mcg-helper研发小助手介绍
- 在Mac OS X 环境下配置Intellij IDEA+Tomcat及Tomcat出现权限问题
- VS2017常用快捷键
- git检出
- 计算机网络之TCP协议段格式及解析 ,TCP定时器
- iPhone SDK 应用程序开发 第一章
- C语音动态链表的建立与输出