项目地址:GitHub
从最早的 Android 入门教程,到 iOS SwiftUI 官方示例,再到 React、Flutter、鸿蒙... 天气应用几乎是所有 UI 框架的"Hello World Plus"------它足够简单,能让你快速上手;又足够丰富,涵盖了数据展示、图表绑定等核心技能。
今天,我们用 Kuikly 来完成这个demo:用一套 Kotlin 代码,画一个能在 Android、iOS、鸿蒙三端运行的雨量预测图表。
别担心,这篇文章专门为新手准备,我们会从最基础的概念讲起。准备好了吗?Let's go!
我们要做什么?
先看看最终效果:一张带毛玻璃效果的卡片,上面有一条蓝色的曲线,展示未来 90 分钟的雨量变化。左边是雨量等级(暴雨、大雨、中雨、小雨),底部是时间轴(现在、30分钟、60分钟、90分钟)。

是不是很眼熟?没错,这就是各大天气 App 里那个"分钟级降水预报"的同款!
第一步:搭建页面骨架
在 Kuikly 中,一个页面就是一个继承自 BasePager 的类:
kotlin
@Page("WeatherCanvasPage") // 给页面取个名字,用于路由跳转
internal class WeatherCanvasPage : BasePager() {
override fun body(): ViewBuilder {
return {
// 页面内容写在这里
}
}
}
tips:
-
@Page("xxx")就像给页面起了个门牌号 -
body()方法返回页面的 UI 结构,所有组件都写在大括号里
第二步:铺一张好看的背景图
天气应用嘛,氛围感很重要!
kotlin
override fun body(): ViewBuilder {
return {
attr {
flex(1f) // flex 弹性布局
backgroundColor(Color(0x436082, 1f)) // 蓝灰色底色
}
// 背景图
Image {
attr {
src("https://qq-weather.cdn-go.cn/weather/latest/rain-bg/rain-comming.png")
positionAbsolute() // 绝对定位
left(0f); right(0f); top(0f); bottom(0f) // 铺满全屏
}
}
}
}
tips:
-
attr { }用来设置组件样式,类似 CSS -
positionAbsolute()+ 四边为 0 = 铺满父容器
第三步:自定义组件
接下来我们要添加图表卡片。但 RainFallNodeCanvas 不是内置组件,而是我们自己造的!
这就是 Kuikly 的魅力:把复杂 UI 封装成可复用的积木块。
一个自定义组件需要三样东西:
| 组成部分 | 作用 |
|---|---|
| 属性类 (Attr) | 定义组件接收什么数据 |
| 组件类 (ComposeView) | 定义组件长什么样 |
| 扩展函数 | 让组件用起来更顺手 |
3.1 定义属性:这个组件需要什么数据?
kotlin
class RainFallNodeAttr : ComposeAttr() {
// 雨量数据:24个点,代表未来90分钟的降水量
var nodes: List<Float> by observable(
listOf(1.3f, 2.6f, 4.2f, 5.1f, 3.5f, ...)
)
// 顶部提示文字
var title: String by observable("雨渐大,30分钟后雨会停")
// Y轴标签
var rainLevel1Title: String by observable("暴雨")
var rainLevel2Title: String by observable("大雨")
var rainLevel3Title: String by observable("中雨")
var rainLevel4Title: String by observable("小雨")
}
划重点 :by observable(...) 是 Kuikly 的响应式魔法!数据一变,UI 自动刷新,不用手动调用任何刷新方法。
3.2 定义组件:它长什么样?
kotlin
class WeatherRainFallNodeCanvas : ComposeView<RainFallNodeAttr, ComposeEvent>() {
private val nodeAttr = RainFallNodeAttr()
override fun body(): ViewBuilder {
val ctx = this
return {
attr {
size(335f, 250f) // 卡片大小
borderRadius(BorderRectRadius(16f, 16f, 16f, 16f)) // 圆角
}
// 1. 毛玻璃背景
Blur {
attr {
absolutePosition(0f, 0f, 0f, 0f)
blurRadius(10.0f)
}
}
// 2. 顶部提示文字
Text { attr { text(ctx.nodeAttr.title) } }
// 3. Canvas 画布(核心!)
Canvas({ ... }) { context, width, height ->
// 在这里画曲线
}
// 4. Y轴标签:暴雨、大雨...
// 5. X轴标签:现在、30分钟...
}
}
override fun createAttr() = nodeAttr
override fun createEvent() = ComposeEvent()
}
第四步:用 Canvas 画曲线(核心!)
Canvas 就是一块"画布",你可以在上面画任何东西。
4.1 Canvas 基本用法
kotlin
Canvas(
{ attr { absolutePosition(55f, 0f, 24f, 0f) } }
) { context, width, height ->
// context = 画笔
// width, height = 画布尺寸
}
4.2 画一条丝滑的曲线
直接用直线连接数据点会很丑(像心电图),我们用贝塞尔曲线让它丝滑:
kotlin
// 1. 设置画笔
context.beginPath()
context.strokeStyle(Color(0x0085FF, 1f)) // 天空蓝
context.lineWidth(5.0f) // 线条粗细
context.lineCapRound() // 圆头端点
context.moveTo(0f, height) // 起点:左下角
// 2. 遍历数据点,画曲线
for ((index, node) in nodeAttr.nodes.withIndex()) {
val x = index * width / 24 // X坐标
val y = height - node * height / 10 // Y坐标(数据映射)
// 用贝塞尔曲线连接,比直线更丝滑
context.quadraticCurveTo(controlX, controlY, nextX, nextY)
}
// 3. 完成绑定
context.stroke()
什么是贝塞尔曲线? 简单说:
-
直线:A → B,走直线
-
贝塞尔:A → B,但要"绕过"控制点 C,形成弧线
4.3 画虚线(参考线)
没有虚线 API?没关系,手搓一个:
kotlin
private fun renderDashLine(context: CanvasContext, width: Float, y: Float) {
context.beginPath()
context.strokeStyle(Color(0xffffff, 0.2f)) // 半透明白
var x = 10f
while (x < width) {
context.lineTo(x + 5f, y) // 画 5 像素
x += 7.5f
context.moveTo(x, y) // 跳过 2.5 像素
}
context.stroke()
}
原理:画一段、跳一段、画一段、跳一段...
第五步:让组件更好用
写个扩展函数,让自定义组件用起来和内置组件一样:
kotlin
internal fun ViewContainer<*, *>.RainFallNodeCanvas(
init: WeatherRainFallNodeCanvas.() -> Unit
) {
addChild(WeatherRainFallNodeCanvas(), init)
}
现在可以这样用了:
kotlin
RainFallNodeCanvas {
attr {
positionAbsolute()
top(178.5f)
left(20f)
}
}
和 Text、Image 一模一样的写法!
总结
恭喜完成 Kuikly "天气App"!回顾一下核心技能:
| 技能 | 总结 |
|---|---|
| 页面定义 | 继承 BasePager + @Page 注解 |
| 布局系统 | attr { } 设样式,positionAbsolute() 绝对定位 |
| 自定义组件 | 继承 ComposeView,定义 Attr + body() |
| 响应式数据 | by observable() 数据变 → UI 自动刷新 |
| Canvas bindbindbindbindbindbindbindind | 回调式 API,拿到 context 就能画 |
| 贝塞尔曲线 | quadraticCurveTo() 让曲线更丝滑 |
| 毛玻璃效果 | Blur 组件一行搞定 |
下次产品经理甩给你"参考天气 App 做个图表"的需求,你就可以自信地说:小场面!
代码已经躺在 GitHub 上了,还不快去 Clone 下来跑一跑?