DEV Community

HarmonyOS
HarmonyOS

Posted on

How to add and dynamically modify global watermarks

Read the original article:How to add and dynamically modify global watermarks

Problem Description

How to add global watermarks in HarmonyOS development, dynamically control the display and hiding of watermarks, and modify watermark content so that the watermark content is different on each page?

Background Knowledge

  • overlay: Adds a mask text or overlays a customized component and ComponentContent as the floating layer of the current component.
  • hitTestBehavior: Sets the touch test type for the component.
  • Navigation: The Navigation component serves as the root view container for route navigation and is typically used as the root container for a Page. By default, it includes a title bar, content area, and toolbar. The content area displays navigation content (a child component of NavDestination) by default on the home page or non-home page (a child component of NavDestination). The home page and non-home page are switched through routing.
  • Canvas: Provides a canvas component for custom drawing of shapes.

Solution

  • Scenario 1: To add a global watermark to the page, see the official example Adding watermarks to pages.

  • Scenario 2: If you need to customize the watermark content on each page, you can modify the solution in scenario 1. Specifically, customize the watermarkBuilder function on the target page and pass the public watermark method to the function. You can pass different parameters to display different content. In addition, set the touch test type to None or Transparent. Otherwise, the normal component cannot respond to events.
    To add a global watermark, set the overlay attribute on the root node of the page where the watermark is to be added and configure the watermarkBuilder function. You can also set a button and use @State Decorator to declare a state variable to dynamically control the display and hiding of the watermark. For details, see the following code:

  // Index.ets
  import { WaterMark} from '../pages/WaterMark';

  @Builder
  // Display different content by passing different parameters.
  export function watermarkBuilder(watermarkText = '', rotationAngle = -10,
    fillColor: string | number | CanvasGradient | CanvasPattern = '#99158ef3', font = '16vp') {
    Column() {
      WaterMark({
        watermarkText: watermarkText,
        rotationAngle: rotationAngle,
        fillColor: fillColor,
        font: font
      });
    }.hitTestBehavior(HitTestMode.Transparent) // Adding a floating layer and configuring the public watermark method.
  }

  @Entry
  @Component
  struct waterOver {
    @State text: string = '';
    @State state: Boolean = true;

    aboutToAppear(): void {
      setInterval(() => {
        this.text += '_+';
      }, 500);
    };

    build() {
      Column() {
        Button('Redirecting to another page')
          .margin({ bottom: 100 })
          .onClick(() => {
            this.getUIContext().getRouter().pushUrl({
              url: 'pages/PageSecond',
            });
          });
        Button('Show Hidden Files')
          .onClick(() => {
            // Use the state variable to control the display and hiding of watermarks.
            this.state = !this.state;
          });
      }
      .overlay(this.state? watermarkBuilder(this.text) : undefined) // Adding a floating layer and configuring the public watermark method.
      .width('100%');
    }
  }
Enter fullscreen mode Exit fullscreen mode
  // Other Pages
  import { WaterMark } from '../pages/WaterMark';

  @Builder
  // Display different content by passing different parameters.
  export function watermarkBuilder(watermarkText = '', rotationAngle = -10,
    fillColor: string | number | CanvasGradient | CanvasPattern = '#9937470c', font = '16vp') {
    Column() {
      WaterMark({
        watermarkText: watermarkText,
        rotationAngle: rotationAngle,
        fillColor: fillColor,
        font: font
      });
    }.hitTestBehavior(HitTestMode.Transparent)
  };

  @Entry
  @Component
  struct waterOver {
    @State text: string = '';

    aboutToAppear(): void {
      setInterval(() => {
        this.text = 'Watermarking';
      }, 500);
    };

    build() {
      Column() {
        Button('Other Pages')
      }
      .width('100%')
      .height('100%')
      .overlay(watermarkBuilder(this.text))
    };
  };
Enter fullscreen mode Exit fullscreen mode
  // Public Methods for Customizing Watermarks
  @Entry
  @Component
  export struct WaterMark {
    @Prop watermarkWidth: number = 120;
    @Prop watermarkHeight: number = 120;
    @Prop watermarkText: string = '0000-2025-01-01';
    @Prop rotationAngle: number = -30;
    @Prop fillColor: string | number | CanvasGradient | CanvasPattern = '#10000000';
    @Prop font: string = '16vp';
    private settings: RenderingContextSettings = new RenderingContextSettings(true);
    private context: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings);

    build() {
      Canvas(this.context)
        .width('100%')
        .height('100%')
        .hitTestBehavior(HitTestMode.Transparent)
        .onReady(() => this.draw());
    };

    draw() {
      this.context.fillStyle = this.fillColor;
      this.context.font = this.font;
      const colCount = Math.ceil(this.context.width / this.watermarkWidth);
      const rowCount = Math.ceil(this.context.height / this.watermarkHeight);
      for (let col = 0; col <= colCount; col++) {
        let row = 0;
        for (; row <= rowCount; row++) {
          const angle = this.rotationAngle * Math.PI / 180;
          this.context.rotate(angle);
          const positionX = this.rotationAngle > 0? this.watermarkHeight * Math.tan(angle) : 0;
          const positionY = this.rotationAngle > 0? 0 : this.watermarkWidth * Math.tan(-angle);
          this.context.fillText(this.watermarkText, positionX, positionY);
          this.context.rotate(-angle);
          this.context.translate(0, this.watermarkHeight);
        };
        this.context.translate(0, -this.watermarkHeight * row);
        this.context.translate(this.watermarkWidth, 0);
      };
    };
  };
Enter fullscreen mode Exit fullscreen mode

The effect of scenario 2 is as follows:

