智能客服
你问我答,随时在线为你解决问题
一般情况下,UI开发都是在矩形窗口上操作,但在PC/2in1设备上,应用还可以使用各类异形窗口来展现交互内容。这里的异形是指除默认矩形以外的形状,包括但不限于圆形、三角形以及其他不规则的形状等。开发者通过使用这些异形形状的窗口,结合窗口中的内容展示UI交互的多样性,一定程度上能够提升应用体验。类似的示例有:文字应用中的放大镜、带指示箭头的气泡框、提供辅助功能的悬浮窗等。
本文主要介绍在PC/2in1设备上实现异形窗口。根据窗口的形状类别,可分为以下场景:
在创建窗口时,ArkUI以宽、高为参数描绘窗口初始大小,其形状默认为矩形,可以通过设置掩码改变窗口最终呈现的形状。
该掩码是一个二维数组,其大小对应初始窗口的宽和高,所以可以把该数组中的元素与窗口中的像素一一对应起来;数组中的每一项值只能是0或1,其中数字0表示窗口对应位置的像素是透明的,数字1代表对应像素不透明。
简单来说,掩码即是由数字1描绘的窗口形状点阵图。具体使用可参考setWindowMask()接口。
掩码windowMask二维数组的大小需要与窗口初始宽、高一致,且每项值只能是0或1,否则设置的形状不会生效。
待开发的异形窗口,其形状是具有一定规则的几何图形,通过描述语言能得到精确且唯一的形状。本节以下面两种形状的实现为例进行说明:直径为500的圆形;底500、高500的等腰三角形。
在得到异形窗口前,需要先创建一个默认的矩形窗口,开发步骤如下:
- // entry/src/main/syscap.json
- {
- "devices": {
- "general": [
- "2in1"
- ]
- },
- "development": {
- "addedSysCaps": [
- "SystemCapability.Window.SessionManager"
- ]
- }
- }
- windowStage = AppStorage.get('windowStage');
- if (windowStage === null) {
- hilog.error(0x0000, 'Sample', 'Failed to create the subwindow. Cause: windowStage is null');
- return;
- }
- const windowWidth = 500;
- const windowHeight = 500;
- try {
- subWindow = await windowStage!.createSubWindow('mySubWindow');
- subWindow.moveWindowTo(300, 300);
- subWindow.resize(windowWidth, windowHeight);
- subWindow.setUIContent('pages/SubPage');
- // ...
- subWindow.showWindow();
- } catch (exception) {
- hilog.error(0x0000, 'Sample', 'Failed to create sub window. Cause: ' + JSON.stringify(exception));
- }
- function fillCircle(array: number[][], x: number, y: number, radius: number): void {
- for (let i = x - radius; i <= x + radius; i++) {
- for (let j = y - radius; j <= y + radius; j++) {
- const distSquared = (i - x)**2 + (j - y)**2;
- if (distSquared <= radius**2 && i >= 0 && j >= 0 && i < array.length && j < array[0].length) {
- array[i][j] = 1;
- }
- }
- }
- }
-
- async function getCircleMask(width: number, height: number): Promise<number[][]> {
- const radius = Math.min(width, height) / 2;
- const maskArray: number[][] = new Array(height).fill(null).map(() => new Array(width).fill(0));
- fillCircle(maskArray, height / 2, width / 2, radius);
- return maskArray;
- }
- function fillTriangle(array: number[][], base: number, height: number): void {
- // i < height-10 in order to remove the round corner
- for (let i = 0; i < height - 10; i++) {
- for (let j = 0; j < base; j++) {
- if (j >= base / 2 - base / 2 * i / height && j <= base / 2 + base / 2 * i / height) {
- array[i][j] = 1;
- }
- }
- }
- }
-
- async function getTriangleMask(width: number, height: number): Promise<number[][]> {
- const maskArray: number[][] = new Array(height).fill(null).map(() => new Array(width).fill(0));
- fillTriangle(maskArray, width, height);
- return maskArray;
- }
- async function setWindowShape(win: window.Window, width: number,
- height: number, getMaskFunc: (w: number, h: number, picPath?: string) => Promise<number[][]>,
- picPath?: string): Promise<void> {
- const windowMask = await getMaskFunc(width, height, picPath);
- if (canIUse('SystemCapability.Window.SessionManager')) {
- try {
- win.setWindowMask(windowMask);
- } catch (e) {
- let err = e as BusinessError;
- hilog.error(0x0000, 'Simple', `failed code=${err.code}, message=${err.message}`);
- }
- } else {
- hilog.info(0x0000, 'Simple', 'can not use SessionManager syscap');
- }
- }
-
- /**
- * Set the window with circle shape
- * @param win The target window
- * @param width The original window width
- * @param height The original window height
- */
- export async function setWindowCircleShape(win: window.Window, width: number,
- height: number): Promise<void> {
- setWindowShape(win, width, height, getCircleMask)
- }
-
- /**
- * Set the window with triangle shape
- * @param win The target window
- * @param width The original window width
- * @param height The original window height
- */
- export async function setWindowTriangleShape(win: window.Window, width: number,
- height: number): Promise<void> {
- setWindowShape(win, width, height, getTriangleMask)
- }
- subWindow.showWindow();


