# UI 框架
在 JSKit App 中,所有的页面都会使用 JS 语言来描述。JSKit App 的 UI 框架采用单线程模型,所有的代码都是同步的,运行在主线程的。
另,JSKit App 提供有
定时器 Timer接口可做主线程延时调度,和多线程 Worker接口做多线程支持。
UI 模块提供了将 View 节点(VNode)树渲染成页面(VPage)的渲染器(Renderer),和控制页面跳转的路由器(Router)。
JSKit App 所实现的 UI 框架的接口比较底层,使用的流程大致是:
- 根据预想的 VPage 页面结构(
VNode 树),解构成一系列的VNode - 在恰当的时机(VPageLifecycle#onLoad)多次调用
createNode传递 VNode 给 JSKit App - 在使用
VPage 路由方法加载页面时,JSKit App 根据 VNode 的pidid两个属性对VNode 树进行重建,渲染页面显示。
UI 的核心
数据结构是树(opens new window)。
提示
现在已经有了新的 jsk-ui 扩展能够提供更友好的 UI API。jsk-ui 扩展的底层依然是基于此框架提供的接口实现。
官方扩展仓库的所有代码,包括 jsk-ui 扩展在内,都是开源的:https://github.com/jskitapp/jskitapp-addon (opens new window) ,欢迎 PR 。
# VNode
VNode 代表基础的 View 组件。
基础的 VNode 有如下的结构:
interface VNode {
pid?: number;
id: number;
style?: LayoutStyle;
}
interface VNodeGroup extends VNode {}
pid:父节点的 id,父节点一般为 VNodeGroup 的子类。id:当前节点的 ID。style:作用于当前节点的样式,详见布局与样式
pid、id 建议使用本模块提供的 generateId() 来生成。
VNode 按是否可有 VNode 子节点,可分为两类:
- 不可包含子节点的 VNode
- 可包含子节点的 VNodeGroup
VNode 的子类 T 详细描述见 View 组件。
UI 框架对调用 createNode 创建 VNode 的顺序有要求:后创建的 VNode 的 pid 指代的必须是 VNodeGroup 的子类并已经存在(之前已经使用 createNode 创建过)。
# 管理 VNode
interface VNodeManager {
createNode<T extends VNode>(obj: T): void;
updateNode<T extends VNode>(obj: T): void;
removeNode(nodeId: number): void;
invoke(nodeId: number, args: any): void;
}
createNode创建 VNode。updateNode更新 VNode。removeNode根据 nodeId 删除 VNode。invoke执行 nodeId 上的方法,执行方法的信息使用 args 参数来描述
createNode、updateNode 和 removeNode 三个方法用来控制节点的属性,invoke 用来执行节点的方法。
# VPage
VPage 在 JSKit App 中表示一个页面。
# 添加 VPage
function addPage(pageId: number, plife: VPageLifecycle): VNodeManager;
- pageId:VPage 的 唯一的 ID,同时也是 VPage 所管理的
VNode 树的根 VNode的 id。 - plife:
VPageLifecycle对象,见 Page 状态机 - Returns:
VNodeManager对象,见管理 VNode
注意
添加 VPage 只是向 UI 框架注册了页面,添加的 VPage 并不会立刻被渲染成页面。只在调用了 VPage 路由的相关方法才会进入渲染流程。
# 获取 VPage
function getPage(pageId: number): VNodeManager;
获取 VNodeManager,见管理 VNode。
# 移除 VPage
function removePage(pageId: number): void;
- pageId:要移除的 VPage 的 ID
注意
不可移除非 UNLOAD 状态的 VPage。
# VPage 状态机
初始状态为 UNLOAD 状态,其所有的状态转换如下:
+------+
+------> |UNLOAD| <-------+
| +------+ |
| |
+ + ^ +
| |
+------+ <-------------+ +------+
|HIDDEN| | | |SHOWED|
+------+ +-------------> +------+
| |
^ v + ^
| |
| +------+ |
+------+ |LOADED| +-------+
+------+
状态转换过程中会回调以下钩子方法:
interface VPageLifecycle {
style?: LayoutStyle;
onLoad?: (cb: VNodeManager) => void;
onShow?: (cb: VNodeManager) => void;
onHide?: (cb: VNodeManager) => void;
onUnload?: (cb: VNodeManager) => void;
onEvent?: (name: string, args: any) => boolean;
}
style:作用于 VPage 的根节点的样式,详见布局与样式onLoad:VPage 的VNode 树节点正在加载,应该在此回调中使用 VNodeManager#createNode 构建VNode 树。onShow:VPage 已显示,当 VPage 移到 VPage 栈的栈顶或 APP 切换到前台时回调。onHide:VPage 已隐藏,当 VPage 移到 VPage 栈的非栈顶或 APP 切换到后台时回调。onUnload:VPage 的VNode 树节点即将卸载时回调。onEvent:来自于当前 VPage 的事件回调。
onEvent 事件:
| 事件 name | 事件 args | 返回值含义 | 触发场景 |
|---|---|---|---|
| onBackPressed | null | true 表示消费了事件,false 表示未处理 | 按手机的返回按键时,当前栈顶的 VPage 会收到此事件 |
# VPage 路由
JSKit App 会维护一个 VPage 的栈结构,来实现多页面的路由跳转。
interface VPageInfo {
id: number;
}
- id:指定的 VPage 的 pageId
function loadPage(arg: VPageInfo): void;
加载某个页面。VPage 栈如果不为空,将会把当前栈中的所有 VPage 转换至 UNLOAD 状态,再将此指定页面转换至 SHOWED 状态。
注意
这里的状态转换,都是按照上面的 VPage 状态机表示的转换路径进行转换的。例如,将一个 VPage 从 UNLOAD 状态转换至 SHOWED 状态,按照状态机,会经过 LOADED 再到 SHOWED 。
function navigateTo(arg: VPageInfo): void;
如果指定页面在当前栈中不存在:保留当前页面,跳转到应用内的某个页面。即,如果存在当前页面,当前页面会转至
HIDDEN状态,然后加载指定页面到SHOWED状态。如果指定页面在当前栈中已存在:栈中当前页面之前的页面会转至
UNLOAD状态,再将当前页面转至SHOWED状态。
function redirectTo(arg: VPageInfo): void;
如果指定页面在当前栈中不存在:关闭当前页面,跳转到应用内的某个页面。即,如果存在当前页面,当前页面会转至
UNLOAD状态,然后加载指定页面到SHOWED状态。如果指定页面在当前栈中已存在:栈中当前页面之前的页面会转至
UNLOAD状态,再将当前页面转至SHOWED状态。
function navigateBack(arg = { delta: 1 }): void;
关闭当前页面,返回上一页面或多级页面。
参数:arg
| 属性 | 类型 | 默认值 | 是否必填 | 说明 |
|---|---|---|---|---|
| delta | number | 1 | 否 | 返回的页面数,如果 delta 大于现有页面数,则返回到首页。 |
# VTemplate 与 VTempData
View 模版是一系列 VNode 的集合,也可称之为组件。
其中 VTemplate 的定义是:
interface VTemplate {
id: number,
name: 'view-temp';
children: VNode[];
}
模版中,使用 ${+ prop +} 来标记模版中哪些属性的值是可以更改的,其中,prop 与 VTempData#data 中的 prop 需要一一对应。
VTempData 是模版的实例数据:
interface VTempData extends VNode {
name: 'view-data';
typeRef: number;
data: {
[prop: string]: any;
}
}
- typeRef 的值为指定模版的 id
例如:
//模版
let templateNode = {
id: tmpNodeId,
name: "view-temp",
style: {
flexGrow: 1,
borderColor: "blue",
borderWidth: "2dp"
},
children: [//注意,这是个数组
{
pid: tmpNodeId,
id: ui.generateId(),
name: "text",
textSize: "17sp",
gravity: "center",
style: {
flexGrow: 1,
width: "100%",
borderColor: "red",
borderWidth: "2dp"
},
text: "${txtholder}",//属性的占位符
onClick: "${onTap}"//方法属性也可被占位
},
]
};
//模版数据
let templateDataNode = {
pid: pid0,
id: vid,
typeRef: tmpNodeId,
name: "view-data",
data: {
txtholder: "Hello Temp World",//模版数据,注意名字
onTap: (name, obj) => {//被占位的方法的实现
//
return true;
}
}
}
# 添加 VTemplate
添加的模版是全局的,能够在所有 VPage 里共用。
function addTemplate(obj: VTemplate): void;
# 移除 VTemplate
可以通过模版 id 移除对应的 VTemplate:
function removeTemplate(id: number): void;
# VPageNode
VPageNode 是一个特殊的 VNode,它让一个 VPage 可以像一个 VNode 一样组织进另一个 VPage。这填补了 VTemplate 不能动态管理的设计空白。
interface VPageNode extends VNode {
name: 'page-ref';
typeRef: number;
}
- typeRef 的值为指定 VPage 的 id
一个 VPageNode 节点逻辑上连结了两个 VPage:我们可以把 VPageNode 所在的 VPage 叫做宿主 VPage;把 VPageNode 引用的 VPage 叫做子 VPage。
注意,子 VPage在同一时刻只能被一个 宿主 VPage 引用,被引用时将不允许使用 VPage 路由的相关方法再次加载;子 VPage的生命周期将与宿主 VPage保持同步。
例如:
let pageRefNode = {
pid: homePageId,
id: tabContentId,
name: "page-ref",
style: {
flexGrow: 1,
},
typeRef: page1, // page1 为另一个 VPage 的 pageId
};
# 获取环境常量
function getEnv(key: string): any;
提供了获取环境常量的接口,获取诸如 API 版本号,Android 系统版本等信息,来判断接口可用性,做适配性的兼容操作等。
| key | 返回值类型 | 说明 |
|---|---|---|
| jsk_version | string | jskit 的 version name |
| jsk_code | number | jskit 的 version code |
| android_sdk_int | number | 手机 andorid sdk level |
| args | string | null | 项目启动的参数 |
# ID 生成器
function generateId(): number;
生成一个唯一 ID。此方法能在一定范围内保证生成的数的唯一性。
# Toast 消息
function toast(msg: string): void;
显示一条 Toast 消息。
# 显示 Console
ui.invoke({method: 'showConsole'})
显示 Console 页面。
← JSK-UI API View 组件 →