如何利用OpenHarmony ArkUI的Canvas组件实现涂鸦功能?

OpenHarmony(11) —— GUI底层 GUI底层使用情况 阅读详情

目录

简介

效果展示

目录结构

源码分析

一、Canvas组件介绍

二、分析源码页面布局

三、逻辑代码

总结


简介

ArkUI是一套UI开发框架,提供了开发者进行应用UI开发时所需具备的能力。随着OpenAtom OpenHarmony(以下简称“OpenHarmony”)不断更新迭代,ArkUI也提供了很多新的组件,例如Canvas、OffscreenCanvas、XComponent组件等。

新增的功能可以帮助开发者开发出更流畅、更美观的应用。本篇文章将为大家分享如何通过Canvas组件实现涂鸦功能,用户可以选择空白画布或者简笔图进行自由绘画。

效果展示

以下为效果图:

首页显示了涂鸦的图片以及最后一张空白图片,在点击图片进入涂鸦页面后,可以对画笔的颜色、粗细进行设置。如果涂鸦过程中有错误,可以用橡皮擦将画面擦除,也可点击清除按钮,清空涂鸦的内容,重新进行涂鸦操作。

相关代码已经上传至SIG仓库,链接如下:

https://gitee.com/openharmony-sig/knowledge_demo_entainment/tree/master/FA/FreeDraw

目录结构

源码分析

一、Canvas组件介绍

本篇样例主要利用ArkUI的Canvas组件实现涂鸦的功能,首先介绍一下Canvas组件。

Canvas组件主要包含了Canvas和CanvasRenderingContext2D,Canvas提供了画布功能,CanvasRenderingContext2D提供了绘画的属性和方法。通过CanvasRenderingContext2D可以修改画笔的样色、粗细等属性,从而画出各式各样的图形。

以下是Canvas和CanvasRenderingContext2D在样例开发中使用的相关接口信息。

CanvasRenderingContext2D

二、分析源码页面布局

第一个模块是首页布局,首页显示所有涂鸦包含的图片,点击图片可以进入页面;第二个模块是涂鸦模块,可以设置画笔的颜色、边条宽度等。

1. 首页布局

Column() {
      Text('选择涂鸦的图片:').margin('10vp').fontSize('30fp').fontColor(Color.Blue).height('5%')
      Grid() {
        ForEach(this.images, (item, index) => {
          GridItem() {
            Image(this.images[index])
              .onClick((event) => {
                router.push(
                  {
                    url: "pages/detailPage",
                    params: {
                      imgSrc: this.images[index],
                    },
                  }
                )
              })
              .width('100%')
              .height('100%')
              .objectFit(ImageFit.Contain)
          }
        })
      }
      .padding({left: this.columnSpace, right: this.columnSpace})
      .columnsTemplate("1fr 1fr 1fr")      // Grid宽度均分成3份
      .rowsTemplate("1fr 1fr")     // Grid高度均分成2份
      .rowsGap(this.rowSpace)                  // 设置行间距
      .columnsGap(this.columnSpace)            // 设置列间距
      .width('100%')
      .height('95%')
    }
    .backgroundColor(Color.Pink)

2. 涂鸦页面 - 画布Canvas的布局通过Stack组件进行包裹,并将Canvas画布覆盖在选择的背景图片之上,这些背景图片主要是水果简笔画。

Stack() {

        Image(this.imgSrc).width('100%').height('100%').objectFit(ImageFit.Contain)

        Canvas(this.context)
          .width('100%')
          .height('100%')
//          .backgroundColor('#00ffff00')
          .onReady(() => {
          })
          .onTouch((event) => {
            if (event.type === TouchType.Down) {
              this.eventType = 'Down';
              this.drawing = true;
              [this.x, this.y] = [event.touches[0].x, event.touches[0].y];
              this.context.beginPath();
              this.context.lineCap = 'round';
              if (this.isEraserMode) {
                //橡皮擦模式
                this.context.clearRect(this.x, this.y, 20, 20);
              }

              console.log('gyf Down');
            }
            if (event.type === TouchType.Up) {
              this.eventType = 'Up';
              this.drawing = false;
              console.log('gyf Up!');
              this.context.closePath();
            }
            if (event.type === TouchType.Move) {
              if (!this.drawing) return;
              this.eventType = 'Move';
              console.log('gyf Move');

              if (this.isEraserMode) {
                //橡皮擦模式
                this.context.clearRect(event.touches[0].x, event.touches[0].y, 20, 20);
              } else {
                this.context.lineWidth = this.lineWidth;
                this.context.strokeStyle = this.color;

                this.context.moveTo(this.x, this.y);
                this.x = event.touches[0].x;
                this.y = event.touches[0].y;
                this.context.lineTo(this.x, this.y);
                this.context.stroke();
              }
            }
          })

      }.width('100%').height('75%')