待开发的异形窗口,呈现出不规则的形状。这种情况通常先由设计人员提供图形文件,开发人员以文件为输入,基于ArkUI提供的图片处理能力,转化为相应的形状掩码,最后实现不规则的窗口形状。

本节以上图为例说明不规则形状窗口的实现过程。
首先同样需要先创建子窗口,然后再依以下步骤开发。
- /**
- * Convert image to PixelMap object
- */
- export async function image2PixelMap(Context: Context, icon: string, w: number, h: number): Promise<image.PixelMap> {
- try {
- const rawFileDescriptor: resourceManager.RawFileDescriptor = Context.resourceManager.getRawFdSync(icon);
- const imageSource: image.ImageSource = image.createImageSource(rawFileDescriptor);
- const pixelMap: Promise<PixelMap> = imageSource.createPixelMap({
- editable: false,
- desiredPixelFormat: image.PixelMapFormat.BGRA_8888,
- desiredSize: { width: w, height: h }
- });
- imageSource.release()
- return pixelMap;
- } catch (e) {
- let err = e as BusinessError;
- hilog.error(0x0000, 'Simple', `failed code=${err.code}, message=${err.message}`);
- }
- return new Promise<image.PixelMap>((resolve, reject) => {
- reject('null')
- });
- }
- export async function getPicMask(Context: Context,width: number, height: number, picPath: string): Promise<number[][]> {
- const maskArray: number[][] = new Array(height).fill(null).map(() => new Array(width).fill(0));
- const pixelMap: image.PixelMap = await image2PixelMap(Context, picPath, width, height);
- const pixelArrayBuffer: ArrayBuffer = new ArrayBuffer(width * height * 4);
- await pixelMap.readPixelsToBuffer(pixelArrayBuffer);
- const allPixels: number[] = [];
- const unit8Pixels: Uint8Array = new Uint8Array(pixelArrayBuffer);
- for (let i = 0, j = 0; i < unit8Pixels.length; i += 4, j++) {
- // unit8Pixels[i+3] is alpha channel of BGRA_8888
- allPixels[j] = unit8Pixels[i + 3] > 0 ? 1 : 0;
- }
- pixelMap.release()
- let k = 0;
- for (let i = 0; i < height; i++) {
- for (let j = 0; j < width; j++) {
- maskArray[i][j] = allPixels[k++];
- }
- }
- return maskArray;
- }
- async function setWindowShape(win: window.Window, width: number,
- height: number, getMaskFunc: (w: number, h: number, picPath?: string) => Promise<number[][]>,
- picPath?: string): Promise<void> {
- const windowMask = await getMaskFunc(width, height, picPath);
- if (canIUse('SystemCapability.Window.SessionManager')) {
- try {
- win.setWindowMask(windowMask);
- } catch (e) {
- let err = e as BusinessError;
- hilog.error(0x0000, 'Simple', `failed code=${err.code}, message=${err.message}`);
- }
- } else {
- hilog.info(0x0000, 'Simple', 'can not use SessionManager syscap');
- }
- }
- /**
- * Set the window with pic shape
- * @param win The target window
- * @param width The original window width
- * @param height The original window height
- * @param picPath The pic path in rawfile
- */
- export async function setWindowPicShape(win: window.Window, Context: Context, width: number,
- height: number, picPath: string): Promise<void> {
- setWindowShape(win, width, height, (w, h, picPath) => {
- return getPicMask(Context,w, h, picPath!)
- }, picPath)
- }
- subWindow.showWindow();
