/** * 第 6 章配套代码 · 消息列表(List + vfor + 下拉刷新 + 上拉加载) * * 文件位置:shared/src/commonMain/kotlin/com/example/kuikly/pages/MessagePage.kt * * 本示例展示长列表的"标配能力": * - List + vfor 长列表复用机制 * - Refresh 下拉刷新 * - scrollEnd 上拉加载更多 * - 状态机 loading/empty/success 三态切换 */ package com.example.kuikly.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.directives.vfor import com.tencent.kuikly.core.pager.Pager import com.tencent.kuikly.core.reactive.collection.observableListOf import com.tencent.kuikly.core.reactive.handler.observable import com.tencent.kuikly.core.views.Image import com.tencent.kuikly.core.views.List import com.tencent.kuikly.core.views.Refresh import com.tencent.kuikly.core.views.Text import com.tencent.kuikly.core.views.View data class Message( val id: Int, val avatar: String, val name: String, val content: String, val time: String, val unread: Int, ) @Page("MessagePage") internal class MessagePage : Pager() { private val messages = observableListOf() private var loading by observable(true) private var refreshing by observable(false) private var loadingMore by observable(false) private var page = 1 private var nextId = 1 override fun created() { super.created() // 模拟首屏加载 messages.addAll(mockData(page = 1, count = 20)) loading = false } override fun body(): ViewBuilder { val ctx = this return { attr { backgroundColor(Color(0xFFF5F5F7L)) } // ─── 顶栏 ───────────────────────── View { attr { height(48f) flexDirectionRow() allCenter() backgroundColor(Color.WHITE) } Text { attr { text("消息") fontSize(17f) fontWeightBold() color(Color.BLACK) } } } // ─── 内容区:状态机切换 ────────── when { ctx.loading -> renderLoading().invoke(this) ctx.messages.isEmpty() -> renderEmpty().invoke(this) else -> renderList(ctx).invoke(this) } } } /** ── 状态 1:加载中 ── */ private fun renderLoading(): ViewBuilder = { View { attr { flex(1f); allCenter() } Text { attr { text("⏳ 加载中…") fontSize(14f) color(Color(0xFF9E9E9EL)) } } } } /** ── 状态 2:空数据 ── */ private fun renderEmpty(): ViewBuilder = { View { attr { flex(1f); allCenter() } Text { attr { text("📭") fontSize(40f) marginBottom(8f) } } Text { attr { text("暂无消息") fontSize(14f) color(Color(0xFF9E9E9EL)) } } } } /** ── 状态 3:列表 ── */ private fun renderList(ctx: MessagePage): ViewBuilder = { List { attr { flex(1f) paddingTop(8f) } event { scrollEnd { e -> // 触底判断 val nearBottom = e.offsetY + e.viewSize.height >= e.contentSize.height - 100f if (nearBottom && !ctx.loadingMore) { ctx.loadMore() } } } // ★ 下拉刷新 header Refresh { attr { refreshing(ctx.refreshing) } event { refresh { ctx.refresh() } } Text { attr { text(if (ctx.refreshing) "刷新中…" else "↓ 下拉刷新") fontSize(12f) color(Color(0xFF9E9E9EL)) textAlignCenter() paddingVertical(12f) } } } // ★ vfor 渲染列表 vfor({ ctx.messages }) { msg -> renderMessageRow(msg).invoke(this) } // ★ 加载更多 footer if (ctx.loadingMore) { View { attr { height(50f) allCenter() } Text { attr { text("加载更多…") color(Color(0xFF9E9E9EL)) fontSize(12f) } } } } } } /** ── 单条消息行(抽出来便于复用 + 阅读)── */ private fun renderMessageRow(msg: Message): ViewBuilder = { View { attr { flexDirectionRow() padding(12f) backgroundColor(Color.WHITE) marginBottom(1f) // 1px 分割线效果 } event { click { /* TODO 进入聊天详情 */ } } // 头像 Image { attr { src(msg.avatar) size(48f, 48f) borderRadius(24f) backgroundColor(Color(0xFFE0E0E0L)) marginRight(12f) } } // 中间:名字 + 内容 View { attr { flex(1f); justifyContentCenter() } View { attr { flexDirectionRow() justifyContentSpaceBetween() alignItemsCenter() } Text { attr { text(msg.name) fontSize(15f) fontWeightBold() color(Color.BLACK) } } Text { attr { text(msg.time) fontSize(11f) color(Color(0xFF9E9E9EL)) } } } Text { attr { text(msg.content) fontSize(13f) color(Color(0xFF757575L)) marginTop(4f) numberOfLines(1) textOverFlowEllipsis() } } } // 未读红点 if (msg.unread > 0) { View { attr { minWidth(18f) height(18f) allCenter() paddingHorizontal(6f) borderRadius(9f) backgroundColor(Color(0xFFFF3B30L)) marginLeft(8f) alignSelfCenter() } Text { attr { text(if (msg.unread > 99) "99+" else "${msg.unread}") fontSize(10f) color(Color.WHITE) fontWeightBold() } } } } } } // ─── 业务方法 ─────────────────────── private fun refresh() { refreshing = true page = 1 nextId = 1 messages.clear() messages.addAll(mockData(page = 1, count = 20)) refreshing = false } private fun loadMore() { loadingMore = true page++ messages.addAll(mockData(page = page, count = 20)) loadingMore = false } private fun mockData(page: Int, count: Int): List { return (1..count).map { i -> Message( id = nextId++, avatar = "https://example.com/avatar/$i.png", name = "用户$nextId", content = "第 $page 页的第 $i 条消息内容…", time = "${(11 + i % 12)}:${"%02d".format(i % 60)}", unread = if (i % 5 == 0) i % 100 else 0, ) } } }