当前位置: 首页 > news >正文

HarmonyOS:页面滚动时标题悬浮、背景渐变

一、需求场景

  • 进入到app首页或者分页列表首页时,随着页面滚动,分类tab要求固定悬浮在顶部。
  • 进入到app首页、者分页列表首页、商品详情页时,页面滚动时,顶部导航栏(菜单、标题)背景渐变。

二、相关技术知识点

  • Scroll:可滚动容器,其中nestedScroll:设置父组件的滚动联动、onDidScroll:滚动事件回调
  • Stack:堆叠容器

三、解决方案

  1. 使用Stack层叠布局,将标题栏悬浮展示在页面顶部。
  2. 考虑页面滚动以及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%')
  }
}

相关文章:

  • 【微服务管理】深入理解 Gateway 网关:原理与实现
  • fbx/obj/glb/gltf/b3dm等通用格式批量转换成osgb
  • STL之priority_queue的用法与实现
  • 第一阶段补充知识
  • 【信息系统项目管理师】高分论文:论信息系统项目的范围管理(投资信息化全流程管理项目)
  • TestHubo安装及入门指南
  • MuJoCo 机械臂关节路径规划+轨迹优化+末端轨迹可视化(附代码)
  • 计算机网络 - 三次握手相关问题
  • 【CUDA】ubuntu环境下安装cuda
  • 为 docker 拉取镜像配置代理
  • 【5G通信】通过RRC重配实现功率调整的可能性
  • 【Python内置函数的深度解析与应用】id
  • Dify - 整合Ollama + Xinference私有化部署Dify平台(01)
  • C++23 新特性静态operator[]、operator()与Lambda
  • 信号完整性:高速电路设计成功的关键
  • 本地git操作
  • 裂缝检测数据集,支持yolo,coco json,pasical voc xml,darknet格式的标注,1673张原始训练集图片,正确识别率99.4%
  • 趣说区块链隐私智能合约Shielder 实现原理
  • 基于【Lang Chain】构建智能问答系统的实战指南
  • 25.OpenCV中的霍夫圆变换
  • 五一小长假,带着小狗去上海音乐厅
  • 游客曝九寨沟打网约车被出租车围堵,官方:前者违规,后者做法不对
  • 安阳一村支书微信群骂村民被警方行拘,辩称对方先“污蔑造谣”
  • 央行副行长:我们在研究丰富政策工具箱,将适时推出增量政策
  • 央行副行长:增强外汇市场韧性,坚决对市场顺周期行为进行纠偏
  • 重新认识中国女性|婚姻,自古以来就是一桩生意