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

news2025/5/11 2:22:46

一、需求场景

  • 进入到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%')
  }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2335244.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

信息系统项目管理师-第十八章-项目绩效域

本文章记录学习过程中,重要的知识点,是否为重点的依据,来源于官方教材和历年考题,持续更新共勉 本文章记录学习过程中,重要的知识点,是否为重点的依据,来源于官方教材和历年考题,持续更新共勉 在整个生命周期过程中,项目管理者需要始终坚持项目管理原则,通过涵盖 10 …

[NOIP 2003 普及组] 栈 Java

import java.io.*;public class Main {public static void main(String[] args) throws IOException {BufferedReader br new BufferedReader(new InputStreamReader(System.in));int n Integer.parseInt(br.readLine());int[] dp new int[n 1];dp[0] 1; // 空序列只有一种…

面试篇 - Transformer模型中的位置编码

1. 位置编码的引入 背景&#xff1a;Transformer模型通过自注意力机制&#xff08;Self-Attention&#xff09;处理序列数据&#xff0c;但自注意力机制本身并不包含序列中元素的位置信息。因此&#xff0c;需要一种方法来为模型提供位置信息。 解决方案&#xff1a;位置编码&…

vue+flask图书知识图谱推荐系统

文章结尾部分有CSDN官方提供的学长 联系方式名片 文章结尾部分有CSDN官方提供的学长 联系方式名片 关注B站&#xff0c;有好处&#xff01; 编号: F025 架构: vueflaskneo4jmysql 亮点&#xff1a;协同过滤推荐算法知识图谱可视化 支持爬取图书数据&#xff0c;数据超过万条&am…

vue2 走马灯 展示多个

使用 npm install “swiper”: “^11.2.4”, 在这里插入代码片 <template><section class"swiper pc-banner"><div class"swiper-container"><div class"swiper-wrapper"><div v-for"(item, index) in swiperD…

《MySQL从入门到精通》

文章目录 《MySQL从入门到精通》1. 基础-SQL通用语法及分类2. 基础-SQL-DDL-数据库操作3. 基础-SQL-DDL-表操作-创建&查询4. 基础-SQL-DDL-数据类型及案例4.1 数值类型4.2 字符串类型4.3 时间和日期类型 5. 基础-SQL-DDL-表操作-修改&删除5.1 DDL-表操作-修改5.2 DDL-表…

Linux: 线程同步

目录 一 前言 二 线程饥饿 三 线程同步 四 条件变量 1. cond &#xff08; condition&#xff09; 2. pthread_cond_wait() &#xff1a; 3. pthread_cond_signal() 五 条件变量的使用 一 前言 在上篇文章Linux : 多线程互斥-CSDN博客我们讲解了线程互斥的概念&#xff…

golang-context详解

Context是什么 cancel 其实就是通过chan select进行提前中断返回 如果没有context&#xff0c;携程之间怎么做这些交互呢&#xff1f;肯定也能做 跨线程通讯如共享内存&#xff0c;pipe等等都可以做到&#xff0c;但是就需要开发者对通讯设计建模、规划数据同步方式等&#xf…

Spring Boot 集成 RocketMQ 全流程指南:从依赖引入到消息收发

前言 在分布式系统中&#xff0c;消息中间件是解耦服务、实现异步通信的核心组件。RocketMQ 作为阿里巴巴开源的高性能分布式消息中间件&#xff0c;凭借其高吞吐、低延迟、高可靠等特性&#xff0c;成为企业级应用的首选。而 Spring Boot 通过其“约定优于配置”的设计理念&a…

AI与我共创WEB界面

记录一次压测后的自我技术提升 这事儿得从机房停电说起。那天吭哧吭哧做完并发压测,正准备截Zabbix监控图写报告,突然发现监控曲线神秘失踪——系统组小哥挠着头说:“上次停电后,zabbix服务好像就没起来过…” 我盯着空荡荡的图表界面,大脑的CPU温度可能比服务器还高。 其…

基于频率约束条件的最小惯量需求评估,包括频率变化率ROCOF约束和频率最低点约束matlab/simulink

基于频率约束条件的最小惯量评估&#xff0c;包括频率变化率ROCOF约束和频率最低点约束matlab/simulink 1建立了含新能源调频的频域仿真传函模型&#xff0c;虚拟惯量下垂控制 2基于构建的模型&#xff0c;考虑了不同调频系数&#xff0c;不同扰动情况下的系统最小惯量需求

深入理解浏览器的 Cookie:全面解析与实践指南

在现代 Web 开发中&#xff0c;Cookie 扮演着举足轻重的角色。它不仅用于管理用户会话、记录用户偏好&#xff0c;还在行为追踪、广告投放以及安全防护等诸多方面发挥着重要作用。随着互联网应用场景的不断丰富&#xff0c;Cookie 的使用和管理也日趋复杂&#xff0c;如何在保障…

Java 正则表达式综合实战:URL 匹配与源码解析

在 Web 应用开发中&#xff0c;我们经常需要对 URL 进行格式验证。今天我们结合 Java 的 Pattern 和 Matcher 类&#xff0c;深入理解正则表达式在实际应用中的强大功能&#xff0c;并剖析一段实际的 Java 示例源码。 package com.RegExpInfo;import java.util.regex.Matcher; …

【C++】前向声明(Forward Declaration)

前向声明&#xff08;Forward Declaration&#xff09;是在C、C等编程语言中&#xff0c;在使用一个类、结构体或其他类型之前&#xff0c;仅声明其名称而不给出完整定义的一种方式。 作用 减少编译依赖&#xff1a;当一个源文件包含大量头文件时&#xff0c;编译时间会显著增…

numpy.ma.masked_where:屏蔽满足条件的数组

1.函数功能 屏蔽满足条件的数组内容&#xff0c;返回值为掩码数组 2.语法结构 np.ma.masked_where(condition, a, copyTrue)3. 参数 参数含义condition屏蔽条件a要操作的数组copy布尔值&#xff0c;取值为True时&#xff0c;结果复制数组(原始数据不变)&#xff0c;否则返回…

【解决】bartender软件换网之后神秘变慢

下的山寨版本bartender软件&#xff0c;用着一直都挺好&#xff0c;结果一次换网之后&#xff0c;启动&#xff0c;排版&#xff0c;打印各种动作都要转个几分钟才行&#xff0c;非常奇怪。直接说解决过程。 首先联想网络没有动以及脱机的时候&#xff0c;都没有这个问题。那么…

[福游宝——AI智能旅游信息查询平台]全栈AI项目-阶段二:聊天咨询业务组件开发

简言 本项目旨在构建一个以AI智能体为核心的福建省旅游信息查询系统&#xff0c;聚焦景点推荐、路线规划、交通天气查询等功能&#xff0c;为游客提供智能化、便捷化的旅游信息服务。项目采用前后端分离架构&#xff0c;前端基于Vite TypeScript Vue3技术栈&#xff0c;搭配…

【教学类-102-11】蝴蝶外轮廓01——Python对黑白图片进行PS填充三种颜色+图案描边+图案填充白色+制作1图2图6图24图

背景需求: 用Python,对白色255背景的图片进行了透明化、制作点状或线段的描边裁剪线 【教学类-102-10】剪纸图案全套代码09——Python线条虚线优化版04(原图放大白背景)+制作1图2图6图24图-CSDN博客文章浏览阅读1k次,点赞27次,收藏8次。【教学类-102-10】剪纸图案全套代…

MCP的另一面

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…