前言
当前android技术日新月异的现在,注入式编程使用己非常多了.流行的框架也特别多.这里,我并没有重复造轮子.想以另一个角度,带大家了解一下这些注入式编程.
注入式的主要作用
- 简化代码(代替如findViewbyId操作)
- 逻缉解藕
注入式的缺点
- 效率问题(利用反射)
- 形成另一种依赖
以上为我项目的经验总结.下面从一些简单的注入式的实现中介绍以上问题.
注解对象
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ID {
/** 控件 id */
public int id();
/** 是否注册点击事件 */
public boolean click() default false;
/** 给子控件设置点击事件 */
public boolean childClick() default false;
public boolean viewImage() default true;
}
解入操作代码
/**
* 查找控件id
*
* @param object
* @param view
* @param initParent
*/
private static void initView(Object object, View view, boolean initParent) {
initObject(object, object.getClass(), view);
if (initParent) {
initObject(object, object.getClass().getSuperclass(), view);
}
}
private static void initObject(Object object, Class<?> clazz, View view) {
for (Field field : clazz.getDeclaredFields()) {
field.setAccessible(true);
ID id = field.getAnnotation(ID.class);
if (null != id) {
// 设置控件id
if (0 != id.id()) {
try {
View findView = null;
if (object instanceof Activity) {
// activity
findView = ((Activity) object).findViewById(id.id());
} else if (object instanceof Dialog) {
// 对话框---
findView = ((Dialog) object).findViewById(id.id());
} else if (object instanceof View) {
findView = ((View) object).findViewById(id.id());
} else if (null != view) {
// view---用于fragment
findView = view.findViewById(id.id());
}
if (null != findView) {
field.set(object, findView);
// 设置当前控件点击
if (id.click()) {
setOnClickListener(object, findView,id.viewImage());
}
// 设置子控件点击事件
if (id.childClick() && findView instanceof ViewGroup) {
for (int i = 0; i < ((ViewGroup) findView).getChildCount(); i++) {
setOnClickListener(object, ((ViewGroup) findView).getChildAt(i),id.viewImage());
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
以上操作为完成针对所有可能对象的view注入.如常用的activity/fragment/dialog/viewholder等等.但如果只有这个功能,似乎并不足以让人感兴趣.对项目的帮助也并不大.但一切胜在开始,也正是因为有了这个思路.应对需求,扩展这些思路,才有了后面诸多东西
代码解藕
很早以前代码.喜欢用基类去包装一些操作.常规的.或者一些初始化.但是基类同样有很大蔽端,类与类之间藕合性太强.修改的连动代价大.而且,有问题也不易排查.刚工作的时候喜欢用基类.第一个项目完了之后,就很抵触基类了.于是就有了注解替代的方案了.且看,注解初始化actionbar
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Navigation {
/** 标题资源文件 */
public int title() default R.string.empty;
/** 是否启用返回按钮 */
public boolean displayHome() default true;
/** 是否隐藏系统图标 */
public boolean hiddenIcon() default true;
/** 是否隐藏当前actionBar */
public boolean hidden() default false;
/** 设定当前屏幕方向 */
public int orientation() default ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
/** 启用当前界面fragment menu */
public boolean hasOptionMenu() default true;
}
初始化代码
/**
* 初始化actionBar标题
*
* @param activity
* @param field
*/
private static void initActionBar(Object object, View view) {
Navigation navigation = object.getClass().getAnnotation(Navigation.class);
if (null != navigation) {
ActionBar actionBar = null;
if (object instanceof ActionBarActivity) {
actionBar = ((ActionBarActivity) object).getSupportActionBar();
} else if (object instanceof Fragment) {
actionBar = ((ActionBarActivity) ((Fragment) object).getActivity()).getSupportActionBar();
}
// 设置标题
if (-1 != navigation.title()) {
actionBar.setTitle(navigation.title());
} else {
actionBar.setIcon(R.drawable.logo);
}
// 设置是否启用返回键
actionBar.setHomeButtonEnabled(true);
actionBar.setDisplayHomeAsUpEnabled(navigation.displayHome());
Resources resources = App.getAppContext().getResources();
// 设置标题栏色值
actionBar.setBackgroundDrawable(new ColorDrawable(resources.getColor(R.color.title_color)));
// int titleId = resources.getIdentifier("action_bar_title", "id",
// "android");
// View titleView = findView(object, view, titleId);
// if (null != titleView) {
// ((TextView)
// titleView).setTextColor(resources.getColor(R.color.black));
// }
// 设置是否隐藏系统图标
actionBar.setDisplayUseLogoEnabled(!navigation.hiddenIcon());
if (!navigation.hiddenIcon()) {
actionBar.setIcon(R.drawable.logo);
} else {
actionBar.setIcon(new ColorDrawable(Color.TRANSPARENT));
}
// actionBar是否隐藏
if (navigation.hidden()) {
actionBar.hide();
} else {
actionBar.show();
}
// 横屏
// ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
// 竖屏
// ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
if (object instanceof Fragment) {
((Fragment) object).getActivity().setRequestedOrientation(navigation.orientation());
// 是否启用当前fragment actionBar
((Fragment) object).setHasOptionsMenu(navigation.hasOptionMenu());
}
}
}
包括对屏幕的方向设定.都是一个注解属性设定.非常便捷.当然最新的应该是toolbar的操作设定了.原理都一样.在最近几天.突然有一个针对demo项目的改动.希望在我写的demo集内,每个界面插入几条描述,描述该demo完成进度.demo的备注信息等.否则有人看的时候,或者感觉一个demo可用.可能就先入为主就用了.这时候,如果有什么隐式的问题.作者没有标明.对开发者而言可能是一个灾难.所以.萌生这个念头之后,就在考虑最小成本的完成这个功能,但是在现有的几十个demo内.要怎么才能最小代价做到呢.我就想到了代码片断植入.
实现代码
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface RateInfo {
/**
* 获取demo实现进度
* @return rate
*/
Rate rate() default Rate.CODING;
/** 测试信息 */
int beteInfo() default R.string.no_beteinfo;
}
public enum Rate {
/**
* 创建demo
*/
CREATE,
/**
* demo实现中
*/
CODING,
/**
* demo完成,处于测试使用阶段
*/
COMPLETE_BATE,
/**
* demo完结
*/
COMPLETE;
@Override
public String toString() {
String value = null;
switch (this) {
case CREATE:
value = "Demo创建";
break;
case CODING:
value = "Demo实现中";
break;
case COMPLETE_BATE:
value = "Demo功能基本实现,测试中";
break;
case COMPLETE:
value = "Demo功能己实现,并未发现异常.可使用";
break;
}
return value;
}
}
//初始化代码
private static void initRate(Object object, View view) {
//因主界面程序己固定,且布局等都己非常多了.所以默认并不改动每个布局去添加这个文件,
// 而以动态生成布局方式添加.非RelativeLayout,则在外围添加一层RelativeLayout
boolean bateInfo = PrefernceUtils.getRvsBoolean(ConfigName.BATE_INFO);
RateInfo info = object.getClass().getAnnotation(RateInfo.class);
if (bateInfo && null != info && null != view) {
RelativeLayout rateContainer = null;
Context context = view.getContext();
View rateLayout = View.inflate(context, R.layout.rate_layout, null);
TextView rateStatus = (TextView) rateLayout.findViewById(R.id.tv_rate_state);
TextView rateInfo = (TextView) rateLayout.findViewById(R.id.tv_rate_info);
rateStatus.setText(info.rate().toString());//设置demo完成进度状态
rateInfo.setText(info.beteInfo());//设置demo进度备注信息
if (view instanceof RelativeLayout) {
//直接添加
rateContainer = (RelativeLayout) view;
} else if (view instanceof ViewGroup) {
ViewGroup viewGroup = (ViewGroup) view;
LinkedList<View> childViews = new LinkedList<>();
for (int i = 0; i < viewGroup.getChildCount(); ) {
View childView = viewGroup.getChildAt(i);
childViews.add(childView);
viewGroup.removeView(childView);
}
rateContainer = new RelativeLayout(context);
viewGroup.addView(rateContainer, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
LinearLayout container = new LinearLayout(context);
container.setOrientation(LinearLayout.VERTICAL);
int size = childViews.size();
for (int i = 0; i < size; i++) {
container.addView(childViews.removeFirst());
}
rateContainer.addView(container, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
}
if (null != rateContainer) {
RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
layoutParams.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
rateContainer.addView(rateLayout, layoutParams);
}
}
}
以上,在为每个demo配置了一个注解之后,问题就解决.程序片断会自动将进度以及备注信息插入布局内.包括后面的布局调整都不受影响.对现有项目的改动很小.这只是其中一个比较方便的应用.当然还有很多的事情可以简化如针对点击事件的设定.注解绑定方法.等等功能.
代码都是非常简单.可以说.这也是为什么我会了解注入的一大原因.当初去用别人的公源项目,发现用来用去也只是这个用来findView,但是却不得不引入那么重量的一个库,想想,感觉很不应该,才考虑自己去实现一下.根据自己的需要来定制这些.其实也是很简单.再后来进行了大量功能的延伸扩展.感觉是非常的棒.