HarmonyOS Next应用开发实战——商城页面功能实现(part2)

120 阅读1分钟
3. 页面布局搭建

商城页面主要由头部、轮播图、选项卡和商品瀑布流组成。

build() {
  Stack({ alignContent: Alignment.TopStart }) {
    // 头部布局
    Row({ space: CommonConstants.PUBLIC_SPACE }) {
      Text('商城').fontSize(CommonConstants.MAX_FONT_SIZE).fontWeight(FontWeight.Bold)
      Blank()
      Column() {
        Image($r('app.media.AI_search')).iconStyle()
      }.iconBgStyle()

      Column() {
        Image($r('app.media.bag')).iconStyle()
      }.iconBgStyle()

      Column() {
        Image($r('app.media.more')).iconStyle()
      }.iconBgStyle()
    }
    .width(CommonConstants.COLUMN_WIDTH)
    .height(this.headerHeight)
    .padding({
      top: CommonConstants.MAIN_PADDING + this.topHeight,
      bottom: CommonConstants.MAIN_PADDING,
      left: CommonConstants.S_MAIN_PADDING*2,
      right: CommonConstants.MAIN_PADDING
    })
    .zIndex(2)
    .position({ y: 0 })

    // 轮播图和选项卡布局
    Column() {
      Swiper() {
        ForEach(this.swiperList, (item: CommodityData) => {
          Image(item.picture).blur(this.radius / 10).height(this.swiperHeight)
            .onClick(() => {
              this.pageInfos.pushPathByName('CommodityDetailPage', item.id);
            })
        })
      }
      .autoPlay(this.swiperHeight !== this.headerHeight)
      .indicator(
        this.swiperHeight !== this.headerHeight ?
        new DotIndicator()
          .itemWidth(ShoppingMallConstants.DOTINDICATOR_WIDTH)
          .itemHeight(ShoppingMallConstants.DOTINDICATOR_HEIGHT)
          .selectedItemWidth(ShoppingMallConstants.SELE_DOTINDICATOR_WIDTH)
          .selectedItemHeight(ShoppingMallConstants.DOTINDICATOR_HEIGHT)
          .color(ShoppingMallConstants.DOTINDICATOR_DEF_COLOR)
          .selectedColor(ShoppingMallConstants.DOTINDICATOR_SELE_COLOR)
          .bottom(0) : false
      )
      .width(CommonConstants.COLUMN_WIDTH)
      .height(this.swiperHeight)

      Scroll() {
        Row() {
          ForEach(this.optionList, (item: ResourceStr, index: number) => {
            this.tabBuilder(index, item)
          }, (item: ResourceStr) => `${item}`)
        }.width('auto')
        .margin({
          left:CommonConstants.MAIN_MARGIN
        })
        .backgroundImage($r('app.media.background'))
        .backgroundImageSize(ImageSize.Cover)

      }
      .scrollable(ScrollDirection.Horizontal)
      .scrollBar(BarState.Off)
      .height(this.tabHeight)
    }
    .zIndex(1)

    // 商品瀑布流布局
    WaterFlow({ scroller: this.scroller }) {
      FlowItem() {
      }.height(this.whiteboardHeight)

      FlowItem() {
      }.height(this.whiteboardHeight)

      LazyForEach(this.commodityList, (item: CommodityData) => {
        FlowItem() {
          CommodityItemComp({ commodityData: item })
        }
        .onClick(() => {
          this.pageInfos.pushPathByName('CommodityDetailPage', item.id);
        })
        .margin({
          left: CommonConstants.MAIN_PADDING / 2,
          right: CommonConstants.MAIN_PADDING / 2,
          bottom: CommonConstants.MAIN_MARGIN
        })
      }, (item: CommodityData) => `${JSON.stringify(item)}`)
    }
    .columnsTemplate('1fr 1fr')
    .scrollBar(BarState.Off)
    .height(CommonConstants.COLUMN_HEIGHT)
    .padding({
      left: CommonConstants.MAIN_PADDING / 2,
      right: CommonConstants.MAIN_PADDING / 2,
    })
    .onScroll((scrollOffset: number, scrollState: ScrollState) => {
      if (this.whiteboardHeight <= this.defWhiteboardHeight) {
        this.whiteboardHeight -= scrollOffset;
      }
      if (!this.flagReachStart) {
        return;
      }
      if (this.radius >= 0 && this.radius <= this.defWhiteboardHeight) {
        this.changeState(scrollOffset)
      }
      if (scrollOffset > 0) {
        this.flagUP = true;
      } // 向上滑
      if (scrollOffset < 0) {
        this.flagUP = false;
      } // 向下滑
      if (scrollState === 2) {
        this.scrollFun()
      }
    })
    .onScrollIndex((start: number, end: number) => {
      this.flagReachStart = start === 0;
    })
    .onScrollStop(() => {
      this.scrollFun()
    })
  }.width(CommonConstants.COLUMN_WIDTH)
  .backgroundImage($r('app.media.background'))
  .backgroundImageSize(ImageSize.Cover)
}
4. 选项卡交互

