(4.2.50)一种业务控件实现的方式
文章目录
复用级别在UiItem,也就是说需要掌握UiItem及其相关类的创建方法
- TestTestNewwayOfUi
- TestNewwayOfUi
- TestUiItemList
- 对外接口必须加入注释,譬如ViewMaker
一、UiViewPool 业务控件的视图控件池
其核心承载功能就是,根据 业务控件类型,生成其真实的视图控件
public interface UiViewPool {
/**
* 根据 业务控件类型, 返回对应的 真实视图控件
* @param uiType 业务控件类型 {@link com.sangfor.pocket.uin.newway.UiItemTypes.ViewType}
* @param parant 当前控件将被加入的父布局
* @return 真实的视图控件
*/
View getView(int uiType, ViewGroup parant);
}
1.1 业务控件UiType和视图控件ViewType的区别与转换
- 视图控件
- 对应 UiItemTypes.ViewType
- 一种真实的视图控件样式,譬如TextView、LinearLayout、TextImageNormalForm等
- 业务控件
- 对应 UiItemTypes.UiType
- 一种 业务控件样式,是针对ViewType的进一步与业务结合的封装
具体已经支持的业务控件类型和视图控件类型,可以查看com.sangfor.pocket.uin.newway.UiItemTypes
public class UiItemTypes {
public static class ViewType {
public static final int VT_UNKNOWN = 0;
public static final int VT_LinearLayout_Vertical = 10;
...
}
public static class UiType {
public static final int UT_SELECT_NORMAL = 1;
public static final int UT_SINGLE_SELECT_CUSTM = 4;
public static final int UT_MULTIPLE_SELECT_CUSTM = 5;
...
}
}
由于整体框架遵循”业务控件“高于”视图控件“的一致性规则,因此,即便是一些不具备业务属性的基础视图控件,我们也不能直接使用其视图控件。而是需要定义其对应的业务控件去使用
1.1.1 业务控件UiType和视图控件ViewType转换器UiTypeMapToViewType
其实就是一个 对应的转换关系,将 UiType业务控件类型 转换为 viewType视图控件类型
public class UiTypeMapToViewType {
public int mapWithType(int uiType){
int type;
switch (uiType){
case UiItemTypes.UiType.UT_SELECT_NORMAL:
case UiItemTypes.UiType.UT_SELECT_TIME:
case UiItemTypes.UiType.UT_SINGLE_SELECT_CUSTM:
case UiItemTypes.UiType.UT_MULTIPLE_SELECT_CUSTM:
case UiItemTypes.UiType.UT_SINGLE_PERSON:
case UiItemTypes.UiType.UT_SINGLE_SELECT_CUSTM_BY_SALE_ORDER:
type = UiItemTypes.ViewType.VT_TextImageNormalForm;
break;
case UiItemTypes.UiType.UT_CLASS_TITLE:
//左边显示是表单
type = UiItemTypes.ViewType.VT_CLASS_TITLE;
break;
//...
}
}
}
1.2 BaseUiViewPool 基础控件池
上文我们已经说了,UiViewPool的主要目的就是
1.2.1 功能:生成视图控件View并返回
生成视图控件View并返回是UiViewPool的核心功能
它的核心实现就是借助 ViewMaker来实现的:
- 持有
Map<Integer, ViewMaker> pool = new HashMap<>();
视图控件类型ViewType 与对应 ViewMaker视图控件制造器 的Map - 借助
UiTypeMapToViewType
业务控件UiType和视图控件ViewType转换器,对UiType进行转换为viewtype - 使用viewtype对应的ViewMaker,new出真正的视图控件
public abstract class BaseUiViewPool implements UiViewPool {
protected Context context;
/**
* 视图控件类型ViewType 与对应 ViewMaker视图控件制造器 的Map
*/
protected Map<Integer, ViewMaker> pool = new HashMap<>();
public BaseUiViewPool(Context context) {
//上下文对象,用于创建视图控件
this.context = context;
//业务控件UiType 和 视图控件ViewType 转换器
map = new UiTypeMapToViewType();
//构建支持的 视图制造器列表
ViewMaker[] viewMakers = listViewMakers();
if(viewMakers != null && viewMakers.length > 0){
for(ViewMaker vm : viewMakers){
ViewMaker put = pool.put(vm.getViewType(), vm);
}
}
}
@Override
public View getView(int uiType, ViewGroup parent) {
ViewMaker viewMaker;
if(uiType == UiItemTypes.ViewType.VT_UNKNOWN){
viewMaker = UNKNOWN_VIEW_MAKER;
}else {
//1. 借助`UiTypeMapToViewType`业务控件UiType和视图控件ViewType转换器,对UiType进行转换为viewtype
//2. 获取viewtype对应的ViewMaker
viewMaker = pool.get(map.mapWithType(uiType));
if (viewMaker == null) {
if (BuildConfig.DEBUG) {
throw new IllegalStateException("Unsupported uiType [" + uiType + "]");
}
viewMaker = UNSUPPORTED_VIEW_MAKER;
}
}
//3. 使用viewtype对应的ViewMaker,new出真正的视图控件
return viewMaker.makeView(context, parent);
}
}
1.2.2 功能:初始化 持有视图控件ViewMakers 的 HashMap
在上个环节我们讲到了,借助Map<Integer, ViewMaker> pool = new HashMap<>();
视图控件类型ViewType 与对应 ViewMaker视图控件制造器 的Map,来获取对应的 ViewMaker
那么这个 HashMap 是怎么初始化的呢?
- 在我们的实际开发中,有一些基础控件池是全局都会使用的,针对这些我们需要提供一个集中处理器帮我们直接生成
- 同时,在具体的一些业务中,又会需要一些自定义的业务控件,那么我们的集中处理器也必须要有一定的配置功能
public abstract class BaseUiViewPool implements UiViewPool {
protected abstract ViewMaker[] listViewMakers();
}
具体的,我们现在有三个子类,我们分别看下他们的实现:
public class StandardViewPool extends BaseUiViewPool {
public StandardViewPool(Context context) {
super(context);
}
@Override
protected ViewMaker[] listViewMakers() {
return new ViewMaker[]{
new FlexiblePictureLayoutMaker(),
new ApprovalShowViewMaker(),
new RemovableOrangeClassifyTitleMaker(),
new AddButtonViewMaker(),
new InfoStatusBarViewMaker(),
new EmptyViewMaker()
};
}
}
public class FormViewPool extends BaseUiViewPool {
public FormViewPool(Context context) {
super(context);
}
@Override
protected ViewMaker[] listViewMakers() {
return new ViewMaker[]{
new TextEditableFormMaker(),
new TextImageNormalFormMaker(),
new LeftWrapContentTextImageNormalFormMaker(),
new FormTipsMaker(),
new IsolatedFormButtonMaker(),
new LeftRightTitleMaker(),
new PureEditableFormMaker()
};
}
}
public class JxcViewPool extends BaseUiViewPool {
public JxcViewPool(Context context) {
super(context);
}
@Override
protected ViewMaker[] listViewMakers() {
return new ViewMaker[]{
new StockCheckDetailsViewMaker(),
new StockCheckProductCreateViewMaker(),
new StockCheckProductShowViewMaker(),
new JxcProductItemViewMaker(),
};
}
}
1.2.3 功能:视图控件池的合并
需要注意的是,我们在上文列举的StandardViewPool
,FormViewPool
,JxcViewPool
都是直接继承BaseUiViewPool
这也就意味着他们支持的功能区域是各自独立的,那么我如何同时使用不同Pool呢?
/**
* 视图控件池的组合能力
* @param other
* @return
*/
public BaseUiViewPool merge(BaseUiViewPool other) {
pool.putAll(other.pool);
return this;
}
我们不使用继承结构,也是为了遵循开发中的”组合高于继承“的原则
1.3 ViewMaker 单个视图控件的制造器
根据当前指定的int 控件类型,生成对应的实际控件。
这是对**new 视图控件
**能力的一种抽象,借助该制造器赋予框架生成对应试图控件的能力
public interface ViewMaker {
/**
* 当前 制造器 对应的 viewtype视图控件类型
* 该值不能随意定义,必须集中管理为 {@link com.sangfor.pocket.uin.newway.UiItemTypes.ViewType}中自定义
* @return
*/
int getViewType();
/**
* 当前制造器 对应的 视图控件
* @param context new控件时需要的上下文
* @param parent 控件将被包含的parent group
* @return
*/
View makeView(Context context, ViewGroup parent);
}
1.3.1 目前支持的视图控件
目前支持的视图控件,主要包含以下样式(com.sangfor.pocket.uin.newway.UiItemTypes#ViewType
):
//框架自定义
public static final int VT_UNKNOWN = 0;
public static final int VT_STANDARD_GROUP = 6;
//android 原生View
public static final int VT_LinearLayout_Vertical = 10;
public static final int VT_EMPYT = 19;
//moa标准表单项
public static final int VT_TextEditableForm = 1;
public static final int VT_TextImageNormalForm = 2;
public static final int VT_FlexiblePictureLayout = 3;
public static final int VT_LEFT_WRAP_TEXT_IMAGE_NORMAL_FORM = 5;
public static final int VT_CLASS_TITLE = 7;
public static final int VT_ADD_BUTTON_VIEW = 8;//底部的 添加button
public static final int VT_PRODUCT_ITEM_VIEW = 9;//jxc的一个商品item,包含商品名称、批次号、数量、总价等
public static final int VT_FormTips = 11;
public static final int VT_IsolatedFormButton = 12;
public static final int VT_StockCheckDetailsView = 13;
public static final int VT_LeftRightTitle = 14;
public static final int VT_StockCheckProductCreateView = 15;
public static final int VT_InfoStatusBar = 16;
public static final int VT_StockCheckProductShowView = 17;
public static final int VT_PURE_EDITABLEFORM = 18;
//审批人
public static final int VT_APPROVAL_SHOW_VIEW = 4;
更直观的,我们简单的看两个实例:
public class TextImageNormalFormMaker implements ViewMaker {
@Override
public int getViewType() {
return UiItemTypes.ViewType.VT_TextImageNormalForm;
}
@Override
public View makeView(Context context, ViewGroup parent) {
return new TextImageNormalForm(context);
}
}
public class VerticalLinearLayoutMaker implements ViewMaker {
@Override
public int getViewType() {
return UiItemTypes.ViewType.VT_LinearLayout_Vertical;
}
@Override
public View makeView(Context context, ViewGroup parent) {
LinearLayout ll = new LinearLayout(context);
ll.setOrientation(LinearLayout.VERTICAL);
return ll;
}
}
二、UIItem业务控件
2.1 UiItem接口
UIItem是对业务控件的一种抽象
在了解 UiItem之前,我们必须清晰的意识到,业务控件其本质还是一种视图控件。因此,视图控件所具备的一些能力,业务控件也会被具有和抽象出来
我们思考一个场景:选择客户,并据此进行分析
-
针对这是一个视图控件
- 标识它对应TextImageNormalForm视图控件
- 当前视图中的唯一标识?
- 它具备 渲染到实际布局中、从实际布局中移除、更新等生命周期
- 它可能有一定的 Margins、Visible、Clickable要求
- 他可能有一定的 上下分割线Divider的显示及缩进要求
- 一些点击或者长按事件的监听
- 感知自己父视图是谁的能力?
-
针对它具备一定的业务能力,也就是说和 CustmerVo相关
- 需要了解 使用CustmerVo 如何 进行界面上相关 字符串或者颜色的如何转换?----> UIValue
- UiItem 如何取出来 CustmerVo实例对象的引用 作为自己的展示vo?---->Getter
- UiItem被用户进行重选后,要将返回的新的CustmerVo实例对象 赋值到哪个引用上?---->Setter
- 需要一个生命周期回调,能够在适当时机处理这种转化的UI渲染?—>renderView
- UiItem对应的展示vo是否发生了变化?—>backup\isChanged
- 本身所具备的一些对点击或者长按的处理,譬如点击跳转到选择客户?—>onClick\onLongClick
- 一些检测规则,用于用户调用去校验是否满足要求?----> CheckInfo
- 对支持的ActivityResult的int Code的过滤? ----> Tag
-
UiItem的不保留活动的支持
- 本身是可以 序列化的?
- 本身不可以序列化,则需要一个生命周期函数,先提前自己new出来?HardSetting
2.1.1 业务能力的抽象
2.1.1.1 界面渲染值 UIValue
public interface UiValue<V> extends Parcelable, Cloneable{
/**
* 获取当前UiValue对应的界面渲染值
*/
String getStringValue();
/**
* 获取当前UiValue对应的VO实例对象
* 将会被{@link UiItem#extractFromGetter()} 赋值为 Getter中的引用
* 或者被更新操作赋值为一个新的引用
*
* 一般来说 UiValue的实现类中会持有一个 vo对象的引用:
* 1. **在初始化Getter过程中,该引用指向 业务代码中的原对象;**
* 2. **在更新后(譬如选择了一个新的供应商分类),该引用会指向一个新的对象**
*/
V getValue();
/**
* 判断UiValue是否差异,用于在 {@link UiItem#isChanged()}
*/
boolean isTheSame(UiValue other);
/**
* 用于UiValue的备份,用于在 {@link UiItem#backup()}
* 表示为界面初始值,将最终支持 {@link UiItem#isChanged()},以判断是否发生更新
*/
UiValue<V> copy();
}
我们来看一个供应商分类选择的实例:
public class JxcSelectSupplierclassUiValue extends BaseUiValue<SupplierClass> implements Parcelable {
private SupplierClass supplierClass;
public JxcSelectSupplierclassUiValue(SupplierClass sClass) {
this.supplierClass = sClass;
}
@Override
public String getStringValue() {
if (supplierClass != null && !TextUtils.isEmpty(supplierClass.classname)) {
return supplierClass.classname;
}
return "";
}
@Override
public SupplierClass getValue() {
return supplierClass;
}
@Override
public boolean isTheSame(UiValue other) {
if (other instanceof JxcSelectSupplierclassUiValue) {
SupplierClass otherSupplierClass = ((JxcSelectSupplierclassUiValue) other).supplierClass;
return supplierClass.classId == otherSupplierClass.classId && supplierClass.classname == otherSupplierClass.classname;
}
return false;
}
}
尤其需要注意一点,一般来说 UiValue的实现类中会持有一个 vo对象的引用:
- 在初始化Getter过程中,该引用指向 业务代码中的原对象;
- 在更新后(譬如选择了一个新的供应商分类),该引用会指向一个新的对象
2.1.1.2 VO转接器Getter|Setter
Getter|Setter是作用在类级别的,如果想针对某个VO操作,必须是传入某个Vo对象实例的内部成员变量,从而实现get|setter的响应都会针对当前Vo对象实例
public interface Getter<T> {
T get();
}
public interface Setter<T> {
void set(T data);
}
我们来看个例子:
public static class MyData {
public CustomerLineVo custm;
private Setter<CustomerLineVo> custmSetter = new Setter<CustomerLineVo>() {
@Override
public void set(CustomerLineVo data) {
custm = data;
}
};
private Getter<CustomerLineVo> custmGetter = new Getter<CustomerLineVo>() {
@Override
public CustomerLineVo get() {
return custm;
}
};
}
MyData myData = new MyData();
SingleSelectCustmUiItem selectCustmUiItem = new SingleSelectCustmUiItem(this);
selectCustmUiItem.setSetter(myData.custmSetter());
selectCustmUiItem.setGetter(myData.custmGetter);
2.1.1.3 UIValue、Getter和Setter的串联
在BaseUiItem中,我们可以观察到这种串联关系
从某种层次上,Getter、Setter的操作是高于 UIValue的。
这是由于,如果说模型”用户–>VO–>UIValue—>界面渲染“中UIValue割据了用户对View的直接操作,Getter|Setter相当于 框架和Vo打交道,而UiValue则是框架如何根据VO进行界面渲染和反向返回
- Getter
- void setGetter(Getter getter);
- void setGetConvertor(Convertor convertor);
- void extractFromGetter();
- Setter
- void setGetter(Getter getter);
- void setGetConvertor(Convertor convertor);
- void extractFromGetter();
- UiValue
- UiValue getValue();
- void setValue(UiValue value);
2.1.1.4 ”VO->UIValue–>界面渲染“的过程
我们先来看 ”VO->UIValue–>界面渲染“的过程,extractFromGetter()
:
@Override
public void extractFromGetter() {
if(getter != null) {
Object value = getter.get();
if (getConvertor != null) {
value = getConvertor.convert(value);
}
UiValue uiValue = getUiValueGenerator().generateUiValue(value);
setValue(uiValue);
}
}
@Override
public void setValue(UiValue value){
this.uiValue = value;
VT vt = mutexHolder.get();
if(vt != null){
updateValue(vt, uiValue);
}
}
可以看出基本遵循以下路线:
- 以
extractFromGetter()
作为入口 - 调用
Getter
,获取到VO对象实例 - 如果有转换器,则将Vo对象实例转换为指定类型的VO对象实例。此步骤,主要用于衔接Vo和UiValue的实际类型不统一
- 调用
UIValue构造器UiValueGenerator
构建UiValue - 调用
setValue(UiValue value)
- 内部可能触发更新更新界面逻辑
2.1.1.5 UiValueGenerator界面渲染值转换器
在BaseUiItem基本业务控件
提供了以下和UiValueGenerator相关的函数,基本可以看出就是以下默认的get|set函数
@Override
public void setUiValueGenerator(UiValueGenerator uiValueGenerator) {
this.uiValueGenerator = uiValueGenerator;
}
/**
* 提供一个 默认的 UiValue生成器
* @return
*/
protected UiValueGenerator provideDefaultUiValueGenerator(){
return null;
}
protected UiValueGenerator getUiValueGenerator(){
if(uiValueGenerator == null){
uiValueGenerator = provideDefaultUiValueGenerator();
}
return uiValueGenerator;
}
那么 UiValueGenerator 到底是什么呢?
它实际上承载着 ”VO->UIValue–>界面渲染“的过程中,”Vo–>UiValue”的过程“
不同的自定UiItem通过 重写provideDefaultUiValueGenerator()
实现 当前Item控制自己的转换
2.1.1.6 ”VO<–UIValue<–界面渲染“的过程
UIValue被更新后,getValue()所指向的引用将不再是原来Getter的对象地址,而是一个新的对象
譬如选了一个新的客户被赋值到 UIValue的一个自定义的引用里,因此需要我们调用setter去赋值
@Override
public UiValue getValue() {
return uiValue;
}
@Override
public void fillInSetter() {
if(setter != null) {
UiValue uiValue = getValue();
Object value = null;
if(uiValue != null) {
value = uiValue.getValue();
}
if (setConvertor != null) {
value = setConvertor.convert(value);
}
setter.set(value);
}
}
可以看出基本遵循以下路线:
- 以
fillInSetter()
作为入口 - 调用
getValue()
,获取到 界面渲染值。 - 通过界面渲染值拿到当前指向的的 vo实例
- 如果有转换器,则将Vo对象实例转换为指定类型的VO对象实例。此步骤,主要用于衔接Vo和UiValue的实际类型不统一
- 调用
Setter
的set函数给目标对象赋值
三、UiInteraction
UiInteraction是暴露给用户的操作手柄
从某种意义上,UiInteraction类似于RecyleView,回想下我们使用RecyleView时,仅需要配置datas、bindView和bindData就可以实现界面渲染
- 针对List items的操作
- 针对bindView的操作 也就是构建我们常规意义上的xml中各个view的过程
- 针对bindData的基础布局的vo渲染,也就是我们常规意义上的针对xml的一些View配置成我们vo的某些界面显示
- notifyItemSetChanged
- 针对Result响应的
- 针对判断界面内容是否改变
- 针对各个uiItem的vo合法性的检测
- 配置项和辅助utils
Item的Ui视图控件生成通过调用
UiInteraction#commit
或者UiInteraction#notifySetChanged()
触发,当然内部会对视图控件进行 属性和渲染值的初始化设定
Item的Ui属性由ITem自己控制,调用其UiItem#commitUiProps()
的函数触发其私有函数updateUiProps(VT view)
Item的UI渲染值通过UiInteraction#dumpOnItem()
—>item.extractFromGetter
—>item.setValue
—>item.updateValue
下一篇: Hive on Spark 注意事项