【HarmonyOS 7 沉浸光感深度实战】 07 手势、动画与动态浮动面板怎么组合 原创
头像 小雨同学 2026-09-04 13:23:26    发布
2 浏览 0 点赞 0 收藏



前言

一、先给每一种动态效果分工

二、把拖动、展开和滚动串进同一块面板

拖动时直接更新位置

展开和收起使用一次状态动画

滚动时只在方向改变后切换一次状态

三、面板动得多以后,代码反而要收一点

拖动位置属于高频状态

展开收起属于低频状态

材质对象没有必要跟着动画更新

interactive 开启以后,按压缩放可以先省掉

拖动区域尽量小一些

这种面板更适合临时高频操作

总结

完整代码

Main.ets

前言

悬浮面板和普通材质卡片相比,麻烦的地方在于它会同时处理好几种变化。

手指按下时,组件需要有反馈;拖动标题区域时,面板要跟着手势移动;点开以后,面板还要展开更多操作;底层内容开始滚动时,它又不能一直挡在阅读区域上方。代码写到这里,interactive、lightEffect、PanGesture、translate、opacity、scale 和 animateTo 往往会同时出现。

我自己在组合这类效果时,最容易出现的问题就是动画加得太多。材质已经提供了按压反馈,页面又补一层缩放;拖动位置已经跟着手指更新,外面再套一层显式动画。单独看每段代码都说得通,实际操作后却容易出现跟手发黏、缩放幅度过大,或者面板停下来以后还有动画继续追位置。

后来我更愿意先把这些变化分清楚。按压形变和触点光感交给沉浸材质;拖动位置交给手势;展开收起和滚动显隐由页面状态控制。这样每一种变化都有自己的来源,出了问题也更容易找到对应位置。

这次实验页会做一块可以拖动的动态浮动面板。收起时只显示标题和展开按钮,展开以后增加三个操作项;拖动标题区域可以改变位置;列表向上滚动时,面板会暂时移出页面,反向滚动后再回来。

目前我的测试设备还没有 HarmonyOS 7 实机测试权限,因此当前先使用模拟器检查手势、状态切换和基础动画。材质按压反馈、触点光感和最终操作手感仍建议在具备权限的真机上继续确认。

一、先给每一种动态效果分工

面板开始移动之前,先把谁负责什么确定下来。

ImmersiveMaterial 的 interactive 可以开启材质的交互形变,lightEffect 用于触摸时的动态光感反馈。这两项都属于材质本身的能力,从 API 26.0.0 开始提供。

当前面板准备两份材质:

private readonly thinMaterial: uiMaterial.Material =
new uiMaterial.ImmersiveMaterial({

   style: uiMaterial.ImmersiveStyle.THIN,

   interactive: true,

   lightEffect: {

     color: Color.White

   }

 });

private readonly regularMaterial: uiMaterial.Material =
new uiMaterial.ImmersiveMaterial({

   style: uiMaterial.ImmersiveStyle.REGULAR,

   interactive: true,

   lightEffect: {

     color: Color.White

   }

 });

收起状态使用 THIN,展开以后使用 REGULAR。

这里我不会再给按下事件补一套额外的 scale。系统材质已经负责按压阶段的视觉反馈,再增加一套自定义按压缩放,很容易让两种变化叠加。

自定义动画只负责面板自身的状态:

当前变化页面负责的属性
手指拖动面板translate
展开和收起内容高度、opacity、translate
页面滚动时显隐translate、opacity、scale
材质状态THIN、REGULAR

因此,这块面板实际可以拆成四条很直观的操作关系:

按下卡片

→ interactive + lightEffect


拖动标题区域

→ PanGesture + translate


点击展开或收起

→ 状态变化 + animateTo


页面上下滚动

→ onDidScroll + 显示状态

PanGesture 可以识别平移手势,并允许设置方向和触发距离;当前官方文档中,非手写笔输入源的默认触发距离为 5vp,因此示例也直接使用 5vp。

这一点和拖动手感关系很直接。识别距离设置得过大以后,手指已经移动了一段距离,面板才开始响应,操作就会显得迟。

