KuiklyUI 科普:UI 如何映射到 Android View 并完成渲染
KuiklyUI 科普:UI 如何映射到 Android View 并完成渲染
最小示例:从 Kuikly DSL 到 Android View
以下片段摘自 AllInOnePage.kt
(行 37–55),展示了一个最小的 Kuikly UI 声明,以及点击事件触发原生 Toast:
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
→KRTextAreaView
Image
→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
→KRAPNGView
PAG/Animation
→KRPAGView
Video
→KRVideoView
Recycler/List
→KRRecyclerView
/KRRecyclerContentView
Canvas
→KRCanvasView
ActivityIndicator
→KRActivityIndicatorView
Modal
→KRModalView
Hover
→KRHoverView
Blur
→KRBlurView
Mask
→KRMaskView
View/Container
→KRView
以上映射的注册源可在 KuiklyRenderViewBaseDelegator.registerRenderView(...)
查阅,各组件的 VIEW_NAME
常量定义位于对应的 KR*View.kt
文件。
渲染承载:KuiklyRenderActivity 与 Delegator
Android 端通过 KuiklyRenderActivity
承载 Kuikly 页面,并使用 KuiklyRenderViewBaseDelegator
完成视图挂载与生命周期转发:
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 → 原生的能力与样式链路:
override fun registerExternalModule(export: IKuiklyRenderExport) {with(export) {moduleExport(KRBridgeModule.MODULE_NAME) { KRBridgeModule() } // HRBridgeModulemoduleExport(KRShareModule.MODULE_NAME) { KRShareModule() }}
}with(KuiklyRenderAdapterManager) {krImageAdapter = KRImageAdapter(application) // 图片加载krLogAdapter = KRLogAdapter // 日志krUncaughtExceptionHandlerAdapter = KRUncaughtExceptionHandlerAdapterkrFontAdapter = 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
以及滚动相关事件(如适配层支持)。
- 典型属性:
代码解析:关键片段与注释
// 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()
通过布局参数设置重心,确保内容居中。
// 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() } // 释放资源
// 伪代码:将 Text 节点映射为 KRRichTextView(简化示例)
fun mountTextNode(ctx: Context, props: Props, onClick: (() -> Unit)?): KRRichTextView {val view = KRRichTextView(ctx)// 属性映射由渲染层应用(文本/尺寸/颜色/对齐)applyRichTextProps(view, props)// 事件桥接:通过手势识别器绑定 clickbindGesture(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)
// 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(...))
完成模块注册与桥接。
// 路由承载(已经在文档前面给出):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 类似):
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 级 承载:
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、小程序与鸿蒙等多端,实现“一套代码,多端渲染”的目标。