注意:博主有个鸿蒙专栏,里面从上到下有关于鸿蒙next的教学文档,大家感兴趣可以学习下

如果大家觉得博主文章写的好的话,可以点下关注,博主会一直更新鸿蒙next相关知识

目录

4. 鸿蒙原生能力

4.1 音视频播放

4.2 抖音小案例

4.3 绘画能力-画布组件

4.4 签字版


4. 鸿蒙原生能力

4.1 音视频播放

arkUI提供了Video组件可以直接播放视频,并提供自带的播放-暂停 全屏,拖动进度等功能

用法

  • Video提供构造参数 Video({ src: string | Resource })
  • src支持在线路径-和本地资源路径

  • 示例

 Video({
          src:'https://video19.ifeng.com/video09/2024/05/23/p7199260608686989961-0-122707.mp4'
        })

版权说明: 上述代码中的视频链接为参考学习,并非用作商业用途,请同学们自行放置的外链视频链接

  • 放映本地视频

本地视频我们需要放置在资源目录的原始文件中rawfile目录下,使用$rawfile函数来获取路径进行赋值即可

 Video({
            src: $rawfile('hm.mp4')
          })
            .width('100%')
            .aspectRatio(1.4)

  • 完整代码
@Entry
  @Component
  struct VideoCase {
    build() {
      Row() {
        Column() {
          Tabs(){
            TabContent(){
              Video({
                src:'https://video19.ifeng.com/video09/2024/05/23/p7199260608686989961-0-122707.mp4'
              })
            }.tabBar('在线视频')
            TabContent(){
              Video({
                src:$rawfile('hm.mp4')
              })
            }.tabBar('本地视频')
          }
        }
        .width('100%')
      }
      .height('100%')
    }
  }

  • 视频控制-播放-暂停--倍速-全屏-进度

我们可以通过构造函数传入currentProgressRate 控制倍速,它来自PlaybackSpeed的枚举,目前支持

0.75-1-1.25-1.75-2倍速设置

  • 同时我们可以通过传入VideoController来获取视频播放的控制权

  • 实现控制倍速播放
@Entry
  @Component
  struct VideoCase {
    @State
    speed: number = 1

    build() {
      Row() {
        Tabs() {
          TabContent() {
            Column({ space: 20 }) {
              Video({
                currentProgressRate: this.speed,
                src: 'https://vd3.bdstatic.com/mda-pmj5ajqd7p4b6pgb/576p/h264/1703044058699262355/mda-pmj5ajqd7p4b6pgb.mp4?auth_key=1703138418-0-0-618ea72b33be241c96c6cff86c06e080&bcevod_channel=searchbox_feed&cr=1&cd=0&pd=1&pt=4&logid=0018430194&vid=9762003448174112444&abtest=all'
              })
                .width('100%')
                .aspectRatio(1.4)
              Slider({
                value: this.speed,
                min: 0.75,
                step: 0.25,
                max: 2,
                style: SliderStyle.InSet
              })
                .showSteps(true)
                .onChange(value => {
                  this.speed = value
                })
              Text(this.speed+"倍速").fontSize(14).textAlign(TextAlign.Center).width('100%')

            }
            .width('100%')
          }.tabBar("在线视频")
          TabContent() {
            Video({
              src: $rawfile('hm.mp4')
            })
              .width('100%')
              .aspectRatio(1.4)
          }
          .tabBar("本地视频")
        }
        .animationDuration(300)

      }
      .height('100%')
    }
  }

  • 实现通过controller控制视频 暂停- 播放-停止-全屏-静音-播放速度- 播放进度

自定义controller,手动控制视频播放

import { media } from '@kit.MediaKit'
import { image } from '@kit.ImageKit'

