在生产型Android客户端软件(企业级应用)开发中,界面可能存在多个输入(EditText
)和多个操作(MotionEvent
和KeyEvent
),且操作依赖于输入的状态。如下图所示的场景:
设定图中
- 确认操作依赖于商品编码和储位的状态
- 跳过操作不依赖于输入状态
- 登记差异操作依赖于储位和数量的状态
输入框有三种状态:
- 待输入;
- 待校验;
- 校验成功。
操作需要当其依赖的输入数据校验成功,才能执行。
如果在Activity中去判断输入框状态,那么实际需要调用(3个输入框)*
(3种状态)*
(3个按钮) = 27个 if 判断,对于状态的维护将使得整个程序可维护性极差,并随着输入和操作的增加,维护的状态呈指数增长。
通过对这种场景的抽象,实现了Android控件状态依赖框架,其使用方法如下:
使用方法:
- 布局文件引用
WatchEditText
和WatchButton
由于
Library Module
中的控件id
不是常量(可参考ButterKnife
对Library Module
的支持采用R2
的原因),这里采用了tag
的方式。
- 在
Activity中
通过注解申明依赖
@ViewName("商品编码")
private WatchEditText editQuery1;
@ViewName("储位")
private WatchEditText editQuery2;
@ViewName("数量")
private WatchEditText editQuery3;
@ViewDependency(name = @ViewName("确认"), dependency = {"editQuery1", "editQuery2"})
private WatchButton buttonSearch1;
@ViewDependency(name = @ViewName("跳过")/*不依赖输入*/)
private WatchButton buttonSearch2;
@ViewDependency(name = @ViewName("登记缺货"), dependency = {"editQuery2", "editQuery3"})
private WatchButton buttonSearch3;
ViewName
定义控件名称,ViewDependency
中dependency
指定其依赖的控件tag
。
- 直接执行
onClick
和onEditorAction
(修改状态)
@Override
public void onClick(View v) {
if (v == buttonSearch1) {
Toast.makeText(this, "调接口", Toast.LENGTH_SHORT).show();
} else if (v == buttonSearch2) {
Toast.makeText(this, "跳下一页", Toast.LENGTH_SHORT).show();
} else if (v == buttonSearch3) {
Toast.makeText(this, "登记缺货", Toast.LENGTH_SHORT).show();
}
}
可以看出,这里并没有通过if
判断各个输入控件的状态。
@Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if (actionId == EditorInfo.IME_ACTION_NEXT && v == editQuery1
&& (query1Str = editQuery1.getText().toString()).isEmpty()) {
if (query1Str.equals("12345")) {
editQuery1.complete();
return true;
}
}
// 省略代码
return false;
}
onEditorAction
模拟调用软件的Enter
进行校验,这里需要注意通过editQuery1.complete()
修改该EidtText
的状态。
实现原理
整个框架分为三个package:annotation
、state
和view
。
- 在
annotation
中定义ViewName
和ViewDependency
注解,分别用于WatchEditText
和WatchButton
。ViewName
指定WatchEditText
控件在业务中的名称,ViewDependency
指定WatchButton
依赖的WatchEditText
控件;
/**
* 控件状态依赖
* Created by yanghao1 on 2016/12/19.
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ViewDependency {
/**
* 控件名称(嵌套注解)
*
* @return
*/
ViewName name() default @ViewName;
/**
* 控件状态依赖
*
* @return
*/
String[] dependency() default {};
}
- 在
state
中通过状态模式定义Enter
、Verify
、Complete
,其基类为抽象类Operator
,定义方法operator
;
/**
- 操作抽象接口
- Created by yanghao1 on 2016/12/15.
*/
public abstract class Operator {
// 操作对应的上下文
protected Context context;
/**
* 操作
*
* @param operatorName 操作名称
* @param viewName 控件名称
* @return 是否可以执行操作
*/
public abstract boolean operator(String operatorName, String viewName);
}
/**
- 待输入状态(初始状态)
- Created by yanghao1 on 2016/12/19.
*/
public class Enter extends Operator {
private static Enter enter;
private Enter(Context context) {
this.context = context;
}
public static Enter getInstance(Context context) {
if (enter == null) {
enter = new Enter(context);
}
return enter;
}
@Override
public boolean operator(String operatorName, String viewName) {
Toast.makeText(context, String.format("[%s]为空,不允许执行[%s]", viewName, operatorName),
Toast.LENGTH_SHORT).show();
return false;
}
}
-
WatchEditText
和WatchButton
定义控件的依赖关系。WatchEditText
实现ViewState
接口,其包含三种状态的转换方法。
/**
* 控件状态
* Created by yanghao1 on 2016/12/15.
*/
public interface ViewState {
/**
* 待输入状态(初始状态)
*/
void enter();
/**
* 待校验状态(有输入(不为空),但未进行校验,或校验不成功)
*/
void verify();
/**
* 有输入,并且校验成功
*/
void complete();
}
以上,Github地址:https://github.com/yhthu/AndroidViewDependency.git