ioc框架現(xiàn)在成熟的已經(jīng)有很多了,xutils,butterknife等等都是ioc框架,利用這些框架我們可以快速的進行開發(fā)而不必重復(fù)寫一些代碼。但是在實際開發(fā)中我們往往有自己的需求,這時候我們就需要自定義一個ioc框架,根據(jù)需求我們可以在里面加入不同的注解簡化我們的操作。下面我們自己仿照butterknife的功能來自定義一個findViewById和onClick()的ioc框架。
首先我們定義一個注解,使用@interface表示一個注解,代碼如下:
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ViewById {
int value();
}
其中value()是我們使用注解的時候的參數(shù)值:
@ViewById(R.id.test_tv)
private TextView mTestTv;
就是上述代碼中的R.id.test_tv
@Target(ElementType.XXX)代表的是放在那個位置:
@Target(ElementType.TYPE) 接口、類、枚舉、注解
@Target(ElementType.FIELD) 字段、枚舉的常量
@Target(ElementType.METHOD) 方法
@Target(ElementType.PARAMETER)方法參數(shù)
@Target(ElementType.CONSTRUCTOR) 構(gòu)造函數(shù)
@Target(ElementType.LOCAL_VARIABLE)局部變量
@Target(ElementType.ANNOTATION_TYPE)注解
@Target(ElementType.PACKAGE) 包
@Retention(RetentionPolicy.XXX)代表什么時候檢測:
RetentionPolicy.RUNTIME代表運行時檢測,class文件中存在
RetentionPolicy.CLASS代表編譯時檢測,存在于class文件中,運行時無法獲取
RetentionPolicy.SOURCE代表在源文件中有效(在.java文件中有效)
接下來我們需要通過一個類來獲取這個注解,并且通過反射來拿到相應(yīng)的View,最后在賦值給該Filed:
代碼如下:
package ioc.zzw.com.ioclibrary;
import android.app.Activity;
import android.view.View;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* Created by zzw on 2017/5/4.
* ioc注入工具類
*/
public class ViewUtils {
public static void inject(Activity activity) {
inject(new ViewHelper(activity), activity);
}
//為了兼容View
public static void inject(View v) {
inject(new ViewHelper(v), v);
}
//為了兼容Fragment
public static void inject(View v, Object o) {
inject(new ViewHelper(v), o);
}
/**
* 最終都調(diào)用這個方法
*
* @param helper View的幫助類 通過這個類根據(jù)id找到相應(yīng)View
* @param o 相關(guān)對象 Activity view 等 從那個類傳進來的
*/
private static void inject(ViewHelper helper, Object o) {
injectField(helper, o);
injectMethod(helper, o);
}
/**
* 通過@ViewById得到id注入相應(yīng)的View
*
* @param helper View幫助類
* @param o 相關(guān)對象 Activity view 等 從那個類傳進來的
*/
private static void injectField(ViewHelper helper, Object o) {
//1.獲取到Object中所有的帶有@ViewById的字段
Class<?> clazz = o.getClass();
//獲取所有的字段
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
//2.獲取到相應(yīng)的value值,也就是id值得到相應(yīng)的View
ViewById viewById = field.getAnnotation(ViewById.class);
if (viewById != null) {
int viewId = viewById.value();
View view = helper.findViewById(viewId);
if (view != null) {
try {
//3.設(shè)置字段值,也就是給字段賦值
field.setAccessible(true);//為了使不被修飾符梭影響
field.set(o, view);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
}
/**
* 設(shè)置點擊時間
*
* @param helper View幫助類
* @param o 相關(guān)對象 Activity view 等 從那個類傳進來的
*/
private static void injectMethod(ViewHelper helper, Object o) {
//1.獲取到所有帶有@OnClick的方法
Class<?> clazz = o.getClass();
Method[] methods = clazz.getDeclaredMethods();//獲取所有方法
for (Method method : methods) {
OnClick onClick = method.getAnnotation(OnClick.class);
if (onClick != null) {
//2.獲取到相應(yīng)的value值,也就是要設(shè)置點擊時間的id數(shù)組
int[] values = onClick.value();
for (int viewId : values) {
//3.通過id獲取到相應(yīng)的Vie,然后設(shè)置點擊事件
View view = helper.findViewById(viewId);
if (view != null) {
view.setOnClickListener(new DeclaredOnClickListener(method, o));
}
}
}
}
}
private static class DeclaredOnClickListener implements View.OnClickListener {
//設(shè)置點擊事件的方法
private Method mMethod;
//在那個類中
private Object mObject;
public DeclaredOnClickListener(Method method, Object o) {
this.mMethod = method;
this.mObject = o;
}
@Override
public void onClick(View v) {
try {
mMethod.setAccessible(true);//所有修飾符都可以搞事
mMethod.invoke(mObject, v);//可以避免點擊閃退
} catch (Exception e) {
e.printStackTrace();
try {
mMethod.invoke(mObject, (Object[]) null);//當(dāng)方法體里面沒有參數(shù)時候調(diào)用改方法,執(zhí)行沒有方法體的修飾的函數(shù)
} catch (IllegalAccessException e1) {
e1.printStackTrace();
} catch (InvocationTargetException e1) {
e1.printStackTrace();
}
}
}
}
}
package ioc.zzw.com.ioclibrary;
import android.app.Activity;
import android.support.annotation.IdRes;
import android.view.View;
/**
* Created by zzw on 2017/5/4.
* View幫助類
*/
public class ViewHelper {
private Activity mActivity;
private View mView;
public ViewHelper(Activity activity) {
this.mActivity = activity;
}
public ViewHelper(View v) {
this.mView = v;
}
public View findViewById(@IdRes int id) {
return mActivity == null ? mView.findViewById(id) : mActivity.findViewById(id);
}
}
package ioc.zzw.com.ioclibrary;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Created by zzw on 2017/5/4.
*
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface ViewById {
int value();
}
package ioc.zzw.com.ioclibrary;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Created by zzw on 2017/5/4.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface OnClick {
int []value();
}
package com.zzw.ioc;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;
import ioc.zzw.com.ioclibrary.OnClick;
import ioc.zzw.com.ioclibrary.ViewById;
import ioc.zzw.com.ioclibrary.ViewUtils;
public class MainActivity extends AppCompatActivity {
@ViewById(R.id.test_tv)
private TextView mTestTv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ViewUtils.inject(this);
mTestTv.setText("ioc_");
}
@OnClick({R.id.test_tv, R.id.test_button})
private void onClick(View view) {
switch (view.getId()) {
case R.id.test_tv:
Toast.makeText(this, "點擊了TextView", Toast.LENGTH_SHORT).show();
break;
case R.id.test_button:
Toast.makeText(this, "點擊了Button", Toast.LENGTH_SHORT).show();
break;
}
}
}
我們可以根據(jù)需求做一些自定義需要的注解,這里用網(wǎng)絡(luò)檢測來做個例子,當(dāng)點擊了檢測一下網(wǎng)絡(luò),如果沒有網(wǎng)絡(luò)的話就顯示網(wǎng)絡(luò)的提示,有的話就繼續(xù)往下執(zhí)行:
我們加上CheckNet
package ioc.zzw.com.ioclibrary;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Created by zzw on 2017/5/4.
* 點擊之后是否要執(zhí)行網(wǎng)絡(luò)檢測
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface CheckNet {
String value() default "親,您的網(wǎng)絡(luò)鏈接有問題哦!";
}
ViewUtils類中點擊事件注入的時候加上判斷:
/**
* 設(shè)置點擊事件
*
* @param helper View幫助類
* @param o 相關(guān)對象 Activity view 等 從那個類傳進來的
*/
private static void injectMethod(ViewHelper helper, Object o) {
//1.獲取到所有帶有@OnClick的方法
Class<?> clazz = o.getClass();
Method[] methods = clazz.getDeclaredMethods();//獲取所有方法
for (Method method : methods) {
OnClick onClick = method.getAnnotation(OnClick.class);
if (onClick != null) {
//網(wǎng)絡(luò)檢測
CheckNet checkNet = method.getAnnotation(CheckNet.class);
String hint = null;
if (checkNet != null) {
hint = checkNet.value();
}
//2.獲取到相應(yīng)的value值,也就是要設(shè)置點擊時間的id數(shù)組
int[] values = onClick.value();
for (int viewId : values) {
//3.通過id獲取到相應(yīng)的Vie,然后設(shè)置點擊事件
View view = helper.findViewById(viewId);
if (view != null) {
view.setOnClickListener(new DeclaredOnClickListener(method, o, hint));
}
}
}
}
}
private static class DeclaredOnClickListener implements View.OnClickListener {
//設(shè)置點擊事件的方法
private Method mMethod;
//在那個類中
private Object mObject;
//是否檢查網(wǎng)絡(luò)
private String mNoNetHint;
public DeclaredOnClickListener(Method method, Object o, String hint) {
this.mMethod = method;
this.mObject = o;
this.mNoNetHint = hint;
}
@Override
public void onClick(View v) {
try {
mMethod.setAccessible(true);//所有修飾符都可以搞事
if (mNoNetHint != null && !isNetConnected(v.getContext())) {
Toast.makeText(v.getContext(), mNoNetHint, Toast.LENGTH_SHORT).show();
return;
}
mMethod.invoke(mObject, v);//可以避免點擊閃退
} catch (Exception e) {
e.printStackTrace();
try {
mMethod.invoke(mObject, (Object[]) null);//當(dāng)方法體里面沒有參數(shù)時候調(diào)用改方法,執(zhí)行沒有方法體的修飾的函數(shù)
} catch (Exception e1) {
e1.printStackTrace();
}
}
}
}
/**
* 檢測網(wǎng)絡(luò)是否連接
*
* @return
*/
private static boolean isNetConnected(Context context) {
ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
if (cm != null) {
NetworkInfo[] infos = cm.getAllNetworkInfo();
if (infos != null) {
for (NetworkInfo ni : infos) {
if (ni.isConnected()) {
return true;
}
}
}
}
return false;
}
這樣,我們在需要判斷的時候加上這個注解:
@OnClick({R.id.test_tv, R.id.test_button})
@CheckNet("您網(wǎng)絡(luò)有問題哦!")
private void onClick(View view) {
switch (view.getId()) {
case R.id.test_tv:
Toast.makeText(this, "點擊了TextView", Toast.LENGTH_SHORT).show();
break;
case R.id.test_button:
Toast.makeText(this, "點擊了Button", Toast.LENGTH_SHORT).show();
break;
}
}
這樣的話就能夠提示你需要的文字
如果你直接這樣:
@OnClick({R.id.test_tv, R.id.test_button})
@CheckNet
private void onClick(View view) {
switch (view.getId()) {
case R.id.test_tv:
Toast.makeText(this, "點擊了TextView", Toast.LENGTH_SHORT).show();
break;
case R.id.test_button:
Toast.makeText(this, "點擊了Button", Toast.LENGTH_SHORT).show();
break;
}
}
這樣的話就會提示默認(rèn)的文字。
最后加上一個小技巧:
在我們利用反射來賦值或者執(zhí)行方法的時候一般會try catch,這個時候我們把異常改為Exception,這樣的話就能避免閃退,這樣用戶體驗增加了,但是會有一些小問題,比如點擊的時候出了異常,這時候點擊就沒反應(yīng),所以我們要細(xì)心查看logcat打印信息找到問題所在,從而解決問題。就是想下面這幾段代碼一樣:
try {
//3.設(shè)置字段值,也就是給字段賦值
field.setAccessible(true);//為了使不被修飾符梭影響
field.set(o, view);
} catch (Exception e) {
e.printStackTrace();
}
try {
mMethod.setAccessible(true);//所有修飾符都可以搞事
if (mNoNetHint != null && !isNetConnected(v.getContext())) {
Toast.makeText(v.getContext(), mNoNetHint, Toast.LENGTH_SHORT).show();
return;
}
mMethod.invoke(mObject, v);//可以避免點擊閃退
} catch (Exception e) {
e.printStackTrace();
try {
mMethod.invoke(mObject, (Object[]) null);//當(dāng)方法體里面沒有參數(shù)時候調(diào)用改方法,執(zhí)行沒有方法體的修飾的函數(shù)
} catch (Exception e1) {
e1.printStackTrace();
}
}