152.[HarmonyOS NEXT 实战案例十二:List系列] 卡片样式列表组件实战:打造精美电商应用 进阶篇

2025-06-30 22:27:09
102次阅读
0个评论

[HarmonyOS NEXT 实战案例十二:List系列] 卡片样式列表组件实战:打造精美电商应用 进阶篇

项目已开源,开源地址: https://gitcode.com/nutpi/HarmonyosNextCaseStudyTutorial , 欢迎fork & star

效果演示

image.png

一、引言

在基础篇中,我们学习了如何使用HarmonyOS NEXT的Grid组件实现一个基本的卡片样式商品列表。本篇教程将在此基础上,探索更多进阶特性和优化技巧,帮助你打造一个功能更加丰富、用户体验更加流畅的电商应用。

我们将重点关注以下几个方面:

  1. 卡片样式的高级定制
  2. 瀑布流布局实现
  3. 高级交互效果
  4. 列表加载与刷新
  5. 动画与过渡效果
  6. 高级筛选功能

二、卡片样式的高级定制

2.1 自定义卡片样式

在基础篇中,我们实现了基本的商品卡片样式。现在,我们可以进一步定制卡片样式,使其更加精美:

@Builder
EnhancedProductCard(product: ProductType) {
  Column() {
    // 商品图片区域
    Stack() {
      Image(product.image)
        .width('100%')
        .aspectRatio(1)
        .objectFit(ImageFit.Cover)
        .borderRadius({ topLeft: 8, topRight: 8 })
        .overlay('rgba(0, 0, 0, 0.03)') // 添加轻微的遮罩
      
      // 商品标签容器
      Column() {
        // 新品标签
        if (product.isNew) {
          Row() {
            Image($r('app.media.ic_new'))
              .width(16)
              .height(16)
              .margin({ right: 4 })
            
            Text('新品')
              .fontSize(12)
              .fontColor('#FFFFFF')
          }
          .padding({ left: 8, right: 8, top: 4, bottom: 4 })
          .backgroundColor('#FF5722')
          .borderRadius({ topRight: 8, bottomRight: 8 })
          .margin({ top: 8 })
        }
        
        // 热卖标签
        if (product.isHot) {
          Row() {
            Image($r('app.media.ic_hot'))
              .width(16)
              .height(16)
              .margin({ right: 4 })
            
            Text('热卖')
              .fontSize(12)
              .fontColor('#FFFFFF')
          }
          .padding({ left: 8, right: 8, top: 4, bottom: 4 })
          .backgroundColor('#FF9800')
          .borderRadius({ topRight: 8, bottomRight: 8 })
          .margin({ top: 8 })
        }
      }
      .alignItems(HorizontalAlign.Start)
      .position({ x: 0, y: 0 })
      
      // 收藏按钮
      Button({ type: ButtonType.Circle, stateEffect: true }) {
        Image(product.isFavorite ? $r('app.media.ic_favorite_filled') : $r('app.media.ic_favorite'))
          .width(20)
          .height(20)
          .fillColor(product.isFavorite ? '#FF5722' : '#FFFFFF')
      }
      .width(36)
      .height(36)
      .backgroundColor('rgba(255, 255, 255, 0.8)')
      .position({ x: '100%', y: 0 })
      .translate({ x: -44, y: 8 })
      .onClick(() => {
        // 切换收藏状态
        product.isFavorite = !product.isFavorite;
      })
    }
    .width('100%')
    
    // 商品信息区域
    Column() {
      // 商品名称
      Text(product.name)
        .fontSize(14)
        .fontColor('#333333')
        .fontWeight(FontWeight.Medium)
        .maxLines(2)
        .textOverflow({ overflow: TextOverflow.Ellipsis })
        .width('100%')
      
      // 价格和折扣信息
      Row() {
        Column() {
          // 价格区域
          Row() {
            Text('¥')
              .fontSize(12)
              .fontColor('#FF5722')
              .fontWeight(FontWeight.Bold)
            
            Text(product.price.toFixed(2))
              .fontSize(16)
              .fontColor('#FF5722')
              .fontWeight(FontWeight.Bold)
          }
          
          // 原价和折扣
          Row() {
            Text(`¥${product.originalPrice.toFixed(2)}`)
              .fontSize(12)
              .fontColor('#999999')
              .decoration({ type: TextDecorationType.LineThrough })
            
            Text(product.discount)
              .fontSize(12)
              .fontColor('#FF5722')
              .backgroundColor('#FFF0E6')
              .padding({ left: 4, right: 4, top: 1, bottom: 1 })
              .borderRadius(2)
              .margin({ left: 4 })
          }
        }
        .alignItems(HorizontalAlign.Start)
        .layoutWeight(1)
        
        // 购物车按钮
        Button({ type: ButtonType.Circle }) {
          Image($r('app.media.ic_cart_add'))
            .width(20)
            .height(20)
            .fillColor('#FFFFFF')
        }
        .width(32)
        .height(32)
        .backgroundColor('#FF5722')
        .onClick(() => {
          // 添加到购物车
          this.addToCart(product);
        })
      }
      .width('100%')
      .margin({ top: 8 })
      
      // 销量和评分
      Row() {
        Text(`${product.sales}人已购买`)
          .fontSize(12)
          .fontColor('#999999')
        
        if (product.rating) {
          Row() {
            ForEach([1, 2, 3, 4, 5], (item) => {
              Image($r('app.media.ic_star'))
                .width(12)
                .height(12)
                .fillColor(item <= product.rating ? '#FFC107' : '#E0E0E0')
                .margin({ right: item < 5 ? 2 : 0 })
            })
          }
          .margin({ left: 8 })
        }
      }
      .width('100%')
      .margin({ top: 4 })
    }
    .width('100%')
    .padding({ left: 12, right: 12, top: 12, bottom: 12 })
  }
  .width('100%')
  .backgroundColor('#FFFFFF')
  .borderRadius(8)
  .shadow({
    radius: 8,
    color: 'rgba(0, 0, 0, 0.1)',
    offsetX: 0,
    offsetY: 2
  })
  .stateStyles({
    pressed: {
      transform: { scale: { x: 0.98, y: 0.98 } },
      opacity: 0.9,
      shadow: {
        radius: 6,
        color: 'rgba(0, 0, 0, 0.08)',
        offsetX: 0,
        offsetY: 1
      }
    },
    normal: {
      transform: { scale: { x: 1.0, y: 1.0 } },
      opacity: 1.0,
      shadow: {
        radius: 8,
        color: 'rgba(0, 0, 0, 0.1)',
        offsetX: 0,
        offsetY: 2
      }
    }
  })
  .onClick(() => {
    // 点击商品卡片的处理逻辑
    this.navigateToProductDetail(product);
  })
}

