鸿蒙UI开发——实时动态模糊效果

560 阅读8分钟

1、概 述

模糊效果可以用来体现界面空间的纵深感,区分前后元素的层级关系。ArkUI提供了5种接口来实现实时模糊。分别是:

  • backdropBlur

  • blur

以调节模糊半径方式来实现背景模糊和前景(内容)模糊

  • backgroundBlurStyle

  • foregroundBlurStyle

提供了更丰富的模糊样式设置来设置背景模糊和前景(内容)模糊

  • motionBlur

为当前组件添加由缩放大小或位移变化引起的运动过程中的动态模糊效果,入参为模糊半径和锚点坐标。

【注意,我们避免滥用这个效果,实时模糊会增加性能开销】

下面针对这5中接口做详细介绍。

2、接口介绍

2.1、 backdropBlur

为组件添加背景模糊效果(可以自定设置模糊半径和灰阶参数)。接口定义如下:

// value为模糊半径,模糊半径越大越模糊,为0时不模糊。
backdropBlur(value: number, options?: BlurOptions)

其中,BlurOptions是灰阶梯参数,定义如下:

struct BlurOptions {
  grayscale: [number, number];
}

// grayscale属性说明如下:
grayscale是灰阶模糊参数,两参数取值范围均为[0,127]。
对图像中的黑白色进行色阶调整,使其趋于灰色更为柔和美观,对图像中的彩色调整没有效果。
参数一表示对黑色的提亮程度,参数二表示对白色的压暗程度,
参数值越大调整效果越明显(黑白色变得越灰),有效值范围0-127。
例如:设置参数为(20,20),
图片中的黑色像素RGB:[0, 0, 0]会调整为[20,20,20],
白色像素RGB:[255,255,255]会调整为[235,235,235]
(255-20),图像中的彩色像素维持不变。

一个案例效果如下:

image.png 代码如下(注意第12行):

@Entry
@Component
struct BlurEffectsExample {
  build() {
    Column({ space: 10 }) {
      Text('backdropBlur')
        .width('90%')
        .height('90%')
        .fontSize(20)
        .fontColor(Color.White)
        .textAlign(TextAlign.Center)
        .backdropBlur(10) // 对背景进行模糊
        .backgroundImage($r('图片资源路径'))
        .backgroundImageSize({ width: 400, height: 300 })
    }
    .width('100%')
    .height('50%')
    .margin({ top: 20 })
  }
}

2.2、blur

为组件添加前景(内容)模糊效果。接口定义如下:

// value为模糊半径,模糊半径越大越模糊,为0时不模糊。
blur(value: number, options?: BlurOptions)

其中value与options的含义与backdropBlur相同,这里不赘述。

一个案例效果如下:

image.png

代码如下(第22行):

@Entry
@Component
struct Index1 {
  @State radius: number = 0;
  @State text: string = '';
  @State y: string = '手指不在屏幕上';

  aboutToAppear() {
    this.text = "按住屏幕上下滑动\n" + "当前手指所在y轴位置 :" + this.y +
    "\n" + "当前图片模糊程度为 : " + this.radius;
  }

  build() {
    Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.SpaceBetween }) {
      Text(this.text)
        .height(200)
        .fontSize(20)
        .fontWeight(FontWeight.Bold)
        .fontFamily("cursive")
        .fontStyle(FontStyle.Italic)
      Image($r("图片资源路径"))
        .blur(this.radius) // 使用blur接口为照片组件添加内容模糊效果
        .height('100%')
        .width("100%")
        .objectFit(ImageFit.Cover)
    }.height('100%')
    .width("100%")
    .onTouch((event?: TouchEvent) => {
      if(event){
        if (event.type === TouchType.Move) {
          this.y = Number(event.touches[0].y.toString()).toString();
          this.radius = Number(this.y) / 10; // 根据跟手过程中的滑动距离修改模糊半径,配合模糊接口,形成跟手模糊效果
        }
        if (event.type === TouchType.Up) {
          this.radius = 0;
          this.y = '手指离开屏幕';
        }
      }
      this.text = "按住屏幕上下滑动\n" + "当前手指所在y轴位置 :" + this.y +
      "\n" + "当前图片模糊程度为 : " + this.radius;
    })
  }
}

