• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# 绘制几何图形 (Shape)
2<!--Kit: ArkUI-->
3<!--Subsystem: ArkUI-->
4<!--Owner: @zjsxstar-->
5<!--Designer: @sunbees-->
6<!--Tester: @liuli0427-->
7<!--Adviser: @HelloCrease-->
8
9
10绘制组件用于在页面绘制图形,Shape组件是绘制组件的父组件,父组件中会描述所有绘制组件均支持的通用属性。具体用法请参考[Shape](../reference/apis-arkui/arkui-ts/ts-drawing-components-shape.md)。
11
12
13## 创建绘制组件
14
15绘制组件可以由以下两种形式创建:
16
17- 绘制组件使用Shape作为父组件,实现类似SVG的效果。接口调用为以下形式:
18
19  ```ts
20  Shape(value?: PixelMap)
21  ```
22
23  该接口用于创建带有父组件的绘制组件,其中value用于设置绘制目标,可将图形绘制在指定的PixelMap对象中,若未设置,则在当前绘制目标中进行绘制。
24
25  ```ts
26  Shape() {
27    Rect().width(300).height(50)
28  }
29  ```
30
31
32- 绘制组件单独使用,用于在页面上绘制指定的图形。有7种绘制类型,分别为[Circle](../reference/apis-arkui/arkui-ts/ts-drawing-components-circle.md)(圆形)、[Ellipse](../reference/apis-arkui/arkui-ts/ts-drawing-components-ellipse.md)(椭圆形)、[Line](../reference/apis-arkui/arkui-ts/ts-drawing-components-line.md)(直线)、[Polyline](../reference/apis-arkui/arkui-ts/ts-drawing-components-polyline.md)(折线)、[Polygon](../reference/apis-arkui/arkui-ts/ts-drawing-components-polygon.md)(多边形)、[Path](../reference/apis-arkui/arkui-ts/ts-drawing-components-path.md)(路径)、[Rect](../reference/apis-arkui/arkui-ts/ts-drawing-components-rect.md)(矩形)。以Circle的接口调用为例:
33
34  ```ts
35  Circle(value?: { width?: string | number, height?: string | number })
36  ```
37
38    该接口用于在页面绘制圆形,其中width用于设置圆形的宽度,height用于设置圆形的高度,圆形直径由宽高最小值确定。
39
40  ```ts
41  Circle({ width: 150, height: 150 })
42  ```
43
44  ![创建2](figures/创建2.jpg)
45
46
47## 形状视口viewPort
48
49
50```ts
51viewPort(value: { x?: number | string, y?: number | string, width?: number | string, height?: number | string })
52```
53
54形状视口viewPort指定用户空间中的一个矩形,该矩形映射到为关联的SVG元素建立的视区边界。viewPort属性的值包含x、y、width和height四个可选参数,x和y表示视区的左上角坐标,width和height表示其尺寸。
55
56以下三个示例说明如何使用viewPort:
57
58- 通过形状视口对图形进行放大与缩小。
59
60  ```ts
61  class tmp {
62    x: number = 0
63    y: number = 0
64    width: number = 75
65    height: number = 75
66  }
67
68  class tmp1 {
69    x:number = 0
70    y:number = 0
71    width:number = 300
72    height:number = 300
73  }
74
75  @Entry
76  @Component
77  struct Index {
78    viep: tmp = new tmp();
79    viep1: tmp1 = new tmp1();
80
81    build() {
82      Column() {
83        // 画一个宽高都为75的圆
84        Text('原始尺寸Circle组件')
85        Circle({ width: 75, height: 75 }).fill('#E87361')
86
87        Row({ space: 10 }) {
88          Column() {
89            // 创建一个宽高都为150的shape组件,背景色为黄色,一个宽高都为75的viewPort。
90            // 用一个蓝色的矩形来填充viewPort,在viewPort中绘制一个直径为75的圆。
91            // 绘制结束,viewPort会根据组件宽高放大两倍。
92            Text('shape内放大的Circle组件')
93            Shape() {
94              Rect().width('100%').height('100%').fill('#0097D4')
95              Circle({ width: 75, height: 75 }).fill('#E87361')
96            }
97            .viewPort(this.viep)
98            .width(150)
99            .height(150)
100            .backgroundColor('#F5DC62')
101          }
102
103          Column() {
104            // 创建一个宽高都为150的shape组件,背景色为黄色,一个宽高都为300的viewPort。
105            // 用一个绿色的矩形来填充viewPort,在viewPort中绘制一个直径为75的圆。
106            // 绘制结束,viewPort会根据组件宽高缩小两倍。
107            Text('Shape内缩小的Circle组件')
108            Shape() {
109              Rect().width('100%').height('100%').fill('#BDDB69')
110              Circle({width: 75, height: 75}).fill('#E87361')
111            }
112            .viewPort(this.viep1)
113            .width(150)
114            .height(150)
115            .backgroundColor('#F5DC62')
116          }
117        }
118      }
119    }
120  }
121  ```
122
123  ![2023032401632](figures/2023032401632.jpg)
124
125- 创建一个宽高都为300的shape组件,背景色为黄色,创建一个宽高都为300的viewPort。用一个蓝色的矩形来填充viewPort,在viewPort中绘制一个半径为75的圆。
126
127  ```ts
128  class tmp {
129    x: number = 0
130    y: number = 0
131    width: number = 300
132    height: number = 300
133  }
134
135  @Entry
136  @Component
137  struct Index {
138    viep: tmp = new tmp();
139
140    build() {
141      Column() {
142        Shape() {
143          Rect().width("100%").height("100%").fill("#0097D4")
144          Circle({ width: 150, height: 150 }).fill("#E87361")
145        }
146        .viewPort(this.viep)
147        .width(300)
148        .height(300)
149        .backgroundColor("#F5DC62")
150      }
151    }
152  }
153  ```
154
155  ![viewport(2)](figures/viewport(2).jpg)
156
157- 创建一个宽高都为300的shape组件,背景色为黄色,创建一个宽高都为300的viewPort。用一个蓝色的矩形来填充viewPort,在viewPort中绘制一个半径为75的圆,将viewPort向右方和下方各平移150。
158
159  ```ts
160  class tmp {
161    x: number = -150
162    y: number = -150
163    width: number = 300
164    height: number = 300
165  }
166
167  @Entry
168  @Component
169  struct Index {
170    viep: tmp = new tmp();
171
172    build() {
173      Column() {
174        Shape() {
175          Rect().width("100%").height("100%").fill("#0097D4")
176          Circle({ width: 150, height: 150 }).fill("#E87361")
177        }
178        .viewPort(this.viep)
179        .width(300)
180        .height(300)
181        .backgroundColor("#F5DC62")
182      }
183    }
184  }
185  ```
186
187  ![viewport(3)](figures/viewport(3).jpg)
188
189
190## 自定义样式
191
192绘制组件支持通过各种属性更改组件样式。
193
194- 通过[fill](../reference/apis-arkui/arkui-ts/ts-drawing-components-path.md#fill)可以设置组件填充区域颜色。
195
196  ```ts
197  Path()
198    .width(100)
199    .height(100)
200    .commands('M150 0 L300 300 L0 300 Z')
201    .fill("#E87361")
202    .strokeWidth(0)
203  ```
204
205  ![2023022792216](figures/2023022792216.jpg)
206
207- 通过[stroke](../reference/apis-arkui/arkui-ts/ts-drawing-components-path.md#stroke)可以设置组件边框颜色。
208
209  ```ts
210  Path()
211    .width(100)
212    .height(100)
213    .fillOpacity(0)
214    .commands('M150 0 L300 300 L0 300 Z')
215    .stroke(Color.Red)
216  ```
217
218  ![stroke](figures/stroke.jpg)
219
220- 通过[strokeOpacity](../reference/apis-arkui/arkui-ts/ts-drawing-components-path.md#strokeopacity)可以设置边框透明度。
221
222  ```ts
223  Path()
224    .width(100)
225    .height(100)
226    .fillOpacity(0)
227    .commands('M150 0 L300 300 L0 300 Z')
228    .stroke(Color.Red)
229    .strokeWidth(10)
230    .strokeOpacity(0.2)
231  ```
232
233  ![strokeopacity](figures/strokeopacity.jpg)
234
235- 通过[strokeLineJoin](../reference/apis-arkui/arkui-ts/ts-drawing-components-polyline.md#strokelinejoin)可以设置线条拐角绘制样式。拐角绘制样式分为Bevel(使用斜角连接路径段)、Miter(使用尖角连接路径段)、Round(使用圆角连接路径段)。
236
237  ```ts
238  Polyline()
239    .width(100)
240    .height(100)
241    .fillOpacity(0)
242    .stroke(Color.Red)
243    .strokeWidth(8)
244    .points([[20, 0], [0, 100], [100, 90]])
245     // 设置折线拐角处为圆弧
246    .strokeLineJoin(LineJoinStyle.Round)
247  ```
248
249  ![strokeLineJoin](figures/strokeLineJoin.jpg)
250
251- 通过[strokeMiterLimit](../reference/apis-arkui/arkui-ts/ts-drawing-components-polyline.md#strokemiterlimit)设置斜接长度与边框宽度比值的极限值。
252  斜接长度表示外边框外边交点到内边交点的距离,边框宽度即[strokeWidth](../reference/apis-arkui/arkui-ts/ts-drawing-components-polyline.md#strokewidth)属性的值。strokeMiterLimit取值需大于等于1,且在[strokeLineJoin](../reference/apis-arkui/arkui-ts/ts-drawing-components-polyline.md#strokelinejoin)属性取值LineJoinStyle.Miter时生效。
253
254  ```ts
255  Polyline()
256    .width(100)
257    .height(100)
258    .fillOpacity(0)
259    .stroke(Color.Red)
260    .strokeWidth(10)
261    .points([[20, 0], [20, 100], [100, 100]])
262    // 设置折线拐角处为尖角
263    .strokeLineJoin(LineJoinStyle.Miter)
264    // 设置斜接长度与线宽的比值
265    .strokeMiterLimit(1/Math.sin(45))
266  Polyline()
267    .width(100)
268    .height(100)
269    .fillOpacity(0)
270    .stroke(Color.Red)
271    .strokeWidth(10)
272    .points([[20, 0], [20, 100], [100, 100]])
273    .strokeLineJoin(LineJoinStyle.Miter)
274    .strokeMiterLimit(1.42)
275  ```
276
277  ![2023032405917](figures/2023032405917.jpg)
278
279- 通过[antiAlias](../reference/apis-arkui/arkui-ts/ts-drawing-components-circle.md#antialias)设置是否开启抗锯齿,默认值为true(开启抗锯齿)。
280
281  ```ts
282  //开启抗锯齿
283  Circle()
284    .width(150)
285    .height(200)
286    .fillOpacity(0)
287    .strokeWidth(5)
288    .stroke(Color.Black)
289  ```
290
291  ![无标题](figures/无标题.png)
292
293  ```ts
294  //关闭抗锯齿
295  Circle()
296    .width(150)
297    .height(200)
298    .fillOpacity(0)
299    .strokeWidth(5)
300    .stroke(Color.Black)
301    .antiAlias(false)
302  ```
303
304  ![2023032411518](figures/2023032411518.jpg)
305
306- 通过[mesh](../reference/apis-arkui/arkui-ts/ts-drawing-components-shape.md#mesh8)设置网格效果,实现图像局部扭曲。
307
308```ts
309import { FrameNode, NodeController, RenderNode } from '@kit.ArkUI';
310import { image } from '@kit.ImageKit';
311import { drawing } from '@kit.ArkGraphics2D';
312
313let offCanvas: OffscreenCanvas = new OffscreenCanvas(150, 150);
314let ctx = offCanvas.getContext("2d")
315
316class DrawingRenderNode extends RenderNode {
317  verts_: Array<number> = [0, 0, 50, 0, 410, 0, 0, 180, 50, 180, 410, 180, 0, 360, 50, 360, 410, 360]
318
319  setVerts(verts: Array<number>): void {
320    this.verts_ = verts
321  }
322
323  async draw(context: DrawContext) {
324    console.log("Kee draw");
325    const canvas = context.canvas;
326    let pixelMap = ctx.getPixelMap(0, 0, 150, 150)
327    const brush = new drawing.Brush(); // 只支持brush,使用pen没有绘制效果。
328    canvas.attachBrush(brush);
329    let verts: Array<number> = [0, 0, 410, 0, 50, 0, 0, 180, 50, 180, 410, 180, 0, 360, 410, 360, 50, 360];
330    ; // 18
331    canvas.drawPixelMapMesh(pixelMap, 2, 2, verts, 0, null, 0);
332    canvas.detachBrush();
333  }
334}
335
336const renderNode = new DrawingRenderNode();
337renderNode.frame = {
338  x: 0,
339  y: 0,
340  width: 150,
341  height: 150
342};
343
344class MyNodeController extends NodeController {
345  private rootNode: FrameNode | null = null;
346
347  makeNode(uiContext: UIContext): FrameNode | null {
348    this.rootNode = new FrameNode(uiContext);
349
350    const rootRenderNode = this.rootNode.getRenderNode();
351    if (rootRenderNode !== null) {
352      rootRenderNode.appendChild(renderNode);
353    }
354    return this.rootNode;
355  }
356}
357
358@Entry
359@Component
360struct Index {
361  private myNodeController: MyNodeController = new MyNodeController();
362  @State showShape: boolean = false;
363  @State pixelMap: image.PixelMap | undefined = undefined
364  @State shapeWidth: number = 150
365  @State strokeWidth: number = 1
366  @State meshArray: Array<number> = [0, 0, 50, 0, 410, 0, 0, 180, 50, 180, 410, 180, 0, 360, 50, 360, 410, 360]
367
368  aboutToAppear(): void {
369    // "common/image/tree.png"需要替换为开发者所需的图像资源文件
370    let img: ImageBitmap = new ImageBitmap("common/image/tree.png")
371    ctx.drawImage(img, 0, 0, 100, 100)
372    this.pixelMap = ctx.getPixelMap(0, 0, 150, 150)
373  }
374
375  build() {
376    Column() {
377      Image(this.pixelMap)
378        .backgroundColor(Color.Red)
379        .width(150)
380        .height(150)
381        .onClick(() => {
382          // "common/image/foreground.png"需要替换为开发者所需的图像资源文件
383          let img: ImageBitmap = new ImageBitmap("common/image/foreground.png")
384          ctx.drawImage(img, 0, 0, 100, 100)
385          this.pixelMap = ctx.getPixelMap(1, 1, 150, 150)
386          this.myNodeController.rebuild()
387          this.strokeWidth += 1
388        })
389
390      NodeContainer(this.myNodeController)
391        .width(150)
392        .height(150)
393        .backgroundColor(Color.Grey)
394        .onClick(() => {
395          this.meshArray = [0, 0, 50, 0, 410, 0, 0, 180, 50, 180, 410, 180, 0, 360, 50, 360, 410, 360, 0]
396        })
397      Button("change mesh")
398        .margin(5)
399        .onClick(() => {
400          this.meshArray = [0, 0, 410, 0, 50, 0, 0, 180, 50, 180, 410, 180, 0, 360, 410, 360, 50, 360];
401        })
402      Button("Show Shape")
403        .margin(5)
404        .onClick(() => {
405          this.showShape = !this.showShape
406        })
407
408      if (this.showShape) {
409        Shape(this.pixelMap) {
410          Path().width(150).height(60).commands('M0 0 L400 0 L400 150 Z')
411        }
412        .fillOpacity(0.2)
413        .backgroundColor(Color.Grey)
414        .width(this.shapeWidth)
415        .height(150)
416        .mesh(this.meshArray, 2, 2)
417        .fill(0x317AF7)
418        .stroke(0xEE8443)
419        .strokeWidth(this.strokeWidth)
420        .strokeLineJoin(LineJoinStyle.Miter)
421        .strokeMiterLimit(5)
422
423        Shape(this.pixelMap) {
424          Path().width(150).height(60).commands('M0 0 L400 0 L400 150 Z')
425        }
426        .fillOpacity(0.2)
427        .backgroundColor(Color.Grey)
428        .width(this.shapeWidth)
429        .height(150)
430        .fill(0x317AF7)
431        .stroke(0xEE8443)
432        .strokeWidth(this.strokeWidth)
433        .strokeLineJoin(LineJoinStyle.Miter)
434        .strokeMiterLimit(5)
435        .onDragStart(() => {
436        })
437
438        // mesh只对shape传入pixelMap时生效,此处不生效
439        Shape() {
440          Path().width(150).height(60).commands('M0 0 L400 0 L400 150 Z')
441        }
442        .fillOpacity(0.2)
443        .backgroundColor(Color.Grey)
444        .width(this.shapeWidth)
445        .height(150)
446        .mesh(this.meshArray, 2, 2)
447        .fill(0x317AF7)
448        .stroke(0xEE8443)
449        .strokeWidth(this.strokeWidth)
450        .strokeLineJoin(LineJoinStyle.Miter)
451        .strokeMiterLimit(5)
452        .onClick(() => {
453          this.pixelMap = undefined;
454        })
455      }
456    }
457  }
458}
459
460```
461![ShapeMeshDemo](figures/ShapeMeshDemo.png)
462
463## 场景示例
464
465### 绘制封闭路径
466
467  在Shape的(-80, -5)点绘制一个封闭路径,填充颜色0x317AF7,线条宽度3,边框颜色红色,拐角样式锐角(默认值)。
468
469  ```ts
470  @Entry
471  @Component
472  struct ShapeExample {
473    build() {
474      Column({ space: 10 }) {
475        Shape() {
476          Path().width(200).height(60).commands('M0 0 L400 0 L400 150 Z')
477        }
478        .viewPort({ x: -80, y: -5, width: 500, height: 300 })
479        .fill(0x317AF7)
480        .stroke(Color.Red)
481        .strokeWidth(3)
482        .strokeLineJoin(LineJoinStyle.Miter)
483        .strokeMiterLimit(5)
484      }.width('100%').margin({ top: 15 })
485    }
486  }
487  ```
488
489  ![场景1](figures/场景1.jpg)
490
491### 绘制圆和圆环
492
493  绘制一个直径为150的圆,和一个直径为150、线条为红色虚线的圆环(宽高设置不一致时以短边为直径)。
494
495  ```ts
496  @Entry
497  @Component
498  struct CircleExample {
499    build() {
500      Column({ space: 10 }) {
501        //绘制一个直径为150的圆
502        Circle({ width: 150, height: 150 })
503        //绘制一个直径为150、线条为红色虚线的圆环
504        Circle()
505          .width(150)
506          .height(200)
507          .fillOpacity(0)
508          .strokeWidth(3)
509          .stroke(Color.Red)
510          .strokeDashArray([1, 2])
511      }.width('100%')
512    }
513  }
514  ```
515
516  ![场景2](figures/场景2.jpg)
517
518### UI视觉属性作用效果
519
520>  **说明:**
521>
522> [backgroundColor](../reference/apis-arkui/arkui-ts/ts-universal-attributes-background.md)、[linearGradient](../reference/apis-arkui/arkui-ts/ts-universal-attributes-gradient-color.md)等通用属性作用于组件的背景区域,而不会在组件具体的内容区域生效。
523
524
525  ```ts
526  @Entry
527  @Component
528  struct CircleExample {
529    build() {
530      Column({ space: 10 }) {
531        //绘制一个直径为150的圆
532        Circle()
533          .width(150)
534          .height(200)
535          .backgroundColor(Color.Pink) // 会生效在一个150*200大小的矩形区域,而非仅在绘制的一个直径为150的圆形区域
536      }.width('100%')
537    }
538  }
539  ```
540  ![场景3](figures/VirtualEffect.jpg)
541