KuiklyUI 科普:UI 如何映射到 Android View 并完成渲染

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 → 原生实现),例如:
      • TextKRRichTextView(或 KRGradientRichTextView
      • TextFieldKRTextFieldView(底层使用 EditText + TextView
      • TextAreaKRTextAreaView
      • ImageKRImageView(通过图片适配器加载)
      • ViewKRView(容器,最终承载于 ViewGroup
      • Recycler/ListKRRecyclerView/KRRecyclerContentView
  • 属性映射(示例)
    • fontSize(16f) → 文本尺寸为 16sp(由渲染层在 KRRichTextView 上应用)
    • color(Color.BLUE) → 文本颜色为蓝色(由渲染层在 KRRichTextView 上应用)
    • backgroundColor(Color.WHITE)view.setBackgroundColor(Color.WHITE)
    • allCenter() → 通过容器的布局参数与重心设置实现内容居中(gravity/布局参数)
  • 事件映射
    • event { click { ... } } → 通过手势识别器(KRCSSGestureDetector/KRCSSGestureListener)绑定点击事件,并分发到 DSL 回调。
    • 支持 clickdoubleClicklongPress 等事件类型,事件回调通过 addEventListener(type, callback) 注册,主线程执行由适配层保障。

DSL→Android 映射总览(Android 端)

  • TextKRRichTextView(viewName: "KRRichTextView")
  • GradientTextKRGradientRichTextView(viewName: "KRGradientRichTextView")
  • TextFieldKRTextFieldView(viewName: "KRTextFieldView";底层 EditText + TextView
  • TextAreaKRTextAreaView(viewName: "KRTextAreaView")
  • ImageKRImageView(viewName: "KRImageView")
  • APNGKRAPNGView
  • PAG/AnimationKRPAGView
  • VideoKRVideoView
  • Recycler/ListKRRecyclerView/KRRecyclerContentView
  • CanvasKRCanvasView
  • ActivityIndicatorKRActivityIndicatorView
  • ModalKRModalView
  • HoverKRHoverView
  • BlurKRBlurView
  • MaskKRMaskView
  • View/ContainerKRView

以上映射的注册源可在 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,它只是"渲染意图"的数据表示。
  • 步骤 2:创建与挂载渲染视图

    • KuiklyRenderActivity 通过 KuiklyRenderViewBaseDelegator 创建 Kuikly 的渲染视图并挂载到你的容器 ViewGroup
    • 挂载时携带 pageName/pageData/pagerId 等上下文,后续事件与能力调用都会依赖这些上下文。
  • 步骤 3:VNode → 原生 View 映射

    • 基于组件类型选择对应原生渲染类:TextKRRichTextView(或 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 负责把声明式意图转换为这些布局约束。
  • 步骤 5:状态变更、Diff 与 Patch

    • 当数据/状态变化(或事件触发导致状态更新)时,引擎对"旧 VNode 树"和"新 VNode 树"做最小化 Diff。
    • 根据 Diff 结果对原生 View 树做 Patch:只创建需要的新视图、只更新变化的属性、只移除必要的旧视图。
    • key 的子节点能显著提升列表/动态区域的 Diff 准确性与性能(避免不必要重建)。
  • 步骤 6:线程模型与调度

    • 所有视图操作最终在 Android 主线程执行,KRThreadAdapter 负责把跨端的操作调度到主线程(避免崩溃/错乱)。
    • 重/耗时任务建议放入原生模块或后台线程处理,页面只拿结果并刷新 UI。
  • 步骤 7:生命周期一致性

    • 宿主 Activity 的 onResume/onPause/onDestroy 通过 Delegator 转发给渲染视图与引擎。
    • 页面挂载/卸载时,引擎会做对应的资源清理与监听解绑,防止泄漏与"幽灵回调"。
  • 步骤 8:能力调用(以 Toast 为例)

    • DSL 中的点击事件触发:event.click { ctx.acquireModule<BridgeModule>(...).toast("弹框") }
    • 引擎通过模块导出把这次调用路由到 Android 的 KRBridgeModule,最终执行 Toast.makeText(...).show()
    • 若调用发生在非主线程,线程适配器会切换到主线程确保安全展示。
  • 一个"点击→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_NAMEKRBridgeModule)。
  • 术语小抄(便于团队沟通)

    • DSL(声明式 UI):用代码描述"是什么",而不是"怎么做"。
    • VNode(虚拟节点树):渲染意图的数据化表示,便于 Diff。
    • Patch/Diff:找变更并最小化更新原生 View。
    • Delegator/RenderView:负责挂载、生命周期转发与实际渲染承载。
    • Adapter/Module:样式与能力的"翻译官",把跨端语义变成 Android 的真实操作。

代码索引与常量映射

  • 注册入口

    • KuiklyRenderViewBaseDelegator.registerRenderView(...):集中注册 Android 端的渲染类与 VIEW_NAME
    • 常见注册项:KRRichTextView/KRGradientRichTextViewKRTextFieldViewKRTextAreaViewKRImageViewKRPAGViewKRAPNGViewKRVideoViewKRCanvasViewKRRecyclerView/KRRecyclerContentViewKRModalViewKRActivityIndicatorViewKRHoverViewKRBlurViewKRMaskView 等。
    • delegate.registerExternalRenderView(this):支持外部视图扩展注册。
  • 文本组件的 viewName 决策

    • 核心层 TextView.kt/RichTextView.ktviewName() 会根据是否启用渐变文本返回 ViewConst.TYPE_RICH_TEXTViewConst.TYPE_GRADIENT_RICH_TEXT
    • ViewConst.kt 将上述类型常量映射为 Android 端的 VIEW_NAMETYPE_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 组件细节

    • KRTextFieldViewVIEW_NAME = "KRTextFieldView",底层使用 android.widget.EditTextTextView 协同实现输入、测量与文本展示。
    • KRTextAreaViewVIEW_NAME = "KRTextAreaView",为多行输入/展示区,支持 onChange/onFocus/onBlur 等事件。

文本输入组件说明

  • TextFieldKRTextFieldView

    • 典型属性:textfontSizecolortextAlignmaxLengthnumberOfLinessecureTextEntry 等。
    • 重要映射:numberOfLinesmaxLines/singleLinetextAligngravitysecureTextEntryInputType.TYPE_TEXT_VARIATION_PASSWORD
    • 底层实现:基于 EditTextTextView 协同实现输入、测量与文本展示。
    • 事件:onChange/onFocus/onBlur 均可通过事件桥接注册到原生监听。
  • TextAreaKRTextAreaView

    • 典型属性:textfontSizecolortextAlignnumberOfLines(多行)等。
    • 重要映射: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.ktKRFontAdapter.ktKRImageAdapter.kt

小结

KuiklyUI 通过 DSL 构建虚拟节点树,再由渲染引擎与适配层将其映射为 Android 原生 View 树。宿主容器负责视图挂载与生命周期转发,模块与适配器打通能力与样式。在这样的架构下,跨端页面可以用统一的声明式代码同时跑在 Android、iOS、H5、小程序与鸿蒙等多端,实现"一套代码,多端渲染"的目标。

相关推荐
阿巴斯甜9 小时前
Android 报错:Zip file '/Users/lyy/develop/repoAndroidLapp/l-app-android-ble/app/bu
android
Kapaseker10 小时前
实战 Compose 中的 IntrinsicSize
android·kotlin
xq952711 小时前
Andorid Google 登录接入文档
android
黄林晴12 小时前
告别 Modifier 地狱,Compose 样式系统要变天了
android·android jetpack
冬奇Lab1 天前
Android触摸事件分发、手势识别与输入优化实战
android·源码阅读
城东米粉儿1 天前
Android MediaPlayer 笔记
android
Jony_1 天前
Android 启动优化方案
android
阿巴斯甜1 天前
Android studio 报错:Cause: error=86, Bad CPU type in executable
android
张小潇1 天前
AOSP15 Input专题InputReader源码分析
android
_小马快跑_1 天前
Kotlin | 协程调度器选择:何时用CoroutineScope配置,何时用launch指定?
android