2.3、 backgroundBlurStyle

为当前组件提供一种在背景和内容之间的模糊能力,通过枚举值的方式封装了不同的模糊半径、蒙版颜色、蒙版透明度、饱和度、亮度。接口定义如下:

// value: 背景模糊样式。模糊样式中封装了模糊半径、蒙版颜色、蒙版透明度、饱和度、亮度五个参数。
// options: 背景模糊选项。
backgroundBlurStyle(value: BlurStyle, options?: BackgroundBlurStyleOptions)

其中BlurStyle是一个枚举,定义如下:

enum BlurStyle {
 Thin, //轻薄材质模糊。
 Regular, //普通厚度材质模糊。
 Thick, //厚材质模糊。
 BACKGROUND_THIN, //近距景深模糊。
 BACKGROUND_REGULAR, //中距景深模糊。
 BACKGROUND_THICK, // 远距景深模糊。
 BACKGROUND_ULTRA_THICK, //超远距景深模糊。

 NONE// 关闭模糊。
 COMPONENT_ULTRA_THIN, //组件超轻薄材质模糊。
 COMPONENT_THIN, //组件轻薄材质模糊。
 COMPONENT_REGULAR,// 组件普通材质模糊。
 COMPONENT_THICK, // 组件厚材质模糊。
 COMPONENT_ULTRA_THICK, // 组件超厚材质模糊。
}

BackgroundBlurStyleOptions继承自BlurOptions,BlurOptions在前文中有介绍,BackgroundBlurStyleOptions定义如下:

struct BackgroundBlurStyleOptions extends BlurOptions {
 params: BackgroundBrightnessOptions;
}

// BackgroundBrightnessOptions 结构定义如下
struct BackgroundBrightnessOptions {
 //亮度变化速率。亮度变化速率越大,提亮程度下降速度越快。若rate为0,则lightUpDegree将不生效,即不会产生任何提亮效果。
 // 默认值:0.0,取值范围:(0.0, +∞)
 rate: number, 
 // 提亮程度。提亮程度越大,亮度提升程度越大。
 // 默认值:0.0,取值范围:[-1.0, 1.0]
lightUpDegree: number, 
}
对于组件背景内容,每个像素自身的亮度(灰阶值)的计算公式为:Y = (0.299R + 0.587G + 0.114B)/ 255.0(R、G、B分别表示像素红色、绿色和蓝色通道的值,Y表示灰阶值),通过上述公式将像素点的灰阶值归一化至0~1的范围。每个像素的亮度提升计算公式为:ΔY = -rate*Y + lightUpDegree。例如,当rate=0.5,lightUpDegree=0.5时,对于灰阶值为0.2的像素点,亮度增加值为-0.5*0.2 + 0.5 = 0.4,对于灰阶值为1的像素点,亮度增加值为-0.5*1 + 0.5 = 0。

一个示例效果如下:

image.png

代码如下:

@Entry
@Component
struct BackDropBlurStyleDemo {
  build() {
    Grid() {
      GridItem() {
        Column() {
          Column() {
            Text('原图')
              .fontSize(20)
              .fontColor(Color.White)
              .textAlign(TextAlign.Center)
              .width('100%')
              .height('100%')
          }
          .height(100)
          .aspectRatio(1)
          .borderRadius(10)
          .backgroundImage($r('app.media.share'))

          Text('原图')
            .fontSize(12)
            .fontColor(Color.Black)
        }
        .height('100%')
        .justifyContent(FlexAlign.Start)
      }
      .width(200)
      .height(200)

      GridItem() {
        Column() {
          Column() {
            Text('Thin')
              .fontSize(20)
              .fontColor(Color.White)
              .textAlign(TextAlign.Center)
              .width('100%')
              .height('100%')
          }
          .height(100)
          .aspectRatio(1)
          .borderRadius(10)
          .backgroundImage($r('app.media.share'))
          // BlurStyle.Thin: 为组件添加轻薄材质模糊效果
          // ThemeColorMode.LIGHT: 固定使用浅色模式效果
          // AdaptiveColor.DEFAULT: 不使用取色模糊,使用默认的颜色作为蒙版颜色
          // scale: 背景材质模糊效果程度,默认值是1
          .backgroundBlurStyle(BlurStyle.Thin, {
            colorMode: ThemeColorMode.LIGHT,
            adaptiveColor: AdaptiveColor.DEFAULT,
            scale: 0.1
          })

          Text('Thin')
            .fontSize(12)
            .fontColor(Color.Black)
        }
        .height('100%')
        .justifyContent(FlexAlign.Start)
      }
      .width(200)
      .height(200)

      GridItem() {
        Column() {
          Column() {
            Text('Regular')
              .fontSize(20)
              .fontColor(Color.White)
              .textAlign(TextAlign.Center)
              .width('100%')
              .height('100%')
          }
          .height(100)
          .aspectRatio(1)
          .borderRadius(10)
          .backgroundImage($r('app.media.share'))
          .backgroundBlurStyle(BlurStyle.Regular, {
            colorMode: ThemeColorMode.LIGHT,
            adaptiveColor: AdaptiveColor.DEFAULT,
            scale: 0.1
          })

          Text('Regular')
            .fontSize(12)
            .fontColor(Color.Black)
        }
        .height('100%')
        .justifyContent(FlexAlign.Start)
      }
      .width(200)
      .height(200)

      GridItem() {
        Column() {
          Column() {
            Text('Thick')
              .fontSize(20)
              .fontColor(Color.White)
              .textAlign(TextAlign.Center)
              .width('100%')
              .height('100%')
          }
          .height(100)
          .aspectRatio(1)
          .borderRadius(10)
          .backgroundImage($r('app.media.share'))
          .backgroundBlurStyle(BlurStyle.Thick, {
            colorMode: ThemeColorMode.LIGHT,
            adaptiveColor: AdaptiveColor.DEFAULT,
            scale: 0.1
          })

          Text('Thick')
            .fontSize(12)
            .fontColor(Color.Black)
        }
        .height('100%')
        .justifyContent(FlexAlign.Start)
      }
      .width(200)
      .height(200)

      GridItem() {
        Column() {
          Column() {
            Text('BACKGROUND_THIN')
              .fontSize(12)
              .fontColor(Color.White)
              .textAlign(TextAlign.Center)
              .width('100%')
              .height('100%')
          }
          .height(100)
          .aspectRatio(1)
          .borderRadius(10)
          .backgroundImage($r('app.media.share'))
          .backgroundBlurStyle(BlurStyle.BACKGROUND_THIN, {
            colorMode: ThemeColorMode.LIGHT,
            adaptiveColor: AdaptiveColor.DEFAULT,
            scale: 0.1
          })

          Text('BACKGROUND_THIN')
            .fontSize(12)
            .fontColor(Color.Black)
        }
        .height('100%')
        .justifyContent(FlexAlign.Start)
      }
      .width(200)
      .height(200)

      GridItem() {
        Column() {
          Column() {
            Text('BACKGROUND_REGULAR')
              .fontSize(12)
              .fontColor(Color.White)
              .textAlign(TextAlign.Center)
              .width('100%')
              .height('100%')
          }
          .height(100)
          .aspectRatio(1)
          .borderRadius(10)
          .backgroundImage($r('app.media.share'))
          .backgroundBlurStyle(BlurStyle.BACKGROUND_REGULAR, {
            colorMode: ThemeColorMode.LIGHT,
            adaptiveColor: AdaptiveColor.DEFAULT,
            scale: 0.1
          })

          Text('BACKGROUND_REGULAR')
            .fontSize(12)
            .fontColor(Color.Black)
        }
        .height('100%')
        .justifyContent(FlexAlign.Start)
      }
      .width(200)
      .height(200)

      GridItem() {
        Column() {
          Column() {
            Text('BACKGROUND_THICK')
              .fontSize(12)
              .fontColor(Color.White)
              .textAlign(TextAlign.Center)
              .width('100%')
              .height('100%')
          }
          .height(100)
          .aspectRatio(1)
          .borderRadius(10)
          .backgroundImage($r('app.media.share'))
          .backgroundBlurStyle(BlurStyle.BACKGROUND_THICK, {
            colorMode: ThemeColorMode.LIGHT,
            adaptiveColor: AdaptiveColor.DEFAULT,
            scale: 0.1
          })

          Text('BACKGROUND_THICK')
            .fontSize(12)
            .fontColor(Color.Black)
        }
        .height('100%')
        .justifyContent(FlexAlign.Start)
      }
      .width(200)
      .height(200)

      GridItem() {
        Column() {
          Column() {
            Text('BACKGROUND_ULTRA_THICK')
              .fontSize(12)
              .fontColor(Color.White)
              .textAlign(TextAlign.Center)
              .width('100%')
              .height('100%')
          }
          .height(100)
          .aspectRatio(1)
          .borderRadius(10)
          .backgroundImage($r('app.media.share'))
          .backgroundBlurStyle(BlurStyle.BACKGROUND_ULTRA_THICK, {
            colorMode: ThemeColorMode.LIGHT,
            adaptiveColor: AdaptiveColor.DEFAULT,
            scale: 0.1
          })

          Text('BACKGROUND_ULTRA_THICK')
            .fontSize(12)
            .fontColor(Color.Black)
        }
        .height('100%')
        .justifyContent(FlexAlign.Start)
      }
      .width(200)
      .height(200)
    }
    .columnsTemplate('1fr 1fr')
    .rowsTemplate('1fr 1fr 1fr 1fr')
    .width('100%')
    .height('100%')
    .margin({ top: 40 })
  }
}

