一、需求場景
- 進入到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 = 28aboutToAppear(): 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 : tempStatusBarHeightthis.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// })}@StyleslistCard() {.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 / 240if (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%')}
}