# 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 的pid
id
两个属性对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 组件 →