Skip to content

在腾讯Kuikly跨平台框架中实现交互式首页功能(支持鸿蒙)

前言

Kuikly 是腾讯推出的基于 Kotlin Multiplatform (KMP) 的跨平台 UI 框架,支持 Android、iOS、HarmonyOS、Web、小程序和 macOS 等多个平台。本文将详细介绍如何在 Kuikly 框架中实现一个功能丰富的交互式首页,包括实时时间显示、设备信息检测、用户交互统计等功能,并确保在 HarmonyOS 平台上的完美运行。

image-20260116213334831

一、项目背景与目标

1.1 项目背景

在跨平台开发中,创建一个功能完善的首页是应用开发的基础。本文通过实现一个交互式首页示例,展示如何:

  1. 使用 Kuikly DSL 构建声明式 UI
  2. 实现响应式状态管理
  3. 处理定时器和生命周期
  4. 获取和显示设备信息
  5. 实现用户交互功能
  6. 确保跨平台兼容性(特别是 HarmonyOS)

1.2 功能目标

本次实现的首页包含以下功能:

  • Hello World 展示:显示应用标题
  • 实时时间显示:每秒更新的时钟
  • 设备信息检测:显示平台、系统版本、屏幕尺寸等信息
  • 交互功能:点击计数和刷新功能
  • 交互统计:动态显示用户操作统计
  • 跨平台支持:完美支持 HarmonyOS、Android、iOS 等平台

二、技术架构

2.1 Kuikly 框架核心概念

在开始实现之前,我们需要了解 Kuikly 框架的几个核心概念:

Pager(页面)

Pager 是 Kuikly 中的页面入口类,类似于 Android 的 Activity 或 iOS 的 ViewController。每个页面都需要:

  • 使用 @Page 注解注册
  • 继承 BasePager 或实现 Pager 接口
  • 实现 body() 方法返回 UI 结构

ViewBuilder(视图构建器)

ViewBuilder 是一个闭包(lambda),用于描述 UI 结构。Kuikly 使用声明式 DSL 来构建 UI,类似于 React 或 Flutter。

Observable(响应式状态)

Kuikly 提供了 observable 属性委托,用于创建响应式状态。当状态改变时,配合 syncFlushUI() 可以刷新 UI。

指令系统

  • vif:条件渲染指令,根据条件显示/隐藏组件
  • vbind:绑定指令,用于响应式更新
  • vfor:列表渲染指令

2.2 项目结构

demo/src/commonMain/kotlin/com/tencent/kuikly/demo/pages/
└── HomePage.kt          # 首页实现

三、详细实现步骤

3.1 创建页面类

首先,在 demo/src/commonMain/kotlin/com/tencent/kuikly/demo/pages/ 目录下创建 HomePage.kt 文件:

kotlin
package com.tencent.kuikly.demo.pages

import com.tencent.kuikly.core.annotations.Page
import com.tencent.kuikly.core.base.Color
import com.tencent.kuikly.core.base.ViewBuilder
import com.tencent.kuikly.core.datetime.DateTime
import com.tencent.kuikly.core.directives.vif
import com.tencent.kuikly.core.reactive.handler.observable
import com.tencent.kuikly.core.timer.Timer
import com.tencent.kuikly.core.utils.PlatformUtils
import com.tencent.kuikly.core.views.Text
import com.tencent.kuikly.core.views.View
import com.tencent.kuikly.core.views.compose.Button
import com.tencent.kuikly.demo.pages.base.BasePager

/**
 * 首页 - 显示 Hello World 和设备信息
 */
@Page("HomePage")
internal class HomePage : BasePager() {
    // 实现代码
}

关键点说明:

  • @Page("HomePage"):使用 @Page 注解注册页面,"HomePage" 是页面名称,用于路由跳转
  • BasePager:继承自 Kuikly 的基础页面类,提供了页面生命周期和常用功能
  • internal:使用 internal 修饰符,表示该类只在模块内可见

3.2 定义响应式状态

在类中定义需要响应式更新的状态变量:

kotlin
@Page("HomePage")
internal class HomePage : BasePager() {

    // 响应式状态
    private var currentTime by observable("")
    private var clickCount by observable(0)
    private var refreshCount by observable(0)
    private lateinit var timer: Timer
}