这个增强版的商品卡片包含以下改进:

  1. 更精美的标签设计:新品和热卖标签添加了图标,使用了更美观的样式
  2. 收藏按钮:右上角添加了收藏按钮,用户可以直接收藏商品
  3. 购物车按钮:添加了快速加入购物车的按钮
  4. 评分展示:使用星星图标展示商品评分
  5. 更丰富的状态样式:点击时不仅有缩放效果,还有阴影变化
  6. 图片遮罩:添加轻微的遮罩,提升视觉效果

2.2 卡片样式主题化

为了支持不同的视觉主题(如日间/夜间模式),我们可以实现卡片样式的主题化:

// 定义主题颜色
class ThemeColors {
  static readonly light = {
    background: '#FFFFFF',
    cardBackground: '#FFFFFF',
    primaryText: '#333333',
    secondaryText: '#999999',
    accentColor: '#FF5722',
    borderColor: '#E5E5E5',
    shadowColor: 'rgba(0, 0, 0, 0.1)'
  }
  
  static readonly dark = {
    background: '#121212',
    cardBackground: '#1E1E1E',
    primaryText: '#E0E0E0',
    secondaryText: '#9E9E9E',
    accentColor: '#FF7043',
    borderColor: '#333333',
    shadowColor: 'rgba(0, 0, 0, 0.2)'
  }
}

// 使用AppStorage存储当前主题
AppStorage.SetOrCreate('themeMode', 'light');

// 在组件中使用主题颜色
@StorageLink('themeMode') themeMode: string = 'light';

get colors() {
  return this.themeMode === 'light' ? ThemeColors.light : ThemeColors.dark;
}

// 在卡片样式中应用主题颜色
.backgroundColor(this.colors.cardBackground)
.shadow({
  radius: 8,
  color: this.colors.shadowColor,
  offsetX: 0,
  offsetY: 2
})

