HarmonyOS:页面滚动时标题悬浮、背景渐变
一、需求场景
- 进入到app首页或者分页列表首页时,随着页面滚动,分类tab要求固定悬浮在顶部。
- 进入到app首页、者分页列表首页、商品详情页时,页面滚动时,顶部导航栏(菜单、标题)背景渐变。
二、相关技术知识点
- Scroll:可滚动容器,其中nestedScroll:设置父组件的滚动联动、onDidScroll:滚动事件回调
- Stack:堆叠容器
三、解决方案
- 使用Stack层叠布局,将标题栏悬浮展示在页面顶部。
- 考虑页面滚动以及tabContent里面的list滚动就要考虑滚动嵌套问题目前场景需要选择:
向上滚动时,父组件先滚动,父组件滚动到边缘以后自身滚动;
向下滚动时:自身先滚动,自身滚动到边缘以后父组件滚动。
四、示例
效果图
示例代码:TestStickyNestedScroll.ets
import AppStorageConstants from '../../common/AppStorageConstants';
@Entry
@Component
struct TestStickyNestedScroll {
@State arr: number[] = [];
@State opacityNum: number = 0;
@State curYOffset: number = 0;
@State statusBarHeight: number = 20
@State bottomNavBarHeight: number = 20
@State navIndicatorHeight: number| undefined = 28
aboutToAppear(): void {
for (let index = 0; index < 40; index++) {
this.arr.push(index);
}
let tempStatusBarHeight: number | undefined = AppStorage.get(AppStorageConstants.STATUS_BAR_HEIGHT)
this.statusBarHeight = tempStatusBarHeight == undefined ? 20 : tempStatusBarHeight
this.navIndicatorHeight = AppStorage.get(AppStorageConstants.NAV_INDICATOR_HEIGHT)
// let typeSys = window.AvoidAreaType.TYPE_SYSTEM;
// let typeNavIndicator = window.AvoidAreaType.TYPE_NAVIGATION_INDICATOR;
// window.getLastWindow(getContext(this)).then((data) => {
// // 获取系统默认区域,一般包括状态栏、导航栏
// let avoidArea1 = data.getWindowAvoidArea(typeSys);
// // 顶部状态栏高度
// let orginStatusBarHeight = avoidArea1.topRect.height;
//
//
// this.statusBarHeight = this.getUIContext().px2vp(orginStatusBarHeight);
// console.log("顶部状态栏高度 statusBarHeight = " + this.statusBarHeight + " vp, orginStatusBarHeight = " +
// orginStatusBarHeight + " px");
// // 部状态栏高度 statusBarHeight = 32.92307692307692 vp, orginStatusBarHeight = 107 px
//
// // 底部导航条区域高度
// let avoidArea2 = data.getWindowAvoidArea(typeNavIndicator);
// let orginNavIndicator = avoidArea2.bottomRect.height
// this.navIndicatorHeight = this.getUIContext().px2vp(orginNavIndicator);
// console.log("底部导航条区域高度 navIndicatorHeight = " + this.navIndicatorHeight +
// " vp, orginNavIndicator = " +
// orginNavIndicator + " px");
// // 底部导航条区域高度 navIndicatorHeight = 28 vp, orginNavIndicator = 91 px
//
// //底部导航栏的高度
// let orginBottomStatusBarHeight = avoidArea1.bottomRect.height;
// this.bottomNavBarHeight = this.getUIContext().px2vp(orginBottomStatusBarHeight);
// console.log("底部导航栏的高度 statusBarHeight = " + this.bottomNavBarHeight + " vp, orginBottomStatusBarHeight = " +
// orginBottomStatusBarHeight + " px");
// // 底部导航栏的高度 statusBarHeight = 0 vp, orginBottomStatusBarHeight = 0 px
// })
}
@Styles
listCard() {
.backgroundColor(Color.White)
.height(72)
.width('calc(100% - 20vp)')
.borderRadius(12)
.margin({ left: 10, right: 10 })
}
build() {
Stack() {
Scroll() {
Column({ space: 10 }) {
Image($r('app.media.mount'))
.width('100%')
.height(300)
Tabs({ barPosition: BarPosition.Start }) {
TabContent() {
List({ space: 10 }) {
ForEach(this.arr, (item: number) => {
ListItem() {
Text("item " + item)
.fontSize(20)
.fontColor(Color.Black)
}.listCard()
}, (item: number) => item.toString())
}
.edgeEffect(EdgeEffect.Spring)
.nestedScroll({
scrollForward: NestedScrollMode.PARENT_FIRST,
scrollBackward: NestedScrollMode.SELF_FIRST
})
}.tabBar("待处理")
TabContent() {
}.tabBar("已处理")
}
.scrollable(false) // 禁掉滚动
.vertical(false)
.width("100%")
.height('calc(100% - 60vp)')
}
.width('100%')
}
.edgeEffect(EdgeEffect.Spring)
.friction(0.6)
.backgroundColor('#DCDCDC')
.scrollBar(BarState.Off)
.width('100%')
.height('100%')
.onDidScroll((xOffset: number, yOffset: number, scrollState: ScrollState): void => {
// 累计计算当前父组件滚动在Y轴方向的偏移量
this.curYOffset += yOffset
// 根据父组件一共可以滚动的距离计算当前每帧的当前透明度
let opacity = this.curYOffset / 240
if (opacity >= 1) {
opacity = 1
}
if (opacity <= 0) {
opacity = 0
}
this.opacityNum = opacity
})
RelativeContainer() { //顶部菜单栏
Text("返回")
.fontSize(16)
.fontColor(Color.Black)
.fontWeight(FontWeight.Medium)
.padding({ left: 20 })
.height('100%')
.alignRules({
top: { anchor: '__container__', align: VerticalAlign.Top },
left: { anchor: '__container__', align: HorizontalAlign.Start }
})
.id('back')
Text("标题")
.fontSize(16)
.fontColor(Color.Black)
.fontWeight(FontWeight.Medium)
.textAlign(TextAlign.Center)
.alignRules({
top: { anchor: '__container__', align: VerticalAlign.Top },
bottom: { anchor: '__container__', align: VerticalAlign.Bottom },
left: { anchor: 'back', align: HorizontalAlign.End },
right: { anchor: 'share', align: HorizontalAlign.Start }
})
Text("分享")
.fontSize(16)
.fontColor(Color.Black)
.fontWeight(FontWeight.Medium)
.padding({ right: 20 })
.height('100%')
.alignRules({
top: { anchor: '__container__', align: VerticalAlign.Top },
right: { anchor: '__container__', align: HorizontalAlign.End }
})
.id('share')
}
.width('100%')
.height(44 + this.statusBarHeight)
.padding({ top: this.statusBarHeight })
.position({ x: 0, y: 0 })
.backgroundColor(`rgba(255,255,255,${this.opacityNum})`)
}
.height('100%')
.width('100%')
}
}