HT UI 入门手册

索引


概述

本文档是 UI 库的入门介绍手册,希望开发者通过阅读此文档能了解 UI 库的特性并且掌握组件的基础用法,如设置边框、背景、增加事件监听和使用布局器对组件进行布局

UI 库是一套功能强大的界面组件库,基于 HT 核心包的优秀架构和 HTML5 先进的 Canvas 机制,具有易上手、高性能、易扩展、组件丰富、跨平台等特点。

主要特性:

下面列出了 UI 库中包含的组件和接口:

使用 UI 组件需要引入类库:

<script sr='ht.js'></script>
<script sr='ht-ui.js'></script>

接下来我们看一个最简单的例子,创建一个按钮,显示在页面中:

// 创建按钮
var button = new ht.ui.Button();
button.setText('Hello World');
// 增加 click 事件监听
button.on('click', function(e) {
    alert('Hello World');
});
// 将按钮加到页面中
button.addToDOM(window, {x: 10, y: 10, width: 100, height: 26});

DOM 结构

UI 库中每个组件都拥有一个 DOM 树结构,包含交互 Div、绘制 Canvas 等。典型组件的结构如下:

组件的每个 DOM 结构都有 ht 属性说明用途,在浏览器的 DOM 树面板(如 Chrome 浏览器的 Elements 面板)中可以清晰的看到组件的 DOM 结构, 如 button.getView()ht 属性值为 ht-view ht.ui.Button

界面布局出现问题时,比如一个按钮不显示,我们需要确定按钮的 DOM 布局出现了问题还是绘制逻辑有问题,通过在浏览器查看 DOM 结构就可以确认是否是 DOM 布局问题

如果要通过 API 获取 ht 属性值,需要使用:button.getView().getAttribute('ht')

Border 接口

UI 库中 ht.ui.border 包下包含了边框接口 ht.ui.border.Border 和一系列的实现类;所有的组件都可以通过 setBorder 设置这些类型的边框;下面的例子我们通过文本框的例子查看各种边框的效果:

本质上,边框分为两类:Canvas 绘制的边框和 CSS 边框;接下来我们尝试自定义这两种类型的边框:

上面 demo 中前两个示例我们自定义了 Canvas 绘制的边框,思路是自定义类从 ht.ui.border.Border 继承(类继承请参考类继承请参考定义类文档), 重写 getLeftgetRightgetTopgetBottom 四个函数指定边框宽度;重写 drawBorder 函数绘制边框;如果需要序列化,还需要重写 getSerializableProperties 函数;需要注意,绘制任意内容(包括自定义组件时的绘制)之前,一定要调用 g.beginPath() 开始一个新路径,否则会导致 HT 内部的路径被重新绘制

最后一个例子我们自定义了一个 CSS 类型的边框,与前两个例子不同的是,需要重写 isCSSBorder 函数返回 true 以通知组件边框类型; 并且重写 tearDownBorder 函数清空 CSS 样式

Drawable 接口

UI 库的 ht.ui.drawable 包中包含了可绘制接口 ht.ui.drawable.Drawable 和一系列的实现类; 一般来说,组件的背景、图标都支持设置 Drawable 对象(并且会有相应的简化 API,如 Button 组件的 setIconDrawable 函数有对应的简化 API: setIcon),具体哪些属性支持 Drawable 可参考相关组件的 API 文档; 下面的例子我们通过组件背景查看通用 Drawable 的效果:

所有组件都有 background 属性用于控制显示的背景,并且这是个可绘制类型,意味着组件的背景可以是颜色、图片、九宫格,甚至是自己绘制的内容

上面 demo 中第一个例子演示了 ht.ui.drawable.ColorDrawable 的用法,通过参数我们可以实现圆角颜色背景

第二个例子演示了 ht.ui.drawable.ImageDrawable 的用法,这个绘制类型提供 filluniformcenterUniformcenter 四种 stretch 方式,例子中演示了四种 stretch 方式的绘制区别;stretch 默认为 centerUniform