另一方面,拖动过程本身是连续输入。我不希望每一次位置更新都重新启动一段动画,所以拖动阶段只计算坐标。

把这些职责确定以后,后面真正需要处理的就只剩三个动作:拖动、展开、滚动。

二、把拖动、展开和滚动串进同一块面板

页面底层是一张普通 List,悬浮面板放在 Stack 上层。

我比较喜欢这种结构,因为浮动面板和内容区彼此独立。列表照常滚动,面板只处理自己的位置和状态。

整个面板主要有三种状态:

收起

展开

滚动隐藏

拖动位置则单独保存,不会因为展开或滚动显隐而丢掉。

拖动时直接更新位置

当前示例使用 PanGesture。

手势开始时,先记住面板现在的位置:

private dragStartX: number = 0;
private dragStartY: number = 0;


@State private panelOffsetX: number = 0;

@State private panelOffsetY: number = 0;

随后再根据手势偏移计算新坐标:

PanGesture({

 direction: PanDirection.All,

 distance: 5

})

 .onActionStart(() => {
this.dragStartX = this.panelOffsetX;
this.dragStartY = this.panelOffsetY;

 })

 .onActionUpdate((event: PanGestureEvent) => {
this.panelOffsetX = this.clamp(
this.dragStartX + event.offsetX,

     -110,

     110

   );

this.panelOffsetY = this.clamp(
this.dragStartY + event.offsetY,

     -260,

     120

   );

 })

PanGesture 支持 All、Horizontal、Vertical 等方向配置。这里允许所有方向移动,再通过 clamp() 限制可拖动范围,避免把整个面板甩出屏幕。

这个地方我没有使用 animateTo。

拖动过程中,面板需要尽量贴着手指。每一帧收到新的坐标以后直接更新 translate,操作会更自然:

.translate({

 x: this.panelOffsetX,

 y: this.panelOffsetY + this.visibilityShiftY

})

还有一个细节我觉得比较重要。

拖动手势只绑定在面板标题区域,没有覆盖整个面板,更没有覆盖底层页面。List 自身已经包含滚动手势,再大范围加入自定义手势时,需要留意组件之间的手势响应关系。官方 List 文档也特别提醒,列表内部已经绑定了用于跟手滚动的手势,自定义手势需要注意冲突处理。

所以当前页面的操作会比较明确:

拖动面板标题

→ 移动悬浮面板


滑动列表区域

→ 滚动页面


点击面板按钮

→ 执行对应操作

大家不需要在同一个区域里猜测这次滑动究竟是拖面板还是滚列表。

展开和收起使用一次状态动画

拖动属于连续操作,展开则完全不同。

点击一次按钮以后,面板会从收起进入展开,或者从展开回到收起。这个变化有明确的起点和终点,所以更适合使用显式动画。

当前会同时修改几个状态:

面板高度

内容透明度

内容纵向位置

当前材质

代码如下:

private togglePanel(): void {
const nextExpanded: boolean =

   !this.isExpanded;

this.materialMode =

   nextExpanded ? 1 : 0;

this.getUIContext().animateTo({

   duration: 260,

   curve: Curve.EaseOut

 }, () => {
this.isExpanded = nextExpanded;

this.panelHeight =

     nextExpanded ? 216 : 96;

this.detailOpacity =

     nextExpanded ? 1 : 0;

this.detailTranslateY =

     nextExpanded ? 0 : 12;

 });

}

收起时使用 THIN,展开以后选择 REGULAR。

这里的材质对象早就已经创建完成。点击按钮只是在两份现成材质之间选择,没有在动画回调中反复 new ImmersiveMaterial()。

我觉得这个区别值得保留。

材质配置本身变化频率很低,而动画过程中的尺寸、透明度和位移会持续变化。把材质对象跟高频状态放在一起更新,会让代码越来越难看清楚。

