ArkUI List × Scroller:平行视界筛选后阅读锚点稳定恢复【鸿蒙心迹】
把文档目录放进大屏的左栏右栏显示正文这种结构本身并不难。真正让人觉得别扭的往往是一个很小的动作读者正在看第十六份文档随手切换到“待处理”筛选再返回目录列表却跳到了第一条。右边文章还在左边却找不到刚才那份材料用户需要重新滑动。对阅读类应用来说这种丢失位置的感觉比布局从两栏变成一栏更明显。这篇用SplitLedger作为演示项目专门处理这个问题。它不模拟系统提供了某种自动保持滚动位置的神奇能力而是把业务层的稳定文档 ID和 ArkUIList的当前索引分开保存。示例含 36 份文档其中 18 份属于“待处理”。读者的锚点为DOC_016筛选前它的索引是 15筛选后是 7最后状态显示RESTORED。这些数字是为了解释算法而固定的示例不是实测性能数据。图 01平行视界目录筛选与阅读锚点恢复的技术封面左右列表只是同一逻辑的示意。## 一、索引不是身份过滤以后尤其不是最容易写的方案是记一个firstIndex离开页面时存起来回来执行scrollToIndex(firstIndex)。这种写法在数据不变时通常能工作换成筛选列表就不可靠了。最极端的情况是筛选前有 36 条筛选后只剩 18 条原先第 27 项甚至可能越界。即便没有越界原先索引 15 的位置也早已不是同一份文档。所以需要保存两个值滚动位置属于视图稳定 ID 属于数据。锚点应该存成DOC_016需要恢复时再到当前筛选结果里计算它的新索引。这样的映射逻辑与屏幕宽度、列数以及列表项样式都无关后续从手机单栏切到折叠屏双栏也不必重写规则。先做一个不依赖 ArkUI 的小模块。它解决的是筛选后从稳定 ID 重新定位而不是在业务层记录一个随时可能过期的数字。arktsinterface DocItem { id: string title: string pending: boolean}function makeDocs(): DocItem[] { const rows: DocItem[] [] for (let i 1; i 36; i) { const id DOC_${String(i).padStart(3, 0)} rows.push({ id, title: i 16 ? 接口联调问题记录 : 文档${i}, pending: i % 2 0 }) } return rows}class AnchorKeeper { private anchorId: string remember(id: string): void { if (id.length 0) { this.anchorId id } } resolve(items: DocItem[]): number { return items.findIndex((item: DocItem) item.id this.anchorId) } currentId(): string { return this.anchorId }}findIndex() 返回的是当前结果数组里的索引不是原始数组的旧索引。返回 -1 也不是异常如果 DOC_016 已经被当前筛选条件排除就没有理由强行把它滚到某个“差不多”的位置。生产环境可以设计降级方案例如定位到与锚点相邻的可见项目、清除筛选或提示“当前条件下不可见”但必须让选择规则显式、可解释。## 二、让 List 的滚动事件只负责观测位置华为 ArkUI 的 List 可以绑定 Scroller通过 onScrollIndex 获得列表可见索引也可以调用 scrollToIndex() 滚动到指定项。这里真正需要警惕的是把观察与恢复写进同一个循环筛选导致列表重排滚动事件更新锚点随后恢复函数反而找到了一个已经被替换的 ID。因此示例先把待恢复的 ID 冻结再替换数组。下面代码重点解决两个动作的分工onScrollIndex 观测当前可见项applyFilter 计算新索引恢复动作由数据更新后的显式按钮触发。为保持文章短而可读makeDocs() 只是业务数据准备函数在真实项目中由本地仓储或分页接口提供不是平台 API。arktsEntryComponentstruct SplitLedgerPage { State docs: DocItem[] makeDocs() // 演示数据36 项 State restoreIndex: number -1 State status: string ‘READY’ private scroller: Scroller new Scroller() private keeper: AnchorKeeper new AnchorKeeper() private savedId: string ‘’ private allDocs: DocItem[] makeDocs() private filtering: boolean false private applyFilter(): void { this.savedId this.keeper.currentId() || ‘DOC_016’ this.filtering true this.docs this.allDocs.filter((doc: DocItem) doc.pending) this.keeper.remember(this.savedId) this.restoreIndex this.keeper.resolve(this.docs) this.status ‘READY’ } private restore(): void { if (this.restoreIndex 0) { this.status ‘NOT_FOUND’ return } this.scroller.scrollToIndex(this.restoreIndex) this.status ‘RESTORED’ this.filtering false } build() { Column({ space: 12 }) { Button(‘筛选待处理’).onClick(() this.applyFilter()) List({ scroller: this.scroller }) { ForEach(this.docs, (item: DocItem) { ListItem() { Text(${item.id} ${item.title}) } }, (item: DocItem) item.id) } .onScrollIndex((first: number) { if (!this.filtering this.docs[first]) { this.keeper.remember(this.docs[first].id) } }) Button(‘恢复阅读位置’).onClick(() this.restore()) Text(this.status) } }}这个 Demo 特意把“筛选”和“恢复”做成两个操作避免在修改数据数组的同一个同步回调里立刻调用滚动指令。实际产品可以在列表完成布局更新后自动恢复但要根据所用 ArkUI 版本选择明确的布局完成时机而不是依赖一个没有保证的零毫秒延迟。示例也没有处理分组列表如果目录引入 ListItemGroup组本身会占据索引应重新设计映射关系。![](https://i-blog.csdnimg.cn/direct/4ea934d4868a4c059b89071a0f12e777.png)*图 02DevEco Studio 白色主题风格演示图。左侧 SplitLedger 工程树、中间稳定 ID 恢复代码、右侧模拟器和底部 HiLog 的数值均是人工设定的调试示意不是实际运行证据。*## 三、大屏的双栏和手机的单栏数据锚点应该共用平行视界界面很容易把左侧目录选中项与滚动首项混在一起。其实二者并不完全相同右侧正文可能停留在 DOC_016左侧用户又滑动浏览到其它条目。用户再次点击某个文档时需要更新**阅读选中项**用户手动滚动时需要更新**可见锚点**。如果强行用一个状态变量控制两者筛选和回退就会互相牵连。我更倾向于把选中项视为业务状态把阅读锚点视为界面状态。两栏、单栏可以共享 selectedId但每种形态的 scrollAnchorId 可以分别保存。本文为了聚焦问题用 DOC_016 同时展示阅读项和恢复锚点正式应用应该在数据模型中保留两者的区别。尤其要注意用户主动触发筛选之后与系统因窗口形态变化触发的重排不应该无条件使用同一恢复策略。代码还需要解释列表重排时的额外风险有些业务会排序、合并或插入消息。恢复的目标应该是**最稳定的记录标识**不能由 index、标题文案或 UI 节点生成。下面给出纯数据层的最小回归校验解决“筛选之后确实能把 ID 转换为预期索引”的问题。这段 JavaScript 只作逻辑示例无需依赖设备。typescriptconst rows Array.from({ length: 36 }, (_, i) ({ id:DOC_${String(i 1).padStart(3, 0)}, pending: (i 1) % 2 0}));const previousIndex rows.findIndex(row row.id ‘DOC_016’);const filtered rows.filter(row row.pending);const currentIndex filtered.findIndex(row row.id ‘DOC_016’);console.log({ previousIndex, currentIndex, filteredCount: filtered.length });// 预期{ previousIndex: 15, currentIndex: 7, filteredCount: 18 }这个检查的好处是不会掺杂 UI 渲染时序。如果这里先算错了索引后续再调Scroller也不可能得到稳定结果。另一方面纯数据检查通过不等于真机视觉位置完全一致。实际列表项高度不同、资源未加载完成、动态字号变化都可能让锚点落点产生可见偏移必须在目标设备上继续验证。## 四、把异常路径当作正式测试的一部分示例截图展示的最终状态统一是RESTORED筛选“待处理18”后定位DOC_016恢复索引是 7。不能因为截图是绿色成功状态就只覆盖成功路径。至少还需要人为构造三种反例锚点 ID 不在筛选结果中恢复期间第二次触发筛选用户在恢复前先手动滑动。第一种应该得到NOT_FOUND不能盲目复用旧索引第二种需要版本号或取消机制第三种应尊重最新的用户操作避免自动滚动抢走控制权。图 039:16 纯屏演示图以DOC_016、新索引 7 和RESTORED作为一致性检查点。演示并不表示已经完成真机适配测试。## 五、这套办法真正解决的边界这不是一篇完整的平行视界系统能力接入说明而是一个能在手机列表和双栏界面中复用的滚动恢复策略。它把“记住我在看什么”和“把列表滚到哪里”拆开不假设过滤前后的数组下标永远相等。Scroller负责视图定位业务主键负责身份判定二者相互配合但职责不能互换。接入真实项目时还应检查长列表虚拟化、列表项异步高度变化以及系统返回后页面对象是否被重建。对于极长目录可以在数据仓储层维护 ID 到索引的映射避免每次线性查找但只有性能测量证明它有价值时才需要额外缓存。代码中的示例数据和日志均为演示尚未在 DevEco Studio 完成编译与设备实测。资料参考华为开发者联盟《创建列表List》 https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-layout-development-create-list 。