选项卡点击时切换当前选中项,并模拟请求重新加载商品数据。

@Builder
tabBuilder(index: number, name: ResourceStr) {
  Column() {
    Text(name)
      .fontColor(this.currentIndex === index ? ShoppingMallConstants.SELE_COLOR : ShoppingMallConstants.DEFA_COLOR)
      .fontSize(CommonConstants.M_FONT_SIZE)
      .padding(CommonConstants.MAIN_PADDING)
  }
  .justifyContent(FlexAlign.Center)
  .borderRadius(21)
  .backgroundColor(this.currentIndex === index ? ShoppingMallConstants.SELE_BG_COLOR :
  ShoppingMallConstants.DEFA_BG_COLOR)
  .margin({
    top: CommonConstants.M_MAIN_MARGIN,
    bottom: CommonConstants.M_MAIN_MARGIN,
    left: CommonConstants.MAIN_MARGIN,
  })
  .width(ShoppingMallConstants.TAB_WIDTH)
  .onClick(() => {
    this.currentIndex = index;
    // 模拟请求
    this.commodityList.clear();
    setTimeout(() => {
      this.commodityList.pushArrayData(this.commodityModel.getAllList());
    }, 1000)
  })
}
5. 滚动动画效果

根据滚动方向和偏移量实现轮播图的收缩和展开动画。

scrollFun() {
  if (this.radius === this.defWhiteboardHeight && !this.flagReachStart) {
    return;
  } // 继续向上/向下滑
  let radius = 0;
  if (this.radius < 0) {
    radius = -this.radius;
  } else {
    radius = this.radius;
  }
  if (this.flagUP) { // 向上滑
    if (radius >= this.defWhiteboardHeight / 2) {
      animateTo({ duration: this.animateTime }, () => {
        this.radius = this.defWhiteboardHeight;
        this.swiperHeight = this.headerHeight;
        this.whiteboardHeight = this.headerHeight + this.tabHeight;
      })
    } else {
      animateTo({ duration: this.animateTime }, () => {
        this.radius = 0;
        this.swiperHeight = ShoppingMallConstants.SWIPER_HEIGHT;
        this.whiteboardHeight = this.defWhiteboardHeight;
      })
      this.scroller.scrollToIndex(0)
    }
  } else { // 向下滑
    if (this.swiperHeight >= ShoppingMallConstants.SWIPER_HEIGHT) {
      animateTo({ duration: this.animateTime }, () => {
        this.radius = 0;
        this.swiperHeight = ShoppingMallConstants.SWIPER_HEIGHT;
        this.whiteboardHeight = this.defWhiteboardHeight;
        this.scroller.scrollToIndex(0)
      })
    } else {
      if (radius >= this.defWhiteboardHeight / 2) {
        animateTo({ duration: this.animateTime }, () => {
          this.radius = 0;
          this.swiperHeight = ShoppingMallConstants.SWIPER_HEIGHT;
          this.whiteboardHeight = this.defWhiteboardHeight;
        })
      } else {
        animateTo({ duration: this.animateTime }, () => {
          this.radius = this.defWhiteboardHeight;
          this.swiperHeight = this.headerHeight;
          this.whiteboardHeight = this.headerHeight + this.tabHeight;
        })
      }
    }
  }
}

通过以上核心功能的实现,开发者可以在HarmonyOS Next应用中创建一个功能完善的商城页面。