当前面板结构简单,展开时直接调整高度没有太大问题。如果真实项目中的面板内容很多,布局变化已经造成动画掉帧,可以把更多视觉变化移到 scale、translate、opacity 这些图形变换属性上。HarmonyOS 当前的性能检查规则也建议,在布局属性动画出现性能问题时优先使用图形变换属性。

滚动时只在方向改变后切换一次状态

面板能够拖,也能够展开以后,页面还有一个实际问题。

长列表阅读过程中,如果面板一直停在底部,它会持续挡住一部分内容。

所以示例加入一个很轻的滚动显隐逻辑:

页面内容向上移动

→ 面板退出底部


页面反向滚动

→ 面板重新出现

当前 List 使用 onDidScroll。官方文档中,onDidScroll 会返回当前帧的滚动偏移量和滚动状态;内容向上滚动时偏移为正,向下滚动时为负。旧的 onScroll 已经废弃,并推荐使用 onDidScroll。

因此方向判断可以写得很简单:

private handleListScroll(

 scrollOffset: number,

 scrollState: ScrollState

): void {
if (scrollState === ScrollState.Idle) {
return;

 }

if (scrollOffset > 4) {
this.hidePanel();
return;

 }

if (scrollOffset < -4) {
this.showPanel();

 }

}

这里留了 4vp 的小阈值。

细微滚动和回弹过程中会产生一些很小的偏移,如果完全按照正负号切换,面板容易在边界附近频繁显隐。加入一点缓冲以后,操作会稳定一些。

隐藏面板时:

private hidePanel(): void {
if (!this.panelVisible) {
return;

 }

this.panelVisible = false;

this.getUIContext().animateTo({

   duration: 180,

   curve: Curve.EaseOut

 }, () => {
this.panelOpacity = 0;
this.visibilityShiftY = 260;
this.panelScale = 0.96;

 });

}

重新显示:

private showPanel(): void {
if (this.panelVisible) {
return;

 }

this.panelVisible = true;

this.getUIContext().animateTo({

   duration: 220,

   curve: Curve.EaseOut

 }, () => {
this.panelOpacity = 1;
this.visibilityShiftY = 0;
this.panelScale = 1;

 });

}

这里真正帮到我的,是 panelVisible 这个状态。

onDidScroll 会在滚动过程中持续回调。面板已经隐藏以后,后续同方向滚动会直接返回,不会每一帧再重新启动隐藏动画。官方文档确认 onDidScroll 会在列表滑动过程中持续提供当前帧偏移和滚动状态。

因此整个过程会变成:

第一次向上滚动

→ 执行隐藏动画


继续向上滚动

→ 保持隐藏


开始向下滚动

→ 执行显示动画


继续向下滚动

→ 保持显示

这个小状态判断比不断调整动画参数更有用。

完成这一部分以后,手势和动画已经能够一起运行。后面更值得关注的是,哪些状态适合持续更新,哪些状态只应该偶尔发生变化。

三、面板动得多以后,代码反而要收一点

动态页面很容易越做越热闹。

已经有拖动,可以再加惯性;已经有展开,可以再改变圆角;已经有滚动显隐,也可以顺便修改模糊、阴影和材质颜色。

我自己更愿意少加几项。

判断一个动画怎么写时,我通常先看这个状态变化得有多频繁。

拖动位置属于高频状态

拖动时,手势会不断提供新的位移。

这种情况下最直接的方式就是更新:

panelOffsetX

panelOffsetY

然后交给:

.translate({

 x: this.panelOffsetX,

 y: this.panelOffsetY

})

页面没有必要在拖动的每一步重新创建材质,也没有必要反复启动 animateTo。

HarmonyOS 的性能规则建议,在布局动画出现丢帧时优先使用图形变换属性,scale、translate 等方式通常比频繁改变宽高更适合作为动画手段。

所以当前页面把高频变化尽量集中在:

translate

opacity

scale

这几个属性上。

展开收起属于低频状态

展开按钮只有在用户点击时才会触发。

这种变化频率低,而且有明确的开始和结束,所以用 animateTo 处理更自然。显式动画本身就是用状态变化描述起点和终点,再由系统完成中间过渡。