img

  • Scenario 3: If the page uses the Navigation layout and you want to add a global watermark, you can use a Canvas with a transparent background to draw the text watermark, and then overlay it on the Navigation component using the Stack component. For details, please refer to the following code:
  import { SymbolGlyphModifier } from '@kit.ArkUI';

  @Component
  struct MyPageOne {
    private listScroller: Scroller = new Scroller();
    private scrollScroller: Scroller = new Scroller();
    private arr: number[] = [];

    aboutToAppear(): void {
      for (let i = 0; i < 5; i++) {
        this.arr.push(i);
      }
    }

    build() {
      NavDestination() {
        Column() {
          Scroll(this.scrollScroller) {
            Column() {
              List({ space: 0, initialIndex: 0, scroller: this.listScroller }) {
                ForEach(this.arr, (item: number) => {
                  ListItem() {
                    Text('' + item)
                      .height(100)
                      .fontSize(16)
                      .textAlign(TextAlign.Center)
                      .width('90%')
                      .margin({ left: '5%', top:'5%' })
                      .borderRadius(15)
                      .backgroundColor('#e5e5ea');
                  };
                }, (item: string) => item);
              }.width('100%').scrollBar(BarState.Off)
              .height('90%')
              .margin(80)
              .nestedScroll({ scrollForward: NestedScrollMode.SELF_FIRST, scrollBackward: NestedScrollMode.SELF_FIRST });
            }
          }
          .width('100%')
          .scrollBar(BarState.Off)
          .scrollable(ScrollDirection.Vertical)
          .edgeEffect(EdgeEffect.Spring);
        }
      }
      // .height('100%')
      .title('PageOne', { backgroundColor: '#f1f3f5', barStyle: BarStyle.STACK })
      .toolbarConfiguration([
        {
          value: 'item1',
          symbolIcon: new SymbolGlyphModifier($r('sys.symbol.phone_badge_star'))
        }
      ], { backgroundColor: '#f1f3f5', barStyle: BarStyle.STACK })
      // Scrollable container component bound with parent-child relationships.
      .bindToNestedScrollable([{ parent: this.scrollScroller, child: this.listScroller }]);
    }
  }

  @Component
  struct MyPageTwo {
    private listScroller: Scroller = new Scroller();
    private arr: number[] = [];

    aboutToAppear(): void {
      for (let i = 0; i < 5; i++) {
        this.arr.push(i);
      }
    }

    build() {
      NavDestination() {
        List({ scroller: this.listScroller }) {
          ForEach(this.arr, (item: number) => {
            ListItem() {
              Text('' + item)
                .height(100)
                .fontSize(16)
                .textAlign(TextAlign.Center)
                .width('90%')
                .borderRadius(15)
                .backgroundColor('#e5e5ea')
                .margin({ left: '5%', top: '5%' });
            };
          }, (item: string) => item);
        }
        .margin(80)
        .width('100%');
      }

      .title('PageTwo', { backgroundColor: '#f1f3f5', barStyle: BarStyle.STACK })
      .toolbarConfiguration([
        {
          value: 'item1',
          symbolIcon: new SymbolGlyphModifier($r('sys.symbol.phone_badge_star'))
        }
      ],
        { backgroundColor: '#f1f3f5', barStyle: BarStyle.STACK })
      // Binding a scrollable container component.
      .bindToScrollable([this.listScroller]);
    }
  }

  @Entry
  @Component
  struct Index {
    private settings: RenderingContextSettings = new RenderingContextSettings(true);
    private context: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings);
    private stack: NavPathStack = new NavPathStack();

    @Builder
    MyPageMap(name: string) {
      if (name === 'myPageTwo') {
        MyPageTwo();
      } else {
        MyPageOne();
      }
    }

    build() {
      Stack({ alignContent: Alignment.Center }) {
        Navigation(this.stack) {
          Column() {

            Button('push PageOne')
              .onClick(() => {
                this.stack.pushPath({ name: 'myPageOne' });
              });

            Button('push PageTwo')
              .margin({ top: 20 })
              .onClick(() => {
                this.stack.pushPath({ name: 'myPageTwo' });
              });
          }
          .padding({ top: '35%' })
          .height('45%').justifyContent(FlexAlign.SpaceAround);
        }
        .width('100%')
        .height('100%')
        .title({ main: 'MainTitle', sub: 'subTitle' })
        .navDestination(this.MyPageMap)
        .backgroundColor('rgba(128, 243, 245, 1.00)')
        .mode(NavigationMode.Auto)
        .mode(NavigationMode.Stack)
        .backgroundColor('rgba(200,200,200,0.4)');

        Canvas(this.context)
          .width('100%')
          .height('100%')
          .hitTestBehavior(HitTestMode.Transparent)
          .onReady(() => {
            this.context.fillStyle = '#10000000';
            this.context.font = '16vp';
            this.context.textAlign = 'center';
            this.context.textBaseline = 'middle';
            // Draw text watermark here, or it can be an image watermark
            for (let i = 0; i < this.context.width / 120; i++) {
              this.context.translate(120, 0);
              let j = 0;
              for (; j < this.context.height / 120; j++) {
                this.context.rotate(-Math.PI / 180 * 30);
                // The watermark data is hardcoded. Replace it with your own watermark.
                this.context.fillText('Watermark watermark', -60, -60);
                this.context.rotate(Math.PI / 180 * 30);
                this.context.translate(0, 120);
              }
              this.context.translate(0, -120 * j);
            }
          });
      };
    }
  }
Enter fullscreen mode Exit fullscreen mode

The effect of scenario 3 is as follows:

img

Written by Emrecan Karakas

Top comments (0)