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  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  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 .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 .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  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  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  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  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  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  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  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 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  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  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  541