@Entry
  @Component
  struct VideoControlCase {
    @State
    currentSpeed: number = 1
    @State
    isMuted: boolean = false
    @State
    showController: boolean = false
    @State
    currentTime: number = 0
    @State
    videoTime: number = 0
    controller: VideoController = new VideoController()
    @State previewImage: image.PixelMap | undefined = undefined

    build() {
      Row() {
        Column({ space: 20 }) {
          Stack({ alignContent: Alignment.BottomEnd }) {
            Video({
              // 视频源
              src: $rawfile('hm.mp4'),
              // 封面图
              previewUri: this.previewImage,
              // 倍速 0.75 ~ 2,0.25一个档
              currentProgressRate: this.currentSpeed,
              // 控制器
              controller: this.controller

            })
              .height(400)
              .objectFit(ImageFit.Contain)// 填充模式
              .autoPlay(true)// 自动播放
              .loop(true)// 循环播放
              .muted(this.isMuted)// 是否静音
              .controls(this.showController)//是否展示控制栏
              .onAppear(async () => {
                //获取视频的内部的截图
                const generator = await media.createAVImageGenerator()
                generator.fdSrc = await getContext().resourceManager.getRawFd('1.mp4')
                const previewUri =
                  await generator.fetchFrameByTime(5000, media.AVImageQueryOptions.AV_IMAGE_QUERY_NEXT_SYNC,
                                                   { height: 400, width: 300 })
                this.previewImage = previewUri
              })
              .onPrepared((time) => { // 视频准备好了可以获取视频的时长
                this.videoTime = time.duration
              })
              .onUpdate((time) => { // 视频播放中可以获取播放的时长
                this.currentTime = time.time
              })
              .onFullscreenChange((screen) => { // 根据是否全屏判断是否展示控制条
                this.showController = screen.fullscreen
              })
            Row() {
              Button('全屏')
                .onClick(() => {
                  this.controller.requestFullscreen(true)
                })
              // 一般不需要手动全屏,可以过几秒自动退出,提示该充值了!
              // Button('退出全屏')
              //   .onClick(() => {
              //     this.controller.exitFullscreen()
              //   })
            }
          }


          Row({ space: 20 }) {
            Text('播放进度:')
            Slider({
              value: $$this.currentTime,
              min: 0,
              max: this.videoTime,
            })
              .layoutWeight(1)// 改变时设置视频播放时长
              .onChange((val) => {
                this.controller.setCurrentTime(val)
              })
          }
          .padding(20)

          Row({ space: 20 }) {
            Text('播放速度:')
            Slider({
              value: $$this.currentSpeed,
              min: 0.75,
              max: 2,
              step: 0.25
            })
              .layoutWeight(1)

          }
          .padding(20)

          Row({ space: 20 }) {
            Button('播放')
              .onClick(() => {
                this.controller.start()
              })
            Button('暂停')
              .onClick(() => {
              this.controller.pause()
            })
          Button('停止')
            .onClick(() => {
              this.controller.stop()
            })
          Button('静音')
            .onClick(() => {
              this.isMuted = !this.isMuted
            })
        }
      }
      .width('100%')
    }
    .height('100%')
  }
}

同理- 如果我们想播放一段音频-用同样的方式给到我们的Video的src属性就可以了Video同时支持

4.2 抖音小案例

  • 声明类型和数据

class VideoItem {
  videoUrl: string = ''
  title: string = ""
}