状态说明:

  • currentTime:当前时间字符串,用于实时显示
  • clickCount:点击计数,记录用户点击"点击计数"按钮的次数
  • refreshCount:刷新计数,记录用户点击"刷新"按钮的次数
  • timer:定时器对象,用于定时更新时间

observable 使用说明:

observable 是 Kuikly 提供的属性委托,用于创建响应式状态。当状态值改变时,需要手动调用 syncFlushUI() 来刷新 UI。

3.3 实现生命周期方法

3.3.1 viewDidLoad - 页面加载完成

viewDidLoad() 中初始化定时器:

kotlin
override fun viewDidLoad() {
    super.viewDidLoad()
    // 启动定时器,每秒更新一次时间
    updateTime()
    timer = Timer()
    timer.schedule(0, 1000) {
        updateTime()
        getPager().syncFlushUI()
    }
}

关键点:

  • updateTime():立即更新一次时间,避免初始显示为空
  • Timer().schedule(0, 1000):创建定时器,延迟 0ms,每 1000ms(1秒)执行一次
  • getPager().syncFlushUI():在定时器回调中手动刷新 UI,确保时间显示更新

3.3.2 pageWillDestroy - 页面销毁

在页面销毁时清理定时器,避免内存泄漏:

kotlin
override fun pageWillDestroy() {
    super.pageWillDestroy()
    // 清理定时器
    if (::timer.isInitialized) {
        timer.cancel()
    }
}

关键点:

  • ::timer.isInitialized:检查 timer 是否已初始化,避免未初始化时调用 cancel() 导致异常
  • timer.cancel():取消定时器,停止定时任务

3.4 实现时间更新逻辑

kotlin
private fun updateTime() {
    val now = DateTime.currentTimestamp()
    val totalSeconds = now / 1000
    // 使用 rem 代替 % 运算符(Kotlin Native 兼容)
    val hours = ((totalSeconds / 3600).rem(24) + 8).rem(24) // 简单计算,实际应使用平台API
    val minutes = (totalSeconds / 60).rem(60)
    val seconds = totalSeconds.rem(60)
    
    // Kotlin Native 不支持 String.format,手动格式化
    val hoursStr = if (hours < 10) "0$hours" else hours.toString()
    val minutesStr = if (minutes < 10) "0$minutes" else minutes.toString()
    val secondsStr = if (seconds < 10) "0$seconds" else seconds.toString()
    currentTime = "$hoursStr:$minutesStr:$secondsStr"
}

关键技术点:

  1. 跨平台时间获取

    • 使用 DateTime.currentTimestamp() 而不是 System.currentTimeMillis()
    • DateTime 是 Kuikly 提供的跨平台时间 API,兼容所有平台
  2. Kotlin Native 兼容性

    • 使用 rem() 代替 % 运算符,确保在 Kotlin Native 平台上正常工作
    • Kotlin Native 对某些运算符的支持可能不同,使用 rem() 更安全
  3. 时间格式化

    • Kotlin Native 不支持 String.format(),需要手动格式化
    • 使用条件表达式确保时间显示为两位数(如 "09:05:03")
  4. 时区处理

    • 代码中简单计算了 UTC+8 时区,实际项目中应使用平台 API 获取本地时区

3.5 实现 UI 构建方法

3.5.1 获取设备信息

body() 方法开始处获取设备信息:

kotlin
override fun body(): ViewBuilder {
    val ctx = this
    val pageData = ctx.pagerData
    
    // 获取设备信息
    val platform = PlatformUtils.getPlatform()        // 平台名称
    val osVersion = PlatformUtils.getOSVersion()      // 系统版本
    val deviceWidth = pageData.deviceWidth            // 设备宽度
    val deviceHeight = pageData.deviceHeight          // 设备高度
    val pageViewWidth = pageData.pageViewWidth         // 页面宽度
    val pageViewHeight = pageData.pageViewHeight      // 页面高度
    val density = pageData.density                     // 屏幕密度
    val appVersion = pageData.appVersion              // 应用版本
    
    // 构建平台描述
    val platformDesc = when {
        PlatformUtils.isAndroid() -> "Android"
        PlatformUtils.isIOS() -> "iOS"
        PlatformUtils.isMacOS() -> "macOS"
        PlatformUtils.isOhOs() -> "HarmonyOS"
        else -> platform
    }
    
    return { /* UI 代码 */ }
}

