首先看看最终完成的效果
public class MainActivity extends AppCompatActivity { @ViewById(R.id.tv_hello) private TextView tv_hello; @ViewById(R.id.iv_img) private ImageView iv_img; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ViewUtils.inject(this); tv_hello.setText("你好,IOC"); } @OnClick({R.id.tv_hello, R.id.iv_img}) @CheckNet private void sayHello(){ Toast.makeText(this, tv_hello.getText().toString(), Toast.LENGTH_SHORT).show(); } }运行效果
有网状态下,可以正常点击然后提示,无网状态下提示网络的问题。
下面看看具体实现 我阅读了xUtils和butterknife的源码,xUtils使用有点麻烦,butterknife的定义(变量,方法等)只能是public,所以我结合两者,造出适合自己的,习惯是private定义,以及追求使用方便。
首先要了解一下反射机制,不懂的话百度一下 好。coding 属性注入 : 利用反射去 获取Annotation –> value –> findViewById –> 反射注入属性 事件注入 :利用反射去 获取Annotation –> value –> findViewById –> setOnclickListener –> 动态代理反射执行方法
View注解的Annotation
/** * @creation_time: 2017/5/7 * @author: Vegen * @e-mail: vegenhu@163.com * @describe: View注解的Annotation */ //@Target(ElementType.FIELD) 代表Annotation的位置 FIELD代表属性 TYPE类上 CONSTRUCTOR构造函数上 METHOD方法上面 @Target(ElementType.FIELD) //@Retention(RetentionPolicy.CLASS) 什么时候生效 CLASS代表编译时 RUNTIME运行时 SOURCE源码资源 @Retention(RetentionPolicy.RUNTIME) public @interface ViewById { //@ViewById(R.id,xxx) int value(); }View辅助类
/** * @creation_time: 2017/5/7 * @author: Vegen * @e-mail: vegenhu@163.com * @describe: View辅助类 */ public class ViewFinder { private Activity activity; private View view; public ViewFinder(Activity activity) { this.activity = activity; } public ViewFinder(View view) { this.view = view; } public View findViewById(int viewId){ return activity != null ? activity.findViewById(viewId) : view.findViewById(viewId); } }View工具类
/** * @creation_time: 2017/5/7 * @author: Vegen * @e-mail: vegenhu@163.com * @describe: View工具类 */ public class ViewUtils { public static void inject(Activity activity){ inject(new ViewFinder(activity), activity); } public static void inject(View view){ inject(new ViewFinder(view), view); } public static void inject(View view, Object object){ inject(new ViewFinder(view), object); } // 兼容上面三个方法 object是反射需要执行的类 public static void inject(ViewFinder finder, Object object){ // 注入属性 injectFile(finder, object); // 注入事件 injectEvent(finder, object); } /** * 事件注入 * @param finder * @param object */ private static void injectEvent(ViewFinder finder, Object object) { // 1、获取类的所有方法 Class<?> clazz = object.getClass(); Method[] methods = clazz.getDeclaredMethods(); // 2、获取OnClick里面的value值 for (Method method : methods){ OnClick onClick = method.getAnnotation(OnClick.class); if (onClick != null){ int[] viewIds = onClick.value(); for (int viewId : viewIds){ // 3、findViewById找到View View view = finder.findViewById(viewId); //拓展功能 检测网络 boolean isCheckNet = method.getAnnotation(CheckNet.class) != null; if (view != null){ // 4、setOnClickListener view.setOnClickListener(new DeclaredOnClickListener(method, object, isCheckNet)); } } } } } private static class DeclaredOnClickListener implements View.OnClickListener{ private Object mObject; private Method mMethod; private boolean mIsCheckNet; public DeclaredOnClickListener(Method method, Object object, boolean isCheckNet) { this.mMethod = method; this.mObject = object; this.mIsCheckNet = isCheckNet; } @Override public void onClick(View view) { //判断是否需要检测网络 if (mIsCheckNet){ //需要 if (!isNetConnected(view.getContext())){ Toast.makeText(view.getContext(), "请检查网络,请稍后重试", Toast.LENGTH_SHORT).show(); return; } } //点击会调用该方法 try { // 所有方法都可以 包括私有公有 mMethod.setAccessible(true); // 5、反射注入执行方法 mMethod.invoke(mObject, view); } catch (Exception e) { e.printStackTrace(); try { mMethod.invoke(mObject, null); } catch (Exception e1) { e1.printStackTrace(); } } } } /** * 检测手机数据是否可用 */ public static boolean isMobileNetConnected(Context context){ ConnectivityManager cm = (ConnectivityManager) context.getSystemService(context.CONNECTIVITY_SERVICE); return cm.getNetworkInfo(ConnectivityManager.TYPE_MOBILE).isConnected(); } /** * 检测WIFI是否开启可用 */ public static boolean isWifiNetConnected(Context context){ ConnectivityManager cm = (ConnectivityManager) context.getSystemService(context.CONNECTIVITY_SERVICE); return cm.getNetworkInfo(ConnectivityManager.TYPE_WIFI).isConnected(); } /** * 检测网络是否可用 */ public static boolean isNetConnected(Context context){ return isMobileNetConnected(context) || isWifiNetConnected(context); } /** * 注入属性 * @param finder * @param object */ private static void injectFile(ViewFinder finder, Object object) { // 1、获取类里面的所有的属性 Class<?> clazz = object.getClass(); // 获取所有属性包括私有和公有 Field[] fields = clazz.getDeclaredFields(); // 2、获取ViewById里面的value值 for (Field field : fields){ ViewById viewById = field.getAnnotation(ViewById.class); if (viewById != null){ // 获取注解里面的id值 int viewId = viewById.value(); // 3、findViewById找到View View view = finder.findViewById(viewId); if (view != null) { // 能够注入所有的修饰符 private public field.setAccessible(true); // 4、动态注入找到的View try { field.set(object, view); } catch (IllegalAccessException e) { e.printStackTrace(); } } } } } }View 事件注解的Annotation
/** * @creation_time: 2017/5/7 * @author: Vegen * @e-mail: vegenhu@163.com * @describe: View 事件注解的Annotation */ //@Target(ElementType.FIELD) 代表Annotation的位置 FIELD代表属性 TYPE类上 CONSTRUCTOR构造函数上 METHOD方法上面 @Target(ElementType.METHOD) //@Retention(RetentionPolicy.CLASS) 什么时候生效 CLASS代表编译时 RUNTIME运行时 SOURCE源码资源 @Retention(RetentionPolicy.RUNTIME) public @interface OnClick { //@ViewById(R.id,xxx) int[] value(); }View 网络状态注解的Annotation
/** * @creation_time: 2017/5/7 * @author: Vegen * @e-mail: vegenhu@163.com * @describe: View 网络状态注解的Annotation */ //@Target(ElementType.FIELD) 代表Annotation的位置 FIELD代表属性 TYPE类上 CONSTRUCTOR构造函数上 METHOD方法上面 @Target(ElementType.METHOD) //@Retention(RetentionPolicy.CLASS) 什么时候生效 CLASS代表编译时 RUNTIME运行时 SOURCE源码资源 @Retention(RetentionPolicy.RUNTIME) public @interface CheckNet { }好,到此为止已经完成注解框架了,下面看看使用 MainActivity
public class MainActivity extends AppCompatActivity { @ViewById(R.id.tv_hello) private TextView tv_hello; @ViewById(R.id.iv_img) private ImageView iv_img; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ViewUtils.inject(this); tv_hello.setText("你好,IOC"); } @OnClick({R.id.tv_hello, R.id.iv_img}) @CheckNet //方法名随意 private void sayHello(){ Toast.makeText(this, tv_hello.getText().toString(), Toast.LENGTH_SHORT).show(); } }布局文件activity_main.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.vegen.study.ioctest.MainActivity" android:orientation="vertical"> <TextView android:id="@+id/tv_hello" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Hello World!" android:textSize="24sp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" /> <ImageView android:id="@+id/iv_img" android:layout_marginTop="20dp" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@mipmap/ic_launcher"/> </LinearLayout>别忘了添加网络访问权限
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />具体代码以及demo已经开源到我的github,欢迎star,有问题交流一下,地址:Vegen的github
