我们平时为view赋值和绑定事件,通常做法是
...
Button btn = findViewById(R.id.btn);
btn.setOnClickListener(new OnClickListener...);
...
用过xutil的人应该知道,为view赋值和绑定事件,只需要添加相应的注解就可以了,那么背后的实现原理到底是怎样呢?
@ViewInject(R.id.btn)
private Button btn;
@Event(value = R.id.btn, type = View.OnClickListener.class)
public void save(View view) {
}
有用到注解的地方,一般会通过反射获取注解原数据信息,再根据原数据信息做相应的处理,下面仿xutil,简单实现组件的初始化和事件绑定
@ViewInject 为属性注入值。
@Event 为控件绑定事件。
EventType 事件类型,点击,长按事件等。
ViewUtil 解析属性和方法上的注解信息,完成属性的赋值和控件的事件绑定。
ViewHelper 辅助类,用于获取view控件。
@ViewInject 该注解用于标注属性
/**
* 标注控件注解
* @author han
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ViewInject {
int value();
}
@Event 该注解用于标注方法,为控件绑定事件
/**
* 标注方法注解
* @author han
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Event {
/**
* 控制id数组
*/
int[] values();
/**
* 事件类型
*/
EventType type();
}
ViewHelper 用于获取并缓存view
/**
* 用于获取并缓存view
* @author han
*/
public class ViewHelper {
private SparseArray<View> mViewCache = new SparseArray<>();
private Activity mActivity;
private View mView;
public ViewHelper(Activity activity) {
this.mActivity = activity;
}
public ViewHelper(View view) {
this.mView = view;
}
public View findViewById(int id) {
View view = mViewCache.get(id);
if (view == null) {
view = mActivity != null ? mActivity.findViewById(id)
: mView.findViewById(id);
mViewCache.put(id, view);
}
return view;
}
}
ViewUtil 使用注解和反射,给属性注入值和绑定事件
- injectValue方法,先获取当前类的所有属性,再遍历属性,如果属性上有@ViewInject注解,则获取注解的value()属性,即获取控件的id。
再利用ViewHelper.findViewById获取实际的控件,最后通过反射为控件赋值。- bindEvent跟injectValue方法道理一样
/**
* View工具类,使用注解和反射,给属性注入值和绑定事件
* @author han
*/
public class ViewUtil {
public static void inject(Activity activity) {
inject(new ViewHelper(activity), activity);
}
public static void inject(Fragment fragment, View view) {
inject(new ViewHelper(view), fragment);
}
public static void inject(View view) {
inject(new ViewHelper(view), view);
}
private static void inject(ViewHelper viewHelper, Object object) {
injectValue(viewHelper, object);
bindEvent(viewHelper, object);
}
/**
* 注入属性值
*/
private static void injectValue(ViewHelper viewHelper, Object object) {
Class clazz = object.getClass();
// 获取所有属性
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
// 获取带有ViewInject注解的属性
ViewInject viewInject = field.getAnnotation(ViewInject.class);
if (viewInject != null) {
int viewId = viewInject.value();
View view = viewHelper.findViewById(viewId);
try {
// 设置可以访问私有属性
field.setAccessible(true);
field.set(object, view);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
/**
* 绑定事件
*/
private static void bindEvent(ViewHelper viewHelper, Object object) {
Class clazz = object.getClass();
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods) {
// 获取方法上的注解
Event event = method.getAnnotation(Event.class);
if (event != null) {
// 控件id
int[] viewIds = event.values();
// 事件类型
EventType eventType = event.type();
if (viewIds != null) {
for (int index = 0; index < viewIds.length; index++) {
View view = viewHelper.findViewById(viewIds[index]);
// EventStrategy eventStrategy = EventStrategyContext.getEvent(eventType);
// eventStrategy.invoke(object, method, view);
if (eventType == EventType.SINGLE_CLICK) {
view.setOnClickListener(v -> {
try {
method.invoke(object, view);
} catch (Exception e) {
e.printStackTrace();
}
});
}
}
}
}
}
}
}
测试
public class InjectActivity extends AppCompatActivity {
@ViewInject(R.id.save_btn)
private Button saveBtn;
private String name;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_inject);
ViewUtil.inject(this);
}
@Event(values = {R.id.save_btn}, type = EventType.SINGLE_CLICK)
public void singleClick(View view) {
Toast.makeText(this, "single click", Toast.LENGTH_LONG).show();
}
@Event(values = {R.id.save_btn}, type = EventType.LONG_CLICK)
public void longClick(View view) {
Toast.makeText(this, "long click", Toast.LENGTH_LONG).show();
}
}
布局文件
<?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/save_btn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Save" />
</LinearLayout>