3.涂鸦页面 - 画笔设置区域的布局

Column() {
        Row() {
          Text('粗细:')

          Button('小').onClick(() => {
            //设置画笔的宽度
            this.lineWidth = 5;
            this.context.lineWidth = this.lineWidth;
            this.isEraserMode = false;
            console.log('gyf small button');
          }).margin($r('app.float.wh_value_10'))

          Button('中').onClick(() => {
            //设置画笔的宽度
            this.lineWidth = 15;
            this.context.lineWidth = this.lineWidth;
            this.isEraserMode = false;
            console.log('gyf middle button');
          }).margin($r('app.float.wh_value_10'))

          Button('大').onClick(() => {
            //设置画笔的宽度
            this.lineWidth = 25;
            this.context.lineWidth = this.lineWidth;
            this.isEraserMode = false;
            console.log('gyf big button');
          }).margin($r('app.float.wh_value_10'))

          Button('超大').onClick(() => {
            //设置画笔的宽度
            this.lineWidth = 40;
            this.context.lineWidth = this.lineWidth;
            this.isEraserMode = false;
            console.log('gyf super big button');
          })
        }.padding($r('app.float.wh_value_10')).margin($r('app.float.wh_value_5'))

        //画笔颜色
        Scroll() {
          Row() {
            Text('颜色:')

            Button(' ', { type: ButtonType.Circle })
              .onClick(() => {
                //黑色
                this.color = '#000000';
                this.context.strokeStyle = this.color;
                this.isEraserMode = false;
                console.log('gyf black button');
              })
              .backgroundColor('#000000')
              .width('40vp')
              .width('40vp')
              .margin($r('app.float.wh_value_10'))

            Button(' ', { type: ButtonType.Circle })
              .onClick(() => {
                //红色
                this.color = '#FF0000';
                this.context.strokeStyle = this.color;
                this.isEraserMode = false;
                console.log('gyf red button');
              })
              .backgroundColor('#FF0000')
              .width('40vp')
              .width('40vp')
              .margin($r('app.float.wh_value_10'))

            Button(' ', { type: ButtonType.Circle })
              .onClick(() => {
                //绿色
                this.color = '#00FF00';
                this.context.strokeStyle = this.color;
                this.isEraserMode = false;
                console.log('gyf green button');
              })
              .backgroundColor('#00FF00')
              .width('40vp')
              .width('40vp')
              .margin($r('app.float.wh_value_10'))

            Button(' ', { type: ButtonType.Circle })
              .onClick(() => {
                //蓝色
                this.color = '#0000FF';
                this.context.strokeStyle = this.color;
                this.isEraserMode = false;
              })
              .backgroundColor('#0000FF')
              .width('40vp')
              .width('40vp')
              .margin($r('app.float.wh_value_10'))

            Button(' ', { type: ButtonType.Circle })
              .onClick(() => {
                //棕色
                this.color = '#A52A2A';
                this.context.strokeStyle = this.color;
                this.isEraserMode = false;
              })
              .backgroundColor('#A52A2A')
              .width('40vp')
              .width('40vp')
              .margin($r('app.float.wh_value_10'))

            Button(' ', { type: ButtonType.Circle })
              .onClick(() => {
                //紫色
                this.color = '#800080';
                this.context.strokeStyle = this.color;
                this.isEraserMode = false;
              })
              .backgroundColor('#800080')
              .width('40vp')
              .width('40vp')
              .margin($r('app.float.wh_value_10'))

            Button(' ', { type: ButtonType.Circle })
              .onClick(() => {
                //紫红色
                this.color = '#FF00FF';
                this.context.strokeStyle = this.color;
                this.isEraserMode = false;
              })
              .backgroundColor('#FF00FF')
              .width('40vp')
              .width('40vp')
              .margin($r('app.float.wh_value_10'))

            Button(' ', { type: ButtonType.Circle })
              .onClick(() => {
                //深蓝色
                this.color = '#00008B';
                this.context.strokeStyle = this.color;
                this.isEraserMode = false;
              })
              .backgroundColor('#00008B')
              .width('40vp')
              .width('40vp')
              .margin($r('app.float.wh_value_10'))

            Button(' ', { type: ButtonType.Circle })
              .onClick(() => {
                //深天蓝
                this.color = '#00BFFF';
                this.context.strokeStyle = this.color;
                this.isEraserMode = false;
              })
              .backgroundColor('#00BFFF')
              .width('40vp')
              .width('40vp')
              .margin($r('app.float.wh_value_10'))

            Button(' ', { type: ButtonType.Circle })
              .onClick(() => {
                //绿色
                this.color = '#008000';
                this.context.strokeStyle = this.color;
                this.isEraserMode = false;
              })
              .backgroundColor('#008000')
              .width('40vp')
              .width('40vp')
              .margin($r('app.float.wh_value_10'))

            Button(' ', { type: ButtonType.Circle })
              .onClick(() => {
                //青绿色
                this.color = '#32CD32';
                this.context.strokeStyle = this.color;
                this.isEraserMode = false;
              })
              .backgroundColor('#32CD32')
              .width('40vp')
              .width('40vp')
              .margin($r('app.float.wh_value_10'))

            Button(' ', { type: ButtonType.Circle })
              .onClick(() => {
                //橙色
                this.color = '#FFA500';
                this.context.strokeStyle = this.color;
                this.isEraserMode = false;
              })
              .backgroundColor('#FFA500')
              .width('40vp')
              .width('40vp')
              .margin($r('app.float.wh_value_10'))

            Button(' ', { type: ButtonType.Circle })
              .onClick(() => {
                //黄色
                this.color = '#FFFF00';
                this.context.strokeStyle = this.color;
                this.isEraserMode = false;
              })
              .backgroundColor('#FFFF00')
              .width('40vp')
              .width('40vp')
              .margin($r('app.float.wh_value_10'))

          }.padding('10vp')
        }
        .scrollable(ScrollDirection.Horizontal) // 设置滚动条水平方向滚动
        .margin($r('app.float.wh_value_5'))

        Row() {
          Image('/common/images/eraser.png')
            .onClick(() => {
              //橡皮擦模式
              this.isEraserMode = true;
              console.log('gyf eraser button');
            })
            .width('50vp')
            .height('50vp')
            .margin('10vp')

          Button('清理画板').onClick(() => {
            this.context.clearRect(0, 0, 1000, 1000);
          })
        }
        .margin($r('app.float.wh_value_5'))

      }
      .width('100%')
      .height('25%')
      .alignItems(HorizontalAlign.Start)

