HarmonyOS自API12版本后,推出了全新的状态管理V2。V2是V1的增强版本,为我们带来了更多功能和灵活性。对于新开发的应用,建议直接使用V2版本范式来进行开发。对于已经使用V1的应用,如果V1的功能和性能已能满足需求,则不必立即切换到V2。如果开发者在开发过程中受限于V1不能深度观察等特性,则建议开发者尽早规划向V2的迁移,以便未来实现平滑过渡和改进。
目前,华为开发者联盟官网给出的案例,大多都是应用V1版来编码实现,如《基于Canvas实现画布的功能》这个案例,gitee地址为:HarmonyOS_Samples/CustomCanvas。效果图预览如下:
现在,我们尝试将这个案例的状态管理升级到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.1. 自定义组件@Component装饰器迁移
- V1版
@Entry
@Component
struct DrawCanvas {
// ...
}
- V2版
直接将@Component修改为@ComponentV2即可。
@Entry
@ComponentV2
struct DrawCanvas {
// ...
}
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';
- 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.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;
}
}
- 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.4. @Provide迁移
- V1版
@Provide mPaint: Paint = new Paint(0, '', 1);
@Provide mBrush: IBrush = new NormalBrush();
- V2版
V2版采用@Provider来代替@Provide。注意@Provider可以不用定义别名(alias),通过变量名来供@Consumer消费,但小括号必须保留。
@Provider() mPaint: Paint = new Paint(0, '', 1);
@Provider() mBrush: IBrush = new NormalBrush();
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
})
}
}
- 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!!
})
}
}
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)
}
}
2.1. 自定义组件@Component装饰器迁移
- V1版
@Component
export struct myPaintSheet {
// ...
}
- V2版
直接将@Component修改为@ComponentV2即可。
@ComponentV2
export struct myPaintSheet {
// ...
}
2.2. @Link升级
- V1版
@Link isMarker: boolean;
@Link alpha: number;
@Link percent: string;
@Link color: string;
@Link thicknessesValue: number;
@Link strokeWidth: number;
- 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) => {}
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();
})
这是为什么呢?这个问题直接原因是:使用@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}`);
}
})
}
}
}
在上面的示例中,点击文字触发@Event函数事件改变子组件的值,打印出的日志为:
in changeIndex 20
after changeIndex 0
那上面的问题找到了,但是要如何解决呢?我的思路是,让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)
})
这里延迟时间设置要多于10秒。
4. 思考
这个添加定时器的方案可能不是好方案,你还有什么更好的方案,请留言,大家一起探讨。
更多HarmonyOS应用开发实战案例,请观看视频教程:《HarmonyOS应用开发实战指南》