设备信息说明:

  • PlatformUtils:Kuikly 提供的平台工具类,用于判断当前运行平台
  • pageData:页面数据对象,包含设备尺寸、系统信息等
  • 支持的平台检测方法:
    • isAndroid():是否为 Android 平台
    • isIOS():是否为 iOS 平台
    • isMacOS():是否为 macOS 平台
    • isOhOs():是否为 HarmonyOS 平台

3.5.2 构建主容器

使用 Kuikly DSL 构建页面主容器:

kotlin
return {
    attr {
        backgroundColor(Color.WHITE)
        flexDirectionColumn()
        alignItemsCenter()
        justifyContentCenter()
    }
    
    // UI 组件
}

布局说明:

  • backgroundColor(Color.WHITE):设置背景色为白色
  • flexDirectionColumn():设置布局方向为纵向(列)
  • alignItemsCenter():设置子元素水平居中对齐
  • justifyContentCenter():设置子元素垂直居中对齐

3.5.3 Hello World 标题

kotlin
// Hello World 标题
View {
    attr {
        marginBottom(20f)
    }
    Text {
        attr {
            text("Hello World")
            fontSize(48f)
            color(Color(0xFF2196F3))
            fontWeightBold()
        }
    }
}

3.5.4 实时时间显示

kotlin
// 实时时间显示
View {
    attr {
        marginBottom(20f)
        flexDirectionRow()
        alignItemsCenter()
    }
    Text {
        attr {
            text("当前时间: ")
            fontSize(16f)
            color(Color(0xFF666666))
            marginRight(8f)
        }
    }
    Text {
        attr {
            text(ctx.currentTime)
            fontSize(18f)
            color(Color(0xFF2196F3))
            fontWeightBold()
        }
    }
}

关键点:

  • 使用 ctx.currentTime 显示时间,这是一个 observable 属性
  • 当定时器更新 currentTime 并调用 syncFlushUI() 时,这里的文本会自动更新

3.5.5 交互按钮区域

kotlin
// 交互按钮区域
View {
    attr {
        flexDirectionRow()
        marginBottom(20f)
        width(pageViewWidth * 0.9f)
        justifyContentSpaceAround()
    }
    
    // 刷新按钮
    Button {
        attr {
            titleAttr {
                text("刷新")
                fontSize(14f)
                color(Color.WHITE)
            }
            backgroundColor(Color(0xFF4CAF50))
            size(width = 100f, height = 40f)
            borderRadius(8f)
        }
        event {
            click {
                ctx.refreshCount++
                // 手动刷新 UI
                ctx.getPager().syncFlushUI()
            }
        }
    }

    // 点击计数按钮
    Button {
        attr {
            titleAttr {
                text("点击计数")
                fontSize(14f)
                color(Color.WHITE)
            }
            backgroundColor(Color(0xFF2196F3))
            size(width = 100f, height = 40f)
            borderRadius(8f)
        }
        event {
            click {
                ctx.clickCount++
                // 手动刷新 UI
                ctx.getPager().syncFlushUI()
            }
        }
    }
}

关键点:

  1. 按钮样式

    • titleAttr:设置按钮标题文本的样式
    • backgroundColor:设置按钮背景色
    • size:设置按钮尺寸
    • borderRadius:设置圆角
  2. 事件处理

    • event { click { ... } }:处理点击事件
    • 在点击事件中更新 observable 状态
    • 必须调用 syncFlushUI() 来刷新 UI,否则状态变化不会反映到界面上

3.5.6 交互统计显示

使用 vif 指令实现条件渲染:

kotlin
// 交互信息显示 - 使用 vif 实现条件渲染
vif({ ctx.clickCount > 0 || ctx.refreshCount > 0 }) {
    View {
        attr {
            backgroundColor(Color(0xFFE3F2FD))
            borderRadius(8f)
            padding(all = 12f)
            marginBottom(20f)
            width(pageViewWidth * 0.9f)
            flexDirectionColumn()
        }
        Text {
            attr {
                text("交互统计")
                fontSize(16f)
                color(Color(0xFF1976D2))
                fontWeightBold()
                marginBottom(8f)
            }
        }
        vif({ ctx.clickCount > 0 }) {
            Text {
                attr {
                    text("按钮点击次数: ${ctx.clickCount}")
                    fontSize(14f)
                    color(Color(0xFF666666))
                    marginTop(4f)
                }
            }
        }
        vif({ ctx.refreshCount > 0 }) {
            Text {
                attr {
                    text("刷新次数: ${ctx.refreshCount}")
                    fontSize(14f)
                    color(Color(0xFF666666))
                    marginTop(4f)
                }
            }
        }
    }
}