三、逻辑代码

逻辑代码存在于Canvas的onTouch事件中,通过TouchType的Down、Up、Move来判断开始、移动和结束的动作。一笔完整的绘制包含一次Down和Up,其中有若干次的Move。橡皮擦模式通过clearRect接口实现擦除的功能。

.onTouch((event) => {
            if (event.type === TouchType.Down) {
              this.eventType = 'Down';
              this.drawing = true;
              [this.x, this.y] = [event.touches[0].x, event.touches[0].y];
              this.context.beginPath();
              this.context.lineCap = 'round';
              if (this.isEraserMode) {
                //橡皮擦模式
                this.context.clearRect(this.x, this.y, 20, 20);
              }

              console.log('gyf Down');
            }
            if (event.type === TouchType.Up) {
              this.eventType = 'Up';
              this.drawing = false;
              console.log('gyf Up!');
              this.context.closePath();
            }
            if (event.type === TouchType.Move) {
              if (!this.drawing) return;
              this.eventType = 'Move';
              console.log('gyf Move');

              if (this.isEraserMode) {
                //橡皮擦模式
                this.context.clearRect(event.touches[0].x, event.touches[0].y, 20, 20);
              } else {
                this.context.lineWidth = this.lineWidth;
                this.context.strokeStyle = this.color;

                this.context.moveTo(this.x, this.y);
                this.x = event.touches[0].x;
                this.y = event.touches[0].y;
                this.context.lineTo(this.x, this.y);
                this.context.stroke();
              }
            }
          })