当前 Demo 为了真正腾出内容空间,会改变 panelHeight。

页面只有几个按钮时,这样处理比较直观。换成复杂工具箱以后,如果高度动画已经影响流畅度,可以把固定容器保留下来,再主要使用内容的 opacity、translate 和 scale 表现展开状态。

这里没有统一答案,还是要看面板里面到底放了多少内容。

材质对象没有必要跟着动画更新

当前页面只需要:

THIN

REGULAR

两种材质。

所以直接提前创建:

private readonly thinMaterial =
new uiMaterial.ImmersiveMaterial(...);

private readonly regularMaterial =
new uiMaterial.ImmersiveMaterial(...);

拖动过程中不会创建新对象。

列表滚动时不会创建新对象。

展开动画的每一帧也不会创建新对象。

状态变化只决定当前使用哪一份材质。

我会把材质对象看成组件比较稳定的视觉配置,而面板位置、透明度和展开状态才属于动态数据。两者分开以后,后面再加按钮或业务逻辑也不会把材质代码搅进手势事件里。

interactive 开启以后,按压缩放可以先省掉

当前两份材质都打开了:

interactive: true

因此,面板本身已经具备系统材质的交互反馈。

页面中的 panelScale 只用于滚动隐藏和重新出现,没有拿它继续制作按压效果。

如果后面再增加:

按下

→ 自定义 scale


同时

→ interactive 系统形变

两种效果会同时作用在组件上。

对于这种面板,我更喜欢先看系统材质反馈够不够。真机上觉得反馈不足时,再决定是否增加自定义动画,这样调节范围会更清楚。

拖动区域尽量小一些

面板可以拖动,底层列表也需要滚动,这两个需求天然会争夺手势。

当前做法是只让标题区域响应 PanGesture。

操作 A、操作 B、操作 C 仍然是正常按钮;列表区域也继续保持正常滚动。

官方 PanGesture 支持配置触发方向和距离,而 List 自身已经绑定滚动手势,所以组件内增加自定义拖动时,手势区域和响应关系值得单独检查。(华为开发者)

放回业务页面以后,可以先按这个思路划区域:

页面区域主要操作
面板标题栏拖动
展开按钮展开或收起
面板操作区点击
面板外列表滚动

这样比整块浮层都可以拖更容易控制。

这种面板更适合临时高频操作

动态浮动面板并不适合所有页面。

我觉得比较合适的场景有三类。

第一类是内容阅读。图片、文章或视频页面需要一两个随时可用的操作,又不希望工具条长期占据固定空间。

第二类是编辑和创作。图片编辑、文档批注和轻量画布中,工具面板可以拖到当前内容比较空的位置。

第三类是播放和控制。收起时显示核心状态,展开后提供三到五个临时操作。

内容继续增加以后,我会开始考虑其他结构。

当前需求更合适的形式
一个高频操作悬浮按钮
两到五个临时操作动态浮动面板
面板会遮住主要内容增加拖动
阅读滚动时容易挡内容增加滚动显隐
已经形成较长操作流程Sheet 或独立页面

浮动面板的价值还是让操作更顺手。

拖动、展开、材质和光感都只是手段。操作数量已经多到需要不断扩张面板时,页面结构本身可能已经需要重新安排。

总结

这块动态浮动面板做完以后,我觉得最值得保留的习惯,是先把动态效果分工。

材质负责按压形变和触点光感。

PanGesture 负责手指拖到哪里,面板就移动到哪里。

展开、收起以及滚动显隐属于明确的状态变化,可以交给 animateTo。

拖动和滚动过程中频繁变化的部分,则尽量集中在 translate、opacity 和 scale 上。HarmonyOS 当前也建议在动画性能出现问题时优先考虑图形变换属性,减少布局属性动画带来的额外开销。

材质对象也没有跟着动画过程重复创建。THIN 和 REGULAR 提前准备好,页面只根据展开状态选择当前材质。

我更喜欢这样的代码,因为出了问题以后方向比较明确。拖动不跟手,就看 PanGesture 和位移计算;列表滚动时反复闪动,就看显隐状态判断;按压反馈不合适,再回到 interactive 和 lightEffect。