通过这种方式,我们可以轻松实现卡片样式的主题切换,适应不同的使用场景和用户偏好。

三、瀑布流布局实现

瀑布流布局是卡片样式列表的一种变体,它允许卡片具有不同的高度,更适合展示不规则内容。

3.1 使用WaterFlow组件

HarmonyOS NEXT提供了WaterFlow组件,专门用于实现瀑布流布局:

WaterFlow() {
  ForEach(this.productList, (product: ProductType) => {
    FlowItem() {
      this.EnhancedProductCard(product)
    }
  })
}
.columnsTemplate('1fr 1fr') // 两列布局
.itemConstraintSize({ // 设置项目尺寸约束
  minWidth: 160,
  maxWidth: '100%'
})
.columnsGap(8) // 列间距
.rowsGap(8) // 行间距
.padding(8) // 内边距
.layoutDirection(FlexDirection.Column) // 布局方向
.onReachStart(() => { // 到达顶部时触发
  console.info('Reached start');
})
.onReachEnd(() => { // 到达底部时触发
  this.loadMoreProducts();
})

WaterFlow组件的主要特点:

  • 使用FlowItem作为子组件,每个FlowItem可以有不同的高度
  • 支持onReachStartonReachEnd事件,方便实现下拉刷新和上拉加载更多
  • 通过itemConstraintSize设置项目的尺寸约束

3.2 动态计算卡片高度

为了实现真正的瀑布流效果,我们需要根据内容动态计算卡片高度:

// 扩展商品数据类型,添加内容高度属性
type EnhancedProductType = ProductType & {
  contentHeight?: number
}

// 预处理商品数据,计算内容高度
preprocessProducts() {
  this.productList.forEach((product: EnhancedProductType) => {
    // 基础高度
    let baseHeight = 280;
    
    // 根据名称长度调整高度
    const nameLines = Math.ceil(product.name.length / 12); // 假设每行约12个字符
    baseHeight += (nameLines - 1) * 20; // 每多一行增加20高度
    
    // 根据是否有标签调整高度
    if (product.isNew || product.isHot) {
      baseHeight += 10;
    }
    
    // 根据是否有评分调整高度
    if (product.rating) {
      baseHeight += 20;
    }
    
    product.contentHeight = baseHeight;
  });
}

// 在FlowItem中应用动态高度
FlowItem() {
  this.EnhancedProductCard(product)
    .height(product.contentHeight)
}

通过这种方式,我们可以根据商品内容的不同,动态计算卡片高度,实现真正的瀑布流效果。

四、高级交互效果

4.1 滑动操作

我们可以为商品卡片添加滑动操作,如收藏、加入购物车等:

@Builder
ProductCardWithSwipe(product: ProductType) {
  SwipeAction() {
    // 卡片内容
    this.EnhancedProductCard(product)
    
    // 滑动操作按钮
    SwipeActionButton() {
      Column() {
        Image($r('app.media.ic_favorite'))
          .width(24)
          .height(24)
          .fillColor('#FFFFFF')
        
        Text('收藏')
          .fontSize(12)
          .fontColor('#FFFFFF')
          .margin({ top: 4 })
      }
      .width('100%')
      .height('100%')
      .justifyContent(FlexAlign.Center)
    }
    .backgroundColor('#FF9800')
    
    SwipeActionButton() {
      Column() {
        Image($r('app.media.ic_cart_add'))
          .width(24)
          .height(24)
          .fillColor('#FFFFFF')
        
        Text('加入购物车')
          .fontSize(12)
          .fontColor('#FFFFFF')
          .margin({ top: 4 })
      }
      .width('100%')
      .height('100%')
      .justifyContent(FlexAlign.Center)
    }
    .backgroundColor('#FF5722')
  }
  .onSwipeActionButtonClick((index: number) => {
    if (index === 0) {
      // 收藏操作
      product.isFavorite = !product.isFavorite;
    } else if (index === 1) {
      // 加入购物车操作
      this.addToCart(product);
    }
  })
}

这段代码使用SwipeAction组件实现滑动操作:

  • 向左滑动可以显示"收藏"和"加入购物车"按钮
  • 点击按钮时触发相应的操作

4.2 拖拽排序

我们可以实现拖拽排序功能,让用户自定义商品的显示顺序:

@State dragIndex: number = -1