总结

本文介绍了如何使用ArkUI框架提供的Canvas组件实现涂鸦功能。首先,通过Canvas的onTouch事件来跟踪Down、Move和Up的事件,再设置CanvasRenderingContext2D的相关属性并调用相关的方法,最终实现涂鸦的功能。除了文中分享的涂鸦样例,开发者还可以通过拓展其他相关的属性和方法,实现更多好玩的、高性能的样例。

华为官方解析开源鸿蒙 OpenHarmony 3.1关键特性画布,教你如何完成飞机大战小游戏 华为技术有限公司的江英杰为大家揭晓了关于开源鸿蒙 OpenHarmony 3.1 Beta 版中的一个关键特性,也就是 ArkUI 开发框架中的 canvas 画布。 据介绍,canvas 是 ArkUI 开发框架里的画布组件,常用于自定义绘制图形。因为其轻量、灵活、高效等优点,被广泛应用于 UI 界面开发中。本期,我们将为大家介绍 ArkUI 开发框架中 canvas 组件的使用。 canvas 介绍 1.1 什么是 canvas? 了解到,在 Web 浏览器中,canvas 是一个可自定义 wi 阅读详情

相关推荐

OpenHarmony实战:如何利用OpenHarmony ArkUI的Canvas组件实现涂鸦功能?

ArkUI是一套UI开发框架,提供了开发者进行应用UI开发时所需具备的能力。随着OpenAtom OpenHarmony(以下简称“OpenHarmony”)不断更新迭代,ArkUI也提供了很多新的组件,例如Canvas、OffscreenCanvas、XComponent组件等。新增的功能可以帮助开发者开发出更流畅、更美观的应用。本篇文章将为大家分享如何通过Canvas组件实现涂鸦功能,用户可以选择空白画布或者简笔图进行自由绘画。

xianglizaibie的博客 1094

HarmonyOS ArkUI Canvas(你相信光吗?)

Canvas是 ArkUI 提供的画布组件,用于自定义绘制图形。它使用类似于HTML5 Canvas的方式进行绘制,可以实现高效、灵活的自定义UI界面。Canvas可以实现在画布上自由绘制图形,包括线条、矩形、圆形、文本、图片等,并且可以通过控制绘制顺序和渲染顺序来实现图层管理和遮盖效果。

我怀里的猫的博客 987

【鸿蒙南向开发】如何利用OpenHarmony ArkUI的Canvas组件实现涂鸦功能

本篇样例主要利用ArkUI的Canvas组件实现涂鸦的功能,首先介绍一下Canvas组件。Canvas组件主要包含了Canvas和CanvasRenderingContext2D,Canvas提供了画布功能,CanvasRenderingContext2D提供了绘画的属性和方法。通过CanvasRenderingContext2D可以修改画笔的样色、粗细等属性,从而画出各式各样的图形。以下是Canvas和CanvasRenderingContext2D在样例开发中使用的相关接口信息。

HarmonyOS_666的博客 965

【FFH】Canvas实现帧动画及封装(OpenHarmony JS UI)

基于鸿蒙JS UI canvas组件实现帧动画播放

weixin_61965578的博客 1020

OpenHarmony ArkUI Canvas涂鸦功能实现与优化

Canvas作为图形绘制的核心技术,在现代UI开发中扮演着重要角色。其基于像素缓冲区的即时模式绘图机制,特别适合实现需要高频更新的动态绘图场景。在OpenHarmony的ArkUI框架中,Canvas组件经过深度优化,在嵌入式设备上展现出卓越的渲染性能。通过处理TouchType.Down/Move/Up等触摸事件,开发者可以实现流畅的涂鸦功能。结合路径累积技术和双缓冲等优化手段,能显著提升绘制性能。这类技术可广泛应用于手写笔记、签名采集、创意绘图等场景,特别是在分布式操作系统中实现跨设备协同绘制时更具优势

weixin_29166013的博客 267

OpenHarmony ArkUI Canvas组件与涂鸦功能开发指南