2.4、 foregroundBlurStyle

为当前组件提供前景(内容)模糊能力。接口介绍如下:

 // value: 内容模糊样式。
// options: 内容模糊选项
foregroundBlurStyle(value: BlurStyle, options?: ForegroundBlurStyleOptions)

其中BlurStyle是枚举,我们在backgroundBlurStyle中已经介绍过,ForegroundBlurStyleOptions定义如下:

struct ForegroundBlurStyleOptions extends BlurOptions {
 //内容模糊效果使用的深浅色模式。取值有:SYSTEM | LIGHT | DARK
 // 默认值:ThemeColorMode.SYSTEM, 
 colorModeThemeColorMode, 

 // 内容模糊效果使用的取色模式。取值有:DEFAULT | AVERAGE
 // 默认值:AdaptiveColor.DEFAULT
 adaptiveColor: AdaptiveColor, 
 // 灰阶模糊参数。
 // 默认值:grayScale: [0,0]
 blurOptionsBlurOptions,

 // 内容模糊效果程度。默认值:1.0,取值范围:[0.0, 1.0]
 scalenumber,
}

一个案例如下:

image.png

代码如下:

@Entry
@Component
struct ForegroundBlurStyleDemo {
  build() {
    Grid() {
      GridItem() {
        Column() {
          Column() {
            Text('原图')
              .fontSize(20)
              .fontColor(Color.White)
              .textAlign(TextAlign.Center)
              .width('100%')
              .height('100%')
          }
          .height(100)
          .aspectRatio(1)
          .borderRadius(10)
          .backgroundImage($r('app.media.share'))

          Text('原图')
            .fontSize(12)
            .fontColor(Color.Black)
        }
        .height('100%')
        .justifyContent(FlexAlign.Start)
      }
      .width(200)
      .height(200)

      GridItem() {
        Column() {
          Column() {
            Text('Thin')
              .fontSize(20)
              .fontColor(Color.White)
              .textAlign(TextAlign.Center)
              .width('100%')
              .height('100%')
          }
          .height(100)
          .aspectRatio(1)
          .borderRadius(10)
          .backgroundImage($r('app.media.share'))
          // BlurStyle.Thin: 为组件添加轻薄材质模糊效果
          // ThemeColorMode.LIGHT: 固定使用浅色模式效果
          // AdaptiveColor.DEFAULT: 不使用取色模糊,使用默认的颜色作为蒙版颜色
          // scale: 背景材质模糊效果程度,默认值是1
          .foregroundBlurStyle(BlurStyle.Thin, {
            colorMode: ThemeColorMode.LIGHT,
            adaptiveColor: AdaptiveColor.DEFAULT,
            scale: 0.1
          })

          Text('Thin')
            .fontSize(12)
            .fontColor(Color.Black)
        }
        .height('100%')
        .justifyContent(FlexAlign.Start)
      }
      .width(200)
      .height(200)

      GridItem() {
        Column() {
          Column() {
            Text('Regular')
              .fontSize(20)
              .fontColor(Color.White)
              .textAlign(TextAlign.Center)
              .width('100%')
              .height('100%')
          }
          .height(100)
          .aspectRatio(1)
          .borderRadius(10)
          .backgroundImage($r('app.media.share'))
          .foregroundBlurStyle(BlurStyle.Regular, {
            colorMode: ThemeColorMode.LIGHT,
            adaptiveColor: AdaptiveColor.DEFAULT,
            scale: 0.1
          })

          Text('Regular')
            .fontSize(12)
            .fontColor(Color.Black)
        }
        .height('100%')
        .justifyContent(FlexAlign.Start)
      }
      .width(200)
      .height(200)

      GridItem() {
        Column() {
          Column() {
            Text('Thick')
              .fontSize(20)
              .fontColor(Color.White)
              .textAlign(TextAlign.Center)
              .width('100%')
              .height('100%')
          }
          .height(100)
          .aspectRatio(1)
          .borderRadius(10)
          .backgroundImage($r('app.media.share'))
          .foregroundBlurStyle(BlurStyle.Thick, {
            colorMode: ThemeColorMode.LIGHT,
            adaptiveColor: AdaptiveColor.DEFAULT,
            scale: 0.1
          })

          Text('Thick')
            .fontSize(12)
            .fontColor(Color.Black)
        }
        .height('100%')
        .justifyContent(FlexAlign.Start)
      }
      .width(200)
      .height(200)

      GridItem() {
        Column() {
          Column() {
            Text('BACKGROUND_THIN')
              .fontSize(12)
              .fontColor(Color.White)
              .textAlign(TextAlign.Center)
              .width('100%')
              .height('100%')
          }
          .height(100)
          .aspectRatio(1)
          .borderRadius(10)
          .backgroundImage($r('app.media.share'))
          .foregroundBlurStyle(BlurStyle.BACKGROUND_THIN, {
            colorMode: ThemeColorMode.LIGHT,
            adaptiveColor: AdaptiveColor.DEFAULT,
            scale: 0.1
          })

          Text('BACKGROUND_THIN')
            .fontSize(12)
            .fontColor(Color.Black)
        }
        .height('100%')
        .justifyContent(FlexAlign.Start)
      }
      .width(200)
      .height(200)

      GridItem() {
        Column() {
          Column() {
            Text('BACKGROUND_REGULAR')
              .fontSize(12)
              .fontColor(Color.White)
              .textAlign(TextAlign.Center)
              .width('100%')
              .height('100%')
          }
          .height(100)
          .aspectRatio(1)
          .borderRadius(10)
          .backgroundImage($r('app.media.share'))
          .foregroundBlurStyle(BlurStyle.BACKGROUND_REGULAR, {
            colorMode: ThemeColorMode.LIGHT,
            adaptiveColor: AdaptiveColor.DEFAULT,
            scale: 0.1
          })

          Text('BACKGROUND_REGULAR')
            .fontSize(12)
            .fontColor(Color.Black)
        }
        .height('100%')
        .justifyContent(FlexAlign.Start)
      }
      .width(200)
      .height(200)

      GridItem() {
        Column() {
          Column() {
            Text('BACKGROUND_THICK')
              .fontSize(12)
              .fontColor(Color.White)
              .textAlign(TextAlign.Center)
              .width('100%')
              .height('100%')
          }
          .height(100)
          .aspectRatio(1)
          .borderRadius(10)
          .backgroundImage($r('app.media.share'))
          .foregroundBlurStyle(BlurStyle.BACKGROUND_THICK, {
            colorMode: ThemeColorMode.LIGHT,
            adaptiveColor: AdaptiveColor.DEFAULT,
            scale: 0.1
          })

          Text('BACKGROUND_THICK')
            .fontSize(12)
            .fontColor(Color.Black)
        }
        .height('100%')
        .justifyContent(FlexAlign.Start)
      }
      .width(200)
      .height(200)

      GridItem() {
        Column() {
          Column() {
            Text('BACKGROUND_ULTRA_THICK')
              .fontSize(12)
              .fontColor(Color.White)
              .textAlign(TextAlign.Center)
              .width('100%')
              .height('100%')
          }
          .height(100)
          .aspectRatio(1)
          .borderRadius(10)
          .backgroundImage($r('app.media.share'))
          .foregroundBlurStyle(BlurStyle.BACKGROUND_ULTRA_THICK, {
            colorMode: ThemeColorMode.LIGHT,
            adaptiveColor: AdaptiveColor.DEFAULT,
            scale: 0.1
          })

          Text('BACKGROUND_ULTRA_THICK')
            .fontSize(12)
            .fontColor(Color.Black)
        }
        .height('100%')
        .justifyContent(FlexAlign.Start)
      }
      .width(200)
      .height(200)
    }
    .columnsTemplate('1fr 1fr')
    .rowsTemplate('1fr 1fr 1fr 1fr')
    .width('100%')
    .height('100%')
    .margin({ top: 40 })
  }
}

