3. 弹出框构建
bindSheetBuilder方法用于构建弹出框内容,包含商品图片、描述、价格、颜色选择、数量选择以及购物操作按钮。
@Builder bindSheetBuilder(){
Column({ space: CommonConstants.MAIN_PADDING * 2 }){
Column({ space: CommonConstants.MAIN_PADDING * 2 }){
Row(){
Text('×').fontSize(24)
.onClick(()=>{
this.isShow = false;
})
}
.justifyContent(FlexAlign.End) .width(CommonConstants.COLUMN_WIDTH)
Row({ space: CommonConstants.PUBLIC_SPACE }){
Image(this.commodityData?.picture)
.width(100).height(100)
Column({ space: CommonConstants.PUBLIC_SPACE }){
Text(this.commodityData?.description)
.fontWeight(FontWeight.Bold)
Blank()
Text(){
Span('¥')
Span(`${this.commodityData?.price}`)
.fontSize(CommonConstants.M_FONT_SIZE)
.fontWeight(FontWeight.Bold)
}
.fontSize(CommonConstants.S_FONT_SIZE)
Text(`已选:${this.checked ? '图片色' : ''}`).fontSize(CommonConstants.S_FONT_SIZE)
}
.layoutWeight(1)
.alignItems(HorizontalAlign.Start)
}
Column({ space: CommonConstants.PUBLIC_SPACE }){
Text('图片色')
.fontSize(CommonConstants.M_FONT_SIZE)
.fontColor(ShoppingMallConstants.NOT_CHECKED_COLOR)
Text('图片色')
.fontColor(this.checked ? ShoppingMallConstants.CHECKED_COLOR : ShoppingMallConstants.NOT_CHECKED_COLOR)
.fontSize(CommonConstants.M_FONT_SIZE)
.padding({
top: CommonConstants.MAIN_PADDING / 2,
bottom: CommonConstants.MAIN_PADDING / 2,
left: CommonConstants.MAIN_PADDING,
right: CommonConstants.MAIN_PADDING
})
.border({ width : 1 , color: this.checked ? ShoppingMallConstants.CHECKED_COLOR : ShoppingMallConstants.NOT_CHECKED_COLOR })
.onClick(()=>{ this.checked = !this.checked; })
}
.width(CommonConstants.COLUMN_WIDTH)
.alignItems(HorizontalAlign.Start)
Column({ space: CommonConstants.PUBLIC_SPACE }){
Text('数量')
.fontSize(CommonConstants.M_FONT_SIZE)
.fontColor(ShoppingMallConstants.NOT_CHECKED_COLOR)
Counter(){
Text(this.addCount.toString())
.fontSize(CommonConstants.M_FONT_SIZE)
}
.onInc(() => { this.addCount++ })
.onDec(() => { this.addCount > 1 ? (this.addCount--) : '' })
}
.width(CommonConstants.COLUMN_WIDTH)
.alignItems(HorizontalAlign.Start)
}
.padding({
top: CommonConstants.MAIN_PADDING * 2,
left: CommonConstants.MAIN_PADDING * 2,
right: CommonConstants.MAIN_PADDING * 2,
})
Row(){
this.footer()
}.width(CommonConstants.COLUMN_WIDTH)
.padding({
right: CommonConstants.MAIN_PADDING,
left: CommonConstants.MAIN_PADDING,
})
}
}
4. 页面构建与状态管理
在build方法中,构建了整个商品详情页的布局,包括顶部导航、商品信息展示、滚动区域和页脚。同时,通过@State装饰器管理页面状态,如isShow控制弹出框的显示与隐藏,scrollHeight用于处理滚动交互。
build() {### 标题:HarmonyOS Next应用开发实战——探索页多标签滑动联动实现
#### 文章概要
本文聚焦于HarmonyOS Next应用开发中探索页面的实现,详细介绍了多标签滑动联动的功能。包括获取屏幕信息、处理组件属性、实现标签下划线动画效果,以及标签与滑动页面的联动,为开发者在HarmonyOS Next应用中实现类似交互效果提供参考。
#### 核心功能介绍
##### 1. 初始化屏幕信息
在页面即将显示时,获取屏幕的实例信息,为后续的布局和动画计算提供基础。
```typescript
aboutToAppear(): void {
this.displayInfo = display.getDefaultDisplaySync(); //获取屏幕实例
}
2. 获取屏幕和组件信息
通过getDisplayWidth方法获取屏幕宽度,getTextInfo方法获取组件的位置和宽度信息。
// 获取屏幕宽度,单位vp
private getDisplayWidth(): number {
return this.displayInfo != null ? px2vp(this.displayInfo.width) : 0;
}
// 获取组件大小、位置、平移缩放旋转及仿射矩阵属性信息。
private getTextInfo(index: number): Record<string, number> {
let modePosition: componentUtils.ComponentInfo = componentUtils.getRectangleById(index.toString());
try {
return { 'left': px2vp(modePosition.windowOffset.x), 'width': px2vp(modePosition.size.width) }
} catch (error) {
return { 'left': 0, 'width': 0 }
}
}
3. 下划线动画效果
getCurrentIndicatorInfo方法根据滑动事件计算当前下划线的位置和宽度,实现下划线跟随页面滑动和宽度渐变的效果。
// 当前下划线动画
private getCurrentIndicatorInfo(index: number, event: SwiperAnimationEvent): Record<string, number> {
let nextIndex = index;
// 滑动范围限制,Swiper不可循环,Scroll保持不可循环
if (index > 0 && event.currentOffset > 0) {
nextIndex--; // 左滑
} else if (index < this.arr.length - 1 && event.currentOffset < 0) {
nextIndex++; // 右滑
}
// 获取当前tabbar的属性信息
let indexInfo = this.getTextInfo(index);
// 获取目标tabbar的属性信息
let nextIndexInfo = this.getTextInfo(nextIndex);
// 滑动页面超过一半时页面切换
this.swipeRatio = Math.abs(event.currentOffset / this.swiperWidth);
let currentIndex = this.swipeRatio > 0.5 ? nextIndex : index; // 页面滑动超过一半,tabBar切换到下一页。
let currentLeft = indexInfo.left + (nextIndexInfo.left - indexInfo.left) * this.swipeRatio;
let currentWidth = indexInfo.width + (nextIndexInfo.width - indexInfo.width) * this.swipeRatio;
this.indicatorIndex = currentIndex;
return { 'index': currentIndex, 'left': currentLeft, 'width': currentWidth };
}
4. 标签与滑动页面联动
在Swiper组件的事件回调中,如onAnimationStart、onAnimationEnd和onGestureSwipe,实现标签与滑动页面的联动,确保标签和页面状态一致。
Swiper(this.swiperController) {
// ...
}
.onChange((index: number) => {
this.swiperIndex = index;
})
// 跟自定义tabbar进行联动效果
.index(this.swiperIndex)
.onAnimationStart((index: number, targetIndex: number, event: SwiperAnimationEvent) => {
// 切换动画开始时触发该回调。下划线跟着页面一起滑动,同时宽度渐变。
this.indicatorIndex = targetIndex;
this.underlineScrollAuto(this.animationDuration, targetIndex);
})
.onAnimationEnd((index: number, event: SwiperAnimationEvent) => {
// 切换动画结束时触发该回调。下划线动画停止。
this.scrollIntoView(index);
})
.onGestureSwipe((index: number, event: SwiperAnimationEvent) => {
// 在页面跟手滑动过程中,逐帧触发该回调。
let currentIndicatorInfo = this.getCurrentIndicatorInfo(index, event);
this.indicatorIndex = currentIndicatorInfo.index; //当前页签index
let positionXOther = (this.getCurrTextWidth(index) - this.indicatorWidth) / 2;
this.indicatorMarginLeft = currentIndicatorInfo.left + positionXOther; //当前页签距离左边margin
})
通过以上核心功能的实现,在HarmonyOS Next应用中可以实现一个具有流畅交互效果的探索页面,提升用户体验。
NavDestination(){
Column() {
Row(){
Image(r('app.media.share_windows'))
.width(ShoppingMallConstants.ICON_WIDTH)
.fillColor(this.scrollHeight > ShoppingMallConstants.SCROLL_hEIGHT ? Color.Black : Color.White)
}
.width(CommonConstants.COLUMN_WIDTH)
.padding({
top: CommonConstants.MAIN_PADDING + this.topHeight,
bottom: CommonConstants.MAIN_PADDING,
left: CommonConstants.MAIN_PADDING,
right: CommonConstants.MAIN_PADDING
})
.position({ y: 0 }).zIndex(1)
.backgroundColor(rgba(255, 255, 255, ${this.scrollHeight > ShoppingMallConstants.SCROLL_hEIGHT ? 0.9 : 0}))
Flex({ direction : FlexDirection.Column }){
Scroll(){
Column({ space: CommonConstants.PUBLIC_SPACE }){
Image(this.commodityData?.picture)
.width(CommonConstants.COLUMN_WIDTH).height(ShoppingMallConstants.SWIPER_HEIGHT)
Column({ space: CommonConstants.PUBLIC_SPACE * 2 }){
Text(this.commodityData?.description)
.fontSize(CommonConstants.L_FONT_SIZE)
.fontWeight(FontWeight.Bold)
Text(){
Span('¥').fontSize(CommonConstants.M_FONT_SIZE)
Span(${this.commodityData?.price})
.fontSize(CommonConstants.L_FONT_SIZE)
.fontWeight(FontWeight.Bold)
}
Column({ space: CommonConstants.PUBLIC_SPACE }){
Text(){
Span('当前库存:')
.fontSize(CommonConstants.M_FONT_SIZE)
Span(${this.commodityData?.inventory})
.fontSize(CommonConstants.M_FONT_SIZE)
.fontWeight(FontWeight.Bold)
}.fontColor(ShoppingMallConstants.NOT_CHECKED_COLOR)
Divider().backgroundColor(ShoppingMallConstants.LINE_COLOR).strokeWidth(1)
Row(){
Text('购买须知').fontSize(CommonConstants.M_FONT_SIZE)
Blank()
Image($r('app.media.mi_ic_right_arrow'))
.width(ShoppingMallConstants.ICON_WIDTH / 2)
}.width(CommonConstants.COLUMN_WIDTH)
Divider().backgroundColor(ShoppingMallConstants.LINE_COLOR).strokeWidth(1)
}
.width(CommonConstants.COLUMN_WIDTH)
.alignItems(HorizontalAlign.Start)
Column(){
Image(this.commodityData?.picture)
}
.width(CommonConstants.COLUMN_WIDTH)
.height(800)
}
.padding(CommonConstants.MAIN_PADDING)
.alignItems(HorizontalAlign.Start)
.width(CommonConstants.COLUMN_WIDTH)
}
}
.layoutWeight(1)
.onScroll((xOffset: number, yOffset: number)=>{
this.scrollHeight += yOffset;
})
Divider().backgroundColor(Color.Red).strokeWidth(0.5)
this.footer(
()=>{ this.isShow = true; },
()=>{ this.isShow = true; }
)
}
.width(CommonConstants.COLUMN_WIDTH)
.height(CommonConstants.COLUMN_HEIGHT)
}
.width(CommonConstants.COLUMN_WIDTH)
.height(CommonConstants.COLUMN_HEIGHT)
.bindSheet($$this.isShow, this.bindSheetBuilder(), {
height: SheetSize.FIT_CONTENT,
showClose: false
})
} .hideTitleBar(true) .onReady((ctx: NavDestinationContext) => { try { const id = ctx?.pathInfo?.param as string; this.commodityData = this.commodityModel.getCommodityDetail(id); } catch (e) { // ... } }) }
在HarmonyOS Next应用开发中,通过合理运用组件、状态管理和事件处理,我们可以构建出功能丰富、交互性强的商品详情页。