当前实验可以在 HarmonyOS 7 模拟器中检查拖动、展开收起、列表滚动和基础动画。材质的交互形变、光感反馈以及最终操作手感仍建议在具备权限的真机上继续确认。

完整代码

Main.ets

/**

* HarmonyOS 7 沉浸光感深度实战 07

*

* 验证环境:

* HarmonyOS SDK API 26

* HarmonyOS 7 模拟器

*/

import { uiMaterial } from '@kit.ArkUI';


@Entry

@Component

struct Main {

 /**

  * 页面中的演示数据。

  */
private readonly items: Array<number> = [

   1, 2, 3, 4, 5, 6,

   7, 8, 9, 10, 11, 12,

   13, 14, 15, 16, 17, 18

 ];


 /**

  * 面板是否展开。

  */

 @State private isExpanded: boolean = false;


 /**

  * 收起状态 96vp。

  * 展开状态 216vp。

  */

 @State private panelHeight: number = 96;


 /**

  * 展开内容使用透明度和纵向位移过渡。

  */

 @State private detailOpacity: number = 0;

 @State private detailTranslateY: number = 12;


 /**

  * 用户拖动产生的位置偏移。

  */

 @State private panelOffsetX: number = 0;

 @State private panelOffsetY: number = 0;


 /**

  * 滚动显隐单独使用一组状态。

  * 这样不会覆盖用户已经拖动出来的位置。

  */

 @State private visibilityShiftY: number = 0;

 @State private panelOpacity: number = 1;

 @State private panelScale: number = 1;

 @State private panelVisible: boolean = true;


 /**

  * 0 表示 THIN。

  * 1 表示 REGULAR。

  */

 @State private materialMode: number = 0;


 /**

  * 每轮 PanGesture 开始时保存当前位置。

  */
private dragStartX: number = 0;
private dragStartY: number = 0;


 /**

  * 材质对象提前创建。

  * 拖动和动画过程不会反复创建新材质。

  */
private readonly thinMaterial: uiMaterial.Material =
new uiMaterial.ImmersiveMaterial({

     style: uiMaterial.ImmersiveStyle.THIN,

     interactive: true,

     lightEffect: {

       color: Color.White

     }

   });

private readonly regularMaterial: uiMaterial.Material =
new uiMaterial.ImmersiveMaterial({

     style: uiMaterial.ImmersiveStyle.REGULAR,

     interactive: true,

     lightEffect: {

       color: Color.White

     }

   });


 /**

  * 限制拖动范围,避免面板完全离开窗口。

  */
private clamp(

   value: number,

   min: number,

   max: number

 ): number {
if (value < min) {
return min;

   }

if (value > max) {
return max;

   }

return value;

 }


 /**

  * 展开或收起面板。

  */
private togglePanel(): void {
const nextExpanded: boolean =

     !this.isExpanded;


   /**

    * 状态变化时直接切换已经创建好的材质。

    */
this.materialMode =

     nextExpanded ? 1 : 0;

this.getUIContext().animateTo({

     duration: 260,

     curve: Curve.EaseOut

   }, () => {
this.isExpanded =

       nextExpanded;

this.panelHeight =

       nextExpanded ? 216 : 96;

this.detailOpacity =

       nextExpanded ? 1 : 0;

this.detailTranslateY =

       nextExpanded ? 0 : 12;

   });

 }


 /**

  * 向上滚动内容时隐藏面板。

  *

  * panelVisible 可以避免连续滚动期间

  * 反复启动同一段隐藏动画。

  */
private hidePanel(): void {
if (!this.panelVisible) {
return;

   }

this.panelVisible = false;

this.getUIContext().animateTo({

     duration: 180,

     curve: Curve.EaseOut

   }, () => {
this.panelOpacity = 0;
this.visibilityShiftY = 260;
this.panelScale = 0.96;

   });

 }