Grid() {
  ForEach(this.filteredProductList, (product: ProductType, index) => {
    GridItem() {
      this.EnhancedProductCard(product)
    }
    .draggable(true)
    .opacity(this.dragIndex === index ? 0.6 : 1.0)
  })
}
.onDragStart((event: DragEvent, itemIndex: number) => {
  this.dragIndex = itemIndex;
  return this.filteredProductList[itemIndex];
})
.onDrop((event: DragEvent, itemIndex: number) => {
  // 交换位置
  if (this.dragIndex !== itemIndex && this.dragIndex !== -1) {
    const temp = this.filteredProductList[this.dragIndex];
    this.filteredProductList[this.dragIndex] = this.filteredProductList[itemIndex];
    this.filteredProductList[itemIndex] = temp;
  }
  this.dragIndex = -1;
})
.onDragEnd(() => {
  this.dragIndex = -1;
})

这段代码实现了拖拽排序功能:

  • 使用draggable属性使GridItem可拖拽
  • 在拖拽过程中改变透明度提供视觉反馈
  • 在拖拽结束时交换商品位置

五、列表加载与刷新

5.1 下拉刷新

我们可以为卡片列表添加下拉刷新功能:

Refresh({ refreshing: this.isRefreshing }) {
  Column() {
    // 分类和排序选项
    this.CategoryTabs()
    this.SortOptions()
    
    // 商品列表
    Grid() {
      ForEach(this.filteredProductList, (product: ProductType) => {
        GridItem() {
          this.EnhancedProductCard(product)
        }
      })
    }
    .columnsTemplate('1fr 1fr')
    .columnsGap(8)
    .rowsGap(8)
    .padding(8)
    .layoutWeight(1)
  }
  .width('100%')
}
.onRefresh(() => {
  // 模拟刷新数据
  this.isRefreshing = true;
  setTimeout(() => {
    // 重新加载商品数据
    this.loadProducts();
    this.isRefreshing = false;
  }, 2000);
})

这段代码使用Refresh组件包裹内容区域,实现下拉刷新功能:

  • 使用isRefreshing状态控制刷新状态
  • onRefresh回调中重新加载商品数据

5.2 无限滚动加载

我们可以实现无限滚动加载功能,当用户滚动到列表底部时自动加载更多商品:

@State hasMoreProducts: boolean = true
@State isLoadingMore: boolean = false
@State currentPage: number = 1

// 在Grid组件底部添加加载更多指示器
Grid() {
  // 商品列表项
  ForEach(this.filteredProductList, (product: ProductType) => {
    GridItem() {
      this.EnhancedProductCard(product)
    }
  })
  
  // 加载更多指示器
  if (this.hasMoreProducts || this.isLoadingMore) {
    GridItem() {
      Column() {
        if (this.isLoadingMore) {
          LoadingProgress()
            .width(24)
            .height(24)
          
          Text('正在加载更多...')
            .fontSize(14)
            .fontColor('#999999')
            .margin({ top: 8 })
        } else {
          Text('上拉加载更多')
            .fontSize(14)
            .fontColor('#999999')
        }
      }
      .width('100%')
      .height(60)
      .justifyContent(FlexAlign.Center)
    }
    .columnStart(0)
    .columnEnd(1)
  }
}
.onReachEnd(() => {
  if (this.hasMoreProducts && !this.isLoadingMore) {
    this.loadMoreProducts();
  }
})

// 加载更多商品
loadMoreProducts() {
  if (this.isLoadingMore) return;
  
  this.isLoadingMore = true;
  this.currentPage++;
  
  // 模拟加载更多数据
  setTimeout(() => {
    // 假设每页加载10个商品
    const newProducts = this.generateMoreProducts(10);
    
    if (newProducts.length > 0) {
      this.productList = this.productList.concat(newProducts);
      this.filterProducts(); // 应用当前的过滤和排序
    } else {
      this.hasMoreProducts = false;
    }
    
    this.isLoadingMore = false;
  }, 1500);
}

这段代码实现了无限滚动加载功能:

  • 使用onReachEnd事件检测滚动到底部
  • 在底部显示加载状态指示器
  • 加载更多数据并追加到现有列表

六、动画与过渡效果

6.1 列表项动画

我们可以为列表项添加进入和退出动画,提升用户体验:

// 定义列表项动画
@State listItemAnimation: AnimateConfig = {
  duration: 300,
  curve: Curve.EaseOut,
  delay: 0,
  iterations: 1,
  playMode: PlayMode.Normal
}

// 在GridItem中应用动画
GridItem() {
  this.EnhancedProductCard(product)
}
.animation(this.listItemAnimation)
.transition({ type: TransitionType.Insert, opacity: 0, scale: { x: 0.8, y: 0.8 } })
.transition({ type: TransitionType.Delete, opacity: 0, scale: { x: 0.8, y: 0.8 } })

这段代码为列表项添加了动画效果:

  • 新项目插入时有淡入和缩放效果
  • 项目删除时有淡出和缩放效果

6.2 分类切换动画

我们可以为分类切换添加平滑的过渡动画:

@Builder
CategoryTabsWithAnimation() {
  Scroll() {
    Row() {
      ForEach(this.categories, (category: CategoryType, index) => {
        Column() {
          Text(category.name)
            .fontSize(14)
            .fontColor(this.currentCategory === index ? '#FF5722' : '#333333')
            .fontWeight(this.currentCategory === index ? FontWeight.Bold : FontWeight.Normal)
          
          // 底部指示器
          Row()
            .width(24)
            .height(3)
            .borderRadius(1.5)
            .backgroundColor('#FF5722')
            .opacity(this.currentCategory === index ? 1 : 0)
            .animation({
              duration: 250,
              curve: Curve.EaseInOut,
              delay: 0,
              iterations: 1,
              playMode: PlayMode.Normal
            })
        }
        .height(40)
        .padding({ left: 16, right: 16 })
        .margin({ right: 8 })
        .alignItems(HorizontalAlign.Center)
        .justifyContent(FlexAlign.Center)
        .onClick(() => {
          this.currentCategory = index;
          this.filterProducts();
        })
      })
    }
  }
  .scrollable(ScrollDirection.Horizontal)
  .scrollBar(BarState.Off)
  .width('100%')
}

这段代码为分类选项卡添加了动画效果:

  • 底部指示器有平滑的显示/隐藏动画
  • 文字颜色和粗细有过渡效果

七、高级筛选功能

7.1 多维度筛选

我们可以实现多维度筛选功能,让用户可以根据多个条件筛选商品:

// 筛选条件
@State filterConditions: {
  priceRange: [number, number],
  categories: string[],
  tags: string[],
  ratings: number[]
} = {
  priceRange: [0, 10000],
  categories: [],
  tags: [],
  ratings: []
}