vif 指令说明:

  • vif({ condition }) { ... }:根据条件决定是否渲染内容
  • vif 会自动监听条件中的 observable 值变化
  • 当条件从 false 变为 true 时,会渲染内容
  • 当条件从 true 变为 false 时,会移除内容

嵌套使用:

  • 外层 vif 控制整个统计卡片的显示
  • 内层 vif 分别控制点击次数和刷新次数的显示

3.5.7 设备信息容器

kotlin
// 设备信息容器
View {
    attr {
        backgroundColor(Color(0xFFF5F5F5))
        borderRadius(12f)
        padding(all = 20f)
        margin(all = 20f)
        width(pageViewWidth * 0.9f)
        flexDirectionColumn()
    }
    
    // 平台信息
    View {
        attr {
            flexDirectionColumn()
            marginBottom(16f)
        }
        Text {
            attr {
                text("平台信息")
                fontSize(18f)
                color(Color(0xFF333333))
                fontWeightBold()
                marginBottom(8f)
            }
        }
        Text {
            attr {
                text("平台: $platformDesc")
                fontSize(14f)
                color(Color(0xFF666666))
                marginTop(4f)
            }
        }
        Text {
            attr {
                text("系统版本: $osVersion")
                fontSize(14f)
                color(Color(0xFF666666))
                marginTop(4f)
            }
        }
        Text {
            attr {
                text("应用版本: $appVersion")
                fontSize(14f)
                color(Color(0xFF666666))
                marginTop(4f)
            }
        }
    }
    
    // 设备尺寸信息
    View {
        attr {
            flexDirectionColumn()
            marginTop(16f)
            marginBottom(16f)
        }
        Text {
            attr {
                text("设备尺寸")
                fontSize(18f)
                color(Color(0xFF333333))
                fontWeightBold()
                marginBottom(8f)
            }
        }
        Text {
            attr {
                text("设备宽度: ${deviceWidth.toInt()}px")
                fontSize(14f)
                color(Color(0xFF666666))
                marginTop(4f)
            }
        }
        Text {
            attr {
                text("设备高度: ${deviceHeight.toInt()}px")
                fontSize(14f)
                color(Color(0xFF666666))
                marginTop(4f)
            }
        }
        Text {
            attr {
                text("页面宽度: ${pageViewWidth.toInt()}px")
                fontSize(14f)
                color(Color(0xFF666666))
                marginTop(4f)
            }
        }
        Text {
            attr {
                text("页面高度: ${pageViewHeight.toInt()}px")
                fontSize(14f)
                color(Color(0xFF666666))
                marginTop(4f)
            }
        }
        Text {
            attr {
                // 格式化密度值,保留两位小数(Kotlin Native 不支持 String.format)
                val rounded = (density * 100f).toInt() / 100f
                val formattedDensity = if (rounded == rounded.toInt().toFloat()) {
                    rounded.toInt().toString()
                } else {
                    rounded.toString()
                }
                text("屏幕密度: ${formattedDensity}x")
                fontSize(14f)
                color(Color(0xFF666666))
                marginTop(4f)
            }
        }
    }
    
    // 其他信息
    View {
        attr {
            flexDirectionColumn()
            marginTop(16f)
        }
        Text {
            attr {
                text("其他信息")
                fontSize(18f)
                color(Color(0xFF333333))
                fontWeightBold()
                marginBottom(8f)
            }
        }
        Text {
            attr {
                text("状态栏高度: ${pageData.statusBarHeight.toInt()}px")
                fontSize(14f)
                color(Color(0xFF666666))
                marginTop(4f)
            }
        }
        if (pageData.isIphoneX) {
            Text {
                attr {
                    text("设备类型: iPhone X 系列")
                    fontSize(14f)
                    color(Color(0xFF666666))
                    marginTop(4f)
                }
            }
        }
    }
}

关键点:

  1. 密度格式化

    • Kotlin Native 不支持 String.format(),需要手动格式化
    • 使用整数除法和条件判断来格式化浮点数
  2. 条件渲染

    • 使用普通的 if 语句进行条件渲染(非响应式)
    • 适用于静态条件,不需要响应式更新

四、配置平台入口

4.1 HarmonyOS 平台配置

