183.[HarmonyOS NEXT 实战案例九:Grid] 电商网格布局高级篇:复杂场景与性能优化
[HarmonyOS NEXT 实战案例九:Grid] 电商网格布局高级篇:复杂场景与性能优化
项目已开源,开源地址: https://gitcode.com/nutpi/HarmonyosNextCaseStudyTutorial , 欢迎fork & star
效果演示
1. 电商网格布局高级应用概述
在前两篇教程中,我们学习了电商网格布局的基础实现和进阶特性。本篇教程将深入探讨电商网格布局的高级应用,包括复杂业务场景实现、高级交互技术、性能优化策略等,帮助开发者打造出专业级的电商应用。
1.1 高级应用概览
高级应用 | 描述 |
---|---|
复杂业务场景 | 多样化商品卡片、混合布局、推荐系统 |
高级交互技术 | 手势操作、拖拽排序、下拉刷新 |
性能优化策略 | 虚拟列表、懒加载、图片优化 |
数据管理 | 分页加载、缓存策略、状态管理 |
特殊布局 | 瀑布流商品展示、分组展示、促销专区 |
2. 复杂业务场景实现
2.1 多样化商品卡片
在实际电商应用中,不同类型的商品可能需要不同样式的卡片展示,例如普通商品、促销商品、限时抢购商品等。
// 商品卡片类型枚举
enum ProductCardType {
NORMAL, // 普通商品
PROMOTION, // 促销商品
FLASH_SALE, // 限时抢购
NEW_ARRIVAL, // 新品
RECOMMEND // 推荐商品
}
// 根据商品属性确定卡片类型
getCardType(product: EcommerceProduct): ProductCardType {
if (product.isFlashSale) {
return ProductCardType.FLASH_SALE
} else if (product.discount && product.discount < 0.8) {
return ProductCardType.PROMOTION
} else if (product.isNew) {
return ProductCardType.NEW_ARRIVAL
} else if (product.isRecommended) {
return ProductCardType.RECOMMEND
} else {
return ProductCardType.NORMAL
}
}
// 多样化商品卡片构建器
@Builder
ProductCard(product: EcommerceProduct) {
const cardType = this.getCardType(product)
Column() {
// 商品图片区域
Stack({ alignContent: Alignment.TopStart }) {
Image(product.image)
.width('100%')
.height(cardType === ProductCardType.PROMOTION ? 180 : 160)
.objectFit(ImageFit.Cover)
.borderRadius({ topLeft: 12, topRight: 12 })
// 根据卡片类型显示不同的标签
if (cardType === ProductCardType.FLASH_SALE) {
Row() {
Image($r('app.media.flash_sale'))
.width(16)
.height(16)
.margin({ right: 4 })
Text('限时抢购')
.fontSize(12)
.fontColor('#FFFFFF')
// 倒计时
Text(this.getFlashSaleTimeRemaining(product))
.fontSize(12)
.fontColor('#FFFFFF')
.margin({ left: 4 })
}
.padding({ left: 8, right: 8, top: 4, bottom: 4 })
.backgroundColor('#FF3B30')
.borderRadius(12)
.margin(8)
} else if (cardType === ProductCardType.PROMOTION) {
Text(`${product.discount * 10}折`)
.fontSize(12)
.fontColor('#FFFFFF')
.backgroundColor('#FF9500')
.borderRadius(12)
.padding({ left: 8, right: 8, top: 4, bottom: 4 })
.margin(8)
} else if (cardType === ProductCardType.NEW_ARRIVAL) {
Text('新品')
.fontSize(12)
.fontColor('#FFFFFF')
.backgroundColor('#34C759')
.borderRadius(12)
.padding({ left: 8, right: 8, top: 4, bottom: 4 })
.margin(8)
} else if (cardType === ProductCardType.RECOMMEND) {
Text('推荐')
.fontSize(12)
.fontColor('#FFFFFF')
.backgroundColor('#007AFF')
.borderRadius(12)
.padding({ left: 8, right: 8, top: 4, bottom: 4 })
.margin(8)
}
}
// 商品信息区域
Column() {
// 商品名称
Text(product.name)
.fontSize(14)
.fontWeight(FontWeight.Bold)
.fontColor('#333333')
.maxLines(2)
.textOverflow({ overflow: TextOverflow.Ellipsis })
.width('100%')
.textAlign(TextAlign.Start)
.margin({ bottom: 6 })
// 价格区域 - 根据卡片类型显示不同样式
if (cardType === ProductCardType.FLASH_SALE) {
Row() {
Text(`¥${product.price}`)
.fontSize(16)
.fontWeight(FontWeight.Bold)
.fontColor('#FF3B30')
Text(`¥${product.originalPrice}`)
.fontSize(12)
.fontColor('#999999')
.decoration({ type: TextDecorationType.LineThrough })
.margin({ left: 6 })
Blank()
Text(`已抢${Math.floor(product.salesCount / product.stock * 100)}%`)
.fontSize(12)
.fontColor('#FF3B30')
}
.width('100%')
.margin({ bottom: 6 })
// 进度条
Row() {
Row()
.width(`${Math.floor(product.salesCount / product.stock * 100)}%`)
.height(4)
.backgroundColor('#FF3B30')
.borderRadius(2)
}
.width('100%')
.height(4)
.backgroundColor('#F0F0F0')
.borderRadius(2)
.margin({ bottom: 8 })
} else {
// 普通价格显示
Row() {
Text(`¥${product.price}`)
.fontSize(16)
.fontWeight(FontWeight.Bold)
.fontColor('#FF3B30')
if (product.originalPrice && product.originalPrice > product.price) {
Text(`¥${product.originalPrice}`)
.fontSize(12)
.fontColor('#999999')
.decoration({ type: TextDecorationType.LineThrough })
.margin({ left: 6 })
}
Blank()
}
.width('100%')
.margin({ bottom: 6 })
// 评分和销量
Row() {
Row() {
ForEach([1,2,3,4,5], (star:number) => {
Image(star <= product.rating ? $r('app.media.star_filled') : $r('app.media.star_outline'))
.width(10)
.height(10)
.fillColor(star <= product.rating ? '#FFD700' : '#E0E0E0')
})
Text(`${product.rating}`)
.fontSize(10)
.fontColor('#666666')
.margin({ left: 2 })
}
Blank()
Text(`${this.formatNumber(product.salesCount)}已售`)
.fontSize(10)
.fontColor('#999999')
}
.width('100%')
.margin({ bottom: 8 })
}
// 操作按钮 - 根据卡片类型显示不同按钮
if (cardType === ProductCardType.FLASH_SALE) {
Button('立即抢购')
.fontSize(12)
.fontColor('#FFFFFF')
.backgroundColor('#FF3B30')
.borderRadius(16)
.width('100%')
.height(32)
.onClick(() => {
this.addToCart(product.id)
})
} else if (cardType === ProductCardType.PROMOTION) {
Button('立即购买')
.fontSize(12)
.fontColor('#FFFFFF')
.backgroundColor('#FF9500')
.borderRadius(16)
.width('100%')
.height(32)
.onClick(() => {
this.addToCart(product.id)
})
} else {
Button('加入购物车')
.fontSize(12)
.fontColor('#FFFFFF')
.backgroundColor('#007AFF')
.borderRadius(16)
.width('100%')
.height(32)
.onClick(() => {
this.addToCart(product.id)
})
}
}
.padding(12)
.alignItems(HorizontalAlign.Start)
}
.width('100%')
.backgroundColor('#FFFFFF')
.borderRadius(12)
.shadow({
radius: 6,
color: 'rgba(0, 0, 0, 0.1)',
offsetX: 0,
offsetY: 2
})
// 根据卡片类型应用不同的样式
.border(cardType === ProductCardType.FLASH_SALE ? {
width: 2,
color: '#FF3B30',
style: BorderStyle.Solid
} : cardType === ProductCardType.PROMOTION ? {
width: 2,
color: '#FF9500',
style: BorderStyle.Solid
} : null)
.onClick(() => {
this.selectedProduct = product
this.showProductDetail = true
})
}
// 获取限时抢购剩余时间
getFlashSaleTimeRemaining(product: EcommerceProduct): string {
// 这里假设 product 有 flashSaleEndTime 属性
const endTime = product.flashSaleEndTime
const now = new Date().getTime()
const remaining = Math.max(0, endTime - now)
const hours = Math.floor(remaining / (60 * 60 * 1000))
const minutes = Math.floor((remaining % (60 * 60 * 1000)) / (60 * 1000))
const seconds = Math.floor((remaining % (60 * 1000)) / 1000)
return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`
}
这个多样化商品卡片构建器根据商品的不同属性(限时抢购、促销、新品、推荐)显示不同样式的卡片,包括不同的标签、价格展示方式、进度条和操作按钮,使商品展示更加丰富多样。
2.2 混合布局
在实际电商应用中,首页通常会包含多种布局,如轮播图、分类导航、促销专区、商品网格等。我们可以实现一个混合布局的首页。
build() {
Scroll() {
Column() {
// 顶部搜索栏
this.SearchBar()
// 轮播图
this.BannerCarousel()
// 分类导航
this.CategoryNav()
// 促销专区
this.PromotionSection()
// 限时抢购
this.FlashSaleSection()
// 新品专区
this.NewArrivalsSection()
// 推荐商品网格
this.RecommendProductsGrid()
}
.width('100%')
}
.scrollBar(BarState.Off)
.scrollable(ScrollDirection.Vertical)
.width('100%')
.height('100%')
}
// 轮播图
@Builder
BannerCarousel() {
Swiper() {
ForEach(this.banners, (banner) => {
Image(banner.image)
.width('100%')
.height('100%')
.objectFit(ImageFit.Cover)
.borderRadius(12)
.onClick(() => {
// 处理轮播图点击
})
})
}
.width('100%')
.height(180)
.autoPlay(true)
.interval(3000)
.indicator(true)
.indicatorStyle({
selectedColor: '#007AFF',
color: '#CCCCCC',
size: 8
})
.margin({ top: 12, bottom: 16 })
.padding({ left: 16, right: 16 })
}
// 分类导航
@Builder
CategoryNav() {
Column() {
Text('商品分类')
.fontSize(16)
.fontWeight(FontWeight.Bold)
.fontColor('#333333')
.width('100%')
.textAlign(TextAlign.Start)
.padding({ left: 16, right: 16 })
.margin({ bottom: 12 })
Grid() {
ForEach(this.categories, (category:string, index) => {
GridItem() {
Column() {
Image($r(`app.media.category_${index % 10}`))
.width(48)
.height(48)
.margin({ bottom: 8 })
Text(category)
.fontSize(12)
.fontColor('#333333')
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
.onClick(() => {
this.selectedCategory = category
})
}
})
}
.columnsTemplate('1fr 1fr 1fr 1fr 1fr')
.rowsTemplate('1fr')
.width('100%')
.height(90)
.padding({ left: 16, right: 16 })
.margin({ bottom: 16 })
}
.width('100%')
.backgroundColor('#FFFFFF')
.padding({ top: 16, bottom: 16 })
.margin({ bottom: 8 })
}
// 促销专区
@Builder
PromotionSection() {
Column() {
Row() {
Text('促销专区')
.fontSize(16)
.fontWeight(FontWeight.Bold)
.fontColor('#333333')
Blank()
Button() {
Row() {
Text('查看更多')
.fontSize(12)
.fontColor('#666666')
Image($r('app.media.arrow_right'))
.width(12)
.height(12)
.fillColor('#666666')
.margin({ left: 4 })
}
}
.backgroundColor('transparent')
.padding(0)
.onClick(() => {
// 查看更多促销商品
})
}
.width('100%')
.padding({ left: 16, right: 16 })
.margin({ bottom: 12 })
List() {
ForEach(this.getPromotionProducts(), (product: EcommerceProduct) => {
ListItem() {
this.PromotionProductCard(product)
}
.margin({ right: 12 })
})
}
.listDirection(Axis.Horizontal)
.scrollBar(BarState.Off)
.width('100%')
.height(220)
.padding({ left: 16, right: 4 })
}
.width('100%')
.backgroundColor('#FFFFFF')
.padding({ top: 16, bottom: 16 })
.margin({ bottom: 8 })
}
// 促销商品卡片
@Builder
PromotionProductCard(product: EcommerceProduct) {
Column() {
Image(product.image)
.width(140)
.height(140)
.objectFit(ImageFit.Cover)
.borderRadius(12)
Text(product.name)
.fontSize(14)
.fontWeight(FontWeight.Bold)
.fontColor('#333333')
.maxLines(1)
.textOverflow({ overflow: TextOverflow.Ellipsis })
.width('100%')
.textAlign(TextAlign.Start)
.margin({ top: 8, bottom: 4 })
Row() {
Text(`¥${product.price}`)
.fontSize(16)
.fontWeight(FontWeight.Bold)
.fontColor('#FF3B30')
Text(`¥${product.originalPrice}`)
.fontSize(12)
.fontColor('#999999')
.decoration({ type: TextDecorationType.LineThrough })
.margin({ left: 6 })
}
.width('100%')
}
.width(140)
.padding(8)
.backgroundColor('#FFFFFF')
.borderRadius(12)
.shadow({
radius: 4,
color: 'rgba(0, 0, 0, 0.1)',
offsetX: 0,
offsetY: 2
})
.onClick(() => {
this.selectedProduct = product
this.showProductDetail = true
})
}
// 限时抢购专区
@Builder
FlashSaleSection() {
// 类似促销专区的实现
}
// 新品专区
@Builder
NewArrivalsSection() {
// 类似促销专区的实现
}
// 推荐商品网格
@Builder
RecommendProductsGrid() {
Column() {
Row() {
Text('为你推荐')
.fontSize(16)
.fontWeight(FontWeight.Bold)
.fontColor('#333333')
}
.width('100%')
.padding({ left: 16, right: 16 })
.margin({ bottom: 12 })
Grid() {
ForEach(this.getRecommendProducts(), (product: EcommerceProduct) => {
GridItem() {
this.ProductCard(product)
}
})
}
.columnsTemplate('1fr 1fr')
.rowsGap(12)
.columnsGap(12)
.width('100%')
.padding({ left: 16, right: 16, bottom: 16 })
}
.width('100%')
.backgroundColor('#FFFFFF')
.padding({ top: 16, bottom: 16 })
}
// 获取促销商品
getPromotionProducts(): EcommerceProduct[] {
return this.products.filter(product => product.discount && product.discount < 0.8)
.sort((a, b) => a.discount - b.discount)
.slice(0, 10)
}
// 获取推荐商品
getRecommendProducts(): EcommerceProduct[] {
return this.products.filter(product => product.isRecommended)
.sort((a, b) => {
const scoreA = a.salesCount * 0.3 + a.rating * 1000 + a.reviewCount * 0.5
const scoreB = b.salesCount * 0.3 + b.rating * 1000 + b.reviewCount * 0.5
return scoreB - scoreA
})
}
这个混合布局首页包含多个不同的区域:
- 顶部搜索栏:用于搜索商品
- 轮播图:展示促销活动、新品上市等信息
- 分类导航:快速进入不同商品分类
- 促销专区:水平滚动列表展示促销商品
- 限时抢购:展示限时抢购商品
- 新品专区:展示新上市商品
- 推荐商品网格:使用 Grid 组件展示推荐商品
这种混合布局能够在有限的屏幕空间内展示更多样化的商品,提升用户体验和转化率。
3. 高级交互技术
3.1 手势操作
在电商应用中,手势操作可以提供更自然、流畅的交互体验。例如,我们可以实现商品卡片的左右滑动操作。
@State swipeOffset: number = 0
@State isSwipeActionVisible: boolean = false
@State swipeThreshold: number = 80
@Builder
SwipeableProductCard(product: EcommerceProduct) {
Row() {
// 商品卡片主体
Column() {
// 商品卡片内容...
}
.width('100%')
.backgroundColor('#FFFFFF')
.borderRadius(12)
.translate({ x: this.swipeOffset })
.gesture(
PanGesture({ direction: PanDirection.Horizontal })
.onActionStart(() => {
// 开始滑动
})
.onActionUpdate((event: GestureEvent) => {
// 限制只能向左滑动(显示操作按钮)
if (event.offsetX < 0) {
this.swipeOffset = Math.max(-this.swipeThreshold, event.offsetX)
} else {
this.swipeOffset = 0
}
})
.onActionEnd(() => {
// 根据滑动距离决定是否显示操作按钮
if (this.swipeOffset < -this.swipeThreshold / 2) {
this.swipeOffset = -this.swipeThreshold
this.isSwipeActionVisible = true
} else {
this.swipeOffset = 0
this.isSwipeActionVisible = false
}
})
)
// 滑动后显示的操作按钮
Row() {
Button() {
Image($r('app.media.favorite'))
.width(24)
.height(24)
.fillColor('#FFFFFF')
}
.width(60)
.height('100%')
.backgroundColor('#FF9500')
.onClick(() => {
// 添加到收藏
this.addToFavorite(product.id)
this.swipeOffset = 0
this.isSwipeActionVisible = false
})
Button() {
Image($r('app.media.cart_add'))
.width(24)
.height(24)
.fillColor('#FFFFFF')
}
.width(60)
.height('100%')
.backgroundColor('#007AFF')
.onClick(() => {
// 添加到购物车
this.addToCart(product.id)
this.swipeOffset = 0
this.isSwipeActionVisible = false
})
}
.width(this.swipeThreshold)
.height('100%')
.margin({ left: -this.swipeThreshold })
}
.width('100%')
.height(240)
.clip(true)
}
这个可滑动的商品卡片实现了左滑显示操作按钮(收藏和加入购物车)的功能,提供了更丰富的交互方式。
3.2 下拉刷新与上拉加载
在电商应用中,下拉刷新和上拉加载是常见的交互模式,用于更新和加载更多商品数据。
@State isRefreshing: boolean = false
@State isLoadingMore: boolean = false
@State hasMoreData: boolean = true
@State currentPage: number = 1
@State pageSize: number = 20
build() {
Refresh({ refreshing: $$this.isRefreshing, offset: 120, friction: 66 }) {
List() {
// 商品网格头部
ListItem() {
Column() {
// 顶部搜索和筛选区域
// ...
}
}
// 商品网格
ListItem() {
Grid() {
ForEach(this.getFilteredProducts(), (product: EcommerceProduct) => {
GridItem() {
this.ProductCard(product)
}
})
}
.columnsTemplate('1fr 1fr')
.rowsGap(12)
.columnsGap(12)
.width('100%')
.padding({ left: 16, right: 16, bottom: 16 })
}
// 加载更多指示器
if (this.hasMoreData) {
ListItem() {
Row() {
if (this.isLoadingMore) {
LoadingProgress()
.width(24)
.height(24)
.color('#999999')
.margin({ right: 8 })
}
Text(this.isLoadingMore ? '正在加载更多...' : '上拉加载更多')
.fontSize(14)
.fontColor('#999999')
}
.width('100%')
.justifyContent(FlexAlign.Center)
.padding({ top: 12, bottom: 12 })
}
} else {
ListItem() {
Text('没有更多商品了')
.fontSize(14)
.fontColor('#999999')
.width('100%')
.textAlign(TextAlign.Center)
.padding({ top: 12, bottom: 12 })
}
}
}
.width('100%')
.height('100%')
.onReachEnd(() => {
if (this.hasMoreData && !this.isLoadingMore) {
this.loadMoreData()
}
})
}
.onRefresh(() => {
this.refreshData()
})
}
// 刷新数据
async refreshData() {
this.isRefreshing = true
try {
// 模拟网络请求
await this.fetchProducts(1)
this.currentPage = 1
this.hasMoreData = true
} catch (error) {
console.error('刷新数据失败', error)
} finally {
this.isRefreshing = false
}
}
// 加载更多数据
async loadMoreData() {
if (this.isLoadingMore) return
this.isLoadingMore = true
try {
// 模拟网络请求
const hasMore = await this.fetchProducts(this.currentPage + 1)
this.currentPage++
this.hasMoreData = hasMore
} catch (error) {
console.error('加载更多数据失败', error)
} finally {
this.isLoadingMore = false
}
}
// 模拟获取商品数据
async fetchProducts(page: number): Promise<boolean> {
return new Promise((resolve) => {
setTimeout(() => {
if (page === 1) {
// 刷新数据,替换现有数据
this.products = this.generateMockProducts(this.pageSize)
} else {
// 加载更多,追加数据
const moreProducts = this.generateMockProducts(this.pageSize)
this.products = [...this.products, ...moreProducts]
}
// 模拟是否还有更多数据
resolve(page < 5) // 假设只有5页数据
}, 1500) // 模拟网络延迟
})
}
// 生成模拟商品数据
generateMockProducts(count: number): EcommerceProduct[] {
const products: EcommerceProduct[] = []
const startId = (this.currentPage - 1) * this.pageSize + 1
for (let i = 0; i < count; i++) {
const id = startId + i
products.push({
id,
name: `商品${id}`,
description: `这是商品${id}的详细描述,包含了商品的各种信息。`,
price: Math.floor(Math.random() * 1000) + 100,
originalPrice: Math.floor(Math.random() * 1000) + 500,
discount: Math.random() * 0.5 + 0.5,
image: $r('app.media.product_placeholder'),
images: [$r('app.media.product_placeholder')],
category: this.categories[Math.floor(Math.random() * this.categories.length)],
brand: `品牌${Math.floor(Math.random() * 10) + 1}`,
rating: Math.random() * 3 + 2,
reviewCount: Math.floor(Math.random() * 1000),
salesCount: Math.floor(Math.random() * 10000),
stock: Math.floor(Math.random() * 100) + 10,
tags: ['标签1', '标签2', '标签3'].slice(0, Math.floor(Math.random() * 3) + 1),
isHot: Math.random() > 0.7,
isNew: Math.random() > 0.8,
isFreeShipping: Math.random() > 0.5,
specifications: {
color: ['红色', '蓝色', '黑色'],
size: ['S', 'M', 'L', 'XL'],
material: '棉质'
},
seller: {
name: `卖家${Math.floor(Math.random() * 10) + 1}`,
rating: Math.random() * 2 + 3,
location: '北京'
}
})
}
return products
}
这个实现包含了下拉刷新和上拉加载更多的功能:
- 下拉刷新:使用 Refresh 组件包裹内容,当用户下拉时触发 onRefresh 回调,刷新第一页数据。
- 上拉加载:监听 List 的 onReachEnd 事件,当用户滚动到底部时加载下一页数据。
- 加载状态指示:显示加载中的进度指示器和文本提示。
- 边界处理:当没有更多数据时,显示"没有更多商品了"的提示。
这种交互模式使用户能够方便地刷新和加载更多商品,提升浏览体验。
4. 性能优化策略
4.1 虚拟列表
当商品数量非常多时,一次性渲染所有商品会导致性能问题。虚拟列表技术只渲染可见区域的商品,可以大幅提升性能。
@Component
struct VirtualGridItem {
product: EcommerceProduct
@Consume addToCart: (productId: number) => void
@Consume showProductDetail: (product: EcommerceProduct) => void
build() {
Column() {
// 商品卡片内容...
}
.width('100%')
.backgroundColor('#FFFFFF')
.borderRadius(12)
.shadow({
radius: 6,
color: 'rgba(0, 0, 0, 0.1)',
offsetX: 0,
offsetY: 2
})
.onClick(() => {
this.showProductDetail(this.product)
})
}
}
@Component
struct VirtualProductGrid {
@Consume products: EcommerceProduct[]
@Consume addToCart: (productId: number) => void
@Consume showProductDetail: (product: EcommerceProduct) => void
build() {
List() {
LazyForEach(new VirtualProductDataSource(this.products), (product: EcommerceProduct) => {
ListItem() {
Row() {
VirtualGridItem({ product })
.layoutWeight(1)
.margin({ right: 6 })
VirtualGridItem({ product })
.layoutWeight(1)
.margin({ left: 6 })
}
.width('100%')
.padding({ left: 16, right: 16 })
.margin({ bottom: 12 })
}
}, (product: EcommerceProduct) => product.id.toString())
}
.width('100%')
.height('100%')
.divider({ strokeWidth: 0 })
}
}
class VirtualProductDataSource implements IDataSource {
private products: EcommerceProduct[]
private listener: DataChangeListener
constructor(products: EcommerceProduct[]) {
this.products = products
}
totalCount(): number {
return Math.ceil(this.products.length / 2) // 每行两个商品
}
getData(index: number): Object {
const productIndex = index * 2
return this.products[productIndex]
}
registerDataChangeListener(listener: DataChangeListener): void {
this.listener = listener
}
unregisterDataChangeListener(): void {
this.listener = null
}
notifyDataReload(): void {
this.listener.onDataReloaded()
}
notifyDataAdd(index: number): void {
this.listener.onDataAdd(index)
}
notifyDataChange(index: number): void {
this.listener.onDataChange(index)
}
notifyDataDelete(index: number): void {
this.listener.onDataDelete(index)
}
}
这个虚拟列表实现使用 LazyForEach 和自定义的 IDataSource 接口,只渲染可见区域的商品卡片,大幅减少内存占用和提升渲染性能。
4.2 图片懒加载
在电商应用中,图片通常是最占用资源的部分。实现图片懒加载可以减少初始加载时间和内存占用。
@Component
struct LazyImage {
src: Resource
@State isLoaded: boolean = false
@State isLoading: boolean = false
@State isError: boolean = false
build() {
Stack({ alignContent: Alignment.Center }) {
if (!this.isLoaded) {
Row()
.width('100%')
.height('100%')
.backgroundColor('#F0F0F0')
if (this.isLoading) {
LoadingProgress()
.width(24)
.height(24)
.color('#999999')
} else if (this.isError) {
Image($r('app.media.image_error'))
.width(32)
.height(32)
.fillColor('#999999')
}
}
Image(this.src)
.width('100%')
.height('100%')
.objectFit(ImageFit.Cover)
.opacity(this.isLoaded ? 1 : 0)
.onComplete(() => {
this.isLoaded = true
this.isLoading = false
})
.onError(() => {
this.isError = true
this.isLoading = false
})
}
.width('100%')
.height('100%')
.onAppear(() => {
this.isLoading = true
})
}
}
然后在商品卡片中使用这个懒加载图片组件:
@Builder
ProductCard(product: EcommerceProduct) {
Column() {
// 使用懒加载图片
LazyImage({ src: product.image })
.width('100%')
.height(160)
.borderRadius({ topLeft: 12, topRight: 12 })
// 其他商品信息...
}
}
这个懒加载图片组件只有在图片出现在可视区域时才开始加载,并显示加载状态和错误状态,提升用户体验和应用性能。
5. 高级数据处理
5.1 分页加载
在实际应用中,商品数据通常需要从服务器分页加载,以减少网络传输和提升性能。
@State products: EcommerceProduct[] = []
@State isLoading: boolean = false
@State hasError: boolean = false
@State errorMessage: string = ''
@State currentPage: number = 1
@State pageSize: number = 20
@State hasMoreData: boolean = true
// 初始加载数据
aboutToAppear() {
this.loadProducts()
}
// 加载商品数据
async loadProducts() {
if (this.isLoading) return
this.isLoading = true
this.hasError = false
try {
// 模拟API请求
const response = await this.fetchProducts({
page: this.currentPage,
pageSize: this.pageSize,
category: this.selectedCategory !== '全部' ? this.selectedCategory : undefined,
minPrice: this.priceRange.min,
maxPrice: this.priceRange.max,
keyword: this.searchKeyword.trim() !== '' ? this.searchKeyword : undefined,
sortBy: this.getSortParam()
})
if (this.currentPage === 1) {
// 第一页,替换现有数据
this.products = response.data
} else {
// 追加数据
this.products = [...this.products, ...response.data]
}
this.hasMoreData = response.hasMore
this.currentPage++
} catch (error) {
this.hasError = true
this.errorMessage = '加载商品失败,请重试'
console.error('加载商品失败', error)
} finally {
this.isLoading = false
}
}
// 刷新数据
refreshData() {
this.currentPage = 1
this.hasMoreData = true
this.loadProducts()
}
// 获取排序参数
getSortParam(): string {
switch (this.sortBy) {
case '价格升序':
return 'price_asc'
case '价格降序':
return 'price_desc'
case '销量':
return 'sales_desc'
case '评分':
return 'rating_desc'
default: // 综合
return 'comprehensive'
}
}
// 模拟API请求
async fetchProducts(params: {
page: number,
pageSize: number,
category?: string,
minPrice?: number,
maxPrice?: number,
keyword?: string,
sortBy?: string
}): Promise<{ data: EcommerceProduct[], hasMore: boolean }> {
return new Promise((resolve) => {
setTimeout(() => {
// 模拟服务器返回的数据
const data = this.generateMockProducts(params.pageSize)
const hasMore = params.page < 5 // 假设只有5页数据
resolve({ data, hasMore })
}, 1000) // 模拟网络延迟
})
}
这个分页加载实现包含以下特点:
- 初始加载:在组件初始化时加载第一页数据
- 分页参数:支持页码、每页数量、分类、价格范围、关键词和排序等参数
- 数据追加:第一页替换现有数据,后续页面追加数据
- 加载状态:跟踪加载状态、错误状态和是否有更多数据
- 错误处理:捕获并显示加载错误
这种分页加载策略能够有效减少网络传输和内存占用,提升应用性能和用户体验。
6. 总结
在本教程中,我们深入探讨了 HarmonyOS NEXT 电商网格布局的高级应用,包括复杂业务场景实现、高级交互技术、性能优化策略和高级数据处理等方面。
- 0回答
- 4粉丝
- 0关注
- 177.[HarmonyOS NEXT 实战案例七:Grid] 嵌套网格布局高级篇:复杂业务场景与高级定制
- 182.[HarmonyOS NEXT 实战案例九:Grid] 电商网格布局进阶篇:打造高级交互与视觉体验
- 165.[HarmonyOS NEXT 实战案例三:Grid] 不规则网格布局高级篇:复杂布局与高级技巧
- 159.[HarmonyOS NEXT 实战案例一:Grid] 基础网格布局高级篇:电商应用的复杂交互与动效实现
- 181.[HarmonyOS NEXT 实战案例九:Grid] 电商网格布局基础篇:打造精美商品展示页面
- 157.[HarmonyOS NEXT 实战案例一:Grid] 基础网格布局:打造精美电商商品列表
- [HarmonyOS NEXT 实战案例一] 电商首页商品网格布局(下)
- [HarmonyOS NEXT 实战案例一] 电商首页商品网格布局(上)
- [HarmonyOS NEXT 实战案例十五] 电商分类导航网格布局
- [HarmonyOS NEXT 实战案例十五] 电商分类导航网格布局(进阶篇)
- 171.[HarmonyOS NEXT 实战案例五:Grid] 动态网格布局高级篇
- 162.[HarmonyOS NEXT 实战案例二:Grid] 照片相册网格布局:高级篇
- 168.[HarmonyOS NEXT 实战案例四:Grid] 可滚动网格布局高级篇
- 174.[HarmonyOS NEXT 实战案例六:Grid] 响应式网格布局 - 高级篇
- 180.[HarmonyOS NEXT 实战案例八:Grid] 瀑布流网格布局高级篇