問題描述
在長列表或複雜頁面中,如何正確使用 Scroll 組件?如何處理 Scroll 嵌套問題?如何實現流暢的滑動效果和彈性邊界?
關鍵字: Scroll、滑動容器、嵌套滑動、edgeEffect、佈局優化
解決方案
完整代碼
@Entry
@Component
struct ScrollDemo {
@State records: string[] = Array.from({ length: 50 }, (_, i) => `記錄 ${i + 1}`);
private scroller: Scroller = new Scroller();
build() {
Column() {
// 頂部固定區域
this.buildHeader()
// 可滾動內容區域
Scroll(this.scroller) {
Column({ space: 12 }) {
// 統計卡片
this.buildStatsCard()
// 篩選器
this.buildFilters()
// 記錄列表
this.buildRecordList()
}
.width('100%')
.padding(16)
}
.width('100%')
.layoutWeight(1)
.scrollable(ScrollDirection.Vertical) // 垂直滾動
.scrollBar(BarState.Auto) // 自動顯示滾動條
.scrollBarColor('#888') // 滾動條顏色
.scrollBarWidth(4) // 滾動條寬度
.edgeEffect(EdgeEffect.Spring) // 彈性邊界效果
.friction(0.6) // 摩擦係數(越小滑動越遠)
.onScroll((xOffset: number, yOffset: number) => {
// 滾動監聽
console.log(`滾動偏移: ${yOffset}`);
})
.onScrollEdge((side: Edge) => {
// 滾動到邊界
console.log(`滾動到邊界: ${side}`);
})
}
.width('100%')
.height('100%')
.backgroundColor('#f5f5f5')
}
@Builder
buildHeader() {
Row() {
Text('記錄列表')
.fontSize(20)
.fontWeight(FontWeight.Bold)
Blank()
Button('回到頂部')
.fontSize(14)
.onClick(() => {
// 滾動到頂部
this.scroller.scrollEdge(Edge.Top);
})
}
.width('100%')
.padding(16)
.backgroundColor(Color.White)
}
@Builder
buildStatsCard() {
Row({ space: 12 }) {
Column() {
Text('500')
.fontSize(24)
.fontWeight(FontWeight.Bold)
.fontColor('#ff6b6b')
Text('總收入')
.fontSize(12)
.fontColor('#999')
}
.layoutWeight(1)
.padding(16)
.backgroundColor(Color.White)
.borderRadius(12)
Column() {
Text('300')
.fontSize(24)
.fontWeight(FontWeight.Bold)
.fontColor('#67c23a')
Text('總支出')
.fontSize(12)
.fontColor('#999')
}
.layoutWeight(1)
.padding(16)
.backgroundColor(Color.White)
.borderRadius(12)
}
.width('100%')
}
@Builder
buildFilters() {
Row({ space: 8 }) {
Text('全部').filterChip(true)
Text('收入').filterChip(false)
Text('支出').filterChip(false)
}
.width('100%')
}
@Builder
buildRecordList() {
Column({ space: 8 }) {
ForEach(this.records, (record: string) => {
Row() {
Text(record)
.fontSize(16)
}
.width('100%')
.padding(16)
.backgroundColor(Color.White)
.borderRadius(8)
})
}
.width('100%')
}
}
// 擴展方法:篩選標籤樣式
@Extend(Text)
function filterChip(selected: boolean) {
.fontSize(14)
.padding({ left: 16, right: 16, top: 8, bottom: 8 })
.backgroundColor(selected ? '#ff6b6b' : '#f5f5f5')
.fontColor(selected ? Color.White : '#333')
.borderRadius(16)
}
/**
* Scroll嵌套解決方案
*/
@Component
struct NestedScrollDemo {
private outerScroller: Scroller = new Scroller();
private innerScroller: Scroller = new Scroller();
build() {
// 外層Scroll
Scroll(this.outerScroller) {
Column({ space: 16 }) {
// 固定內容
Text('外層內容')
.width('100%')
.height(200)
.backgroundColor('#e3f2fd')
// 內層Scroll(橫向)
Scroll(this.innerScroller) {
Row({ space: 12 }) {
ForEach([1, 2, 3, 4, 5], (item: number) => {
Text(`卡片${item}`)
.width(150)
.height(100)
.backgroundColor('#fff3e0')
.textAlign(TextAlign.Center)
.borderRadius(8)
})
}
.padding(16)
}
.width('100%')
.scrollable(ScrollDirection.Horizontal) // 橫向滾動
.scrollBar(BarState.Off) // 隱藏滾動條
// 更多固定內容
Text('更多外層內容')
.width('100%')
.height(400)
.backgroundColor('#f3e5f5')
}
.width('100%')
}
.width('100%')
.height('100%')
.scrollable(ScrollDirection.Vertical) // 縱向滾動
}
}
/**
* Scroller控制器高級用法
*/
@Component
struct ScrollerControlDemo {
private scroller: Scroller = new Scroller();
@State currentOffset: number = 0;
build() {
Column() {
// 控制按鈕
Row({ space: 8 }) {
Button('滾動到頂部')
.onClick(() => {
this.scroller.scrollEdge(Edge.Top);
})
Button('滾動到底部')
.onClick(() => {
this.scroller.scrollEdge(Edge.Bottom);
})
Button('滾動到指定位置')
.onClick(() => {
// 滾動到500px位置,動畫時長300ms
this.scroller.scrollTo({
xOffset: 0,
yOffset: 500,
animation: {
duration: 300,
curve: Curve.EaseInOut
}
});
})
Button('滾動一頁')
.onClick(() => {
// 向下滾動一頁
this.scroller.scrollPage({
next: true,
direction: Axis.Vertical
});
})
}
.padding(16)
Text(`當前偏移: ${this.currentOffset.toFixed(0)}px`)
.padding(8)
// 滾動內容
Scroll(this.scroller) {
Column({ space: 12 }) {
ForEach(Array.from({ length: 30 }, (_, i) => i), (index: number) => {
Text(`內容 ${index + 1}`)
.width('100%')
.height(80)
.backgroundColor('#e0f7fa')
.textAlign(TextAlign.Center)
.borderRadius(8)
})
}
.padding(16)
}
.layoutWeight(1)
.onScroll((xOffset: number, yOffset: number) => {
this.currentOffset += yOffset;
})
}
.width('100%')
.height('100%')
}
}
原理解析
1. Scroll 基本屬性
.scrollable(ScrollDirection.Vertical) // 滾動方向
.scrollBar(BarState.Auto) // 滾動條顯示策略
.edgeEffect(EdgeEffect.Spring) // 邊界效果
.friction(0.6) // 摩擦係數
2. Scroller 控制器
scroller.scrollEdge(Edge.Top) // 滾動到邊界
scroller.scrollTo({ yOffset: 500 }) // 滾動到指定位置
scroller.scrollPage({ next: true }) // 翻頁
3. 嵌套滑動
- 外層 Scroll:縱向滾動
- 內層 Scroll:橫向滾動
- 不同方向不衝突
4. 滾動監聽
.onScroll((xOffset, yOffset) => {
// 滾動偏移量
})
.onScrollEdge((side: Edge) => {
// 滾動到邊界
})
最佳實踐
- 固定頭部: 頭部放在 Scroll 外面,避免滾動
- layoutWeight: Scroll 使用 layoutWeight(1)填充剩餘空間
- 邊界效果: 使用 EdgeEffect.Spring 提升體驗
- 滾動條: 長列表顯示滾動條,短內容隱藏
- 性能優化: 內容過多時使用 List+LazyForEach
避坑指南
- 嵌套方向: 嵌套 Scroll 必須方向不同
- height 設置: Scroll 必須有明確高度
- Column 嵌套: Scroll 內 Column 不要設置 height
- 滾動衝突: 同方向嵌套會導致滾動衝突
- 內存佔用: 內容過多時 Scroll 會全部渲染,考慮用 List
效果展示
- 彈性邊界:滾動到頂部/底部時有彈性效果
- 流暢滑動:摩擦係數 0.6,滑動流暢自然
- 精確控制:通過 Scroller 實現各種滾動效果 相關資源
- 鴻蒙學習資源