const allData: VideoItem[] = [
  {
    videoUrl: 'https://vd4.bdstatic.com/mda-pmia5y0htmibjej2/576p/h264/1702970058650094297/mda-pmia5y0htmibjej2.mp4?auth_key=1703155514-0-0-a92de0b6c32239b242d0e51b151ee2d6&bcevod_channel=searchbox_feed&cr=1&cd=0&pd=1&pt=4&logid=2714832517&vid=9811936085320099438&abtest=all',
    title: '我们只是拿某站的数据进行一下测试'
  },
  {
    title: '请大家自行准备在线素材',
    videoUrl: 'https://vd4.bdstatic.com/mda-pmjxx4ccc8x719t3/hd/h264/1703111503445924222/mda-pmjxx4ccc8x719t3.mp4?auth_key=1703155561-0-0-e7c32efbedae026e0e17c900bbd0cf55&bcevod_channel=searchbox_feed&pd=1&cr=1&cd=0&pt=4&logid=2761194416&vid=7476642150019887968&abtest=all'
  },
  {
    title: '你知道冬天的雪是什么颜色吗, 我猜你不知道',
    videoUrl: 'https://vd4.bdstatic.com/mda-pku9q3zt0rzybip0/hd/cae_h264/1701381974031001593/mda-pku9q3zt0rzybip0.mp4?v_from_s=hkapp-haokan-hbf&auth_key=1703155589-0-0-133df5be4b625ce34e1a75fe3a4baabf&bcevod_channel=searchbox_feed&pd=1&cr=1&cd=0&pt=4&logid=2789259407&vid=4775310688475056528&abtest=all'
  },
  {
    title: '宝子们,我当众社死了,我竟然在众目睽睽之下完成了自己人生中的第一段程序',
    videoUrl: 'https://vd2.bdstatic.com/mda-pkkf9qb7zksdaqs9/576p/h264/1700564765354260319/mda-pkkf9qb7zksdaqs9.mp4?v_from_s=hkapp-haokan-hbf&auth_key=1703155630-0-0-9a47a2910e8d5d90b47ba709fa530b5e&bcevod_channel=searchbox_feed&pd=1&cr=1&cd=0&pt=4&logid=2830328412&vid=8335346471874826776&abtest=all'
  },
  {
    title: '文学,可以在寂静的夜用曼妙的文字勾勒出关于人生,职场,感情的诸多情绪,无奈此生当为程序员',
    videoUrl: 'https://vd2.bdstatic.com/mda-pj8qa65bc9r1v1cf/576p/h264/1696871444324088416/mda-pj8qa65bc9r1v1cf.mp4?auth_key=1703155654-0-0-fdc0ca9c37ec26be3da9809b89e6151c&bcevod_channel=searchbox_feed&pd=1&cr=1&cd=0&pt=4&logid=2854467125&vid=5483608480722064830&abtest=all'
  },
  {
    title: '当你阅读到这段文字的时候,我早已入睡,当我在睡梦中惊醒,你却早已安然睡去',
    videoUrl: 'https://vd2.bdstatic.com/mda-pmexhyfui3e6rbmd/hd/cae_h264/1702705379314308540/mda-pmexhyfui3e6rbmd.mp4?auth_key=1703155684-0-0-5b0145fb4c2ec2f0d1bbd525ddb3d592&bcevod_channel=searchbox_feed&pd=1&cr=1&cd=0&pt=4&logid=2884962294&vid=3059586091403538183&abtest=all'
  },
  {
    title: '每个人的内心都有一段独处的幽闭,不可诉说的窒息感孤独感在每当我们沉静下来的时候变愈发强烈',
    videoUrl: 'https://vd3.bdstatic.com/mda-pmbgjjpkihkf7tjd/576p/h264/1702381478247675613/mda-pmbgjjpkihkf7tjd.mp4?v_from_s=hkapp-haokan-hbf&auth_key=1703155722-0-0-ea3c2453fbbb2cca66b12e9afe3d419f&bcevod_channel=searchbox_feed&pd=1&cr=1&cd=0&pt=4&logid=2922207105&vid=9050628586030215591&abtest=all'
  },
  {
    title: '如果在未来的某一天,某一个早晨 晚上 瞬间,你会偶然想起多年前的一段往事,其实并不是我们有多怀旧,只是因为我们走过了太多的路',
    videoUrl: 'https://vd2.bdstatic.com/mda-pj7ktq9euqchetdc/cae_h264/1696824500894354779/mda-pj7ktq9euqchetdc.mp4?v_from_s=hkapp-haokan-hbf&auth_key=1703155751-0-0-fccb0f110a3b447af67eb0feeabf06ad&bcevod_channel=searchbox_feed&pd=1&cr=0&cd=0&pt=4&logid=2951492012&vid=12162674818438199896'
  },
  {
    title: '什么是知己,有个网红说,当你理解所有人的时候,你一定不能被所有人理解,每个人都或多或少的自私,只是或多或少而已',
    videoUrl: 'https://vd3.bdstatic.com/mda-pmh5hr95fg6u8u0k/hd/cae_h264/1702877143957184120/mda-pmh5hr95fg6u8u0k.mp4?v_from_s=hkapp-haokan-hbf&auth_key=1703155785-0-0-5cfc2be95d00306082c7875a747dd998&bcevod_channel=searchbox_feed&pd=1&cr=1&cd=0&pt=4&logid=2985314718&vid=2720370579167170031&abtest=all'
  }
]

  • 实现代码
@Entry
  @Component
  struct DouyinCase {
    @State
    list: VideoItem[] = allData
    @State
    activeIndex: number = 0

    build() {
      Swiper() {
        // 循环的数据 抖音的列表数据
        ForEach(this.list, (item: VideoItem, index: number) => {
          // 封装单独的组件实现 Video组件
          VideoComp({
            item,
            index,
            activeIndex: this.activeIndex
          })
        })
      }
      .index($$this.activeIndex)
        .cachedCount(3)
        .vertical(true)
        .indicator(false)
        .width('100%')
        .height('100%')
    }
  }