// 筛选面板UI
@Builder
AdvancedFilterPanel() {
  Column() {
    // 价格范围筛选
    Column() {
      Text('价格范围')
        .fontSize(16)
        .fontWeight(FontWeight.Medium)
        .width('100%')
      
      Slider({
        min: 0,
        max: 10000,
        step: 100,
        value: this.filterConditions.priceRange[1],
        style: SliderStyle.OutSet
      })
        .width('100%')
        .margin({ top: 16, bottom: 16 })
        .onChange((value: number) => {
          this.filterConditions.priceRange[1] = value;
        })
      
      Row() {
        Text(`¥${this.filterConditions.priceRange[0]}`)
          .fontSize(14)
          .fontColor('#666666')
        
        Text(`¥${this.filterConditions.priceRange[1]}`)
          .fontSize(14)
          .fontColor('#666666')
      }
      .width('100%')
      .justifyContent(FlexAlign.SpaceBetween)
    }
    .width('100%')
    .padding(16)
    
    Divider().color('#E5E5E5').width('100%')
    
    // 分类筛选
    Column() {
      Text('分类')
        .fontSize(16)
        .fontWeight(FontWeight.Medium)
        .width('100%')
      
      Flex({ wrap: FlexWrap.Wrap }) {
        ForEach(this.categories.slice(1), (category: CategoryType) => {
          Text(category.name)
            .fontSize(14)
            .fontColor(this.filterConditions.categories.includes(category.name) ? '#FFFFFF' : '#333333')
            .backgroundColor(this.filterConditions.categories.includes(category.name) ? '#FF5722' : '#F5F5F5')
            .padding({ left: 12, right: 12, top: 8, bottom: 8 })
            .borderRadius(16)
            .margin({ right: 8, bottom: 8 })
            .onClick(() => {
              // 切换分类选择状态
              if (this.filterConditions.categories.includes(category.name)) {
                this.filterConditions.categories = this.filterConditions.categories.filter(c => c !== category.name);
              } else {
                this.filterConditions.categories.push(category.name);
              }
            })
        })
      }
      .width('100%')
      .margin({ top: 16 })
    }
    .width('100%')
    .padding(16)
    
    Divider().color('#E5E5E5').width('100%')
    
    // 标签筛选
    Column() {
      Text('标签')
        .fontSize(16)
        .fontWeight(FontWeight.Medium)
        .width('100%')
      
      Flex({ wrap: FlexWrap.Wrap }) {
        ForEach(['新品', '热卖', '促销', '包邮', '限时'], (tag: string) => {
          Text(tag)
            .fontSize(14)
            .fontColor(this.filterConditions.tags.includes(tag) ? '#FFFFFF' : '#333333')
            .backgroundColor(this.filterConditions.tags.includes(tag) ? '#FF5722' : '#F5F5F5')
            .padding({ left: 12, right: 12, top: 8, bottom: 8 })
            .borderRadius(16)
            .margin({ right: 8, bottom: 8 })
            .onClick(() => {
              // 切换标签选择状态
              if (this.filterConditions.tags.includes(tag)) {
                this.filterConditions.tags = this.filterConditions.tags.filter(t => t !== tag);
              } else {
                this.filterConditions.tags.push(tag);
              }
            })
        })
      }
      .width('100%')
      .margin({ top: 16 })
    }
    .width('100%')
    .padding(16)
    
    // 确认和重置按钮
    Row() {
      Button('重置')
        .fontSize(16)
        .fontColor('#666666')
        .backgroundColor('#F5F5F5')
        .width('48%')
        .height(44)
        .onClick(() => {
          // 重置筛选条件
          this.resetFilterConditions();
        })
      
      Button('确认')
        .fontSize(16)
        .fontColor('#FFFFFF')
        .backgroundColor('#FF5722')
        .width('48%')
        .height(44)
        .onClick(() => {
          // 应用筛选条件
          this.applyFilterConditions();
          this.isFilterPanelVisible = false;
        })
    }
    .width('100%')
    .justifyContent(FlexAlign.SpaceBetween)
    .padding(16)
  }
  .width('100%')
  .backgroundColor('#FFFFFF')
  .borderRadius({ topLeft: 16, topRight: 16 })
}

// 应用筛选条件
applyFilterConditions() {
  this.filteredProductList = this.productList.filter(product => {
    // 价格范围筛选
    if (product.price < this.filterConditions.priceRange[0] || 
        product.price > this.filterConditions.priceRange[1]) {
      return false;
    }
    
    // 分类筛选
    if (this.filterConditions.categories.length > 0 && 
        !this.filterConditions.categories.includes(product.category)) {
      return false;
    }
    
    // 标签筛选
    if (this.filterConditions.tags.includes('新品') && !product.isNew) {
      return false;
    }
    if (this.filterConditions.tags.includes('热卖') && !product.isHot) {
      return false;
    }
    // 其他标签筛选逻辑...
    
    return true;
  });
  
  // 应用当前的排序
  this.sortProducts();
}

这段代码实现了多维度筛选功能:

  • 价格范围筛选:使用Slider组件选择价格范围
  • 分类筛选:可以选择多个分类
  • 标签筛选:可以选择多个标签(新品、热卖等)
  • 提供重置和确认按钮

7.2 搜索与推荐

我们可以实现搜索和推荐功能,帮助用户更快找到所需商品:

@State searchKeyword: string = ''
@State searchHistory: string[] = []
@State searchSuggestions: string[] = []

