11 KiB
Carousel 走马灯
概述
走马灯(Carousel)是循环展示组件,用于在有限空间内循环播放一组内容(图片、卡片等)。支持自动播放、分页指示器、导航按钮、滑动手势、多种过渡效果和四个方向的分页位置。
AtomUI 的 Carousel 控件复刻了 Ant Design 5.0 Carousel 的设计规范,在 .NET / Avalonia 平台上提供与 Ant Design React 版一致的视觉体验和交互行为。
设计原理
Ant Design 的走马灯设计哲学
Ant Design 对走马灯的定位是:「在有限空间内,循环播放同一类型的图片、文字等内容」。典型使用场景包括:
| 场景 | 说明 |
|---|---|
| 🖼️ 首页横幅 | 多张 Banner 图片轮播展示 |
| 📦 产品展示 | 产品多角度图片切换 |
| 📰 新闻轮播 | 新闻标题自动滚动 |
| 🎯 引导页 | 新用户引导步骤展示 |
核心能力:
| 能力 | 设计意图 |
|---|---|
| 🔄 自动播放 | 内容定时自动切换,无需用户操作 |
| 📍 分页指示器 | 显示当前位置和总页数,支持点击跳转 |
| ⬅️➡️ 导航箭头 | 前进/后退按钮,提供手动翻页 |
| 🔁 无限循环 | 到达末尾后自动回到开头,形成循环 |
| 🎞️ 过渡效果 | 滑动(Scroll)和淡入淡出(Fade)两种切换方式 |
| ⏱️ 进度展示 | 指示器上展示自动播放的进度 |
Avalonia SelectingItemsControl 基础能力
AtomUI 的 Carousel 继承自 Avalonia 框架的 SelectingItemsControl。其继承链为:
Control → TemplatedControl → ItemsControl → SelectingItemsControl
SelectingItemsControl 提供了选中项管理(SelectedIndex / SelectedItem)、选择模式(SelectionMode)等基础能力。Carousel 将其用于管理「当前页」的概念——每个页面就是一个选中项。
AtomUI 的扩展设计
AtomUI Carousel 在 SelectingItemsControl 的基础上,遵循 Ant Design 5.0 规范做了以下扩展:
| 扩展能力 | 实现方式 | 设计动机 |
|---|---|---|
| 自动播放 | IsAutoPlay + AutoPlaySpeed + DispatcherTimer |
内容定时自动切换 |
| 分页指示器 | CarouselPagination + CarouselPageIndicator |
当前页位置可视化 + 点击跳转 |
| 导航箭头 | CarouselNavButton + IsShowNavButtons |
手动前进/后退翻页 |
| 无限循环 | IsInfinite |
到达末尾自动回到开头 |
| 过渡效果 | CarouselTransitionEffect (Scroll / Fade) |
滑动或淡入淡出 |
| 四方向分页 | CarouselPaginationPosition (Top / Bottom / Left / Right) |
指示器位置灵活配置 |
| 进度展示 | IsShowTransitionProgress + 进度动画 |
指示器上展示自动播放进度条 |
| 滑动手势 | IsSwipeEnabled + 指针手势检测 |
鼠标/触摸滑动切换页面 |
| 虚拟化面板 | VirtualizingCarouselPanel |
仅实例化当前页和过渡页,节省资源 |
| Design Token | CarouselToken + RegisterTokenResourceScope |
视觉值从 Token 派生 |
功能详解
自动播放(IsAutoPlay)
当 IsAutoPlay = true 时:
- 创建
DispatcherTimer,间隔为AutoPlaySpeed(默认 3 秒) - 每次触发自动调用
Next()切换到下一页 - 手动切换页面时(
SelectedIndex变化),定时器重新计时 - 控件从可视树分离时自动停止,重新附加时自动恢复
分页指示器
分页指示器由 CarouselPagination(内部 SelectingItemsControl)和 CarouselPageIndicator(单个指示点)组成:
- 指示点默认为条形(宽
IndicatorWidth,高IndicatorHeight) - 选中的指示点自动变宽为
IndicatorActiveWidth - 悬浮时不透明度从 0.2 变为 0.75
- 点击指示点可跳转到对应页面
- 通过
IsShowPagination控制显示/隐藏
导航箭头(IsShowNavButtons)
当 IsShowNavButtons = true 时显示前进/后退按钮:
- 使用
CarouselNavButton(继承IconButton) - 图标为
LeftOutlined/RightOutlined - 默认不透明度 0.2,悬浮时变为 1.0
- 非无限循环模式下,到达边界时自动隐藏对应按钮
- 横向模式时按钮位于左右两侧,纵向模式时按钮位于上下两侧
分页位置(PaginationPosition)
分页指示器和导航箭头的位置随 PaginationPosition 变化:
| 位置 | 指示器位置 | 导航按钮位置 | 滑动方向 | PageSlide 方向 |
|---|---|---|---|---|
Bottom(默认) |
底部居中 | 左右两侧 | 水平 | Horizontal |
Top |
顶部居中 | 左右两侧 | 水平 | Horizontal |
Left |
左侧居中(旋转 90°) | 上下两侧 | 垂直 | Vertical |
Right |
右侧居中(旋转 90°) | 上下两侧 | 垂直 | Vertical |
过渡效果(TransitionEffect)
| 效果 | 实现 | 说明 |
|---|---|---|
Scroll(默认) |
PageSlide |
页面滑动切换,方向跟随 PaginationPosition |
Fade |
CrossFade |
淡入淡出切换 |
过渡动画参数通过 PageTransitionDuration、PageInEasing、PageOutEasing 配置。
进度展示(IsShowTransitionProgress)
当 IsAutoPlay = true 且 IsShowTransitionProgress = true 时:
- 选中的指示点上显示进度条动画
- 进度条从左到右填充,周期与
AutoPlaySpeed一致 - 悬浮指示点时进度条隐藏
滑动手势(IsSwipeEnabled)
当 IsSwipeEnabled = true 时:
- 鼠标/触摸按下并拖动超过 30px 阈值即触发页面切换
- 水平模式下左滑 = Next,右滑 = Previous
- 垂直模式下上滑 = Next,下滑 = Previous
- 导航按钮和分页指示器区域不触发滑动
虚拟化面板
VirtualizingCarouselPanel 实现 ILogicalScrollable,仅实例化当前页面和过渡中的页面,未显示的页面被回收到对象池,大幅减少内存占用。
与 Ant Design 规范的对齐程度
| 特性 | Ant Design (React) | AtomUI (Avalonia) | 对齐情况 |
|---|---|---|---|
| 基本走马灯 | ✅ <Carousel> |
✅ <atom:Carousel> |
✅ 完全对齐 |
自动播放 autoplay |
✅ 布尔 | ✅ IsAutoPlay |
✅ 完全对齐 |
自动播放速度 autoplaySpeed |
✅ 毫秒数 | ✅ AutoPlaySpeed (TimeSpan) |
✅ 完全对齐 |
分页位置 dotPosition |
✅ top/bottom/left/right | ✅ PaginationPosition 枚举 |
✅ 完全对齐 |
淡入淡出 effect="fade" |
✅ | ✅ TransitionEffect="Fade" |
✅ 完全对齐 |
箭头 arrows |
✅ 5.17.0 新增 | ✅ IsShowNavButtons |
✅ 完全对齐 |
进度指示 dotProgress |
✅ (未正式发布) | ✅ IsShowTransitionProgress |
✅ 完全对齐 |
无限循环 infinite |
✅ | ✅ IsInfinite |
✅ 完全对齐 |
滑动手势 swipe |
✅ (触屏) | ✅ IsSwipeEnabled |
✅ 扩展(鼠标+触摸) |
beforeChange / afterChange |
✅ 回调 | ⚠️ 通过 SelectedIndex 变化监听 |
⚠️ API 形式不同 |
继承关系
Avalonia.Controls.Primitives.TemplatedControl
└── Avalonia.Controls.ItemsControl
└── Avalonia.Controls.Primitives.SelectingItemsControl
└── AtomUI.Desktop.Controls.Carousel
└── implements IMotionAwareControl
各层级职责划分:
| 层级 | 提供的能力 |
|---|---|
ItemsControl |
子项管理、Items / ItemsSource 数据绑定、ItemTemplate 模板化 |
SelectingItemsControl |
选中项管理(SelectedIndex / SelectedItem)、选择模式 |
AtomUI.Desktop.Controls.Carousel |
自动播放、分页指示器、导航箭头、过渡效果、无限循环、滑动手势、虚拟化面板 |
实现的共享接口:
| 接口 | 定义位置 | 作用 |
|---|---|---|
IMotionAwareControl |
AtomUI.Controls.Shared |
支持动画开关(IsMotionEnabled) |
相关类型
| 类型 | 继承自 | 说明 |
|---|---|---|
CarouselPaginationPosition |
枚举 | 分页位置:Top、Bottom、Left、Right |
CarouselTransitionEffect |
枚举 | 过渡效果:Scroll、Fade |
CarouselPage |
ContentControl |
走马灯页面容器 |
CarouselNavButton |
IconButton |
导航箭头按钮 |
CarouselPageIndicator |
ContentControl(内部) |
单个分页指示点 |
CarouselPagination |
SelectingItemsControl(内部) |
分页指示器面板 |
VirtualizingCarouselPanel |
VirtualizingPanel(内部) |
虚拟化面板,仅实例化当前页 |
源码位置
| 层级 | 文件路径 | 说明 |
|---|---|---|
| 控件类 | src/AtomUI.Desktop.Controls/Carousel/Carousel.cs |
Carousel 主控件 |
| 枚举 | src/AtomUI.Desktop.Controls/Carousel/CarouselEnums.cs |
枚举定义 |
| 导航按钮 | src/AtomUI.Desktop.Controls/Carousel/CarouselNavButton.cs |
导航箭头按钮 |
| 页面容器 | src/AtomUI.Desktop.Controls/Carousel/CarouselPage.cs |
CarouselPage 控件 |
| 指示点 | src/AtomUI.Desktop.Controls/Carousel/CarouselPageIndicator.cs |
分页指示点(内部) |
| 分页面板 | src/AtomUI.Desktop.Controls/Carousel/CarouselPagination.cs |
分页指示器面板(内部) |
| 虚拟化面板 | src/AtomUI.Desktop.Controls/Carousel/VirtualizingCarouselPanel.cs |
虚拟化面板(内部) |
| Token 定义 | src/AtomUI.Desktop.Controls/Carousel/CarouselToken.cs |
组件级 Design Token |
| 主题模板 | src/AtomUI.Desktop.Controls/Carousel/Themes/CarouselTheme.axaml |
ControlTheme AXAML |
| 模板常量 | src/AtomUI.Desktop.Controls/Carousel/Themes/CarouselPageThemeConstants.cs |
模板部件名称常量 |
| Gallery 示例 | controlgallery/AtomUIGallery/ShowCases/Views/DataDisplay/CarouselShowCase.axaml |
使用范例 |
模板结构
Carousel 的 ControlTemplate 采用分层 Panel 布局:
Panel
├── ScrollViewer#PART_ScrollViewer ← 滚动容器(隐藏滚动条)
│ └── ItemsPresenter#PART_ItemsPresenter ← 页面展示(VirtualizingCarouselPanel)
├── CarouselNavButton#PART_PreviousButton ← 前一页导航按钮(LeftOutlined)
├── CarouselNavButton#PART_NextButton ← 下一页导航按钮(RightOutlined)
└── LayoutTransformControl#PaginationLayoutTransform ← 分页指示器布局(支持旋转)
└── CarouselPagination#PART_Pagination ← 分页指示器面板
└── StackPanel (Horizontal, Spacing=IndicatorGap)
└── CarouselPageIndicator × N ← 每个页面一个指示点
分层设计理由:
- 滚动容器独立:使用
ScrollViewer+VirtualizingCarouselPanel实现虚拟化页面切换,隐藏原生滚动条。 - 导航按钮浮动:按钮在 Panel 内浮动定位,根据
PaginationPosition自动调整位置。 - 分页指示器旋转:使用
LayoutTransformControl包裹,左/右位置时旋转 90° 实现纵向指示器。
模板部件常量
| 常量名 | 值 | 说明 |
|---|---|---|
CarouselThemeConstants.ScrollViewerPart |
"PART_ScrollViewer" |
滚动容器 |
CarouselThemeConstants.PaginationPart |
"PART_Pagination" |
分页指示器面板 |
CarouselThemeConstants.PreviousButtonPart |
"PART_PreviousButton" |
前一页按钮 |
CarouselThemeConstants.NextButtonPart |
"PART_NextButton" |
下一页按钮 |
CarouselPageIndicatorThemeConstants.FramePart |
"PART_Frame" |
指示点主框架 |