欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

(4.2.50)一种业务控件实现的方式

程序员文章站 2022-07-14 12:33:48
...

复用级别在UiItem,也就是说需要掌握UiItem及其相关类的创建方法

  • TestTestNewwayOfUi
  • TestNewwayOfUi
  • TestUiItemList
  1. 对外接口必须加入注释,譬如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来实现的:

  1. 持有 Map<Integer, ViewMaker> pool = new HashMap<>();视图控件类型ViewType 与对应 ViewMaker视图控件制造器 的Map
  2. 借助UiTypeMapToViewType业务控件UiType和视图控件ViewType转换器,对UiType进行转换为viewtype
  3. 使用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 是怎么初始化的呢?

  1. 在我们的实际开发中,有一些基础控件池是全局都会使用的,针对这些我们需要提供一个集中处理器帮我们直接生成
  2. 同时,在具体的一些业务中,又会需要一些自定义的业务控件,那么我们的集中处理器也必须要有一定的配置功能
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之前,我们必须清晰的意识到,业务控件其本质还是一种视图控件。因此,视图控件所具备的一些能力,业务控件也会被具有和抽象出来

我们思考一个场景:选择客户,并据此进行分析

  1. 针对这是一个视图控件

    1. 标识它对应TextImageNormalForm视图控件
    2. 当前视图中的唯一标识?
    3. 它具备 渲染到实际布局中、从实际布局中移除、更新等生命周期
    4. 它可能有一定的 Margins、Visible、Clickable要求
    5. 他可能有一定的 上下分割线Divider的显示及缩进要求
    6. 一些点击或者长按事件的监听
    7. 感知自己父视图是谁的能力?
  2. 针对它具备一定的业务能力,也就是说和 CustmerVo相关

    1. 需要了解 使用CustmerVo 如何 进行界面上相关 字符串或者颜色的如何转换?----> UIValue
    2. UiItem 如何取出来 CustmerVo实例对象的引用 作为自己的展示vo?---->Getter
    3. UiItem被用户进行重选后,要将返回的新的CustmerVo实例对象 赋值到哪个引用上?---->Setter
    4. 需要一个生命周期回调,能够在适当时机处理这种转化的UI渲染?—>renderView
    5. UiItem对应的展示vo是否发生了变化?—>backup\isChanged
    6. 本身所具备的一些对点击或者长按的处理,譬如点击跳转到选择客户?—>onClick\onLongClick
    7. 一些检测规则,用于用户调用去校验是否满足要求?----> CheckInfo
    8. 对支持的ActivityResult的int Code的过滤? ----> Tag
  3. UiItem的不保留活动的支持

    1. 本身是可以 序列化的?
    2. 本身不可以序列化,则需要一个生命周期函数,先提前自己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对象的引用:

  1. 在初始化Getter过程中,该引用指向 业务代码中的原对象;
  2. 在更新后(譬如选择了一个新的供应商分类),该引用会指向一个新的对象

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);
        }
    }

可以看出基本遵循以下路线:

  1. extractFromGetter()作为入口
  2. 调用Getter,获取到VO对象实例
  3. 如果有转换器,则将Vo对象实例转换为指定类型的VO对象实例。此步骤,主要用于衔接Vo和UiValue的实际类型不统一
  4. 调用UIValue构造器UiValueGenerator构建UiValue
  5. 调用setValue(UiValue value)
  6. 内部可能触发更新更新界面逻辑

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);
        }
    }

可以看出基本遵循以下路线:

  1. fillInSetter()作为入口
  2. 调用getValue(),获取到 界面渲染值。
  3. 通过界面渲染值拿到当前指向的的 vo实例
  4. 如果有转换器,则将Vo对象实例转换为指定类型的VO对象实例。此步骤,主要用于衔接Vo和UiValue的实际类型不统一
  5. 调用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