ohosApp/entry/src/main/ets/pages/Index.ets 中配置默认页面:

typescript
build() {
  Stack() {
    if (this.showKuikly) {
      Kuikly({
        pageName: this.pageName ?? 'HomePage', // 设置为 HomePage
        pageData: this.pageData ?? {},
        delegate: this.kuiklyViewDelegate,
        contextCode: this.contextCode,
        executeMode: this.contextCodeHandler.getExecuteMode(this.contextCode),
        onControllerReadyCallback: (controller) => {
          this.kuiklyController = controller;
          // ... 其他配置
        },
        nativeManager: globalNativeManager,
      });
    }
  }.expandSafeArea([SafeAreaType.KEYBOARD]);
}

4.2 Android 平台配置

androidApp/src/main/java/com/tencent/kuikly/android/demo/KuiklyRenderActivity.kt 中配置:

kotlin
private val pageName: String
    get() {
        val pn = intent.getStringExtra(KEY_PAGE_NAME) ?: ""
        return pn.ifEmpty { "HomePage" } // 默认加载 HomePage
    }

五、关键技术点详解

5.1 响应式状态管理

Kuikly 使用 observable 属性委托来实现响应式状态管理:

kotlin
private var clickCount by observable(0)

工作原理:

  1. observable 创建一个可观察的属性
  2. 当属性值改变时,不会自动更新 UI
  3. 需要手动调用 syncFlushUI() 来刷新 UI

为什么需要手动刷新?

  • 性能考虑:避免频繁的 UI 更新
  • 控制更新时机:可以在合适的时机批量更新
  • 跨平台兼容:不同平台的 UI 更新机制不同

5.2 定时器使用

Kuikly 提供了跨平台的 Timer 类:

kotlin
timer = Timer()
timer.schedule(0, 1000) {
    updateTime()
    getPager().syncFlushUI()
}

关键点:

  • schedule(delay, period, action):延迟 delay ms 后,每 period ms 执行一次 action
  • 必须在页面销毁时调用 cancel() 清理定时器
  • 在定时器回调中调用 syncFlushUI() 确保 UI 更新

5.3 条件渲染指令

vif 指令

kotlin
vif({ ctx.clickCount > 0 }) {
    Text { /* ... */ }
}

特点:

  • 自动监听条件中的 observable 值
  • 条件变化时自动添加/移除组件
  • 性能优化:只渲染满足条件的组件

与普通 if 的区别

kotlin
// 普通 if - 静态条件,不会响应式更新
if (pageData.isIphoneX) {
    Text { /* ... */ }
}

// vif - 响应式条件,会监听 observable 变化
vif({ ctx.clickCount > 0 }) {
    Text { /* ... */ }
}

5.4 跨平台兼容性处理

5.4.1 时间 API

kotlin
// ❌ 不推荐:可能在某些平台不支持
val now = System.currentTimeMillis()

// ✅ 推荐:跨平台 API
val now = DateTime.currentTimestamp()

5.4.2 取模运算符

kotlin
// ❌ 可能在某些平台有问题
val hours = (totalSeconds / 3600) % 24

// ✅ 推荐:使用 rem()
val hours = (totalSeconds / 3600).rem(24)

5.4.3 字符串格式化

kotlin
// ❌ Kotlin Native 不支持
val formatted = String.format("%.2f", density)

// ✅ 手动格式化
val rounded = (density * 100f).toInt() / 100f
val formatted = if (rounded == rounded.toInt().toFloat()) {
    rounded.toInt().toString()
} else {
    rounded.toString()
}

六、常见问题与解决方案

6.1 点击按钮没有反应

问题现象: 点击按钮后,计数没有增加,UI 没有更新。

原因分析: 更新 observable 状态后,没有调用 syncFlushUI()

解决方案:

kotlin
event {
    click {
        ctx.clickCount++
        ctx.getPager().syncFlushUI() // 必须调用
    }
}

6.2 定时器导致内存泄漏

问题现象: 页面销毁后,定时器仍在运行。

原因分析: 没有在页面销毁时清理定时器。

解决方案:

kotlin
override fun pageWillDestroy() {
    super.pageWillDestroy()
    if (::timer.isInitialized) {
        timer.cancel() // 清理定时器
    }
}

6.3 Kotlin Native 编译错误

问题现象: 使用 String.format()% 运算符导致编译错误。