@Component
  struct VideoComp {
    item: VideoItem = new VideoItem()
    index: number = -1
    @Require
    @Prop
    @Watch('changeVideo')
    activeIndex: number

    changeVideo() {
      this.activeIndex === this.index ? this.controller.start() : this.controller.pause()
    }

    controller: VideoController = new VideoController()

    @State
    isPlay:boolean = true
    build() {
      Stack({ alignContent: Alignment.Bottom }) {
        Stack(){
          Video({
            src: this.item.videoUrl,
            controller: this.controller
          })
            .controls(false)
            .objectFit(ImageFit.Contain)
            .autoPlay(this.activeIndex === this.index ? true : false)
            .loop(true)
            .onPause(()=>{
              this.isPlay = false
            })
            .onStart(()=>{
              this.isPlay = true
            })
            .onClick(()=>{
              this.isPlay?this.controller.pause():this.controller.start()
            })
          if(!this.isPlay){
            Image($r('sys.media.ohos_ic_public_play'))
              .width(100)
              .aspectRatio(1)
              .fillColor('#ccc')
              .onClick(()=>{
                this.controller.start()
              })
          }
        }
        Text(this.item.title)
          .fontSize(14)
          .fontColor(Color.White)
          .padding(20)
      }
    }
  }

class VideoItem {
  videoUrl: string = ''
  title: string = ""
}

const allData: VideoItem[] = [
  {
    videoUrl: 'https://vd4.bdstatic.com/mda-pmia5y0htmibjej2/576p/h264/1702970058650094297/mda-pmia5y0htmibjej2.mp4?auth_key=1703155514-0-0-a92de0b6c32239b242d0e51b151ee2d6&bcevod_channel=searchbox_feed&cr=1&cd=0&pd=1&pt=4&logid=2714832517&vid=9811936085320099438&abtest=all',
    title: '我们只是拿某站的数据进行一下测试'
  },
  {
    title: '请大家自行准备在线素材',
    videoUrl: 'https://vd4.bdstatic.com/mda-pmjxx4ccc8x719t3/hd/h264/1703111503445924222/mda-pmjxx4ccc8x719t3.mp4?auth_key=1703155561-0-0-e7c32efbedae026e0e17c900bbd0cf55&bcevod_channel=searchbox_feed&pd=1&cr=1&cd=0&pt=4&logid=2761194416&vid=7476642150019887968&abtest=all'
  },
  {
    title: '你知道冬天的雪是什么颜色吗, 我猜你不知道',
    videoUrl: 'https://vd4.bdstatic.com/mda-pku9q3zt0rzybip0/hd/cae_h264/1701381974031001593/mda-pku9q3zt0rzybip0.mp4?v_from_s=hkapp-haokan-hbf&auth_key=1703155589-0-0-133df5be4b625ce34e1a75fe3a4baabf&bcevod_channel=searchbox_feed&pd=1&cr=1&cd=0&pt=4&logid=2789259407&vid=4775310688475056528&abtest=all'
  },
  {
    title: '宝子们,我当众社死了,我竟然在众目睽睽之下完成了自己人生中的第一段程序',
    videoUrl: 'https://vd2.bdstatic.com/mda-pkkf9qb7zksdaqs9/576p/h264/1700564765354260319/mda-pkkf9qb7zksdaqs9.mp4?v_from_s=hkapp-haokan-hbf&auth_key=1703155630-0-0-9a47a2910e8d5d90b47ba709fa530b5e&bcevod_channel=searchbox_feed&pd=1&cr=1&cd=0&pt=4&logid=2830328412&vid=8335346471874826776&abtest=all'
  },
  {
    title: '文学,可以在寂静的夜用曼妙的文字勾勒出关于人生,职场,感情的诸多情绪,无奈此生当为程序员',
    videoUrl: 'https://vd2.bdstatic.com/mda-pj8qa65bc9r1v1cf/576p/h264/1696871444324088416/mda-pj8qa65bc9r1v1cf.mp4?auth_key=1703155654-0-0-fdc0ca9c37ec26be3da9809b89e6151c&bcevod_channel=searchbox_feed&pd=1&cr=1&cd=0&pt=4&logid=2854467125&vid=5483608480722064830&abtest=all'
  },
  {
    title: '当你阅读到这段文字的时候,我早已入睡,当我在睡梦中惊醒,你却早已安然睡去',
    videoUrl: 'https://vd2.bdstatic.com/mda-pmexhyfui3e6rbmd/hd/cae_h264/1702705379314308540/mda-pmexhyfui3e6rbmd.mp4?auth_key=1703155684-0-0-5b0145fb4c2ec2f0d1bbd525ddb3d592&bcevod_channel=searchbox_feed&pd=1&cr=1&cd=0&pt=4&logid=2884962294&vid=3059586091403538183&abtest=all'
  },
  {
    title: '每个人的内心都有一段独处的幽闭,不可诉说的窒息感孤独感在每当我们沉静下来的时候变愈发强烈',
    videoUrl: 'https://vd3.bdstatic.com/mda-pmbgjjpkihkf7tjd/576p/h264/1702381478247675613/mda-pmbgjjpkihkf7tjd.mp4?v_from_s=hkapp-haokan-hbf&auth_key=1703155722-0-0-ea3c2453fbbb2cca66b12e9afe3d419f&bcevod_channel=searchbox_feed&pd=1&cr=1&cd=0&pt=4&logid=2922207105&vid=9050628586030215591&abtest=all'
  },
  {
    title: '如果在未来的某一天,某一个早晨 晚上 瞬间,你会偶然想起多年前的一段往事,其实并不是我们有多怀旧,只是因为我们走过了太多的路',
    videoUrl: 'https://vd2.bdstatic.com/mda-pj7ktq9euqchetdc/cae_h264/1696824500894354779/mda-pj7ktq9euqchetdc.mp4?v_from_s=hkapp-haokan-hbf&auth_key=1703155751-0-0-fccb0f110a3b447af67eb0feeabf06ad&bcevod_channel=searchbox_feed&pd=1&cr=0&cd=0&pt=4&logid=2951492012&vid=12162674818438199896'
  },
  {
    title: '什么是知己,有个网红说,当你理解所有人的时候,你一定不能被所有人理解,每个人都或多或少的自私,只是或多或少而已',
    videoUrl: 'https://vd3.bdstatic.com/mda-pmh5hr95fg6u8u0k/hd/cae_h264/1702877143957184120/mda-pmh5hr95fg6u8u0k.mp4?v_from_s=hkapp-haokan-hbf&auth_key=1703155785-0-0-5cfc2be95d00306082c7875a747dd998&bcevod_channel=searchbox_feed&pd=1&cr=1&cd=0&pt=4&logid=2985314718&vid=2720370579167170031&abtest=all'
  }
]