相关新闻

ArkUI Grid:折叠屏图库滑动多选稳定ID与布局切换【鸿蒙心迹】

ArkUI Grid:折叠屏图库滑动多选稳定ID与布局切换【鸿蒙心迹】

相册类应用做成两列网格并不难,真正容易出问题的是用户正在批量选图的时候把折叠屏展开。折叠前第三张照片在第二行,展开后同一张可能移到第一行;如果业务把“第几个格子被选中”作为记录,布局重排以后,勾选标记就会落…

2026/10/16 21:21:41 阅读更多 →
深度学习训练全流程排查:从数据预处理到模型评估的实践指南

深度学习训练全流程排查:从数据预处理到模型评估的实践指南

从上次整理完第一份深度学习笔记到现在,隔了挺长时间。这段时间里我自己试了不少网络结构,也帮身边几个在做项目的同学盯过他们的训练过程,发现一个特别常见的现象:论文里的模型看着都懂,跑开源代码也能出结果&#xf…

2026/10/16 21:20:40 阅读更多 →
井震联合字典学习:稀疏表示实现地震数据高分辨率重建

井震联合字典学习:稀疏表示实现地震数据高分辨率重建

1. 井震联合字典学习到底在解决什么问题第一次看到"井震联合字典学习"这个组合词,很多人会愣一下:字典学习不是图像处理里的东西吗,怎么跟地震勘探扯上关系了?我当初接手这个方向的时候也有同样的疑问。简单说&#xff…

