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应用中创建一个功能完善的商城页面。