4.3 绘画能力-画布组件

  • ArkUI里面的画布和前端的Canvas的用法基本一致
  • 使用方法

1. 放置Canvas组件-给宽和高

2. 初始化画笔对象 CanvasRenderingContext2D,将画笔对象作为构造参数传递给Canvas组件

3. 可以在Canvas的onReady事件中进行动态绘制

4. 绘制方法官方文档

  • 了解绘画的基本条件:画布、画笔、绘制方法
@Entry
  @Component
  struct CanvasCase {
    // 2.准备一根笔,传入画布
    myPen: CanvasRenderingContext2D = new CanvasRenderingContext2D()

    drawLine() {
      // moveTo:笔离开画布移动
      this.myPen.moveTo(0, 0)
      // moveTo:笔在画布移动
      this.myPen.lineTo(100, 100)
      // 线宽
      this.myPen.lineWidth = 4
      // 线条颜色
      this.myPen.strokeStyle = 'red'
      // 绘制
      this.myPen.stroke()
    }

    build() {
      Row() {
        Column() {
          // 1.准备一个画布
          Canvas(this.myPen)
            .width('100%')
            .height(300)
            .backgroundColor(Color.Gray)
            .onReady(() => {
              // 3.准备好后就可以进行绘画了
              this.drawLine()
            })
        }
        .width('100%')
      }
      .height('100%')
    }
  }

绘制其他内容