2.5、motionBlur

在当前组件由缩放大小或位移变化引起的运动过程中,增加动态模糊效果。

注意:

  1. 不建议在组件内转场、共享元素转场、组件内隐式元素转场、粒子动画场景下使用该属性,否则会有非预期效果。

  2. 该属性需要在开始状态将motionBlur的参数radius设置为0,否则冷启动时会有非预期效果。

  3. 该属性需要与动画的AnimateParam的onFinish参数配合使用,需要在运动模糊动画结束后将motionBlur的参数radius置为0,否则会有非预期效果。

  4. 在使用该属性过程中,不要在使用过程中频繁更改同一个组件的模糊半径,否则会有非预期效果。比如示例中的动画,频繁点击会出现模糊效果偶尔失效的情况。

  5. 运动模糊锚点坐标需要与动画缩放的锚点保持一致,否则会有非预期效果。

  6. 模糊半径建议设置1以内,否则会有非预期效果。

接口定义如下:

// value: 定义运动模糊参数。
motionBlur(value: MotionBlurOptions)

MotionBlurOptions结构定义如下:

struct MotionBlurOptions {
// 模糊半径,取值范围[0.0, ∞),建议设置1.0以内。
 radius: number, 
// 运动模糊锚点坐标。运动模糊锚点坐标设置时需要与动画缩放的锚点保持一致设置。
 anchor: MotionBlurAnchor,  
}

