167.[HarmonyOS NEXT 实战案例四:Grid] 可滚动网格布局进阶篇
[HarmonyOS NEXT 实战案例四:Grid] 可滚动网格布局进阶篇
项目已开源,开源地址: https://gitcode.com/nutpi/HarmonyosNextCaseStudyTutorial , 欢迎fork & star
效果演示
1. 引言
在上一篇教程中,我们介绍了HarmonyOS NEXT中可滚动网格布局的基础知识和实现方法。本篇教程将深入探讨可滚动网格布局的进阶技巧,包括多列网格布局、动态列模板、高级滚动控制、自定义网格项样式等内容,帮助开发者构建更加灵活、美观的网格界面。
2. 多列网格布局
2.1 列模板设置
在基础教程中,我们使用了单列布局(columnsTemplate('1fr'))。在实际应用中,多列布局更为常见,特别是在平板等大屏设备上。下面介绍如何实现多列网格布局:
Grid(this.scroller) {
// 网格内容
}
.columnsTemplate('1fr 1fr') // 双列布局
.columnsGap(16) // 列间距
.rowsGap(16) // 行间距
通过设置columnsTemplate为'1fr 1fr',我们创建了一个双列布局,每列占据可用空间的一份。同时,使用columnsGap设置列间距为16。
2.2 多列布局的响应式调整
为了适应不同屏幕尺寸,我们可以根据屏幕宽度动态调整列数:
@State gridColumns: string = '1fr'
aboutToAppear() {
// 获取屏幕宽度
const screenWidth = px2vp(window.getWindowWidth())
// 根据屏幕宽度设置列数
if (screenWidth >= 840) {
this.gridColumns = '1fr 1fr 1fr' // 大屏设备,三列布局
} else if (screenWidth >= 520) {
this.gridColumns = '1fr 1fr' // 中等屏幕,双列布局
} else {
this.gridColumns = '1fr' // 小屏设备,单列布局
}
}
// 在Grid中使用动态列模板
Grid(this.scroller) {
// 网格内容
}
.columnsTemplate(this.gridColumns)
这样,当应用在不同尺寸的设备上运行时,网格布局会自动调整列数,提供最佳的显示效果。
2.3 列宽比例设置
除了等分列宽,我们还可以设置不同的列宽比例:
// 第一列占1份,第二列占2份
.columnsTemplate('1fr 2fr')
// 固定宽度与弹性宽度混合
.columnsTemplate('200px 1fr')
// 多列不等宽
.columnsTemplate('1fr 1.5fr 1fr')
通过灵活设置列模板,可以创建出各种复杂的网格布局效果。
3. 高级滚动控制
3.1 滚动事件与回调
除了基础教程中介绍的onScrollIndex事件外,Grid还支持其他滚动相关事件:
Grid(this.scroller) {
// 网格内容
}
// 滚动开始事件
.onScrollBegin(() => {
console.log('开始滚动')
})
// 滚动停止事件
.onScrollStop(() => {
console.log('停止滚动')
})
// 滚动边缘事件
.onReachStart(() => {
console.log('到达顶部')
})
.onReachEnd(() => {
console.log('到达底部')
// 可以在这里加载更多数据
this.loadMoreApps()
})
这些事件可以帮助我们实现更加精细的滚动控制,如滚动到底部加载更多数据、滚动时显示/隐藏UI元素等。
3.2 编程式滚动控制
使用Scroller控制器,我们可以实现编程式滚动控制:
// 滚动到指定位置
scrollToPosition() {
this.scroller.scrollTo({ xOffset: 0, yOffset: 200 })
}
// 滚动到指定索引的网格项
scrollToItem(index: number) {
this.scroller.scrollToIndex(index)
}
// 滚动到顶部/底部
scrollToTop() {
this.scroller.scrollEdge(Edge.Top)
}
scrollToBottom() {
this.scroller.scrollEdge(Edge.Bottom)
}
// 按页滚动
scrollNextPage() {
this.scroller.scrollPage({ next: true })
}
scrollPrevPage() {
this.scroller.scrollPage({ next: false })
}
这些方法可以在特定场景下使用,如点击按钮滚动到顶部、切换分类时滚动到特定位置等。
3.3 滚动动画与效果
为了提升用户体验,我们可以为滚动添加动画效果:
// 带动画的滚动
scrollWithAnimation() {
this.scroller.scrollTo({
xOffset: 0,
yOffset: 500,
animation: {
duration: 300, // 动画持续时间,单位毫秒
curve: Curve.EaseOut // 动画曲线
}
})
}
通过设置animation参数,可以使滚动过程更加平滑自然,提升用户体验。
4. 自定义网格项样式
4.1 网格项布局与样式
在基础教程中,我们为每个GridItem创建了基本的布局和样式。下面介绍一些进阶的网格项样式技巧:
GridItem() {
Column() {
// 网格项内容
}
.width('100%')
.height('100%')
.backgroundColor('#FFFFFF')
.borderRadius(16)
.shadow({
radius: 8,
color: 'rgba(0, 0, 0, 0.1)',
offsetX: 0,
offsetY: 2
})
// 添加渐变背景
.backgroundImage({
gradient: {
angle: 90,
colors: [['#FFFFFF', 0.0], ['#F8F8F8', 1.0]]
}
})
// 添加边框
.border({
width: 1,
color: '#E0E0E0',
style: BorderStyle.Solid
})
// 添加过渡动画
.transition({
type: TransitionType.All,
opacity: 0.2
})
}
这些样式设置可以使网格项更加美观,提升整体视觉效果。
4.2 网格项交互效果
为了提升用户体验,我们可以为网格项添加交互效果:
@State pressedItemId: number = -1
GridItem() {
Column() {
// 网格项内容
}
.scale(this.pressedItemId === app.id ? 0.95 : 1.0) // 按下时缩小
.opacity(this.pressedItemId === app.id ? 0.8 : 1.0) // 按下时降低透明度
}
.gesture(
LongPressGesture()
.onAction(() => {
// 长按操作
this.showAppOptions(app.id)
})
)
.onTouch((event: TouchEvent) => {
if (event.type === TouchType.Down) {
this.pressedItemId = app.id
} else if (event.type === TouchType.Up || event.type === TouchType.Cancel) {
this.pressedItemId = -1
}
})
通过监听触摸事件和手势,我们可以实现按下反馈、长按菜单等交互效果,提升用户体验。
4.3 网格项动画效果
为了使网格布局更加生动,我们可以为网格项添加动画效果:
@State animatedItems: number[] = []
onPageShow() {
// 页面显示时,逐个显示网格项
this.animatedItems = []
for (let i = 0; i < this.featuredApps.length; i++) {
setTimeout(() => {
this.animatedItems.push(this.featuredApps[i].id)
}, i * 100) // 每隔100毫秒显示一个
}
}
GridItem() {
Column() {
// 网格项内容
}
.opacity(this.animatedItems.includes(app.id) ? 1.0 : 0.0)
.translate({
x: this.animatedItems.includes(app.id) ? 0 : 50,
y: 0
})
.transition({
type: TransitionType.All,
opacity: 0.3,
translate: 0.3
})
}
这段代码实现了网格项的逐个淡入动画效果,使页面加载过程更加生动。
5. 高级网格布局技巧
5.1 网格项跨行跨列
Grid组件支持网格项跨行跨列,可以创建更加复杂的布局效果:
Grid() {
// 占据2行2列的大网格项
GridItem() {
// 内容
}
.rowStart(0)
.rowEnd(2)
.columnStart(0)
.columnEnd(2)
// 普通网格项
GridItem() {
// 内容
}
// 跨2行的网格项
GridItem() {
// 内容
}
.rowStart(0)
.rowEnd(2)
}
.columnsTemplate('1fr 1fr')
.rowsTemplate('1fr 1fr')
通过设置rowStart、rowEnd、columnStart、columnEnd属性,可以控制网格项的跨行跨列,创建出更加丰富的布局效果。
5.2 网格区域命名
Grid组件支持网格区域命名,可以更加直观地定义复杂布局:
Grid() {
GridItem() {
// 头部内容
}
.gridArea('header')
GridItem() {
// 主要内容
}
.gridArea('main')
GridItem() {
// 侧边栏内容
}
.gridArea('sidebar')
GridItem() {
// 底部内容
}
.gridArea('footer')
}
.areasTemplate([
['header', 'header'],
['sidebar', 'main'],
['footer', 'footer']
])
.columnsTemplate('1fr 2fr')
.rowsTemplate('auto 1fr auto')
通过areasTemplate定义网格区域,并使用gridArea将网格项放置到指定区域,可以更加灵活地控制布局。
5.3 网格自适应布局
结合媒体查询,我们可以实现更加复杂的自适应网格布局:
@StorageLink('windowWidth') windowWidth: number = 0
@State gridLayout: GridLayoutConfig = { columns: '1fr', areas: [] }
aboutToAppear() {
this.updateGridLayout()
}
@Watch('windowWidth')
updateGridLayout() {
if (this.windowWidth >= 840) {
// 大屏布局
this.gridLayout = {
columns: '1fr 1fr 1fr',
areas: [
['featured', 'featured', 'sidebar'],
['content', 'content', 'sidebar']
]
}
} else if (this.windowWidth >= 520) {
// 中屏布局
this.gridLayout = {
columns: '1fr 1fr',
areas: [
['featured', 'featured'],
['content', 'sidebar']
]
}
} else {
// 小屏布局
this.gridLayout = {
columns: '1fr',
areas: [
['featured'],
['content'],
['sidebar']
]
}
}
}
// 在Grid中使用动态布局配置
Grid() {
// 网格内容
}
.columnsTemplate(this.gridLayout.columns)
.areasTemplate(this.gridLayout.areas)
这段代码根据窗口宽度动态调整网格布局,包括列数和区域分布,实现了真正的响应式布局。
6. 数据管理与加载
6.1 分页加载
对于大量数据,分页加载是一种常见的优化策略:
@State featuredApps: FeaturedApp[] = []
@State loading: boolean = false
@State hasMore: boolean = true
@State currentPage: number = 1
@State pageSize: number = 10
aboutToAppear() {
this.loadApps()
}
async loadApps() {
if (this.loading || !this.hasMore) return
this.loading = true
try {
// 模拟网络请求
await new Promise(resolve => setTimeout(resolve, 1000))
// 模拟获取数据
const newApps = this.getAppsData(this.currentPage, this.pageSize)
// 添加到现有数据
this.featuredApps = [...this.featuredApps, ...newApps]
// 更新分页信息
this.currentPage++
this.hasMore = newApps.length === this.pageSize
} finally {
this.loading = false
}
}
// 在Grid中添加加载更多逻辑
Grid(this.scroller) {
// 应用列表
ForEach(this.featuredApps, (app:FeaturedApp) => {
GridItem() {
// 网格项内容
}
})
// 加载更多指示器
if (this.loading) {
GridItem() {
LoadingProgress()
.width(24)
.height(24)
}
.justifyContent(FlexAlign.Center)
}
}
.onReachEnd(() => {
this.loadApps() // 滚动到底部时加载更多
})
这段代码实现了滚动到底部加载更多数据的功能,适用于大量数据的展示场景。
6.2 下拉刷新
结合Refresh组件,我们可以实现下拉刷新功能:
@State refreshing: boolean = false
refreshData() {
this.refreshing = true
// 模拟刷新数据
setTimeout(() => {
this.currentPage = 1
this.featuredApps = []
this.hasMore = true
this.loadApps()
this.refreshing = false
}, 1000)
}
build() {
Column() {
// 其他UI元素
Refresh({
refreshing: $$this.refreshing,
onRefresh: () => this.refreshData()
}) {
Grid(this.scroller) {
// 网格内容
}
// Grid属性设置
}
}
}
通过Refresh组件包装Grid,可以实现下拉刷新功能,提升用户体验。
7. 高级交互与动效
7.1 网格项拖拽排序
通过结合手势和动画,我们可以实现网格项的拖拽排序功能:
@State draggingItemId: number = -1
@State itemPositions: Map<number, Position> = new Map()
GridItem() {
Column() {
// 网格项内容
}
.position({
x: this.itemPositions.get(app.id)?.x || 0,
y: this.itemPositions.get(app.id)?.y || 0
})
.zIndex(this.draggingItemId === app.id ? 1 : 0)
.opacity(this.draggingItemId === app.id ? 0.8 : 1.0)
.animation({
duration: this.draggingItemId === app.id ? 0 : 300,
curve: Curve.Ease
})
}
.gesture(
PanGesture()
.onActionStart(() => {
this.draggingItemId = app.id
})
.onActionUpdate((event: GestureEvent) => {
if (this.draggingItemId === app.id) {
// 更新拖拽项位置
const position = this.itemPositions.get(app.id) || { x: 0, y: 0 }
this.itemPositions.set(app.id, {
x: position.x + event.offsetX,
y: position.y + event.offsetY
})
// 检测与其他项的交换
this.checkItemSwap(app.id, event)
}
})
.onActionEnd(() => {
if (this.draggingItemId === app.id) {
// 重置位置并完成排序
this.itemPositions.set(app.id, { x: 0, y: 0 })
this.draggingItemId = -1
}
})
)
这段代码实现了网格项的拖拽排序功能,用户可以通过拖拽调整网格项的顺序。
7.2 网格项展开/折叠
通过状态管理和动画,我们可以实现网格项的展开/折叠效果:
@State expandedItemId: number = -1
GridItem() {
Column() {
// 基本信息
Row() {
// 应用图标和基本信息
}
.onClick(() => {
this.expandedItemId = this.expandedItemId === app.id ? -1 : app.id
})
// 详细信息(展开时显示)
if (this.expandedItemId === app.id) {
Column() {
// 详细信息内容
}
.height(this.expandedItemId === app.id ? 200 : 0)
.opacity(this.expandedItemId === app.id ? 1.0 : 0.0)
.transition({
type: TransitionType.All,
opacity: 0.3,
height: 0.3
})
}
}
}
这段代码实现了网格项的展开/折叠效果,点击网格项时可以显示更多详细信息。
8. 总结
在下一篇教程中,我们将探讨更多高级主题,包括自定义网格布局算法、复杂交互模式、性能优化策略等,敬请期待!
- 0回答
- 4粉丝
- 0关注
- 168.[HarmonyOS NEXT 实战案例四:Grid] 可滚动网格布局高级篇
- 166.[HarmonyOS NEXT 实战案例四:Grid] 可滚动网格布局基础篇
- 161. [HarmonyOS NEXT 实战案例二:Grid] 照片相册网格布局:进阶篇
- 173.[HarmonyOS NEXT 实战案例六:Grid] 响应式网格布局 - 进阶篇
- 170.[HarmonyOS NEXT 实战案例五:Grid] 动态网格布局进阶篇
- 179.[HarmonyOS NEXT 实战案例八:Grid] 瀑布流网格布局进阶篇
- 185.[HarmonyOS NEXT 实战案例十:Grid] 仪表板网格布局进阶篇
- 176.[HarmonyOS NEXT 实战案例七:Grid] 嵌套网格布局进阶篇:高级布局与交互技巧
- [HarmonyOS NEXT 实战案例十八] 日历日程视图网格布局(进阶篇)
- 164.[HarmonyOS NEXT 实战案例三:Grid] 不规则网格布局进阶篇:新闻应用高级布局与交互
- [HarmonyOS NEXT 实战案例十五] 电商分类导航网格布局(进阶篇)
- 182.[HarmonyOS NEXT 实战案例九:Grid] 电商网格布局进阶篇:打造高级交互与视觉体验
- 158.[HarmonyOS NEXT 实战案例一:Grid] 基础网格布局进阶篇:电商商品列表的交互与状态管理
- [HarmonyOS NEXT 实战案例四] 天气应用网格布局(下)
- [HarmonyOS NEXT 实战案例四] 天气应用网格布局(上)