@Entry
  @Component
  struct CanvasCase2 {
    // 2.准备一根笔,传入画布
    myPen: CanvasRenderingContext2D = new CanvasRenderingContext2D()
    @State
    canvasWidth: number = 0
    @State
    canvasHeight: number = 0

    // 清空画布
    drawClear() {
      this.myPen.clearRect(0, 0, this.canvasWidth, this.canvasHeight)
    }

    // 画线
    drawLine() {
      this.myPen.beginPath()
      // moveTo:笔离开画布移动
      this.myPen.moveTo(0, 0)
      // moveTo:笔在画布移动
      this.myPen.lineTo(100, 100)
      // 线宽
      this.myPen.lineWidth = 4
      // 线条颜色
      this.myPen.strokeStyle = 'red'
      // 绘制
      this.myPen.stroke()
      this.myPen.closePath()
    }

    // 画圆
    drawCircle() {
      this.myPen.beginPath()
      this.myPen.lineWidth = 2
      this.myPen.arc(this.canvasWidth / 2, this.canvasHeight / 2, 100, 0, 360)
      this.myPen.stroke()
      this.myPen.closePath()
    }

    // 画矩形
    drawRect() {
      this.myPen.beginPath()
      this.myPen.lineWidth = 2
      this.myPen.strokeRect(50, 50, 100, 80)
      // 实心
      // this.myPen.fillRect(50,50,100,80)
      this.myPen.closePath()
    }

    // 画贝塞尔曲线
    drawBezierCurve() {
      this.myPen.beginPath()
      this.myPen.lineWidth = 2
      this.myPen.moveTo(50, 50)
      this.myPen.bezierCurveTo(100, 233, 30, 327, 111, 343)
      this.myPen.stroke()
      this.myPen.closePath()
    }

    // 画文字
    drawText(){
      this.myPen.beginPath()
      this.myPen.font = '100px  sans-serif '
      this.myPen.fillText('精忠报国',this.canvasWidth/2,this.canvasHeight/2)
      this.myPen.closePath()
    }

    //画图
    drawImage(){
      this.myPen.beginPath()
      this.myPen.drawImage(new ImageBitmap('/assets/1.webp'),0,0)
      this.myPen.closePath()
    }

    build() {
      Column({ space: 15 }) {
        // 1.准备一个画布
        Canvas(this.myPen)
          .width('100%')
          .height(580)
          .backgroundColor(Color.Gray)
          .onReady(() => {
            // 3.准备好后就可以进行绘画了
            // this.drawLine()
          })
          .onAreaChange((_, _val) => {
            this.canvasWidth = _val.width as number
            this.canvasHeight = _val.height as number
          })
        Flex({ wrap: FlexWrap.Wrap }) {
          Button('清空')
            .onClick(() => {
              this.drawClear()
            })
          Button('画线')
            .onClick(() => {
              this.drawLine()
            })
          Button('画圆')
            .onClick(() => {
              this.drawCircle()
            })
          Button('画矩形')
            .onClick(() => {
              this.drawRect()
            })
          Button('画曲线')
            .onClick(() => {
              this.drawBezierCurve()
            })
          Button('画文字')
            .onClick(() => {
              this.drawText()
            })
          Button('画图')
            .onClick(() => {
              this.drawImage()
            })
        }.width('100%')
      }
      .width('100%')
        .height('100%')
    }
  }

4.4 签字版

  • 接下来需要处理什么时候开始在画板上画的时机问题了
  • Canvas有一个onTouch事件, 里面包含 按下,抬起,移动等事件,我们认为按下,表示开始画,抬起表示动作结束,移动表示正式绘制,尝试用事件来测试一下

Canvas(this.context)
         .width(360)
         .height(300)
         .backgroundColor(Color.Pink)
         .onTouch((event: TouchEvent) => {
           if(event.type === TouchType.Down) {
             promptAction.showToast({ message: '开始绘画' })
           }
           if(event.type === TouchType.Move) {
             promptAction.showToast({ message: '绘画中' })
           }
           if(event.type === TouchType.Up) {
             promptAction.showToast({ message: '结束绘画' })
           }
         })

  • 实现绘画

      .onReady(() => {
            this.myPen.lineWidth = 2
            this.myPen.strokeStyle = 'red'
          })
      .onTouch((event) => {
            if (event.type === TouchType.Down) {
              this.myPen.beginPath()
              this.myPen.moveTo(event.touches[0].x, event.touches[0].y)
            } else if (event.type === TouchType.Move) {
              this.myPen.lineTo(event.touches[0].x, event.touches[0].y)
              this.myPen.stroke()
            } else if (event.type === TouchType.Up) {
              this.myPen.closePath()
            }
          })