// 其中 anchor的MotionBlurAnchor类型定义如下:
struc MotionBlurAnchor {
 xnumber, //  锚点坐标x值,取值范围[0.0, 1.0]。
 ynumber, //  锚点坐标y值,取值范围[0.0, 1.0]。
}

示例效果如下:

image.png

代码如下(第39行):

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

@Entry
@Component
struct motionBlurTest {
  @State widthSize: number = 400
  @State heightSize: number = 320
  @State flag: boolean = true
  @State radius: number = 0
  @State x: number = 0
  @State y: number = 0

  build() {
    Column() {
      Column() {
        Image($r('app.media.testImg'))
          .width(this.widthSize)
          .height(this.heightSize)
          .onClick(() => {
            this.radius = 5;
            this.x = 0.5;
            this.y = 0.5;
            if (this.flag) {
              this.widthSize = 100;
              this.heightSize = 80;
            } else {
              this.widthSize = 400;
              this.heightSize = 320;
            }
            this.flag = !this.flag;
          })
          .animation({
            duration: 2000,
            curve: curves.springCurve(10, 1, 228, 30),
            onFinish: () => {
              this.radius = 0;
            }
          })
          .motionBlur({ radius: this.radius, anchor: { x: this.x, y: this.y } })
      }
    }.width('100%').margin({ top: 5 })
  }
}