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%');
}
}
// 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))
};
};
// 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);
};
};
};
The effect of scenario 2 is as follows:
- 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);
}
});
};
}
}
The effect of scenario 3 is as follows:


Top comments (0)