实现保存图片和清空画布方法,画布的高度需要使用onAreaChange的方式来获取

 .onAreaChange((oldArea, newArea) => {
          this.canvasWidth = newArea.width as number
          this.canvasHeight = newArea.height as number
        })

  • 按钮调用对应的方法

 Row () {
        Button("清空画布")
          .onClick(() => {
            this.clearCanvas()
          })
        Button("保存图片")
          .onClick(() => {
            this.savePicture()
          })
      }

  • 清屏方法

  @State
  canvasWidth: number = 0
  @State
  canvasHeight: number = 0
  // 清空画布
  drawClear() {
    this.myPen.clearRect(0, 0, this.canvasWidth, this.canvasHeight)
  }

  • 存储图片

存储图片是将canvas转成base64,可以直接用于展示

 Button("存储图片")
          .onClick(() => {
              this.imageUrl = this.context.toDataURL("image/jpg")
          })

也可以将图片写入沙箱后展示,需要将base64 -> buffer

 Button("存储图片")
          .onClick(() => {
              // 使用下载到沙箱的图片
              let img = this.myPen.toDataURL('image/jpg')
              const filePath = getContext().tempDir + "/" + Date.now() + '.jpeg'
              const file = fileIo.openSync(filePath, fileIo.OpenMode.CREATE | fileIo.OpenMode.READ_WRITE)
              const base64Image = img.split(';base64,').pop();
              // 将base64数据解码为二进制数据
              const imgBuffer = buffer.from(base64Image, "base64")
              fileIo.writeSync(file.fd, imgBuffer.buffer)
              fileIo.closeSync(file)
              this.imageUrl = "file://" + filePath
          })

如果希望手势移动渲染的更加丝滑,可以给画笔加上抗锯齿处理

  context: CanvasRenderingContext2D = new CanvasRenderingContext2D(new RenderingContextSettings(true))

  • 完整代码
import { fileIo } from '@kit.CoreFileKit'
import { buffer } from '@kit.ArkTS'

@Entry
  @Component
  struct SignBoardCase {
    myPen: CanvasRenderingContext2D = new CanvasRenderingContext2D(new RenderingContextSettings(true))
    @State
    canvasWidth: number = 0
    @State
    canvasHeight: number = 0

    @State
    imageUrl:string = ''
    // 清空画布
    drawClear() {
      this.myPen.clearRect(0, 0, this.canvasWidth, this.canvasHeight)
    }

    build() {
      Row() {
        Column({ space: 20 }) {
          Text('签字板')
          Canvas(this.myPen)
            .width('100%')
            .height(300)
            .backgroundColor(Color.Pink)
            .onReady(() => {
              this.myPen.lineWidth = 2
              this.myPen.strokeStyle = 'red'
            })
            .onAreaChange((_, _val) => {
              this.canvasWidth = _val.width as number
              this.canvasHeight = _val.height as number
            })
            .onTouch((event) => {
              if (event.type === TouchType.Down) {
                this.myPen.beginPath()
                this.myPen.moveTo(event.touches[0].x, event.touches[0].y)
              } else if (event.type === TouchType.Move) {
                this.myPen.lineTo(event.touches[0].x, event.touches[0].y)
                this.myPen.stroke()
              } else if (event.type === TouchType.Up) {
                this.myPen.closePath()
              }
            })
          if(this.imageUrl){
            Image(this.imageUrl)
              .width('100%')
          }
          Row({ space: 20 }) {
            Button('保存')
              .onClick(() => {
                // 使用canvas转化的图片
                // this.imageUrl = this.myPen.toDataURL('image/jpg')
                // 使用下载到沙箱的图片
                let img = this.myPen.toDataURL('image/jpg')
                const filePath = getContext().tempDir + "/" + Date.now() + '.jpeg'
                const file = fileIo.openSync(filePath, fileIo.OpenMode.CREATE | fileIo.OpenMode.READ_WRITE)
                const base64Image = img.split(';base64,').pop();
                // 将base64数据解码为二进制数据
                const imgBuffer = buffer.from(base64Image, "base64")
                fileIo.writeSync(file.fd, imgBuffer.buffer)
                fileIo.closeSync(file)
                this.imageUrl = "file://" + filePath
              })
            Button('重签')
              .onClick(() => {
                this.drawClear()
                this.imageUrl = ''
              })
          }
        }
        .width('100%')
      }
      .height('100%')
    }
  }

HarmonyOS赋能资源丰富度建设(第四期)-吴东林

https://developer.huawei.com/consumer/cn/training/classDetail/9fdeeb1a35d64d2fabad3948ae7aab72?type=1?ha_source=hmosclass&ha_sourceId=89000248

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