本节是架构篇开篇 这里先解释一下最终项目的架构
目标
目标就是不需要进行一大堆的findviewbyid以及各种setListener的杂乱代码,本文以findviewbyid和setOnClickListener为例 介绍如何进行属性绑定和事件绑定
开始实现
1.创建测试module
public class ActivityIocTest extends AppCompatActivity {
@ViewById(R.id.tv)
TextView mTextView;
@ViewById(R.id.btn)
Button mButton;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_ioc_test);
ViewUtils.injectActivity(this);
mTextView.setText("Tv text!!!!");
mButton.setText("Button text!!!!");
}
@OnClick({R.id.tv,R.id.btn})
public void onItemClick(View view){
switch (view.getId()){
case R.id.tv:
Toast.makeText(this,"text view clicked",Toast.LENGTH_SHORT).show();
break;
case R.id.btn:
Toast.makeText(this,"button clicked",Toast.LENGTH_SHORT).show();
break;
}
}
}
<?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=".MainActivity">
<TextView
android:id="@+id/tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Ioc Test" />
<Button
android:id="@+id/btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Ioc Test" />
</LinearLayout>
2.创建ioc module 专门生成注解,进行属性和事件绑定
3.测试module依赖ioc模块
dependencies {
...
implementation project(path: ':ioc')//添加ioc模块注解依赖
}
4.编写ioc模块
4.1 注解的声明
/**
* Created by hjcai on 2021/2/7.
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface OnClick {
//可以传入多个参数
int[] value();
}
/**
* Created by hjcai on 2021/2/5.
* 注解声明
*/
@Target(ElementType.FIELD)//该注解表示只能用于类的属性
//其他常见属性如 TYPE用在类上 CONSTRUCTOR用在构造函数上 METHOD用于方法
@Retention(RetentionPolicy.RUNTIME)
public//该注解表示在运行时生效
//其他常见属性如 CLASS编译时 RUNTIME运行时 SOURCE源码级别
@interface ViewById {
//value代表可以该注解可以添加一个参数
int value();
}
4.2 工具类编写
/**
* Created by hjcai on 2021/2/5.
* <p>
* 主要是调用findViewById
*/
class ViewFinder {
private Activity mActivity;
public ViewFinder(Activity activity) {
this.mActivity = activity;
}
public View findViewById(int viewId) {
return mActivity.findViewById(viewId);
}
}
4.3 实际注入属性与方法的类
/**
* Created by hjcai on 2021/2/5.
*/
public class ViewUtils {
//Activity绑定
public static void injectActivity(Activity activity) {
injectActivity(new ViewFinder(activity), activity);
}
private static void injectActivity(ViewFinder finder, Object object) {
injectActivityField(finder, object);
injectActivityEvent(finder, object);
}
//注入属性
private static void injectActivityField(ViewFinder finder, Object object) {
//1 反射 获取class里面所有属性
Class<?> clazz = object.getClass();
Field[] fields = clazz.getDeclaredFields();//获取Activity的所有属性包括私有和共有
for (Field field : fields) {
//2 遍历fields 找到添加了注解ViewById的filed
ViewById viewById = field.getAnnotation(ViewById.class);
if (viewById != null) {
//3 获取注解里面的id值
int viewId = viewById.value();
View view = finder.findViewById(viewId);//相当于调用Activity.findViewById
if (view != null) {
field.setAccessible(true);
try {
//4 动态的注入找到的View
field.set(object, view);//利用反射 将object(activity)中的声明了ViewById注解的地方 替换成刚刚find的view
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
}
private static void injectActivityEvent(ViewFinder finder, Object object) {
//1 反射 获取class里面所有属性
Class<?> clazz = object.getClass();
Method[] methods = clazz.getDeclaredMethods();//获取Activity的所有方法包括私有和共有
for (Method method : methods) {
//2 遍历fields 找到添加了注解OnClick的method
OnClick onClick = method.getAnnotation(OnClick.class);
if (onClick != null) {
//3 获取注解里面的id值
int[] viewIds = onClick.value();
for (int viewId : viewIds) {
View view = finder.findViewById(viewId);//相当于调用Activity.findViewById
if (view != null) {
// 4. 给每个view设置点击事件
view.setOnClickListener(new DeclaredOnClickListener(method, object));
}
}
}
}
}
private static class DeclaredOnClickListener implements View.OnClickListener {
private Object mObject;
private Method mMethod;//实际定义的方法 在这里是 ActivityIocTest.onItemClick
public DeclaredOnClickListener(Method method, Object object) {
this.mObject = object;
this.mMethod = method;
}
@Override
public void onClick(View v) {
try {
// 所有方法都可以 包括私有共有
mMethod.setAccessible(true);
// 反射执行方法
// 当View被点击时执行
mMethod.invoke(mObject, v);//通过反射 调用mObject的声明了onClick注解了的方法 参数为v
} catch (Exception e) {
e.printStackTrace();
try {
mMethod.invoke(mObject, null);
} catch (Exception e1) {
e1.printStackTrace();
}
}
}
}
}
小结
这里的原理和xUtil3里面的ioc注解原理一样都是利用annotation+反射实现的