第三个例子演示了 ht.ui.drawable.NinePatchImageDrawable 的用法,这个绘制类型可实现九宫格绘制; 这里的九宫格图片格式与 Android 中九宫格图片格式基本保持一致:上方像素点和左侧像素点相交区域作为拉伸区域;

Android 九宫格格式的区别是:HT 的九宫格不支持右侧和下侧的边距设置,作为代替,右上角的像素点数量表示屏幕像素精度(非必需);在例子中,我们在右上角填充了两个像素点,表示两倍像素精度,绘制的时候,上部区域 44px 的高度被绘制在 22px 的区域内

为了方便设计九宫格图片,我们将 Android Studio 中设计工具提取出来,需要的开发者可以在我们官网直接下载: draw9patch.jar;注意:这个工具依赖 Java 运行时环境

工具的用法网络上有很多资源,我们这里不再赘述,下面是使用 draw9path 工具编辑例子中九宫格的截图:

上面例子代码中我们设置 Drawable 是通过对象的方式(test.9. 是在 res.js 中注册的图片):

view2.setBackgroundDrawable(new ht.ui.drawable.NinePatchImageDrawable('test.9.'));

我们也可以使用简化 API:(末尾去掉相应的 Drawable):

view2.setBackground('test.9.');

test.9. 会被自动转换成 ht.ui.drawable.NinePatchImageDrawable 对象,创建过程中 test.9. 作为构造函数参数传入

也就是说,如果我们使用 Drawable 的简化 API,会有转换规则将参数转换为相应的 Drawable 对象,自动转换规则如下:

和边框接口类似,我们也可以实现 Drawable 接口自己绘制内容;下面的例子我们实现了渐变类型的绘制类型:

我们的思路是自定义类从 ht.ui.drawable.Drawable 继承(类继承请参考定义类文档), 重写 draw 函数绘制背景内容;如果需要序列化,还需要重写 getSerializableProperties 函数;需要注意,绘制任意内容(包括自定义组件时的绘制)之前,一定要调用 g.beginPath() 开始一个新路径,否则会导致 HT 内部的路径被重新绘制

目前产品中已经提供 ht.ui.drawable.GradientColorDrawable,因此此案例仅供学习使用

布局器与组件的 preferredSize、minSize、maxSize

UI 库提供了一系列布局器(参考本文档第一个例子)用于对其它组件进行布局;HBoxLayoutBorderLayoutRelativeLayout 等通用布局器统一使用 addView(view, layoutParams, index) 添加子组件;DialogTablePane 等专用容器则使用自己的特殊 API 添加组件,如 Dialog 使用 setContent(view) 设置内容区域的子组件

一个组件显示的最终宽度和高度由 widthheight 属性控制,如果组件是通过 addToDOM 直接加到页面中的,那么我们可以手动调用 setWidthsetHeight 设置组件的尺寸;但是如果组件是加到布局器里的,那么布局器在布局时会调用子组件的 setWidthsetHeight,我们手动设置的值就无效了,会被布局器设置的值覆盖掉;

这并不意味着我们不能控制布局器中的子组件的尺寸了,布局器增加子组件时通过第二个参数指定布局参数,允许我们设置子组件的布局宽高甚至对齐方式等:

borderLayout.addView(button, {
    width: 100, // 这里的宽度是布局宽度,不同于按钮的 `width` 属性,最终存储在 button.getLayoutParams()
    height: 'match_parent', // 表明高度要填满父容器
    align: 'left' // 水平方向左对齐
});

所有组件的布局参数中的 widthheight 基本都支持以下三种属性值:

每个组件,包括布局器(除了右键菜单)都有 preferredSizeminSizemaxSize 三个尺寸相关属性;preferredSize 是指组件的首选尺寸,这个属性包含 widthheight 两个子属性;组件的首选尺寸与是否显示、显示在哪里无关:比如按钮的首选尺寸是通过文字的尺寸加上图标的尺寸再加上边框宽度和内边距计算出来