 /**

  * 反向滚动时重新显示面板。

  */
private showPanel(): void {
if (this.panelVisible) {
return;

   }

this.panelVisible = true;

this.getUIContext().animateTo({

     duration: 220,

     curve: Curve.EaseOut

   }, () => {
this.panelOpacity = 1;
this.visibilityShiftY = 0;
this.panelScale = 1;

   });

 }


 /**

  * List 滚动时只判断方向。

  * 4vp 阈值用于过滤较小的滚动变化。

  */
private handleListScroll(

   scrollOffset: number,

   scrollState: ScrollState

 ): void {
if (scrollState === ScrollState.Idle) {
return;

   }

if (scrollOffset > 4) {
this.hidePanel();
return;

   }

if (scrollOffset < -4) {
this.showPanel();

   }

 }


 /**

  * 把面板恢复到默认位置。

  */
private resetPanelPosition(): void {
this.getUIContext().animateTo({

     duration: 240,

     curve: Curve.EaseOut

   }, () => {
this.panelOffsetX = 0;
this.panelOffsetY = 0;

   });

 }


 /**

  * 普通列表内容。

  */

 @Builder
private contentCard(item: number) {

   Column({ space: 8 }) {

     Text(`内容区域 ${item}`)

       .fontSize(17)

       .fontWeight(FontWeight.Bold)

       .fontColor('#17203A')

       .width('100%')


     Text(

       '上下滚动页面,观察浮动面板的显示状态。'

     )

       .fontSize(13)

       .fontColor('#68708A')

       .lineHeight(20)

       .width('100%')

   }

   .width('100%')

   .height(112)

   .padding(16)

   .justifyContent(FlexAlign.Center)

   .alignItems(HorizontalAlign.Start)

   .backgroundColor(Color.White)

   .borderRadius(20)

 }


 /**

  * 标题区域负责拖动。

  * 面板内部其他按钮继续处理点击操作。

  */

 @Builder
private draggableHeader() {

   Row({ space: 10 }) {

     Column({ space: 3 }) {

       Text('动态浮动面板')

         .fontSize(17)

         .fontWeight(FontWeight.Bold)

         .fontColor('#17203A')

         .width('100%')


       Text(
this.materialMode === 0

           ? 'THIN · 拖动这里移动'

           : 'REGULAR · 拖动这里移动'

       )

         .fontSize(11)

         .fontColor('#68708A')

         .width('100%')

     }

     .layoutWeight(1)

     .alignItems(HorizontalAlign.Start)

     .gesture(

       PanGesture({

         direction: PanDirection.All,

         distance: 5

       })

         .onActionStart(() => {
this.dragStartX =
this.panelOffsetX;

this.dragStartY =
this.panelOffsetY;

         })

         .onActionUpdate(

           (event: PanGestureEvent) => {
this.panelOffsetX =
this.clamp(
this.dragStartX

                   + event.offsetX,

                 -110,

                 110

               );

this.panelOffsetY =
this.clamp(
this.dragStartY

                   + event.offsetY,

                 -260,

                 120

               );

           }

         )

     )


     Button(
this.isExpanded

         ? '收起'

         : '展开'

     )

       .height(34)

       .fontSize(12)

       .fontColor('#5065E8')

       .backgroundColor('#EEF1FF')

       .onClick(() => {
this.togglePanel();

       })

   }

   .width('100%')

   .height(52)

   .alignItems(VerticalAlign.Center)

 }


 /**

  * 展开内容始终保留在组件树中。

  * 收起时通过透明度、位移和裁剪隐藏。

  */