原因分析: Kotlin Native 不支持某些 Java API。

解决方案: 使用跨平台兼容的 API,如 rem() 代替 %,手动格式化代替 String.format()

6.4 vbind 语法错误

问题现象: 使用 vbind 时出现类型不匹配错误。

原因分析: vbind 的闭包不接受参数。

错误用法:

kotlin
// ❌ 错误
vbind({ ctx.clickCount }) { count ->
    // ...
}

正确用法:

kotlin
// ✅ 正确方式1:使用 vif
vif({ ctx.clickCount > 0 }) {
    Text {
        attr {
            text("点击次数: ${ctx.clickCount}")
        }
    }
}

// ✅ 正确方式2:vbind 不带参数
vbind({ ctx.clickCount }) {
    Text {
        attr {
            text("点击次数: ${ctx.clickCount}")
        }
    }
}

七、完整代码

完整的 HomePage.kt 代码如下:

kotlin
/*
 * Tencent is pleased to support the open source community by making KuiklyUI
 * available.
 * Copyright (C) 2025 Tencent. All rights reserved.
 * Licensed under the License of KuiklyUI;
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/Tencent-TDS/KuiklyUI/blob/main/LICENSE
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.tencent.kuikly.demo.pages

import com.tencent.kuikly.core.annotations.Page
import com.tencent.kuikly.core.base.Color
import com.tencent.kuikly.core.base.ViewBuilder
import com.tencent.kuikly.core.datetime.DateTime
import com.tencent.kuikly.core.directives.vif
import com.tencent.kuikly.core.reactive.handler.observable
import com.tencent.kuikly.core.timer.Timer
import com.tencent.kuikly.core.utils.PlatformUtils
import com.tencent.kuikly.core.views.Text
import com.tencent.kuikly.core.views.View
import com.tencent.kuikly.core.views.compose.Button
import com.tencent.kuikly.demo.pages.base.BasePager

/**
 * 首页 - 显示 Hello World 和设备信息
 */
@Page("HomePage")
internal class HomePage : BasePager() {

    // 响应式状态
    private var currentTime by observable("")
    private var clickCount by observable(0)
    private var refreshCount by observable(0)
    private lateinit var timer: Timer

    override fun viewDidLoad() {
        super.viewDidLoad()
        // 启动定时器,每秒更新一次时间
        updateTime()
        timer = Timer()
        timer.schedule(0, 1000) {
            updateTime()
            getPager().syncFlushUI()
        }
    }

    override fun pageWillDestroy() {
        super.pageWillDestroy()
        // 清理定时器
        if (::timer.isInitialized) {
            timer.cancel()
        }
    }

    private fun updateTime() {
        val now = DateTime.currentTimestamp()
        val totalSeconds = now / 1000
        // 使用 rem 代替 % 运算符(Kotlin Native 兼容)
        val hours = ((totalSeconds / 3600).rem(24) + 8).rem(24) // 简单计算,实际应使用平台API
        val minutes = (totalSeconds / 60).rem(60)
        val seconds = totalSeconds.rem(60)
        
        // Kotlin Native 不支持 String.format,手动格式化
        val hoursStr = if (hours < 10) "0$hours" else hours.toString()
        val minutesStr = if (minutes < 10) "0$minutes" else minutes.toString()
        val secondsStr = if (seconds < 10) "0$seconds" else seconds.toString()
        currentTime = "$hoursStr:$minutesStr:$secondsStr"
    }