minSize 是指组件的最小尺寸

maxSize 是指组件的最大尺寸

每个组件都有相应的计算函数用于计算这三个尺寸,如果开发者觉得组件自身的计算不精确或者某些情况下需要强制指定大小, 可以通过 setPreferredSizesetMaxSizesetMinSize 来进行强制指定以替换掉自动计算的结果; 当然,如果调用强制指定函数时只传入一个 undefined 参数,那么会重新启用自动计算, 如调用 setPreferredSize(undefined) 后,组件会重新自动计算 preferredSize

布局器在布局组件时需要考虑子组件的这三个属性,以 HBoxLayout 为例:

// addView 时指定 layoutParams 的 width 和 height 为 wrap_content,
// 布局器布局 button 时,使用 button 的 preferredSize
hBox.addView(button, {
    width: 'wrap_content',
    height: 'wrap_content'
});

// addView 时指定 layoutParams 的 width 和 height 为 match_parent,
// 布局器布局 button1 时,将按钮拉伸直至填满容器的剩余空间
// 设置最大尺寸和最小尺寸
button1.setMaxSize(500, 20);
button1.setMinSize(300, 20);
hBox.addView(button1, {
    width: 'match_parent',
    height: 'match_parent'
});

// addView 时指定 layoutParams 的 width 和 height 指定为固定像素,
// 布局器布局 button2 时,button2 尺寸固定为 100 * 20
hBox.addView(button2, {
    width: 100,
    height: 20
});

上面例子中,我们创建了一个 HBoxLayout 放到页面中,因为 addToDOM 没有任何参数,所以布局器会填满整个页面,并且浏览器窗口大小如果发生变化,这个布局器也会自动刷新

例子中的第一个按钮的 layoutParams 中的 widthheight 都是 wrap_content(可以不一致,比如 height 改为 match_parent 也是可以的),那么 button 最终的尺寸就是它的 preferredSize

第二个按钮的 layoutParams 中的 widthheight 都是 match_parent,意味着组件的最终尺寸会尽量填满布局器的剩余空间;如果布局器的尺寸发生变化,button1 的尺寸也会动态调整以尽量填满布局器剩余空间

但是需要注意,我们同时给组件设置了 minSizemaxSizebutton1 的尺寸虽然会随着布局器尺寸动态调整,但是会始终被限制这两个尺寸之间,而不会超过它们

最后一个按钮就比较简单了,我们在 layoutParams 参数中明确地指定了宽度值和高度值,因此 100 * 20 就是 button2 的最终尺寸

布局器其它常用 API

与其它框架集成

在开发完整系统时,经常需要将其它框架的内容嵌入到 UI 库的布局中,比如一段 HTML 内容,或者 HT 核心包中的 GraphView 组件;UI 库提供了下面两种包装类:

下面的例子我们使用 HtmlView 显示 iframe 和使用 HTView 显示核心包里的 3D 拓扑组件

接下来我们看一个 ECharts 的例子(需要访问网络请求 ECharts 类库):

事件监听

所有组件都提供了两种事件监听接口:

下面的例子中,我们监听了文本框的属性变化事件,将文本框的值显示在 Label

// 用户输入过程中立即派发事件
textField.setInstant(true);

// 监听属性变化事件,更新 Label
textField.addPropertyChangeListener(function(e) {
    if (e.property === 'value') {
        label.setText('Value: ' + e.newValue);
    }
});

第三种常用事件是 HT 没有直接封装的 HTML 的原生事件类型,如 mousedownmouseupdblclick 等,如果需要在组件上监听这些事件,可以使用下面的方式:

// 获取到组件的根层 Div,增加原生事件监听
button.getView().addEventListener('mousedown', function(e) {

});