Canvas作为现代UI开发中的核心绘图组件,采用即时渲染模式直接操作像素实现动态图形绘制。在OpenHarmony生态中,ArkUI框架的Canvas组件通过JavaScript API提供丰富的2D绘图能力,包括路径绘制、文本渲染和图像处理等功能。其技术价值在于能够高效实现复杂可视化效果,特别适用于需要自定义绘图的场景如数据可视化、游戏开发和创意工具。本文以涂鸦应用为例,详细解析如何利用ArkUI的Canvas组件和触摸事件系统实现完整的绘图功能,涵盖从基础绘制到高级性能优化的全流程实践。通过OpenH

weixin_31421613的博客 308

鸿蒙面试题:如何利用OpenHarmony ArkUI的Canvas组件实现涂鸦功能?

ArkUI是一套UI开发框架,提供了开发者进行应用UI开发时所需具备的能力。随着OpenAtom OpenHarmony(以下简称“OpenHarmony”)不断更新迭代,ArkUI也提供了很多新的组件,例如Canvas、OffscreenCanvas、XComponent组件等。新增的功能可以帮助开发者开发出更流畅、更美观的应用。本篇文章将为大家分享如何通过Canvas组件实现涂鸦功能,用户可以选择空白画布或者简笔图进行自由绘画。

adaedwa187545的博客 1135

HarmonyOS鸿蒙开发指南:Canvas对象

Canvas添加长按事件,长按后可获取Canvas组件的dataUrl值(toDataURL方法返回的图片信息),打印在下方文本区域内。Canvas组件设置宽(width)、高(height)、背景色(background-color)及边框样式(border)。Canvas默认宽高为width: 300px,height: 150px。在pages/index目录下的hml文件中创建一个Canvas组件。Canvas组件默认背景色与父组件的背景色一致。

斌擎科技 1009

本周四晚19:00知识赋能第八期第3课丨涂鸦小游戏的实现

本次直播将在国庆假期前,为同学们带来涂鸦小游戏的趣味体验,让大家全面了解ArkUI框架的应用,帮助你们在自己已有专业的基础上拓宽知识边界,学习新技术,更快成长!OpenHarmony新增组件的介绍、新增组件的实际应用以及使用canvas组件,开发涂鸦小游戏。9月29日19:00~20:00,第八期知识赋能最后一节直播就要开始啦!欢迎大家长按下方海报扫码,预约直播速速行动!上节直播中,致力于深耕终端设备研发领域,拥有18年移动系统开发经验的。而本节直播将继续由巴延兴老师讲解《主要介绍三方面内容,包括。

OpenHarmony_dev的博客 786

五、2K0300 久久派开发板的GPIO控制与Shell脚本自动化实战

本文详细介绍了基于龙芯2K0300处理器的久久派开发板的GPIO控制方法,通过sysfs文件系统实现LED控制和传感器读取。重点讲解了Shell脚本自动化实战,包括LED闪烁、流水灯效果和传感器数据采集,为物联网项目开发提供完整解决方案。

silver的专栏 686

COMSOL金属氢化物-放氢过程

"层流"的出口设置压降函数,解决“找不到一致的初始值”问题,主要是一开始设置出口为1atm的话,压力场过渡太剧烈,不好找到初始解,要么就先算个稳态流场,这种也能求解。注:P0是根据实测PCT的插值曲线(函数名称为:p_iso)得到的,根据初始质量储氢量wtw0,和初始温度条件T0,然后根据函数p_iso(wtw0)*exp(-deltHd/R_const*(1/T0-1/303.15[K]))求解获得。若"初始步长"选择框不勾上,前面0~5s内的求解容易发散,然后求解失败。2、设置"达西定律"的。

qq_24800941的博客 1929

canvas图像绘制(图像放大、缩小、拖动和截图)

1、canvas略解林大大又来更新啦~这次主要做的是关于canvas图像绘制的部分,要实现的功能主要是绘制、对canvas的放大以及那我们先来大概了解下canvas:1、canvas略解宽高是必须加上的,当然你也可以选择动态宽高,可以设置宽高百分比喔,canvas用于在页面上绘制图像(可以是自己的图片,也可以绘制自定义矢量图形:矩形等),其实我们不用担心大量的重绘canvas,现在浏览器完全扛得住这压力,非常流畅丝滑。

qq_39404437的博客 2万+

XMC1402调试实战:如何用20元的Jlink-OB搞定SWD连接(附DAVE 4.4.2配置)