    override fun body(): ViewBuilder {
        val ctx = this
        val pageData = ctx.pagerData
        
        // 获取设备信息
        val platform = PlatformUtils.getPlatform()
        val osVersion = PlatformUtils.getOSVersion()
        val deviceWidth = pageData.deviceWidth
        val deviceHeight = pageData.deviceHeight
        val pageViewWidth = pageData.pageViewWidth
        val pageViewHeight = pageData.pageViewHeight
        val density = pageData.density
        val appVersion = pageData.appVersion
        
        // 构建平台描述
        val platformDesc = when {
            PlatformUtils.isAndroid() -> "Android"
            PlatformUtils.isIOS() -> "iOS"
            PlatformUtils.isMacOS() -> "macOS"
            PlatformUtils.isOhOs() -> "HarmonyOS"
            else -> platform
        }
        
        return {
            attr {
                backgroundColor(Color.WHITE)
                flexDirectionColumn()
                alignItemsCenter()
                justifyContentCenter()
            }
            
            // Hello World 标题
            View {
                attr {
                    marginBottom(20f)
                }
                Text {
                    attr {
                        text("Hello World")
                        fontSize(48f)
                        color(Color(0xFF2196F3))
                        fontWeightBold()
                    }
                }
            }

            // 实时时间显示
            View {
                attr {
                    marginBottom(20f)
                    flexDirectionRow()
                    alignItemsCenter()
                }
                Text {
                    attr {
                        text("当前时间: ")
                        fontSize(16f)
                        color(Color(0xFF666666))
                        marginRight(8f)
                    }
                }
                Text {
                    attr {
                        text(ctx.currentTime)
                        fontSize(18f)
                        color(Color(0xFF2196F3))
                        fontWeightBold()
                    }
                }
            }

            // 交互按钮区域
            View {
                attr {
                    flexDirectionRow()
                    marginBottom(20f)
                    width(pageViewWidth * 0.9f)
                    justifyContentSpaceAround()
                }
                
                // 刷新按钮
                Button {
                    attr {
                        titleAttr {
                            text("刷新")
                            fontSize(14f)
                            color(Color.WHITE)
                        }
                        backgroundColor(Color(0xFF4CAF50))
                        size(width = 100f, height = 40f)
                        borderRadius(8f)
                    }
                    event {
                        click {
                            ctx.refreshCount++
                            // 手动刷新 UI
                            ctx.getPager().syncFlushUI()
                        }
                    }
                }

                // 点击计数按钮
                Button {
                    attr {
                        titleAttr {
                            text("点击计数")
                            fontSize(14f)
                            color(Color.WHITE)
                        }
                        backgroundColor(Color(0xFF2196F3))
                        size(width = 100f, height = 40f)
                        borderRadius(8f)
                    }
                    event {
                        click {
                            ctx.clickCount++
                            // 手动刷新 UI
                            ctx.getPager().syncFlushUI()
                        }
                    }
                }
            }

            // 交互信息显示 - 使用 vif 实现条件渲染
            vif({ ctx.clickCount > 0 || ctx.refreshCount > 0 }) {
                View {
                    attr {
                        backgroundColor(Color(0xFFE3F2FD))
                        borderRadius(8f)
                        padding(all = 12f)
                        marginBottom(20f)
                        width(pageViewWidth * 0.9f)
                        flexDirectionColumn()
                    }
                    Text {
                        attr {
                            text("交互统计")
                            fontSize(16f)
                            color(Color(0xFF1976D2))
                            fontWeightBold()
                            marginBottom(8f)
                        }
                    }
                    vif({ ctx.clickCount > 0 }) {
                        Text {
                            attr {
                                text("按钮点击次数: ${ctx.clickCount}")
                                fontSize(14f)
                                color(Color(0xFF666666))
                                marginTop(4f)
                            }
                        }
                    }
                    vif({ ctx.refreshCount > 0 }) {
                        Text {
                            attr {
                                text("刷新次数: ${ctx.refreshCount}")
                                fontSize(14f)
                                color(Color(0xFF666666))
                                marginTop(4f)
                            }
                        }
                    }
                }
            }
            
            // 设备信息容器
            View {
                attr {
                    backgroundColor(Color(0xFFF5F5F5))
                    borderRadius(12f)
                    padding(all = 20f)
                    margin(all = 20f)
                    width(pageViewWidth * 0.9f)
                    flexDirectionColumn()
                }
                
                // 平台信息
                View {
                    attr {
                        flexDirectionColumn()
                        marginBottom(16f)
                    }
                    Text {
                        attr {
                            text("平台信息")
                            fontSize(18f)
                            color(Color(0xFF333333))
                            fontWeightBold()
                            marginBottom(8f)
                        }
                    }
                    Text {
                        attr {
                            text("平台: $platformDesc")
                            fontSize(14f)
                            color(Color(0xFF666666))
                            marginTop(4f)
                        }
                    }
                    Text {
                        attr {
                            text("系统版本: $osVersion")
                            fontSize(14f)
                            color(Color(0xFF666666))
                            marginTop(4f)
                        }
                    }
                    Text {
                        attr {
                            text("应用版本: $appVersion")
                            fontSize(14f)
                            color(Color(0xFF666666))
                            marginTop(4f)
                        }
                    }
                }
                
                // 设备尺寸信息
                View {
                    attr {
                        flexDirectionColumn()
                        marginTop(16f)
                        marginBottom(16f)
                    }
                    Text {
                        attr {
                            text("设备尺寸")
                            fontSize(18f)
                            color(Color(0xFF333333))
                            fontWeightBold()
                            marginBottom(8f)
                        }
                    }
                    Text {
                        attr {
                            text("设备宽度: ${deviceWidth.toInt()}px")
                            fontSize(14f)
                            color(Color(0xFF666666))
                            marginTop(4f)
                        }
                    }
                    Text {
                        attr {
                            text("设备高度: ${deviceHeight.toInt()}px")
                            fontSize(14f)
                            color(Color(0xFF666666))
                            marginTop(4f)
                        }
                    }
                    Text {
                        attr {
                            text("页面宽度: ${pageViewWidth.toInt()}px")
                            fontSize(14f)
                            color(Color(0xFF666666))
                            marginTop(4f)
                        }
                    }
                    Text {
                        attr {
                            text("页面高度: ${pageViewHeight.toInt()}px")
                            fontSize(14f)
                            color(Color(0xFF666666))
                            marginTop(4f)
                        }
                    }
                    Text {
                        attr {
                            // 格式化密度值,保留两位小数(Kotlin Native 不支持 String.format)
                            val rounded = (density * 100f).toInt() / 100f
                            val formattedDensity = if (rounded == rounded.toInt().toFloat()) {
                                rounded.toInt().toString()
                            } else {
                                rounded.toString()
                            }
                            text("屏幕密度: ${formattedDensity}x")
                            fontSize(14f)
                            color(Color(0xFF666666))
                            marginTop(4f)
                        }
                    }
                }
                
                // 其他信息
                View {
                    attr {
                        flexDirectionColumn()
                        marginTop(16f)
                    }
                    Text {
                        attr {
                            text("其他信息")
                            fontSize(18f)
                            color(Color(0xFF333333))
                            fontWeightBold()
                            marginBottom(8f)
                        }
                    }
                    Text {
                        attr {
                            text("状态栏高度: ${pageData.statusBarHeight.toInt()}px")
                            fontSize(14f)
                            color(Color(0xFF666666))
                            marginTop(4f)
                        }
                    }
                    if (pageData.isIphoneX) {
                        Text {
                            attr {
                                text("设备类型: iPhone X 系列")
                                fontSize(14f)
                                color(Color(0xFF666666))
                                marginTop(4f)
                            }
                        }
                    }
                }
            }
        }
    }
}

