
小试状态管理V1->V2迁移 原创
HarmonyOS自API12版本后,推出了全新的状态管理V2。V2是V1的增强版本,为我们带来了更多功能和灵活性。对于新开发的应用,建议直接使用V2版本范式来进行开发。对于已经使用V1的应用,如果V1的功能和性能已能满足需求,则不必立即切换到V2。如果开发者在开发过程中受限于V1不能深度观察等特性,则建议开发者尽早规划向V2的迁移,以便未来实现平滑过渡和改进。
目前,华为开发者联盟官网给出的案例,大多都是应用V1版来编码实现,如《基于Canvas实现画布的功能》这个案例,gitee地址为:https://gitee.com/harmonyos_samples/custom-canvas。效果图预览如下:
现在,我们尝试将这个案例的状态管理升级到V2版。整个升级只涉及画布主页面和自定义底部设置面板子组件。
1. 画布主页面
V1版本全部代码如下:
import { display } from '@kit.ArkUI';
import DrawInvoker from '../viewmodel/DrawInvoker';
import DrawPath from '../viewmodel/IDraw';
import { IBrush } from '../viewmodel/IBrush';
import NormalBrush from '../viewmodel/IBrush';
import Paint from '../viewmodel/Paint';
import { CommonConstants } from '../common/CommonConstants';
import { myPaintSheet } from '../view/myPaintSheet';
@Entry
@Component
struct DrawCanvas {
@State @Watch('createDraw') isDrawing: boolean = false;
@State unDoDraw: boolean = false;
@State redoDraw: boolean = false;
@State isPaint: boolean = true;
@State isShow: boolean = false;
@State isMarker: boolean = false;
@State scaleValueX: number = 1;
@State scaleValueY: number = 1;
@State pinchValueX: number = 1;
@State pinchValueY: number = 1;
@State strokeWidth: number = 3;
@State alpha: number = 1;
@State color: string = '#000000';
@State thicknessesValue: number = 3;
@State index: number = -1;
@State clean: boolean = false;
@State percent: string = '100';
@Provide mPaint: Paint = new Paint(0, '', 1);
@Provide mBrush: IBrush = new NormalBrush();
private setting: RenderingContextSettings = new RenderingContextSettings(true);
private context: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.setting);
private drawInvoker: DrawInvoker = new DrawInvoker();
private path2Db: Path2D = new Path2D();
private mPath: DrawPath = new DrawPath(this.mPaint, this.path2Db);
private arr: number[] = [];
aboutToAppear(): void {
this.mPaint = new Paint(CommonConstants.ZERO, CommonConstants.COLOR_STRING, CommonConstants.ONE);
this.mPaint.setStrokeWidth(CommonConstants.THREE);
this.mPaint.setColor(CommonConstants.BLACK);
this.mPaint.setGlobalAlpha(CommonConstants.ONE);
this.mBrush = new NormalBrush();
display.on('foldStatusChange', (data: display.FoldStatus) => {
if (data === 2) {
this.scaleValueX = 0.5;
this.pinchValueX = 0.5;
this.scaleValueY = 1;
this.pinchValueY = 1;
this.context.clearRect(0, 0, this.context.width, this.context.height);
this.drawInvoker.execute(this.context);
} else if (data === 1) {
this.scaleValueX = 1;
this.scaleValueY = 1;
this.pinchValueX = 1;
this.pinchValueY = 1;
this.context.clearRect(0, 0, this.context.width, this.context.height);
this.drawInvoker.execute(this.context);
}
})
}
createDraw() {
if (this.isDrawing) {
this.context.fillStyle = Color.White;
this.context.fillRect(CommonConstants.ZERO, CommonConstants.ZERO, this.context.width, this.context.height);
this.drawInvoker.execute(this.context);
this.isDrawing = false;
}
}
/**
* Add a sketch path.
*/
add(path: DrawPath): void {
this.drawInvoker.add(path);
}
/**
* Toggle weight, color, transparency.
*/
ToggleThicknessColor(): void {
this.mPaint = new Paint(CommonConstants.ZERO, CommonConstants.COLOR_STRING, CommonConstants.ONE);
this.mPaint.setStrokeWidth(this.strokeWidth);
this.mPaint.setColor(this.color);
this.mPaint.setGlobalAlpha(this.alpha);
this.mBrush = new NormalBrush();
}
/**
* Undo operation.
*/
drawOperateUndo(): void {
this.drawInvoker.undo();
this.isDrawing = true;
if (!this.drawInvoker.canUndo()) {
this.unDoDraw = false;
}
this.redoDraw = true;
}
/**
* Redo operation.
*/
drawOperateRedo(): void {
this.drawInvoker.redo();
this.isDrawing = true;
if (!this.drawInvoker.canRedo()) {
this.redoDraw = false;
}
this.unDoDraw = true;
}
/**
* Clear operation.
*/
clear(): void {
this.drawInvoker.clear();
this.isDrawing = true;
this.redoDraw = false;
this.unDoDraw = false;
}
@Builder
myPaintSheet() {
Column() {
myPaintSheet({
isMarker: this.isMarker,
alpha: this.alpha,
percent: this.percent,
color: this.color,
thicknessesValue: this.thicknessesValue,
strokeWidth: this.strokeWidth
})
}
}
build() {
Stack({ alignContent: Alignment.Bottom }) {
Canvas(this.context)
.width(CommonConstants.CANVAS_WIDTH)
.height(CommonConstants.CANVAS_WIDTH)
.backgroundColor($r('sys.color.white'))
.onTouch((event: TouchEvent) => {
this.clean = false;
if (this.index === 1 || event.touches.length > 1) {
return;
}
this.arr.push(event.touches[0].x + event.touches[0].y);
if (event.touches.length === 1 && event.touches[0].id === 0 && event.type === TouchType.Down) {
this.mPath = new DrawPath(this.mPaint, this.path2Db);
this.mPath.paint = this.mPaint;
this.mPath.path = new Path2D();
this.mBrush.down(this.mPath.path, event.touches[0].x, event.touches[0].y);
}
if (event.touches.length === 1 && event.touches[0].id === 0 && event.type === TouchType.Move) {
this.mBrush.move(this.mPath.path, event.touches[0].x, event.touches[0].y);
this.context.clearRect(0, 0, this.context.width, this.context.height);
this.drawInvoker.execute(this.context);
if (this.arr.length > 4) {
this.mPath.draw(this.context);
}
}
if (event.touches.length === 1 && event.touches[0].id === 0 && event.type === TouchType.Up) {
this.add(this.mPath);
this.arr = [];
this.redoDraw = false;
this.unDoDraw = true;
this.isDrawing = true;
this.context.clearRect(0, 0, this.context.width, this.context.height);
this.drawInvoker.execute(this.context);
}
})
.scale({
x: this.scaleValueX,
y: this.scaleValueY,
z: CommonConstants.ONE
})
.gesture(
PinchGesture()
.onActionStart(() => {
this.index = 1;
})
.onActionUpdate((event: GestureEvent) => {
this.context.clearRect(0, 0, this.context.width, this.context.height);
this.drawInvoker.execute(this.context);
if (event) {
this.scaleValueX = this.pinchValueX * event.scale;
this.scaleValueY = this.pinchValueY * event.scale;
}
})
.onActionEnd(() => {
this.pinchValueX = this.scaleValueX;
this.pinchValueY = this.scaleValueY;
this.context.clearRect(0, 0, this.context.width, this.context.height);
this.drawInvoker.execute(this.context);
})
)
Column()
.width(CommonConstants.ONE_HUNDRED_PERCENT)
.height(CommonConstants.ONE_HUNDRED_PERCENT)
.backgroundColor(Color.Transparent)
.zIndex(this.index)
.gesture(
PinchGesture()
.onActionStart(() => {
this.index = 1;
})
.onActionUpdate((event: GestureEvent) => {
this.context.clearRect(0, 0, this.context.width, this.context.height);
this.drawInvoker.execute(this.context);
if (event) {
this.scaleValueX = this.pinchValueX * event.scale;
this.scaleValueY = this.pinchValueY * event.scale;
}
})
.onActionEnd(() => {
this.context.clearRect(0, 0, this.context.width, this.context.height);
this.drawInvoker.execute(this.context);
this.pinchValueX = this.scaleValueX;
this.pinchValueY = this.scaleValueY;
})
)
Row() {
Stack() {
Column() {
Image(this.isPaint && this.index === CommonConstants.NEGATIVE_ONE ? $r('app.media.paintbrush_active') :
$r('app.media.paintbrush'))
.width($r('app.float.image_width'))
.height($r('app.float.image_width'))
.margin({ bottom: $r('app.float.bottom') })
Text($r('app.string.paint'))
.fontSize($r('app.float.font_size_m'))
.fontColor(this.isPaint && this.index === CommonConstants.NEGATIVE_ONE ? $r('app.color.theme_color') :
$r('sys.color.mask_secondary'))
}
.width(CommonConstants.ONE_HUNDRED_PERCENT)
.height(CommonConstants.ONE_HUNDRED_PERCENT)
Button({ type: ButtonType.Normal })
.backgroundColor(Color.Transparent)
.width(CommonConstants.ONE_HUNDRED_PERCENT)
.height(CommonConstants.ONE_HUNDRED_PERCENT)
.onClick(() => {
this.ToggleThicknessColor();
this.isPaint = true;
this.isShow = !this.isShow;
this.index = -1;
this.arr = [];
})
}
.bindSheet($$this.isShow, this.myPaintSheet(), {
height: $r('app.float.height'),
backgroundColor: Color.White,
title: {
title: $r('app.string.paint')
},
detents: CommonConstants.DETENTS
})
.width($r('app.float.paint_width'))
.height($r('app.float.paint_height'))
Stack() {
Column() {
Image(this.isPaint || this.index === CommonConstants.ONE ? $r('app.media.rubbers') :
$r('app.media.rubbers_active'))
.width($r('app.float.image_width'))
.height($r('app.float.image_width'))
.margin({ bottom: $r('app.float.bottom') })
Text($r('app.string.rubber'))
.fontSize($r('app.float.font_size_m'))
.fontColor(this.isPaint || this.index === CommonConstants.ONE ? $r('sys.color.mask_secondary') :
$r('app.color.theme_color'))
}
.width(CommonConstants.ONE_HUNDRED_PERCENT)
.height(CommonConstants.ONE_HUNDRED_PERCENT)
Button({ type: ButtonType.Normal })
.backgroundColor(Color.Transparent)
.width(CommonConstants.ONE_HUNDRED_PERCENT)
.height(CommonConstants.ONE_HUNDRED_PERCENT)
.onClick(() => {
this.mPaint = new Paint(CommonConstants.ZERO, CommonConstants.COLOR_STRING, CommonConstants.ONE);
this.mPaint.setStrokeWidth(CommonConstants.TEN);
this.mPaint.setColor(CommonConstants.WHITE);
this.mPaint.setGlobalAlpha(CommonConstants.ONE);
this.isPaint = false;
})
}
.width($r('app.float.paint_width'))
.height($r('app.float.paint_height'))
Stack() {
Column() {
Image(this.unDoDraw ? $r('app.media.recall_active') : $r('app.media.recall'))
.width($r('app.float.image_width'))
.height($r('app.float.image_width'))
.margin({ bottom: $r('app.float.bottom') })
Text($r('app.string.redo'))
.fontSize($r('app.float.font_size_m'))
.fontColor(this.unDoDraw ? $r('app.color.theme_color') : $r('sys.color.mask_secondary'))
}
.width(CommonConstants.ONE_HUNDRED_PERCENT)
.height(CommonConstants.ONE_HUNDRED_PERCENT)
Button({ type: ButtonType.Normal })
.backgroundColor(Color.Transparent)
.enabled(this.unDoDraw)
.width(CommonConstants.ONE_HUNDRED_PERCENT)
.height(CommonConstants.ONE_HUNDRED_PERCENT)
.onClick(async () => {
this.drawOperateUndo();
this.context.clearRect(0, 0, this.context.width, this.context.height);
this.drawInvoker.execute(this.context);
})
}
.width($r('app.float.paint_width'))
.height($r('app.float.paint_height'))
Stack() {
Column() {
Image(this.redoDraw ? $r('app.media.redo_active') : $r('app.media.redo'))
.width($r('app.float.image_width'))
.height($r('app.float.image_width'))
.margin({ bottom: $r('app.float.bottom') })
Text($r('app.string.undo'))
.fontSize($r('app.float.font_size_m'))
.fontColor(this.redoDraw ? $r('app.color.theme_color') : $r('sys.color.mask_secondary'))
}
.width(CommonConstants.ONE_HUNDRED_PERCENT)
.height(CommonConstants.ONE_HUNDRED_PERCENT)
Button({ type: ButtonType.Normal })
.backgroundColor(Color.Transparent)
.enabled(this.redoDraw)
.width(CommonConstants.ONE_HUNDRED_PERCENT)
.height(CommonConstants.ONE_HUNDRED_PERCENT)
.onClick(async () => {
this.drawOperateRedo();
this.context.clearRect(0, 0, this.context.width, this.context.height);
this.drawInvoker.execute(this.context);
})
}
.width($r('app.float.paint_width'))
.height($r('app.float.paint_height'))
Stack() {
Column() {
Image(this.clean ? $r('app.media.clear_active') : $r('app.media.clear'))
.width($r('app.float.image_width'))
.height($r('app.float.image_width'))
.margin({ bottom: $r('app.float.bottom') })
Text($r('app.string.clear'))
.fontSize($r('app.float.font_size_m'))
.fontColor(this.clean ? $r('app.color.theme_color') : $r('sys.color.mask_secondary'))
}
.width(CommonConstants.ONE_HUNDRED_PERCENT)
.height(CommonConstants.ONE_HUNDRED_PERCENT)
Button({ type: ButtonType.Normal })
.backgroundColor(Color.Transparent)
.width(CommonConstants.ONE_HUNDRED_PERCENT)
.height(CommonConstants.ONE_HUNDRED_PERCENT)
.onClick(async () => {
this.clear();
this.context.clearRect(0, 0, this.context.width, this.context.height);
this.drawInvoker.execute(this.context);
this.clean = true;
})
}
.width($r('app.float.paint_width'))
.height($r('app.float.paint_height'))
}
.justifyContent(FlexAlign.SpaceBetween)
.alignItems(VerticalAlign.Center)
.zIndex(CommonConstants.TEN)
}
.backgroundColor($r('sys.color.comp_background_focus'))
.width(CommonConstants.ONE_HUNDRED_PERCENT)
.height(CommonConstants.ONE_HUNDRED_PERCENT)
}
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
- 33.
- 34.
- 35.
- 36.
- 37.
- 38.
- 39.
- 40.
- 41.
- 42.
- 43.
- 44.
- 45.
- 46.
- 47.
- 48.
- 49.
- 50.
- 51.
- 52.
- 53.
- 54.
- 55.
- 56.
- 57.
- 58.
- 59.
- 60.
- 61.
- 62.
- 63.
- 64.
- 65.
- 66.
- 67.
- 68.
- 69.
- 70.
- 71.
- 72.
- 73.
- 74.
- 75.
- 76.
- 77.
- 78.
- 79.
- 80.
- 81.
- 82.
- 83.
- 84.
- 85.
- 86.
- 87.
- 88.
- 89.
- 90.
- 91.
- 92.
- 93.
- 94.
- 95.
- 96.
- 97.
- 98.
- 99.
- 100.
- 101.
- 102.
- 103.
- 104.
- 105.
- 106.
- 107.
- 108.
- 109.
- 110.
- 111.
- 112.
- 113.
- 114.
- 115.
- 116.
- 117.
- 118.
- 119.
- 120.
- 121.
- 122.
- 123.
- 124.
- 125.
- 126.
- 127.
- 128.
- 129.
- 130.
- 131.
- 132.
- 133.
- 134.
- 135.
- 136.
- 137.
- 138.
- 139.
- 140.
- 141.
- 142.
- 143.
- 144.
- 145.
- 146.
- 147.
- 148.
- 149.
- 150.
- 151.
- 152.
- 153.
- 154.
- 155.
- 156.
- 157.
- 158.
- 159.
- 160.
- 161.
- 162.
- 163.
- 164.
- 165.
- 166.
- 167.
- 168.
- 169.
- 170.
- 171.
- 172.
- 173.
- 174.
- 175.
- 176.
- 177.
- 178.
- 179.
- 180.
- 181.
- 182.
- 183.
- 184.
- 185.
- 186.
- 187.
- 188.
- 189.
- 190.
- 191.
- 192.
- 193.
- 194.
- 195.
- 196.
- 197.
- 198.
- 199.
- 200.
- 201.
- 202.
- 203.
- 204.
- 205.
- 206.
- 207.
- 208.
- 209.
- 210.
- 211.
- 212.
- 213.
- 214.
- 215.
- 216.
- 217.
- 218.
- 219.
- 220.
- 221.
- 222.
- 223.
- 224.
- 225.
- 226.
- 227.
- 228.
- 229.
- 230.
- 231.
- 232.
- 233.
- 234.
- 235.
- 236.
- 237.
- 238.
- 239.
- 240.
- 241.
- 242.
- 243.
- 244.
- 245.
- 246.
- 247.
- 248.
- 249.
- 250.
- 251.
- 252.
- 253.
- 254.
- 255.
- 256.
- 257.
- 258.
- 259.
- 260.
- 261.
- 262.
- 263.
- 264.
- 265.
- 266.
- 267.
- 268.
- 269.
- 270.
- 271.
- 272.
- 273.
- 274.
- 275.
- 276.
- 277.
- 278.
- 279.
- 280.
- 281.
- 282.
- 283.
- 284.
- 285.
- 286.
- 287.
- 288.
- 289.
- 290.
- 291.
- 292.
- 293.
- 294.
- 295.
- 296.
- 297.
- 298.
- 299.
- 300.
- 301.
- 302.
- 303.
- 304.
- 305.
- 306.
- 307.
- 308.
- 309.
- 310.
- 311.
- 312.
- 313.
- 314.
- 315.
- 316.
- 317.
- 318.
- 319.
- 320.
- 321.
- 322.
- 323.
- 324.
- 325.
- 326.
- 327.
- 328.
- 329.
- 330.
- 331.
- 332.
- 333.
- 334.
- 335.
- 336.
- 337.
- 338.
- 339.
- 340.
- 341.
- 342.
- 343.
- 344.
- 345.
- 346.
- 347.
- 348.
- 349.
- 350.
- 351.
- 352.
- 353.
- 354.
- 355.
- 356.
- 357.
- 358.
- 359.
- 360.
- 361.
- 362.
- 363.
- 364.
- 365.
- 366.
- 367.
- 368.
- 369.
- 370.
- 371.
- 372.
- 373.
- 374.
- 375.
- 376.
- 377.
- 378.
- 379.
- 380.
- 381.
- 382.
- 383.
- 384.
1.1. 自定义组件@Component装饰器迁移
- V1版
@Entry
@Component
struct DrawCanvas {
// ...
}
- 1.
- 2.
- 3.
- 4.
- 5.
- V2版
直接将@Component修改为@ComponentV2即可。
@Entry
@ComponentV2
struct DrawCanvas {
// ...
}
- 1.
- 2.
- 3.
- 4.
- 5.
1.2. @State迁移
- V1版
@State @Watch('createDraw') isDrawing: boolean = false;
@State unDoDraw: boolean = false;
@State redoDraw: boolean = false;
@State isPaint: boolean = true;
@State isShow: boolean = false;
@State isMarker: boolean = false;
@State scaleValueX: number = 1;
@State scaleValueY: number = 1;
@State pinchValueX: number = 1;
@State pinchValueY: number = 1;
@State strokeWidth: number = 3;
@State alpha: number = 1;
@State color: string = '#000000';
@State thicknessesValue: number = 3;
@State index: number = -1;
@State clean: boolean = false;
@State percent: string = '100';
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- V2版
直接将@State修改为@Local即可。
@Local @Watch('createDraw') isDrawing: boolean = false;
@Local unDoDraw: boolean = false;
@Local redoDraw: boolean = false;
@Local isPaint: boolean = true;
@Local isShow: boolean = false;
@Local isMarker: boolean = false;
@Local scaleValueX: number = 1;
@Local scaleValueY: number = 1;
@Local pinchValueX: number = 1;
@Local pinchValueY: number = 1;
@Local strokeWidth: number = 3;
@Local alpha: number = 1;
@Local color: string = '#000000';
@Local thicknessesValue: number = 3;
@Local index: number = -1;
@Local clean: boolean = false;
@Local percent: string = '100';
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
1.3. @Watch迁移
- V1版
@State @Watch('createDraw') isDrawing: boolean = false;
createDraw() {
if (this.isDrawing) {
this.context.fillStyle = Color.White;
this.context.fillRect(CommonConstants.ZERO, CommonConstants.ZERO, this.context.width, this.context.height);
this.drawInvoker.execute(this.context);
this.isDrawing = false;
}
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- V2版
V2版采用@Monitor来代替@Watch。
@State isDrawing: boolean = false;
@Monitor('isDrawing')
createDraw() {
if (this.isDrawing) {
this.context.fillStyle = Color.White
this.context.fillRect(CommonConstants.ZERO, CommonConstants.ZERO, this.context.width, this.context.height)
this.drawInvoker.execute(this.context)
this.isDrawing = false
}
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
1.4. @Provide迁移
- V1版
@Provide mPaint: Paint = new Paint(0, '', 1);
@Provide mBrush: IBrush = new NormalBrush();
- 1.
- 2.
- V2版
V2版采用@Provider来代替@Provide。注意@Provider可以不用定义别名(alias),通过变量名来供@Consumer消费,但小括号必须保留。
@Provider() mPaint: Paint = new Paint(0, '', 1);
@Provider() mBrush: IBrush = new NormalBrush();
- 1.
- 2.
1.5. 子组件传参迁移
- V1版
@Builder
myPaintSheet() {
Column() {
myPaintSheet({
isMarker: this.isMarker,
alpha: this.alpha,
percent: this.percent,
color: this.color,
thicknessesValue: this.thicknessesValue,
strokeWidth: this.strokeWidth
})
}
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- V2版
myPaintSheet是子组件,给它传递的全部参数,都会在子组件中修改,并同步给父组件。在V1中,在子组件里,只需要使用@Link装饰器即可,在V2版里,我们得需要自己来实现@Link的功能,这里可以采用双叹号(!!)语法来实现迁移,子组件需要应用@Event,$变量名来配合。
@Builder
myPaintSheet() {
Column() {
myPaintSheet({
isMarker: this.isMarker!!,
alpha: this.alpha!!,
percent: this.percent!!,
color: this.color!!,
thicknessesValue: this.thicknessesValue!!,
strokeWidth: this.strokeWidth!!
})
}
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
2. 自定义底部设置面板组件
V1版全部代码如下:
import { CommonConstants } from '../common/CommonConstants';
import { IBrush } from '../viewmodel/IBrush';
import NormalBrush from '../viewmodel/IBrush';
import Paint from '../viewmodel/Paint';
@Component
export struct myPaintSheet {
@Link isMarker: boolean;
@Link alpha: number;
@Link percent: string;
@Link color: string;
@Link thicknessesValue: number;
@Link strokeWidth: number;
@Consume mPaint: Paint;
@Consume mBrush: IBrush;
ToggleThicknessColor() {
this.mPaint = new Paint(CommonConstants.ZERO, CommonConstants.COLOR_STRING, CommonConstants.ONE);
this.mPaint.setStrokeWidth(this.strokeWidth);
this.mPaint.setColor(this.color);
this.mPaint.setGlobalAlpha(this.alpha);
this.mBrush = new NormalBrush();
}
build() {
Column() {
Column() {
Text($r('app.string.brash'))
.textAlign(TextAlign.Start)
.fontSize($r('app.float.font_size'))
.fontColor($r('sys.color.mask_secondary'))
.margin({ bottom: $r('app.float.margin_bottom') })
Row() {
Column() {
Stack() {
Text()
.width($r('app.float.back_width'))
.height($r('app.float.back_width'))
.backgroundColor(this.isMarker ? $r('app.color.paint_color') : $r('app.color.theme_color'))
.borderRadius($r('app.float.border_radius'))
Image(this.isMarker ? $r('app.media.Ballpoint') : $r('app.media.Ballpoint_active'))
.width($r('app.float.image_width'))
.height($r('app.float.image_width'))
Button({ type: ButtonType.Normal })
.width($r('app.float.back_width'))
.height($r('app.float.back_width'))
.borderRadius($r('app.float.border_radius'))
.backgroundColor(Color.Transparent)
.onClick(() => {
this.isMarker = false;
this.alpha = 1;
this.percent = '100';
this.ToggleThicknessColor();
})
}
Text($r('app.string.ballpoint'))
.fontSize($r('app.float.font_size'))
.fontColor(this.isMarker ? $r('sys.color.mask_secondary') : $r('app.color.theme_color'))
.margin({ top: $r('app.float.margin_top') })
}
.width($r('app.float.brash_width'))
.height($r('app.float.brash_height'))
Column() {
Stack() {
Text()
.width($r('app.float.back_width'))
.height($r('app.float.back_width'))
.backgroundColor(this.isMarker ? $r('app.color.theme_color') : $r('app.color.paint_color'))
.borderRadius($r('app.float.border_radius'))
Image(this.isMarker ? $r('app.media.marker_active') : $r('app.media.marker'))
.width($r('app.float.image_width'))
.height($r('app.float.image_width'))
Button({ type: ButtonType.Normal })
.width($r('app.float.back_width'))
.height($r('app.float.back_width'))
.borderRadius($r('app.float.border_radius'))
.backgroundColor(Color.Transparent)
.onClick(() => {
this.isMarker = true;
this.alpha = 0.5;
this.percent = '50';
this.ToggleThicknessColor();
})
}
Text($r('app.string.marker'))
.fontSize($r('app.float.font_size'))
.fontColor(this.isMarker ? $r('app.color.theme_color') : $r('sys.color.mask_secondary'))
.margin({ top: $r('app.float.margin_top') })
}
.width($r('app.float.brash_width'))
.height($r('app.float.brash_height'))
Column() {
Stack() {
Text()
.width($r('app.float.back_width'))
.height($r('app.float.back_width'))
.backgroundColor($r('app.color.paint_color'))
.borderRadius($r('app.float.border_radius'))
Image($r('app.media.pencils'))
.width($r('app.float.image_width'))
.height($r('app.float.image_width'))
}
Text($r('app.string.pencil'))
.fontSize($r('app.float.font_size'))
.fontColor($r('sys.color.mask_secondary'))
.margin({ top: $r('app.float.margin_top') })
}
.width($r('app.float.brash_width'))
.height($r('app.float.brash_height'))
Column() {
Stack() {
Text()
.width($r('app.float.back_width'))
.height($r('app.float.back_width'))
.backgroundColor($r('app.color.paint_color'))
.borderRadius($r('app.float.border_radius'))
Image($r('app.media.fountain'))
.width($r('app.float.image_width'))
.height($r('app.float.image_width'))
}
Text($r('app.string.fountain_pen'))
.fontSize($r('app.float.font_size'))
.fontColor($r('sys.color.mask_secondary'))
.margin({ top: $r('app.float.margin_top') })
}
.width($r('app.float.brash_width'))
.height($r('app.float.brash_height'))
Column() {
Stack() {
Text()
.width($r('app.float.back_width'))
.height($r('app.float.back_width'))
.backgroundColor($r('app.color.paint_color'))
.borderRadius($r('app.float.border_radius'))
Image($r('app.media.laser'))
.width($r('app.float.image_width'))
.height($r('app.float.image_width'))
}
Text($r('app.string.laser_pointer'))
.fontSize($r('app.float.font_size'))
.fontColor($r('sys.color.mask_secondary'))
.margin({ top: $r('app.float.margin_top') })
}
.width($r('app.float.brash_width'))
.height($r('app.float.brash_height'))
}
.padding({
left: $r('app.float.margin_top'),
right: $r('app.float.margin_top')
})
.width(CommonConstants.ONE_HUNDRED_PERCENT)
.justifyContent(FlexAlign.SpaceBetween)
}
.padding({
left: $r('app.float.padding_left'),
right: $r('app.float.padding_left'),
top: $r('app.float.margin_bottom')
})
.alignItems(HorizontalAlign.Start)
.width(CommonConstants.ONE_HUNDRED_PERCENT)
.margin({ bottom: $r('app.float.title_bottom') })
Column() {
Text($r('app.string.color'))
.textAlign(TextAlign.Start)
.fontSize($r('app.float.font_size'))
.fontColor($r('sys.color.mask_secondary'))
.margin({ bottom: $r('app.float.margin_bottom') })
Row() {
ForEach(CommonConstants.COLOR_ARR, (item: string) => {
Text()
.width($r('app.float.image_width'))
.height($r('app.float.image_width'))
.borderRadius($r('app.float.border_radius_m'))
.backgroundColor(item)
.onClick(() => {
this.color = item;
this.ToggleThicknessColor();
})
}, (item: string) => JSON.stringify(item))
}
.padding({
left: $r('app.float.margin_top'),
right: $r('app.float.margin_top')
})
.justifyContent(FlexAlign.SpaceBetween)
.width(CommonConstants.ONE_HUNDRED_PERCENT)
}
.padding({
left: $r('app.float.padding_left'),
right: $r('app.float.padding_left')
})
.alignItems(HorizontalAlign.Start)
.width(CommonConstants.ONE_HUNDRED_PERCENT)
.margin({ bottom: $r('app.float.margin_bottom') })
Column() {
Text($r('app.string.opacity'))
.textAlign(TextAlign.Start)
.fontSize($r('app.float.font_size'))
.fontColor($r('sys.color.mask_secondary'))
.margin({ bottom: $r('app.float.margin_top') })
Row() {
Stack() {
Slider({
style: SliderStyle.InSet,
value: this.alpha * CommonConstants.ONE_HUNDRED
})
.height($r('app.float.brash_width'))
.width($r('app.float.slider_width'))
.selectedColor(Color.Transparent)
.minResponsiveDistance(CommonConstants.ONE)
.trackColor(new LinearGradient([
{ color: $r('app.color.linear_start'), offset: CommonConstants.ZERO },
{ color: $r('app.color.linear_end'), offset: CommonConstants.ONE }
]))
.onChange((value: number) => {
if (this.isMarker) {
this.alpha = value / 100;
this.percent = value.toFixed(0);
this.ToggleThicknessColor();
}
})
if (!this.isMarker) {
Row()
.backgroundColor(Color.Transparent)
.width($r('app.float.slider_width'))
.height($r('app.float.brash_width'))
}
}
Text(this.percent + CommonConstants.SIGN)
.width($r('app.float.number'))
.height($r('app.float.image_width'))
.fontSize($r('app.float.font_size_l'))
.borderRadius($r('app.float.border_radius_m'))
.textAlign(TextAlign.Center)
.backgroundColor($r('app.color.number_color'))
}
.padding({
left: $r('app.float.margin_top'),
right: $r('app.float.margin_top')
})
.justifyContent(FlexAlign.SpaceBetween)
.width(CommonConstants.ONE_HUNDRED_PERCENT)
}
.padding({
left: $r('app.float.padding_left'),
right: $r('app.float.padding_left')
})
.alignItems(HorizontalAlign.Start)
.width(CommonConstants.ONE_HUNDRED_PERCENT)
.margin({ bottom: $r('app.float.margin_bottom') })
Column() {
Text($r('app.string.thicknesses'))
.textAlign(TextAlign.Start)
.fontSize($r('app.float.font_size'))
.fontColor($r('sys.color.mask_secondary'))
.margin({ bottom: $r('app.float.margin_bottom') })
Row() {
Image($r('app.media.minuses'))
.width($r('app.float.image_width'))
.height($r('app.float.image_width'))
.onClick(() => {
this.thicknessesValue -= 1;
this.strokeWidth = this.thicknessesValue;
this.ToggleThicknessColor();
})
Slider({
value: this.thicknessesValue,
min: CommonConstants.THREE,
max: CommonConstants.TWENTY_ONE
})
.width($r('app.float.slider_width'))
.minResponsiveDistance(CommonConstants.ONE)
.onChange((value: number, _mode: SliderChangeMode) => {
this.thicknessesValue = value;
this.strokeWidth = value;
this.ToggleThicknessColor();
})
Image($r('app.media.add'))
.width($r('app.float.image_width'))
.height($r('app.float.image_width'))
.onClick(() => {
this.thicknessesValue += 1;
this.strokeWidth = this.thicknessesValue;
this.ToggleThicknessColor();
})
}
.padding({
left: $r('app.float.margin_bottom'),
right: $r('app.float.margin_bottom')
})
.justifyContent(FlexAlign.SpaceBetween)
.width(CommonConstants.ONE_HUNDRED_PERCENT)
}
.padding({
left: $r('app.float.padding_left'),
right: $r('app.float.padding_left')
})
.alignItems(HorizontalAlign.Start)
.width(CommonConstants.ONE_HUNDRED_PERCENT)
}
.width(CommonConstants.ONE_HUNDRED_PERCENT)
.height(CommonConstants.ONE_HUNDRED_PERCENT)
}
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
- 33.
- 34.
- 35.
- 36.
- 37.
- 38.
- 39.
- 40.
- 41.
- 42.
- 43.
- 44.
- 45.
- 46.
- 47.
- 48.
- 49.
- 50.
- 51.
- 52.
- 53.
- 54.
- 55.
- 56.
- 57.
- 58.
- 59.
- 60.
- 61.
- 62.
- 63.
- 64.
- 65.
- 66.
- 67.
- 68.
- 69.
- 70.
- 71.
- 72.
- 73.
- 74.
- 75.
- 76.
- 77.
- 78.
- 79.
- 80.
- 81.
- 82.
- 83.
- 84.
- 85.
- 86.
- 87.
- 88.
- 89.
- 90.
- 91.
- 92.
- 93.
- 94.
- 95.
- 96.
- 97.
- 98.
- 99.
- 100.
- 101.
- 102.
- 103.
- 104.
- 105.
- 106.
- 107.
- 108.
- 109.
- 110.
- 111.
- 112.
- 113.
- 114.
- 115.
- 116.
- 117.
- 118.
- 119.
- 120.
- 121.
- 122.
- 123.
- 124.
- 125.
- 126.
- 127.
- 128.
- 129.
- 130.
- 131.
- 132.
- 133.
- 134.
- 135.
- 136.
- 137.
- 138.
- 139.
- 140.
- 141.
- 142.
- 143.
- 144.
- 145.
- 146.
- 147.
- 148.
- 149.
- 150.
- 151.
- 152.
- 153.
- 154.
- 155.
- 156.
- 157.
- 158.
- 159.
- 160.
- 161.
- 162.
- 163.
- 164.
- 165.
- 166.
- 167.
- 168.
- 169.
- 170.
- 171.
- 172.
- 173.
- 174.
- 175.
- 176.
- 177.
- 178.
- 179.
- 180.
- 181.
- 182.
- 183.
- 184.
- 185.
- 186.
- 187.
- 188.
- 189.
- 190.
- 191.
- 192.
- 193.
- 194.
- 195.
- 196.
- 197.
- 198.
- 199.
- 200.
- 201.
- 202.
- 203.
- 204.
- 205.
- 206.
- 207.
- 208.
- 209.
- 210.
- 211.
- 212.
- 213.
- 214.
- 215.
- 216.
- 217.
- 218.
- 219.
- 220.
- 221.
- 222.
- 223.
- 224.
- 225.
- 226.
- 227.
- 228.
- 229.
- 230.
- 231.
- 232.
- 233.
- 234.
- 235.
- 236.
- 237.
- 238.
- 239.
- 240.
- 241.
- 242.
- 243.
- 244.
- 245.
- 246.
- 247.
- 248.
- 249.
- 250.
- 251.
- 252.
- 253.
- 254.
- 255.
- 256.
- 257.
- 258.
- 259.
- 260.
- 261.
- 262.
- 263.
- 264.
- 265.
- 266.
- 267.
- 268.
- 269.
- 270.
- 271.
- 272.
- 273.
- 274.
- 275.
- 276.
- 277.
- 278.
- 279.
- 280.
- 281.
- 282.
- 283.
- 284.
- 285.
- 286.
- 287.
- 288.
- 289.
- 290.
- 291.
- 292.
- 293.
- 294.
- 295.
- 296.
- 297.
- 298.
- 299.
- 300.
- 301.
- 302.
- 303.
- 304.
- 305.
- 306.
- 307.
- 308.
- 309.
- 310.
- 311.
- 312.
- 313.
- 314.
- 315.
- 316.
- 317.
2.1. 自定义组件@Component装饰器迁移
- V1版
@Component
export struct myPaintSheet {
// ...
}
- 1.
- 2.
- 3.
- 4.
- V2版
直接将@Component修改为@ComponentV2即可。
@ComponentV2
export struct myPaintSheet {
// ...
}
- 1.
- 2.
- 3.
- 4.
2.2. @Link升级
- V1版
@Link isMarker: boolean;
@Link alpha: number;
@Link percent: string;
@Link color: string;
@Link thicknessesValue: number;
@Link strokeWidth: number;
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- V2版
@Link迁移至V2版,需要应用@Param装饰器和@Event两个装饰器来实现。上节使用双叹号语法(!!)实现子组件传递信息给父组件,因此这里的@Event装饰的变量名的签名需要加上$符号。
@Param isMarker: boolean = false
@Event $isMarker: (val: boolean) => void = (val: boolean) => {}
@Param alpha: number = 1
@Event $alpha: (val: number) => void = (val: number) => {}
@Param percent: string = '100'
@Event $percent: (val: string) => void = (val: string) => {}
@Param color: string = '#000000'
@Event $color: (val: string) => void = (val: string) => {}
@Param thicknessesValue: number = 3
@Event $thicknessesValue: (val: number) => void = (val: number) => {}
@Param strokeWidth: number = 3
@Event $strokeWidth: (val: number) => void = (val: number) => {}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
3. 遗留问题
理论上,到这里我们的迁移就结束了,但是问题来了,在去修改画笔颜色并重新画笔属性的时候,会发现修改的颜色不能同步,V1代码如下:
ToggleThicknessColor() {
this.mPaint = new Paint(CommonConstants.ZERO, CommonConstants.COLOR_STRING, CommonConstants.ONE);
this.mPaint.setStrokeWidth(this.strokeWidth);
this.mPaint.setColor(this.color);
this.mPaint.setGlobalAlpha(this.alpha);
this.mBrush = new NormalBrush();
}
.onClick(() => {
this.color = item;
this.ToggleThicknessColor();
})
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
这是为什么呢?这个问题直接原因是:使用@Event修改父组件的值是立刻生效的,但从父组件将变化同步回子组件的过程是异步的,即在调用完@Event的方法后,子组件内的值不会立刻变化。这是因为@Event将子组件值实际的变化能力交由父组件处理,在父组件实际决定如何处理后,将最终值在渲染之前同步回子组件。看下面这个例子:
@ComponentV2
struct Child {
@Param index: number = 0;
@Event changeIndex: (val: number) => void;
build() {
Column() {
Text(`Child index: ${this.index}`)
.onClick(() => {
this.changeIndex(20);
console.log(`after changeIndex ${this.index}`);
})
}
}
}
@Entry
@ComponentV2
struct Index {
@Local index: number = 0;
build() {
Column() {
Child({
index: this.index,
changeIndex: (val: number) => {
this.index = val;
console.log(`in changeIndex ${this.index}`);
}
})
}
}
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
在上面的示例中,点击文字触发@Event函数事件改变子组件的值,打印出的日志为:
in changeIndex 20
after changeIndex 0
- 1.
- 2.
那上面的问题找到了,但是要如何解决呢?我的思路是,让this.ToggleThicknessColor();
语句延迟执行,简单处理如下:
ToggleThicknessColor() {
this.mPaint = new Paint(CommonConstants.ZERO, CommonConstants.COLOR_STRING, CommonConstants.ONE);
this.mPaint.setStrokeWidth(this.strokeWidth);
this.mPaint.setColor(this.color);
this.mPaint.setGlobalAlpha(this.alpha);
this.mBrush = new NormalBrush();
}
.onClick(() => {
this.color = item;
setTimeout(() => {
this.ToggleThicknessColor()
}, 100)
})
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
这里延迟时间设置要多于10秒。
4. 思考
这个添加定时器的方案可能不是好方案,你还有什么更好的方案,请留言,大家一起探讨。
更多HarmonyOS应用开发实战案例,请观看HarmonyOS NEXT 应用开发案例实战(基础篇)视频教程
微信扫码分享