本文详细介绍了如何使用成本仅20元的Jlink-OB调试器,成功连接并调试英飞凌XMC1402芯片的完整实战流程。内容涵盖从理解芯片启动模式、使用Memtool完成首次烧录与模式切换,到Jlink-OB驱动安装、连接验证,以及在DAVE 4.4.2中配置调试环境并解决常见问题的全步骤,为个人开发者提供了一套高性价比的SWD调试解决方案。

zz67890的博客 553

使用画布绘制自定义图形(Canvas)

使用CanvasRenderingContext2D对象在Canvas画布上绘制。@Entry@Component//用来配置CanvasRenderingContext2D对象的参数,包括是否开启抗锯齿,true表明开启抗锯齿。//用来创建CanvasRenderingContext2D对象,通过在canvas中调用CanvasRenderingContext2D对象来绘制。build() {//在canvas中调用CanvasRenderingContext2D对象。

2932

信息奥赛课课通(C++)p154-1近似排序

试题描述 读入一对正整数,将这两个数之间(包括这两个数本身)的所有数按下述特别规则排序后输出,该特别规则是按两数倒过来的值进行比较决定其大小,如30倒过来为3,29倒过来为92,则29大于30. 输入要求 1行,包含两个用空格隔开的自然数,这两个自然数给出了待排序数的范围,其中1≤第一个数≤第二个数≤999999999,两个数之差不超过100. 输出要求 输出排序后的数据,每行输出...

mengdicfm的博客 7279

统信UOS开发环境支持Java

集成了JDK和多种IDE,为开发者提供了从代码编写、编译、调试到部署的一站式开发解决方案。

RZer的博客 2254

HarmonyOS NEXT 使用Canvas实现模拟时钟案例

本示例介绍利用 Canvas 和定时器实现模拟时钟场景,该案例多用于用户需要显示自定义模拟时钟的场景。

1 680

Flowable高级篇 -6.8.0 最全表结构说明-主要表结构字段注释

flowable 28张表 详细

billzhen的博客 1万+

OpenHarmony 实战开发——ArkUI之Canvas组件

在 Web 浏览器中,canvas 是一个可自定义 width、height 的矩形画布,画布左上角为坐标原点,以像素为单位,水平向右为 x 轴,垂直向下为 y 轴,画布内所有元素都基于原点进行定位。如图 1 所示,我们通过 标签,创建了一个 width= 1500px,height=900px 的空白画布,我们还需要“画笔”才能绘制图形。canvas 采用轻量的逐像素渲染机制,以 JS 为“画笔”直接控制画布像素,从而实现图形绘制。图 1 canvas 画布。

xianglizaibie的博客 1048

3 路由引入

在大型企业网络中,网络规模十分庞大,选用单一的路由协议无法满足网络的需求,因此多种路由协议共存的情况十分常见。或者出于业务逻辑或行政管理的考虑,会在不同的网络结构中设计和部署不同的路由协议,使路由的层次结构更加清晰可控。路由引入是具有方向性的,将路由信息从路由协议A引入到路由协议B(A-to-B),则路由协议B可获知A中的路由信息,但是此时,A还并不知晓B路由协议中的路由信息,除非配置B-to-A的路由引入。表3-1:各种路由协议的优先级。通过路由引入,可以实现路由信息在不同路由协议间传递。

lwljh134的博客 621

AutoSAR MemMap模块概述及配置参数详解

这个模块是干啥的?它是用来配置Memory Mapping and Compiler Abstraction module。

4508

【OpenHarmony应用开发】eTS实现Canvas绘图板

一个简单的绘图板快速体验ets canvas绘图@Entry@Componentstruct Index { private settings: RenderingContextSettings = new RenderingContextSettings(true) private ctx: CanvasRender...

sinat_15577573的博客 132

NTscan扫描工具

ntscan是一个扫描器,可以扫描没有密码和弱密码的共享,先到配置文件中填写猜测的用户名和密码,扫描过程中右侧出现的是扫描日志。

上一篇: OpenHarmony 3.2 Beta源码分析之MediaLibrary
下一篇: 本周四晚19:00知识赋能第八期第2课丨ArkUI自定义组件
OpenHarmony开发者
博客等级 码龄5年 625粉丝 221原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值