如何实现支持滑动调节护眼强弱的护眼模式
===================

#### 问题现象

在多数场景下应用需要自行设计护眼模式，目前是否有对应的方法实现护眼的功能？

#### 背景知识

*   通过[ArkUI支持ARGB字符串设置背景色](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-types#resourcecolor)的特性，可方便快捷设置背景色透明度。
*   通过[Slider滑动条组件](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-basic-components-slider#示例2设置滑动条样式)，允许用户通过滑动条，滑动调节护眼模式程度。
*   结合ArkUI的[触摸手势透传](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-gesture-events-multi-level-gesture#hittestbehavior对手势和事件的控制)特性，可以保障位于蒙层下方的组件，正常监听触摸手势。
*   若通过[Navigation](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-basic-components-navigation)路由的方式实现页面切换时，则可以通过其特殊的父子组件关系，将护眼的蒙层组件覆盖于Navigation组件上，实现所有[NavDestination](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-basic-components-navdestination)页面的护眼模式。

#### 解决方案

*   **场景一：阅读页面等单个页面的护眼方式。**
    1.  使用Slider组件实现滑动调节。
    2.  在页面上覆盖Canvas，设置Canvas背景色，并设置透传触摸事件。此处任意组件均可代替Canvas组件，无强制要求，主要实现的是护眼蒙层的颜色变化。
    3.  通过获取Slider值，调整Canvas背景色透明度，实现当前页面动态护眼调整。
        
        const RED\_PART\_SINGLE \= 245;
        const YELLOW\_PART\_SINGLE \= 222;
        const BLUE\_PART\_SINGLE \= 73;
        
        @Entry
        @Component
        struct SinglePage {
          @State transparencyVal: number \= 0; _//_ _默认值(0-0.1)_
          @State filterColor: string \= '#D2691E'; _//_ _动态计算_
          @State txt: string \= '应用内容区域';
        
          build() {
            RelativeContainer() {
             _ // 主内容区域_
              Column() {
              _  // 这里放置你的UI组件_
                this.contentTxt();
               _ // 色温调节滑块_
                Slider({
                  value: 0,
                  min: 0,
                  max: 0.3,
                  step: 0.01
                })
                  .onChange((value) \=\> {
                    this.transparencyVal \= value;
                    this.filterColor \=
                      \`rgba(${RED\_PART\_SINGLE}, ${YELLOW\_PART\_SINGLE}, ${BLUE\_PART\_SINGLE}, ${this.transparencyVal.toFixed(2)})\`;
                  })
                  .width('80%')
                  .margin(20);
              }.margin({ top: 40 })
              .width('100%')
              .height('80%')
              .backgroundColor('#FFFFFF');
        
             _ // 色温滤镜层(覆盖整个屏幕)_
              Canvas()
                .width('100%')
                .height('100%')
                .backgroundColor(this.filterColor)
                .hitTestBehavior(HitTestMode.Transparent)
                .align(Alignment.Bottom)
                .position({ x: 0, y: 0 })
                .zIndex(999); _// 确保在最顶层_
            }
            .onAppear(() \=\> {
              this.filterColor \=
                \`rgba(${RED\_PART\_SINGLE}, ${YELLOW\_PART\_SINGLE}, ${BLUE\_PART\_SINGLE}, ${this.transparencyVal.toFixed(2)})\`;
            });
          }
        
          @Builder
          contentTxt() {
           _ // 这里放置你的UI组件_
            Text(this.txt).fontSize(50).fontColor($r('sys.color.black')).onClick(() \=\> {
              this.txt \= '测试点击内容';
            });
          }
        }
        
        效果预览：
        
        ![](https://contentcenter-vali-drcn.dbankcdn.cn/pvt_2/DeveloperAlliance_scene_100_1/bf/v3/dT-o3bMOTM-OqK90GONBCw/zh-cn_image_0000002628758922.gif?HW-CC-KV=V1&HW-CC-Date=20260709T134054Z&HW-CC-Expire=86400&HW-CC-Sign=481BC714C9207AB0CDCF8072BE782ECCF8634FDD4A6F16BCD45AAFBB8C1A30AC "点击放大")
        

*   **场景二：为整个应用设置护眼模式。**
    *   方案一：Navigation组件与护眼组件重叠。
        
        可以使用Navigation路由，将Canvas覆盖在Navigation上，可以实现所有页面都覆盖上护眼模式，且可以通过扩展安全区实现Canvas覆盖安全区。参考如下：
        
        OptionOneIndex.ets主页面：
        
        const RED\_PART \= 245;
        const YELLOW\_PART \= 222;
        const BLUE\_PART \= 73;
        
        @Entry
        @Component
        struct OptionOneIndex {
          transparencyVal: number \= 0; _// 默认值 (0-0.1)_
          @State filterColor: string \= '#D2691E'; _// 动态计算_
          txt: string \= '应用内容区域';
          @Provide('pathStack') pathStack: NavPathStack \= new NavPathStack();
        
          build() {
            RelativeContainer() {
              Navigation(this.pathStack) {
                _// 主内容区域_
                Column() {
                 _ // 这里放置你的UI组件_
                  this.contentTxt();
                 _ // 色温调节滑块_
                  Slider({
                    value: 0,
                    min: 0,
                    max: 0.3,
                    step: 0.01
                  })
                    .onChange((value) \=\> {
                      this.transparencyVal \= value;
                      this.filterColor \= \`rgba(${RED\_PART}, ${YELLOW\_PART}, ${BLUE\_PART}, ${this.transparencyVal.toFixed(2)})\`;
                    })
                    .width('80%')
                    .margin(20);
                }.margin({ top: 40 })
                .width('100%')
                .height('80%')
                .backgroundColor('#FFFFFF');
              };
        
             _ // 色温滤镜层 (覆盖整个屏幕)_
              Canvas()
                .width('100%')
                .height('100%')
                .backgroundColor(this.filterColor)
                .hitTestBehavior(HitTestMode.Transparent)
                .align(Alignment.Bottom)
                .position({ x: 0, y: 0 })
                .zIndex(999) _//_ _确保在最顶层_
                .expandSafeArea(\[SafeAreaType.SYSTEM\], \[SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM\]);
            }
            .onAppear(() \=\> {
              this.filterColor \= \`rgba(${RED\_PART}, ${YELLOW\_PART}, ${BLUE\_PART}, ${this.transparencyVal.toFixed(2)})\`;
            });
          }
        
          @Builder
          contentTxt() {
           _ // 这里放置你的UI组件_
            Text(this.txt).fontSize(50).fontColor($r('sys.color.black'))
              .onClick(() \=\> {
                this.pathStack.pushPathByName('OptionOneIndexTwo', '');
              });
          }
        }
        
        OptionOneIndexTwo.ets子页面：
        
        @Builder
        export function OptionOneIndexTwoBuilder() {
          OptionOneIndexTwo();
        }
        
        @Component
        struct OptionOneIndexTwo {
          build() {
            NavDestination() {
              Column() {
                Text('2');
              };
            };
          }
        }
        
        ![](https://contentcenter-vali-drcn.dbankcdn.cn/pvt_2/DeveloperAlliance_scene_100_1/5d/v3/Htw3dEwETFercNUmxlt3vg/note_3.0-zh-cn.png?HW-CC-KV=V1&HW-CC-Date=20260709T134054Z&HW-CC-Expire=86400&HW-CC-Sign=4DAEA47FE6101EBED0362751E0A06CB84DF366A586FC1A3A9F971806CD08B931)
        
        该方案与下文方案二省略路由表配置步骤，Navigation路由表配置详见官方文档：[系统路由表](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-navigation-navigation#系统路由表)。
        
    *   方案二：Navigation设置[overlay](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-universal-attributes-overlay#overlay)浮层。
        
        Navigation路由下，给Navigation容器设置overlay浮层，并将浮层的宽高设置为屏幕宽高即可：
        
        OptionTwoIndex.ets主页面：
        
        import { display } from '@kit.ArkUI';
        
        const RED\_PART\_TWO \= 245;
        const YELLOW\_PART\_TWO \= 222;
        const BLUE\_PART\_TWO \= 73;
        
        @Entry
        @Component
        struct OptionTwoIndex {
          transparencyVal: number \= 0; _//_ _默认值 (0-0.1)_
          @State filterColor: string \= '#D2691E'; _// 动态计算_
          txt: string \= '应用内容区域';
          @Provide('pathStack') pathStack: NavPathStack \= new NavPathStack();
        
          build() {
            RelativeContainer() {
              Navigation(this.pathStack) {
                _// 主内容区域_
                Column() {
                _  // 这里放置你的UI组件_
                  this.contentTxt();
               _   // 色温调节滑块_
                  Slider({
                    value: 0,
                    min: 0,
                    max: 0.3,
                    step: 0.01
                  })
                    .onChange((value) \=\> {
                      this.transparencyVal \= value;
                      this.filterColor \=
                        \`rgba(${RED\_PART\_TWO}, ${YELLOW\_PART\_TWO}, ${BLUE\_PART\_TWO}, ${this.transparencyVal.toFixed(2)})\`;
                    })
                    .width('80%')
                    .margin(20);
                }
                .margin({ top: 40 })
                .width('100%')
                .height('80%')
                .backgroundColor('#FFFFFF');
              }
              .overlay(this.OverlayNode(), { align: Alignment.Center }); _// 设置浮层_
            }
            .onAppear(() \=\> {
              this.filterColor \=
                \`rgba(${RED\_PART\_TWO}, ${YELLOW\_PART\_TWO}, ${BLUE\_PART\_TWO}, ${this.transparencyVal.toFixed(2)})\`;
            });
          }
        
         _ // 浮层充当遮罩_
          @Builder
          OverlayNode() {
            Column() {
            }
            .width(this.getUIContext().px2vp(display.getDefaultDisplaySync().width))
            .height(this.getUIContext().px2vp(display.getDefaultDisplaySync().height))
            .backgroundColor(this.filterColor)
            .hitTestBehavior(HitTestMode.Transparent); // 点击穿透
          }
        
          @Builder
          contentTxt() {
            // 这里放置你的UI组件
            Text(this.txt).fontSize(50).fontColor($r('sys.color.black'))
              .onClick(() \=\> {
                this.pathStack.pushPathByName('OptionTwoIndexTwo', '');
              });
          }
        }
        
        OptionTwoIndexTwo.ets子页面：
        
        @Builder
        export function OptionTwoIndexTwoBuilder() {
          OptionTwoIndexTwo();
        }
        
        @Component
        struct OptionTwoIndexTwo {
          build() {
            NavDestination() {
              Column() {
                Text('2');
              };
            };
          }
        }
        
        效果预览：
        
        ![](https://contentcenter-vali-drcn.dbankcdn.cn/pvt_2/DeveloperAlliance_scene_100_1/c6/v3/iRksQSpMSwqAecIGSkhgbQ/zh-cn_image_0000002658958251.gif?HW-CC-KV=V1&HW-CC-Date=20260709T134054Z&HW-CC-Expire=86400&HW-CC-Sign=9E50824D81D56BC1BEB40E3502676D7F487197063358673900F94990FD0627AE "点击放大")
        

#### 总结

通过构造护眼蒙层组件，根据滑块自由设定蒙层透明度，实现可滑动调节的效果。