// 搜索框UI
@Builder
EnhancedSearchBar() {
  Column() {
    Row() {
      Image($r('app.media.ic_search'))
        .width(20)
        .height(20)
        .margin({ right: 8 })
      
      TextInput({ placeholder: '搜索商品', text: this.searchKeyword })
        .width('80%')
        .height(36)
        .backgroundColor('transparent')
        .onChange((value: string) => {
          this.searchKeyword = value;
          this.updateSearchSuggestions();
        })
      
      if (this.searchKeyword.length > 0) {
        Image($r('app.media.ic_clear'))
          .width(20)
          .height(20)
          .margin({ left: 8 })
          .onClick(() => {
            this.searchKeyword = '';
            this.searchSuggestions = [];
          })
      }
    }
    .width('100%')
    .height(48)
    .borderRadius(24)
    .backgroundColor('#F5F5F5')
    .padding({ left: 16, right: 16 })
    
    // 搜索建议
    if (this.searchKeyword.length > 0 && this.searchSuggestions.length > 0) {
      Column() {
        ForEach(this.searchSuggestions, (suggestion: string) => {
          Row() {
            Image($r('app.media.ic_search_history'))
              .width(16)
              .height(16)
              .margin({ right: 8 })
            
            Text(suggestion)
              .fontSize(14)
              .fontColor('#333333')
              .layoutWeight(1)
            
            Image($r('app.media.ic_arrow_right'))
              .width(16)
              .height(16)
          }
          .width('100%')
          .height(44)
          .padding({ left: 16, right: 16 })
          .onClick(() => {
            this.searchKeyword = suggestion;
            this.search();
          })
          
          if (this.searchSuggestions.indexOf(suggestion) < this.searchSuggestions.length - 1) {
            Divider().color('#E5E5E5').width('100%').margin({ left: 16, right: 16 })
          }
        })
      }
      .width('100%')
      .backgroundColor('#FFFFFF')
      .borderRadius(8)
      .margin({ top: 8 })
      .shadow({
        radius: 8,
        color: 'rgba(0, 0, 0, 0.1)',
        offsetX: 0,
        offsetY: 2
      })
    }
  }
  .width('100%')
}

// 更新搜索建议
updateSearchSuggestions() {
  if (this.searchKeyword.length === 0) {
    this.searchSuggestions = [];
    return;
  }
  
  // 模拟搜索建议
  this.searchSuggestions = this.productList
    .filter(product => product.name.toLowerCase().includes(this.searchKeyword.toLowerCase()))
    .map(product => product.name)
    .slice(0, 5);
}

// 执行搜索
search() {
  if (this.searchKeyword.trim().length === 0) return;
  
  // 保存搜索历史
  if (!this.searchHistory.includes(this.searchKeyword)) {
    this.searchHistory.unshift(this.searchKeyword);
    if (this.searchHistory.length > 10) {
      this.searchHistory.pop();
    }
  }
  
  // 根据关键词过滤商品
  this.filteredProductList = this.productList.filter(product => {
    return product.name.toLowerCase().includes(this.searchKeyword.toLowerCase()) || 
           product.category.toLowerCase().includes(this.searchKeyword.toLowerCase());
  });
  
  // 清空搜索建议
  this.searchSuggestions = [];
}

这段代码实现了搜索和推荐功能:

  • 实时搜索建议:输入关键词时显示匹配的商品名称
  • 搜索历史记录:保存用户的搜索历史
  • 搜索结果过滤:根据关键词过滤商品列表

八、完整代码结构

下面是进阶版卡片样式列表应用的完整功能和技术点总结:

功能模块 技术点 实现方式
卡片样式高级定制 复杂布局、状态样式 使用Stack、Column、Row组件 + stateStyles
主题化 动态颜色、AppStorage 定义主题颜色 + StorageLink
瀑布流布局 WaterFlow组件 WaterFlow + FlowItem + 动态高度计算
滑动操作 SwipeAction组件 SwipeAction + SwipeActionButton
拖拽排序 拖拽事件 draggable属性 + onDragStart/onDrop事件
下拉刷新 Refresh组件 Refresh + onRefresh回调
无限滚动加载 滚动事件 onReachEnd事件 + 加载状态指示器
列表项动画 过渡动画 animation + transition属性
多维度筛选 复杂筛选面板 Slider、多选UI + 筛选逻辑
搜索与推荐 搜索建议、历史记录 TextInput + 实时建议 + 历史记录

九、总结

本教程详细讲解了如何在HarmonyOS NEXT中实现一个具有进阶特性的卡片样式列表应用,通过这些进阶特性的实现,我们的卡片样式列表应用不仅具备了基本的商品展示功能,还拥有了更加精美的UI设计、更丰富的交互方式和更强大的功能。这些技术和思路不仅适用于电商应用,也可以应用到其他需要卡片式内容展示的场景中,如社交媒体、新闻应用、视频应用等。

收藏00

登录 后评论。没有帐号? 注册 一个。

全栈若城

  • 0回答
  • 4粉丝
  • 0关注
相关话题