 @Builder
private panelDetails() {

   Column({ space: 10 }) {

     Divider()

       .color('#22FFFFFF')


     Row({ space: 10 }) {

       Button('操作 A')

         .layoutWeight(1)

         .height(36)

         .fontSize(12)


       Button('操作 B')

         .layoutWeight(1)

         .height(36)

         .fontSize(12)


       Button('操作 C')

         .layoutWeight(1)

         .height(36)

         .fontSize(12)

     }

     .width('100%')


     Row({ space: 10 }) {

       Column({ space: 3 }) {

         Text('当前位置')

           .fontSize(11)

           .fontColor('#68708A')


         Text(

           `x=${Math.round(this.panelOffsetX)}  `

             + `y=${Math.round(this.panelOffsetY)}`

         )

           .fontSize(12)

           .fontWeight(FontWeight.Medium)

           .fontColor('#17203A')

       }

       .layoutWeight(1)

       .alignItems(HorizontalAlign.Start)


       Button('重置位置')

         .height(34)

         .fontSize(12)

         .fontColor('#5065E8')

         .backgroundColor('#EEF1FF')

         .onClick(() => {
this.resetPanelPosition();

         })

     }

     .width('100%')


     Text(

       '向上滚动时面板退出,反向滚动后重新出现。'

     )

       .fontSize(11)

       .fontColor('#68708A')

       .lineHeight(17)

       .width('100%')

   }

   .width('100%')

   .opacity(this.detailOpacity)

   .translate({

     y: this.detailTranslateY

   })

 }


 /**

  * 完整动态浮动面板。

  */

 @Builder
private floatingPanel() {

   Column({ space: 8 }) {
this.draggableHeader()

this.panelDetails()

   }

   .width(316)

   .height(this.panelHeight)

   .padding({

     left: 16,

     right: 16,

     top: 10,

     bottom: 14

   })

   .backgroundColor(Color.Transparent)

   .borderRadius(28)

   .clip(true)

   .systemMaterial(
this.materialMode === 0

       ? this.thinMaterial

       : this.regularMaterial

   )

   .translate({

     x: this.panelOffsetX,

     y: this.panelOffsetY

       + this.visibilityShiftY

   })

   .scale({

     x: this.panelScale,

     y: this.panelScale

   })

   .opacity(this.panelOpacity)

   .margin({

     bottom: 28

   })

   .zIndex(10)

 }


 build() {

   Stack({

     alignContent: Alignment.Bottom

   }) {

     Column() {

       Column({ space: 6 }) {

         Text('HarmonyOS 7 沉浸光感')

           .fontSize(28)

           .fontWeight(FontWeight.Bold)

           .fontColor('#11182C')

           .width('100%')


         Text('动态浮动控制面板')

           .fontSize(16)

           .fontColor('#68708A')

           .width('100%')

       }

       .width('100%')

       .padding({

         left: 20,

         right: 20,

         top: 24,

         bottom: 14

       })

       .backgroundColor('#F4F6FB')


       List({

         space: 12

       }) {

         ForEach(
this.items,

           (item: number) => {

             ListItem() {
this.contentCard(item)

             }

           },

           (item: number) =>

             item.toString()

         )

       }

       .layoutWeight(1)

       .width('100%')

       .padding({

         left: 20,

         right: 20,

         bottom: 120

       })

       .scrollBar(BarState.Off)

       .onDidScroll(

         (

           scrollOffset: number,

           scrollState: ScrollState

         ) => {
this.handleListScroll(

             scrollOffset,

             scrollState

           );

         }

       )

     }

     .width('100%')

     .height('100%')

     .backgroundColor('#F4F6FB')

this.floatingPanel()

   }

   .width('100%')

   .height('100%')

 }

}


©本站发布的所有内容,包括但不限于文字、图片、音频、视频、图表、标志、标识、广告、商标、商号、域名、软件、程序等,除特别标明外,均来源于网络或用户投稿,版权归原作者或原出处所有。我们致力于保护原作者版权,若涉及版权问题,请及时联系我们进行处理。
分类
HarmonyOS

暂无评论数据

加载中...

发布

地址:北京市朝阳区北三环东路三元桥曙光西里甲1号第三置业A座1508室 商务内容合作QQ:2291221 电话:13391790444或(010)62178877
版权所有:电脑商情信息服务集团 北京赢邦策略咨询有限责任公司
声明:本媒体部分图片、文章来源于网络,版权归原作者所有,我司致力于保护作者版权,如有侵权,请与我司联系删除

京ICP备:2022009079号-2

京公网安备:11010502051901号

ICP证:京B2-20230255