八、测试与验证

8.1 编译项目

在项目根目录执行构建脚本:

bash
# HarmonyOS 平台
./2.0_ohos_demo_build.sh

# Android 平台
./gradlew :androidApp:assembleDebug

8.2 功能验证清单

  • Hello World 标题正常显示
  • 实时时间每秒更新
  • 点击"刷新"按钮,刷新计数增加
  • 点击"点击计数"按钮,点击计数增加
  • 交互统计卡片在首次交互后显示
  • 设备信息正确显示(平台、版本、尺寸等)
  • 在 HarmonyOS 设备上正常运行
  • 在 Android 设备上正常运行
  • 页面销毁时定时器正确清理

九、总结

本文详细介绍了如何在腾讯 Kuikly 跨平台框架中实现一个功能丰富的交互式首页,并确保在 HarmonyOS 平台上的完美运行。主要技术点包括:

9.1 核心技术要点

  1. 响应式状态管理:使用 observable 属性委托创建响应式状态
  2. 定时器处理:使用跨平台 Timer API,注意生命周期管理
  3. 条件渲染:使用 vif 指令实现响应式条件渲染
  4. 跨平台兼容:使用 Kuikly 提供的跨平台 API,避免平台特定代码
  5. UI 刷新机制:理解并正确使用 syncFlushUI() 方法

9.2 最佳实践

  1. 使用跨平台 API:优先使用 Kuikly 提供的跨平台 API,如 DateTime.currentTimestamp()
  2. 避免平台特定代码:避免使用 System.currentTimeMillis()String.format()
  3. 生命周期管理:及时清理资源,避免内存泄漏
  4. 手动刷新 UI:更新 observable 状态后,记得调用 syncFlushUI()
  5. 条件渲染选择:静态条件用 if,响应式条件用 vif

十、参考资料

基于 Kuikly 2.0.21