KuiklyUI 科普:UI 如何映射到 Android View 并完成渲染
最小示例:从 Kuikly DSL 到 Android View
以下片段摘自 AllInOnePage.kt(行 37--55),展示了一个最小的 Kuikly UI 声明,以及点击事件触发原生 Toast:
kotlin
return {
attr {
allCenter()
backgroundColor(Color.WHITE)
}
Text {
attr {
text("Hello Kuikly! wangzhengyi")
fontSize(16f)
color(Color.BLUE)
}
event {
click { clickParams ->
ctx.acquireModule<BridgeModule>(BridgeModule.MODULE_NAME).toast("弹框")
}
}
}
}
Text { attr { ... } event { ... } }是 Kuikly 的声明式组件(DSL),在 Android 端映射为KRRichTextView(或梯度文本KRGradientRichTextView),并非系统自带的android.widget.TextView。attr中的样式(如fontSize/color/backgroundColor)最终由渲染层在KRRichTextView上落地(文本绘制与布局由引擎驱动)。event.click { ... }通过手势监听器绑定为点击事件回调,回调中再调用跨端桥接模块触发原生能力(示例为 Toast)。
映射原则:组件、属性、事件
- 组件映射
- Kuikly 的基础组件会映射到 Android 端的渲染类(viewName → 原生实现),例如:
Text→KRRichTextView(或KRGradientRichTextView)TextField→KRTextFieldView(底层使用EditText+TextView)TextArea→KRTextAreaViewImage→KRImageView(通过图片适配器加载)View→KRView(容器,最终承载于ViewGroup)Recycler/List→KRRecyclerView/KRRecyclerContentView
- Kuikly 的基础组件会映射到 Android 端的渲染类(viewName → 原生实现),例如:
- 属性映射(示例)
fontSize(16f)→ 文本尺寸为16sp(由渲染层在KRRichTextView上应用)color(Color.BLUE)→ 文本颜色为蓝色(由渲染层在KRRichTextView上应用)backgroundColor(Color.WHITE)→view.setBackgroundColor(Color.WHITE)allCenter()→ 通过容器的布局参数与重心设置实现内容居中(gravity/布局参数)
- 事件映射
event { click { ... } }→ 通过手势识别器(KRCSSGestureDetector/KRCSSGestureListener)绑定点击事件,并分发到 DSL 回调。- 支持
click、doubleClick、longPress等事件类型,事件回调通过addEventListener(type, callback)注册,主线程执行由适配层保障。
DSL→Android 映射总览(Android 端)
Text→KRRichTextView(viewName: "KRRichTextView")GradientText→KRGradientRichTextView(viewName: "KRGradientRichTextView")TextField→KRTextFieldView(viewName: "KRTextFieldView";底层EditText+TextView)TextArea→KRTextAreaView(viewName: "KRTextAreaView")Image→KRImageView(viewName: "KRImageView")APNG→KRAPNGViewPAG/Animation→KRPAGViewVideo→KRVideoViewRecycler/List→KRRecyclerView/KRRecyclerContentViewCanvas→KRCanvasViewActivityIndicator→KRActivityIndicatorViewModal→KRModalViewHover→KRHoverViewBlur→KRBlurViewMask→KRMaskViewView/Container→KRView
以上映射的注册源可在 KuiklyRenderViewBaseDelegator.registerRenderView(...) 查阅,各组件的 VIEW_NAME 常量定义位于对应的 KR*View.kt 文件。
渲染承载:KuiklyRenderActivity 与 Delegator
Android 端通过 KuiklyRenderActivity 承载 Kuikly 页面,并使用 KuiklyRenderViewBaseDelegator 完成视图挂载与生命周期转发:
kotlin
private val delegator = KuiklyRenderViewBaseDelegator(this)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_hr)
val container: ViewGroup = findViewById(R.id.hr_container)
delegator.onAttach(container, "", pageName, createPageData())
}
override fun onResume() { super.onResume(); delegator.onResume() }
override fun onPause() { super.onPause(); delegator.onPause() }
override fun onDestroy() { super.onDestroy(); delegator.onDetach() }
onAttach(container, "", pageName, pageData):创建并挂载 Kuikly 的渲染视图(KuiklyRenderView)到宿主ViewGroup。- 生命周期转发:宿主 Activity 的
onResume/onPause/onDestroy被转发给引擎,保证页面状态一致。
适配层:模块与适配器注册(让 UI 真正跑起来)
在宿主容器中,需注册原生能力模块与适配器,打通 UI → 原生的能力与样式链路:
kotlin
override fun registerExternalModule(export: IKuiklyRenderExport) {
with(export) {
moduleExport(KRBridgeModule.MODULE_NAME) { KRBridgeModule() } // HRBridgeModule
moduleExport(KRShareModule.MODULE_NAME) { KRShareModule() }
}
}
with(KuiklyRenderAdapterManager) {
krImageAdapter = KRImageAdapter(application) // 图片加载
krLogAdapter = KRLogAdapter // 日志
krUncaughtExceptionHandlerAdapter = KRUncaughtExceptionHandlerAdapter
krFontAdapter = KRFontAdapter // 字体
krColorParseAdapter = KRColorParserAdapter(application) // 颜色解析
krRouterAdapter = KRRouterAdapter // 路由(open/close)
krThreadAdapter = KRThreadAdapter() // 线程(UI主线程保障)
}
BridgeModule:跨端桥的统一入口,页面通过acquireModule<BridgeModule>(...)调用原生能力(如 Toast)。KRBridgeModule:Android 端具体实现(call("toast", ...)→Toast.makeText(...))。KRFontAdapter/KRColorParserAdapter:把跨端的样式语义翻译为 Android 的字体与颜色。
渲染管线:从 DSL 到原生 View 树
-
总览
- 你写的 Kuikly 声明式 UI(DSL)先转成"虚拟节点树"(VNode),引擎根据这棵树去"创建/更新/销毁"真实的 Android View,并保证这些操作都在主线程、安全且高效地完成。
-
步骤 1:构建虚拟节点树(VNode)
- 将 DSL 如
Text { attr { ... } event { ... } }解析为节点对象,包含:type(组件类型)、props(属性/样式/事件)、children(子节点)、key(稳定标识,便于 Diff)。 - VNode 不直接触达 Android,它只是"渲染意图"的数据表示。
- 将 DSL 如
-
步骤 2:创建与挂载渲染视图
KuiklyRenderActivity通过KuiklyRenderViewBaseDelegator创建 Kuikly 的渲染视图并挂载到你的容器ViewGroup。- 挂载时携带
pageName/pageData/pagerId等上下文,后续事件与能力调用都会依赖这些上下文。
-
步骤 3:VNode → 原生 View 映射
- 基于组件类型选择对应原生渲染类:
Text→KRRichTextView(或KRGradientRichTextView),容器类 →ViewGroup(如FrameLayout/LinearLayout等)。 - 将
attr映射为属性:fontSize→ 文本尺寸计算与绘制、color→ 文本颜色、backgroundColor→ 视图背景颜色等(由KRRichTextView渲染层落地)。 - 将
event.click { ... }绑定为手势点击回调;回调中的跨端能力调用(如BridgeModule.toast("弹框"))经模块适配层路由到 Android 实现。 - 样式单位与跨端语义由适配器负责翻译:如字体、颜色、图片加载等都通过
KRFontAdapter/KRColorParserAdapter/KRImageAdapter统一处理。
- 基于组件类型选择对应原生渲染类:
-
步骤 4:布局与测量(Layout/Measure)
- Kuikly 容器会映射为原生
ViewGroup,其子节点根据LayoutParams与容器策略完成测量与布局。 - 例如
allCenter()会被翻译为容器的重心/布局参数(如gravity或居中布局规则),从而实现"内容居中"。 - 原生
View.onMeasure/onLayout负责最终尺寸与位置,Kuikly 负责把声明式意图转换为这些布局约束。
- Kuikly 容器会映射为原生
-
步骤 5:状态变更、Diff 与 Patch
- 当数据/状态变化(或事件触发导致状态更新)时,引擎对"旧 VNode 树"和"新 VNode 树"做最小化 Diff。
- 根据 Diff 结果对原生 View 树做 Patch:只创建需要的新视图、只更新变化的属性、只移除必要的旧视图。
- 有
key的子节点能显著提升列表/动态区域的 Diff 准确性与性能(避免不必要重建)。
-
步骤 6:线程模型与调度
- 所有视图操作最终在 Android 主线程执行,
KRThreadAdapter负责把跨端的操作调度到主线程(避免崩溃/错乱)。 - 重/耗时任务建议放入原生模块或后台线程处理,页面只拿结果并刷新 UI。
- 所有视图操作最终在 Android 主线程执行,
-
步骤 7:生命周期一致性
- 宿主 Activity 的
onResume/onPause/onDestroy通过 Delegator 转发给渲染视图与引擎。 - 页面挂载/卸载时,引擎会做对应的资源清理与监听解绑,防止泄漏与"幽灵回调"。
- 宿主 Activity 的
-
步骤 8:能力调用(以 Toast 为例)
- DSL 中的点击事件触发:
event.click { ctx.acquireModule<BridgeModule>(...).toast("弹框") }。 - 引擎通过模块导出把这次调用路由到 Android 的
KRBridgeModule,最终执行Toast.makeText(...).show()。 - 若调用发生在非主线程,线程适配器会切换到主线程确保安全展示。
- DSL 中的点击事件触发:
-
一个"点击→Toast"的完整链路(科普视角)
- 用户点击
KRRichTextView(或KRGradientRichTextView) - 手势识别(
KRCSSGestureDetector/KRCSSGestureListener)触发 DSL 的click {}回调 - 回调里拿到
BridgeModule(跨端桥) - 桥接层把"toast"请求传递到 Android 的
KRBridgeModule KRBridgeModule在主线程调用Toast.show(),系统弹出提示
- 用户点击
-
一个"状态更新"的完整链路(类比 React/Vue 的 Diff)
- 触发
setState/notify(Kuikly 的状态刷新手段) - 引擎生成"新 VNode 树",与"旧树"做 Diff
- 只对变化的节点做 Patch(比如文本变了就
setText,不重建整个树) - Delegator/渲染视图把这些最小变更应用到原生 View 树
- 触发
-
性能与实践建议
- 保持组件层级合理、避免过深嵌套;充分利用
key优化列表区域。 - 图片与颜色统一交给适配器(有缓存/解析优化);事件回调只做轻逻辑。
- 批量/频繁更新时,尽量合并状态变更,降低帧内 Patch 次数。
- 保持组件层级合理、避免过深嵌套;充分利用
-
调试排查与容错
KRLogAdapter输出关键日志,KRUncaughtExceptionHandlerAdapter捕获异常便于定位。- 组件不显示:确认
onAttach挂载、节点名/自定义视图是否注册、样式/颜色适配器是否配置。 - 原生能力不可用:校验模块名与注册名一致(
BridgeModule.MODULE_NAME↔KRBridgeModule)。
-
术语小抄(便于团队沟通)
DSL(声明式 UI):用代码描述"是什么",而不是"怎么做"。VNode(虚拟节点树):渲染意图的数据化表示,便于 Diff。Patch/Diff:找变更并最小化更新原生 View。Delegator/RenderView:负责挂载、生命周期转发与实际渲染承载。Adapter/Module:样式与能力的"翻译官",把跨端语义变成 Android 的真实操作。
代码索引与常量映射
-
注册入口
KuiklyRenderViewBaseDelegator.registerRenderView(...):集中注册 Android 端的渲染类与VIEW_NAME。- 常见注册项:
KRRichTextView/KRGradientRichTextView、KRTextFieldView、KRTextAreaView、KRImageView、KRPAGView、KRAPNGView、KRVideoView、KRCanvasView、KRRecyclerView/KRRecyclerContentView、KRModalView、KRActivityIndicatorView、KRHoverView、KRBlurView、KRMaskView等。 delegate.registerExternalRenderView(this):支持外部视图扩展注册。
-
文本组件的 viewName 决策
- 核心层
TextView.kt/RichTextView.kt的viewName()会根据是否启用渐变文本返回ViewConst.TYPE_RICH_TEXT或ViewConst.TYPE_GRADIENT_RICH_TEXT。 ViewConst.kt将上述类型常量映射为 Android 端的VIEW_NAME:TYPE_RICH_TEXT = "KRRichTextView"、TYPE_GRADIENT_RICH_TEXT = "KRGradientRichTextView"。
- 核心层
-
事件桥接
KRCSSViewExtension.addEventListener(view, type, callback):将 DSL 事件注册到手势识别器。KRCSSGestureDetector/KRCSSGestureListener:分发onSingleTapUp/onSingleTapConfirmed/onLongPress/onDoubleTap等为click/longPress/doubleClick。
-
TextField/TextArea 组件细节
KRTextFieldView:VIEW_NAME = "KRTextFieldView",底层使用android.widget.EditText与TextView协同实现输入、测量与文本展示。KRTextAreaView:VIEW_NAME = "KRTextAreaView",为多行输入/展示区,支持onChange/onFocus/onBlur等事件。
文本输入组件说明
-
TextField(KRTextFieldView)- 典型属性:
text、fontSize、color、textAlign、maxLength、numberOfLines、secureTextEntry等。 - 重要映射:
numberOfLines→maxLines/singleLine;textAlign→gravity;secureTextEntry→InputType.TYPE_TEXT_VARIATION_PASSWORD。 - 底层实现:基于
EditText与TextView协同实现输入、测量与文本展示。 - 事件:
onChange/onFocus/onBlur均可通过事件桥接注册到原生监听。
- 典型属性:
-
TextArea(KRTextAreaView)- 典型属性:
text、fontSize、color、textAlign、numberOfLines(多行)等。 - 重要映射:
numberOfLines→ 多行布局;textAlign→ 段落对齐;可选自动高度(AutoHeight)策略。 - 事件:同
TextField,支持onChange/onFocus/onBlur以及滚动相关事件(如适配层支持)。
- 典型属性:
代码解析:关键片段与注释
kotlin
// Kuikly DSL 片段(AllInOnePage.kt)
return {
attr {
allCenter() // 容器居中 -> ViewGroup 重心/布局参数
backgroundColor(Color.WHITE) // 容器背景 -> setBackgroundColor
}
Text {
attr {
text("Hello Kuikly! wangzhengyi") // 文本内容
fontSize(16f) // 字号(渲染层应用)
color(Color.BLUE) // 文本颜色(渲染层应用)
}
event {
click { clickParams ->
// DSL 点击事件 -> 手势回调(KRCSSGestureDetector/KRCSSGestureListener)
ctx.acquireModule<BridgeModule>(BridgeModule.MODULE_NAME)
.toast("弹框") // 跨端桥接 -> KRBridgeModule.toast -> Toast.show()
}
}
}
}
- 上述每一行在 Android 侧都有明确落地:
Text节点对应KRRichTextView(或KRGradientRichTextView),属性由渲染层落地(文本内容/尺寸/颜色等)。event.click对应手势回调;回调中通过BridgeModule派发到 Android 的KRBridgeModule。- 容器级的
allCenter()通过布局参数设置重心,确保内容居中。
kotlin
// KuiklyRenderActivity:承载容器与生命周期转发
private val delegator = KuiklyRenderViewBaseDelegator(this)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_hr)
val container: ViewGroup = findViewById(R.id.hr_container)
// 将 Kuikly 渲染视图挂载到原生容器
delegator.onAttach(container, "", pageName, createPageData())
}
override fun onResume() { super.onResume(); delegator.onResume() } // 生命周期转发
override fun onPause() { super.onPause(); delegator.onPause() }
override fun onDestroy(){ super.onDestroy();delegator.onDetach() } // 释放资源
kotlin
// 伪代码:将 Text 节点映射为 KRRichTextView(简化示例)
fun mountTextNode(ctx: Context, props: Props, onClick: (() -> Unit)?): KRRichTextView {
val view = KRRichTextView(ctx)
// 属性映射由渲染层应用(文本/尺寸/颜色/对齐)
applyRichTextProps(view, props)
// 事件桥接:通过手势识别器绑定 click
bindGesture(view, type = "click") { onClick?.invoke() }
return view
}
// 容器居中:容器是 FrameLayout/LinearLayout 时
val lp = FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT)
lp.gravity = Gravity.CENTER // allCenter()
container.addView(childView, lp)
kotlin
// shared 层:BridgeModule(简化示例)
class BridgeModule {
companion object { const val MODULE_NAME = "BridgeModule" }
fun toast(msg: String) = call("toast", mapOf("message" to msg))
private fun call(api: String, params: Map<String, Any>) {
// 交给平台端实现(通过 export 注册的 KRBridgeModule)
}
}
// Android 端:KRBridgeModule(简化示例)
class KRBridgeModule /* : PlatformModule */ {
fun call(api: String, params: JSONObject) {
when (api) {
"toast" -> {
val message = params.optString("message")
Handler(Looper.getMainLooper()).post {
Toast.makeText(appContext, message, Toast.LENGTH_SHORT).show()
}
}
// ... 其它能力
}
}
}
- 说明:以上为"示例化实现",接口名与工程一致,但代码为讲解所做简化;实际工程中通过
registerExternalModule(moduleExport(...))完成模块注册与桥接。
kotlin
// 路由承载(已经在文档前面给出):Activity 级打开/关闭页面
object KRRouterAdapter : IKRRouterAdapter {
override fun openPage(context: Context, pageName: String, pageData: JSONObject) {
KuiklyRenderActivity.start(context, pageName, pageData)
}
override fun closePage(context: Context) {
(context as? Activity)?.finish()
}
}
- 线程保障提示:若能力调用发生在非主线程,线程适配器会切换到主线程执行(例如
Handler(Looper.getMainLooper()).post { ... }),确保 UI 安全。
自定义视图:如何把你的控件加入映射
- 在宿主容器的
registerExternalRenderView中为某个节点名注册自定义 Android View 的创建器(Android 示例与 H5 类似):
kotlin
override fun registerExternalRenderView(export: IKuiklyRenderExport) {
with(export) {
// 伪代码示例:
// renderViewExport("MyCustomView") { MyCustomAndroidView() }
}
}
- H5 端示例(项目已包含):
kuiklyRenderExport.renderViewExport(KRMyView.VIEW_NAME) { KRMyView() }- Android 端遵循同样的"节点名 → 视图创建器"映射原则。
路由与承载:View 渲染 + Activity 级跳转
- 内容渲染是 View 级 :页面内容作为
KuiklyRenderView挂载在宿主ViewGroup。 - Demo 的页面跳转是 Activity 级 承载:
kotlin
object KRRouterAdapter : IKRRouterAdapter {
override fun openPage(context: Context, pageName: String, pageData: JSONObject) {
KuiklyRenderActivity.start(context, pageName, pageData)
}
override fun closePage(context: Context) {
(context as? Activity)?.finish()
}
}
- 如需"单 Activity + 视图级切换",可改造路由适配器:在当前容器内切换
pageName/pageData,维护视图栈,避免频繁startActivity。
最佳实践与常见问题
- 最佳实践
- 在页面内优先用扩展属性与上下文(如
this.bridgeModule)避免手动传pagerId。 - 样式与图片通过适配器统一配置(字体/颜色/图片),减少平台差异。
- 事件回调只做轻量逻辑;重操作放到后台线程或原生模块中处理。
- 在页面内优先用扩展属性与上下文(如
- 常见问题
- 组件不显示:检查容器是否正确
onAttach、节点名/自定义视图是否注册。 - 颜色/字体异常:确认
KRColorParserAdapter/KRFontAdapter是否配置正确。 - 原生能力不可用:检查
BridgeModule.MODULE_NAME与平台端注册名一致(HRBridgeModule)。
- 组件不显示:检查容器是否正确
关键代码路径
- 宿主容器与挂载:
androidApp/src/main/java/com/wzy/kuiklyui/demo/KuiklyRenderActivity.kt - 路由适配器:
androidApp/src/main/java/com/wzy/kuiklyui/demo/adapter/KRRouterAdapter.kt - 跨端桥接(shared 层入口):
shared/src/commonMain/.../BridgeModule.kt - 原生桥接实现(Android):
androidApp/src/main/java/com/wzy/kuiklyui/demo/module/KRBridgeModule.kt - 颜色/字体/图片适配器:
KRColorParserAdapter.kt、KRFontAdapter.kt、KRImageAdapter.kt
小结
KuiklyUI 通过 DSL 构建虚拟节点树,再由渲染引擎与适配层将其映射为 Android 原生 View 树。宿主容器负责视图挂载与生命周期转发,模块与适配器打通能力与样式。在这样的架构下,跨端页面可以用统一的声明式代码同时跑在 Android、iOS、H5、小程序与鸿蒙等多端,实现"一套代码,多端渲染"的目标。