缩略图预览条水平 Scroll 选中边框高亮前言在「海风日记」的图片查看器中主图区域用Swiper切换但用户往往需要快速跳转到「第 5 张」或「最后一张」。如果只靠左右滑动体验会很糟糕。为此App 在主图下方放置了一条缩略图预览条Thumbnail Strip水平可滚动的小图列表选中项带白色边框高亮。用户点击任意缩略图主图立即跳转。本文将从ImageViewerPage.ets源码出发深入讲解水平Scroll Row的滚动容器缩略图的尺寸、圆角、间距规范选中边框的两种状态2vp 白边 / 无边Scroller.scrollTo自动滚动到当前项与主图Swiper的双向同步一个好的缩略图条应当让用户「看一眼就知道自己在哪、点一下就能跳过去」。一、整体结构Scroll 包 Row 包 ForEachprivatethumbScroller:ScrollernewScroller()BuilderThumbnailBarBuilder(){Scroll(this.thumbScroller){Row({space:8}){ForEach(this.imageUrls,(url:string,idx:number){Stack(){Image(url).width(100%).height(100%).objectFit(ImageFit.Cover).borderRadius(8)}.width(56).height(56).borderRadius(8).backgroundColor(#E8A0A0)// 加载前的占位色.border({width:this.currentIndexidx?2:0,color:#FFFFFF}).onClick((){this.currentIndexidxthis.swiperController.showIndex(idx)this.scrollToThumb(idx)}).animation({duration:150,curve:Curve.EaseOut})},(url:string,idx:number)${url}-${idx})}.padding({left:16,right:16})}.scrollable(ScrollDirection.Horizontal).scrollBar(BarState.Off).width(100%).padding({bottom:12})}1.1 三层嵌套的职责分工层级组件职责外层Scroll控制水平滚动、隐藏滚动条中层Row横向排列所有缩略图左右内边距 16vp内层ForEach数据驱动渲染每个Image一个Stack容器二、缩略图的尺寸规范.width(56).height(56).borderRadius(8)参数值设计理由width56vp比主图小一个数量级不抢焦点height56vp1:1 正方形符合图片缩略图惯例borderRadius8vp圆角与全局CARD_RADIUS对齐space8vp缩略图间距足够区分又不松散2.1 为什么是 56vp 而不是 64vp 或 48vp48vp 太小在 1080p 屏幕上仅 144px图片细节难以辨认64vp 太大一行只能展示 5–6 个需要频繁滚动56vp 折中一行可放 6–7 个常见图片数8–10 张几乎不需要滚动2.2objectFit(ImageFit.Cover)的取舍主图必须用Contain不裁剪但缩略图用Cover场景ContainCover主图✅ 完整显示❌ 裁掉边缘缩略图❌ 周围黑边✅ 填满方块用户看缩略图只需要识别「这是哪张」裁掉边缘不影响识别。三、选中边框2vp 白色实线.border({width:this.currentIndexidx?2:0,color:#FFFFFF})3.1 为什么 width 在 2 和 0 之间切换如果选中项width: 2未选中项width: 0会导致选中时缩略图实际显示区域减少 4vp2vp 边框 × 2 侧视觉上「选中项比其他项略小」解决方案所有项统一预留 2vp 边框空间未选中项用透明边框.border({width:2,// 始终 2vpcolor:this.currentIndexidx?#FFFFFF:transparent})但原实现用width: 0 / 2切换也能工作因为 ArkUI 的边框是「外扩」而非「内嵌」。3.2 颜色选择纯白#FFFFFF在黑色背景下对比度最高避免使用品牌色选中态用品牌色会让用户误以为是「未加载完成」四、自动滚动到当前缩略图当用户左右滑动主图时缩略图条应当自动跟随确保当前项可见scrollToThumb(idx:number){// 每个缩略图占 56 8 64vp左侧 padding 16vp// 目标位置让当前缩略图出现在视口左侧偏中位置constitemWidth568// 64vpconstviewportOffset100// 让当前项离左侧 100vpconsttargetXMath.max(0,idx*itemWidth-viewportOffset)this.thumbScroller.scrollTo({xOffset:targetX,yOffset:0,animation:{duration:250,curve:Curve.EaseInOut}})}4.1 触发时机// 主图 Swiper 切换时Swiper(this.swiperController).onChange((index:number){this.currentIndexindexthis.scrollToThumb(index)// 同步滚动缩略图条})4.2 为什么不直接scrollTo({ xOffset: idx * 64 })直接滚动会让当前项贴到视口左侧体验不佳。我们用viewportOffset 100让当前项出现在「左侧偏中」的位置更符合视觉习惯。五、与主图 Swiper 的双向同步5.1 数据流图用户滑动主图 用户点击缩略图 │ │ ▼ ▼ Swiper.onChange onClick(idx) │ │ ▼ ▼ this.currentIndex idx this.currentIndex idx │ │ ▼ ▼ scrollToThumb(idx) swiperController.showIndex(idx)5.2 关键代码// 点击缩略图 → 跳转主图.onClick((){this.currentIndexidxthis.swiperController.showIndex(idx)this.scrollToThumb(idx)})// 滑动主图 → 同步缩略图条Swiper(this.swiperController).onChange((index){this.currentIndexindexthis.scrollToThumb(index)})六、分页指示器双重视觉反馈除了缩略图条图片查看器还提供了「页码文字 分页圆点」双指示器// 页码文字Text(${this.currentIndex1}/${this.imageUrls.length}).fontSize(15).fontColor(#FFFFFF).fontWeight(FontWeight.Medium)// 分页圆点Row({space:6}){ForEach(this.imageUrls,(url:string,idx:number){Column().width(this.currentIndexidx?16:6).height(6).borderRadius(3).backgroundColor(this.currentIndexidx?#FFFFFF:rgba(255,255,255,0.4)).animation({duration:200,curve:Curve.EaseOut})},(url:string,idx:number)${url}-${idx})}.width(100%).justifyContent(FlexAlign.Center).padding({top:12,bottom:12})6.1 三种指示器的协同指示器主要场景信息密度页码文字多图浏览高精确数字分页圆点少图浏览中相对位置缩略图条快速跳转高视觉预览三者协同覆盖所有用户场景。七、性能优化7.1 唯一 keyForEach(this.imageUrls,(url,idx){// ...},(url,idx)${url}-${idx})key 使用url idx避免相同 url 导致的 diff 错乱。7.2 缩略图懒加载Image(url).syncLoad(false)// 异步加载.alt($r(app.media.placeholder))// 占位图7.3 减少不必要的渲染当currentIndex变化时只有「旧的选中项」和「新的选中项」两个Stack需要重新渲染。ArkUI 的 diff 机制会自动跳过其他项。八、常见问题与踩坑记录Q1缩略图条无法滚动原因Scroll默认是垂直滚动。解决.scrollable(ScrollDirection.Horizontal)Q2点击缩略图后主图没跳转原因swiperController未在aboutToAppear中初始化。解决private swiperController: SwiperController new SwiperController()Q3缩略图加载缓慢原因使用原图作为缩略图带宽浪费。解决让后端生成专门的缩略图如 200×200缩略图条加载这些小图。九、总结本文通过「海风日记」的缩略图预览条和分页指示器深入讲解了图片导航的实现水平 Scroll——.scrollable(ScrollDirection.Horizontal)缩略图尺寸—— 56×56vp 8vp 圆角 8vp 间距选中边框—— 2vp 白色实线自动滚动——Scroller.scrollTo 250ms 动画双向同步—— SwiperonChange↔ 缩略图onClick三种指示器协同—— 页码文字、分页圆点、缩略图条如果这篇文章对你有帮助欢迎点赞、收藏⭐、关注你的支持是我持续创作的动力相关资源Scroll 组件文档Swiper 组件文档animation 属性文档海风日记项目源码HarmonyOS 开发者官网开源鸿蒙跨平台社区