3.0.6 开始,我们提供了更简单的事件监听接口:ononOnceoff,和旧的事件监听对比如下:

属性变化事件处理:

// 旧的监听接口
textField.addPropertyChangeListener(function(e) {
    if (e.property === 'value') {
        console.log(e);
    }
});

// 新的监听接口,p 为 property 的缩写
textField.on('p:value', function(e) {
    console.log(e);
});

// 旧的监听删除
textField.removePropertyChangeListener(handler);

// 新的监听删除
textField.off('p:value', handler);

// 对于自定义的 Attr 属性,可以用下面的方式进行监听(textField.setAttr('customProperty', 'value') 会触发 a: 事件):
textField.on('a:customProperty', function(e) {
    console.log(e);
});

view 事件处理:

// 旧的监听接口
button.addViewListener(function(e) {
    if (e.kind === 'click') {
        console.log(e);
    }
});

// 新的监听接口
button.on('click', function(e) {
    console.log(e);
});

// 旧的监听删除
button.removeViewListener(handler);

// 新的监听删除
button.off('click', handler);

原生 DOM 事件处理:

// 旧的监听接口
button.getView().addEventListener('mousedown', function(e) {
    console.log(e);
});

// 新的监听接口,d 为 DOM 的缩写
button.on('d:mousedown', function(e) {
    console.log(e);
});

// 旧的监听删除
button.getView().removeEventListener('mousedown', handler);

// 新的监听删除
button.off('d:mousedown', handler);

一次性事件处理:

// 旧的监听接口
var handler = function(e) {
    if (e.kind === 'click') {
        console.log(e);
        button.removeViewListener(handler);
    }
};
button.addViewListener(handler);

// 新的监听接口
button.onOnce('click', function(e) {
    console.log(e);
});

新的监听接口本质上是为原来三种事件监听接口提供了快捷方式,因此旧的事件监听接口仍然可用;但是需要注意,同一个事件监听器的注册和删除应该使用同一种方式,比如下面的例子:

var handler = function(e) {
    if (e.kind === 'click') {

    }
}
// 旧接口注册监听器
button.addViewListener(handler);
// 新接口删除监听器
button.off('click', handler);

上面这种方式是错误的,同一个事件监听器不能混用新旧接口,正确方式如下:

都使用旧接口

var handler = function(e) {
    if (e.kind === 'click') {

    }
}
// 旧接口注册监听器
button.addViewListener(handler);
// 旧接口删除监听器
button.removeViewListener(handler);

或者都使用新接口

var handler = function(e) {
    console.log(e);
}
// 旧接口注册监听器
button.on('click', handler);
// 旧接口删除监听器
button.off('click', handler);

组件缩写

有的开发者比较喜欢使用缩写的组件名,如:

// btn 前缀即为 button 的缩写
var btnSubmit = new ht.ui.Button();

如果您习惯这种方式,可以按照下面的规范使用缩写:

btn Button
mnb MenuButton
tgl ToggleButton
chk CheckBox
rad RadioButton
rds Radios
lbl Label
cmb ComboBox
sld Slider
prg ProgressBar
msg Message
txt TextField
num NumberInput
txa TextArea
dtp DateTimePicker
clp ColorPicker
lsv ListView
trv TreeView
tbv TableView
ttv TreeTableView
ttp TreeTablePane
tbp TablePane
prv PropertyView
prp PropertyPane
htv HTView
hmv HtmlView
grd Grid
mnu Menu
ctm ContextMenu
vg ViewGroup
hbx HBoxLayout
vbx VBoxLayout
tab TabLayout
brd BorderLayout
spl splitlayout
tbl TableLayout
tbr TableRow
flw FlowLayout
pop Popover
rlt RelativeLayout
pnl Panel
dlg Dialog
alr Alert

如果碰到没有列出的组件,您应该使用团队内部规范


欢迎交流 service@hightopo.com