2026/10/16 21:20:40 阅读更多 →

最新新闻

Python爬虫实战:从网页剖析到数据落库的完整路径

Python爬虫实战:从网页剖析到数据落库的完整路径

简介:Python网络数据抓取代码是一份面向Python初学者的爬虫与数据采集实践包,适合数据分析师、数据科学家、机器学习与自然语言处理方向入门者自学,也可用于网站内容采集、电商信息监控、舆情监测、市场调研等典型任务。资源围绕网络请求、页…

2026/10/16 21:58:15 阅读更多 →
Linux防火墙完全配置指南:iptables、firewalld与ufw详解

Linux防火墙完全配置指南:iptables、firewalld与ufw详解

1. 为什么Linux服务器需要防火墙:先弄清楚它在挡什么先讲一个我实际遇到过的场景。某开发者给公司内部部署了一套应用,把服务跑在8080端口,自以为端口开了就万事大吉。结果第二天用户反馈访问不了——排查到最后才发现,云厂商安全…

2026/10/16 21:58:15 阅读更多 →
信息学奥赛课课通C++配套资料:用好测试数据从不浪费题库

信息学奥赛课课通C++配套资料:用好测试数据从不浪费题库

简介:《信息学奥赛课课通(C)》完整配套资料包,面向准备参加信息学竞赛(NOIP、CSP等)的中学生和C初学者,内容系统覆盖C语法基础、STL容器、面向对象编程及搜索排序等竞赛常考算法。整个rar压缩包…

2026/10/16 21:58:15 阅读更多 →
Cesium 入门指南:从三维地球到 WebGIS 开发的完整教程

Cesium 入门指南:从三维地球到 WebGIS 开发的完整教程

简介:面向具备一定Web前端基础、希望入门三维地球开发的技术人员,这份Cesium学习教程PPT以入门级教学为核心,系统梳理了从开发环境搭建到核心API应用的全流程,可帮助开发团队快速搭建虚拟地球Web应用。内容从Node、VSCode、Nginx等…

2026/10/16 21:58:15 阅读更多 →
Logistic回归二分类实验全复盘:从原理推导到PyTorch实现

Logistic回归二分类实验全复盘:从原理推导到PyTorch实现

实验三的任务是logistic回归预测二分类。说实话,刚开始我有点看不上这个实验,毕竟周围同学已经开始讨论各种“网络结构”了,而我还在跟一条S曲线打交道。但真正把代码跑起来、把loss曲线拉出来看之后,我的想法完全变了。这个实验表…

2026/10/16 21:58:15 阅读更多 →
TurboVLA 0.2B模型如何实现32Hz实时推理与0.9GB显存优化

TurboVLA 0.2B模型如何实现32Hz实时推理与0.9GB显存优化

1. TurboVLA 到底解决了什么问题第一次看到“0.2B 参数、32Hz 实时推理、RTX 4090 只占 0.9GB 显存”这组数字,我的反应是:这要么是标题党,要么是在某个特定任务上做了极致裁剪。因为按常规经验,一个 0.2B 的模型想在消费级卡上跑…

2026/10/16 21:57:14 阅读更多 →

日新闻

个人 AI Agent 平台接入 TaoToken:统一 Key 与 API 通道的配置大纲

个人 AI Agent 平台接入 TaoToken:统一 Key 与 API 通道的配置大纲

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 0:00:05 阅读更多 →
信号与系统频域分析作业解题指南

信号与系统频域分析作业解题指南

1. 作业背景与核心要求解析信号与系统作为电子信息类专业的核心基础课,其作业设计往往直接关联课程的核心知识框架。2026年春季第六次作业的布置,通常对应着课程进展到频域分析的关键阶段。根据常规教学进度推测,本次作业可能涉及以下核心内容…

2026/10/16 0:00:05 阅读更多 →
Django MVT实战:从零构建图书借阅系统,彻底搞懂Model-View-Template分层

Django MVT实战:从零构建图书借阅系统,彻底搞懂Model-View-Template分层

1. 从一个"四不像"的练手项目说起如果你已经看完了Django的官方教程前几章,跟着敲完了投票应用,大概会有一种"我好像会了,又好像什么都不会"的割裂感。教程里每个知识点都是拆开的——这一章讲模型,下一章讲视…

2026/10/16 0:00:05 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/16 21:35:51 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/15 19:41:51 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/16 15:04:24 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 21:54:38 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 3:49:43 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 14:23:36 阅读更多 →