【每日学点鸿蒙知识】Video播放失败、toggle拖拽、图片裁剪旋转等

1、如何更改TextInput密码输入模式下passwordIcon的大小、颜色、位置?

使用stack容器作为父容器,子容器使用image来实现自定义一个passwordIcon,此时即可对Image组件的位置、大小、颜色做出更改。

@Entry
@Component
struct TextInputExample {
  @State text: string = ''
  @State changeType: InputType = InputType.Password
  @State isVisible: boolean = false
  @State changeState: boolean = false
  controller: TextInputController = new TextInputController()

  build() {
    Column() {
      Flex({ direction: FlexDirection.Row }) {
        Stack() {
          TextInput({ text: this.text, controller: this.controller })
            .type(this.changeType)
            .placeholderFont({ size: 16, weight: 400 })
            .showPasswordIcon(false)
            .width(336)
            .height(56) // 设置内间距让输入内容不超过图标位置
            .padding({
              right: 50
            })
            .onChange((value: string) => {
              this.text = value
            })
          // Image覆盖passwordIcon实现
          Image($r(this.isVisible ? 'app.media.visible' : 'app.media.Invisible'))
            .margin({
              left: 280
              // left: 200
            })
            .backgroundColor('#E7E8EA')
            .width(20)
            .height(20)
            .onClick(() => {
              this.changeState = !this.changeState
              this.isVisible = !this.isVisible
              if (this.changeState) {
                this.changeType = InputType.Normal
              } else {
                this.changeType = InputType.Password
              }
            })
        }
      }
    }.width('100%').height('100%').backgroundColor('#F1F3F5')
  }
}

2、toggle组件设置拖动的同时如何屏蔽其本身的点击手势?

需要手动控制Toggle组件传入的默认值,例如在最上面自定义一个toggleIsOn的boolean变量然后,在onChange回调里面自己手动控制改变其的值。

import { hilog } from '@kit.PerformanceAnalysisKit';

@Entry
@Component
export struct TestDragToggle {
  @State offsetX: number = 0;
  @State offsetY: number = 0;
  @State positionX: number = 0;
  @State positionY: number = 0;
  @State toggleIsOn: boolean = true;
  private isDragging: boolean = false;

  build() {
    Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center }) {
      Toggle({ type: ToggleType.Button, isOn: this.toggleIsOn }) {
        Text('Toggle')
      }
      .selectedColor(Color.Pink)
      // onchange回调先于onActionEnd
      .onChange((isOn: boolean) => {
        hilog.info(0x0000, 'testTag', 'xxx %{public}s', `onClick Toggle, isOn: ${isOn}`);
        console.info('isDragging======' + this.isDragging)
        if (isOn == this.toggleIsOn) {
          return
        } else {
          this.toggleIsOn = isOn
        }
        if (this.isDragging) {
          this.toggleIsOn = !this.toggleIsOn
        }
      })
      .translate({ x: this.offsetX, y: this.offsetY })
      .gesture(
        PanGesture()
          .onActionStart((event: GestureEvent) => {
            this.isDragging = true;
          })
          .onActionUpdate((event: GestureEvent) => {
            this.offsetX = this.positionX + event.offsetX;
            this.offsetY = this.positionY + event.offsetY;
          })
          .onActionEnd((event: GestureEvent) => {
            this.positionX = this.offsetX;
            this.positionY = this.offsetY;
            this.isDragging = false;
          })
      )
    }
  }
}

3、在使用video组件时,为video添加本地视频播放源后,立刻播放,为什么会播放失败?

从给video加载资源,到video播放,中间必须要加载,这个加载需要耗时。点击按钮添加资源,等资源准备完毕后,才会自动播放,因此需要把播放逻辑写在video的onPrepared回调里。

import { common } from '@kit.AbilityKit';

@Entry
@Component
struct LocalSource {
  @State videoUrl: ResourceStr = '';
  @State currentTime: number = 0;
  private context = getContext(this) as common.UIAbilityContext;
  @State prepared : boolean = false;

  private  videoVC = new VideoController();

  build() {
    Column({space: 20}) {
      Video({
        src: this.videoUrl,
        // src: 'file:///data/storage/el2/base/files/xxxx.mp4',
        controller: this.videoVC
      })
        .onPrepared(()=>{
          this.prepared = true;
          this.videoVC.start()
          console.info('onPrepared')
        })
        .onError(() => {
          console.info('onError')
        })
        .objectFit(ImageFit.Contain)
        .onUpdate((e)=>{
          this.currentTime = e.time;
        })
        .controls(false)
        .width('70%')
        .height(300)

      Text(`当前播放时间: ${this.currentTime}`)
      
      Button('立刻播放')
        .onClick(()=>{
          if(this.videoUrl === '') {
            // 设置url为应用沙箱中的本地视频路径
            this.videoUrl = 'file:///data/storage/el2/base/files/xxxx.mp4';
            // 问题: 首次启动,点击按钮设置播放资源路径后, 立即调用start方法, 无法正常播放视频ex
            if (this.prepared === true) {
              this.videoVC.start();
            }
          }
        })

      Button('延迟100ms播放')
        .onClick(()=>{
          if(this.videoUrl === '') {
            this.videoUrl = 'file:///data/storage/el2/base/files/xxxx.mp4';
            // 但是设置完资源路径后, 稍微延迟下这里延迟100ms, 再调用start方法就可以正常播放
            setTimeout(()=>{
              this.videoVC.start();
            }, 100)
          }
        })


      Button('重置播放')
        .fontSize(10)
        .fontColor(Color.White)
        .onClick(()=>{
          this.videoVC.stop();
          this.videoUrl = '';
        })
    }
    .width('100%')
    .height('100%')
  }
}

4、如何实现图片裁剪、旋转?

使用Canvas与媒体的图片处理结合来实现,Canvas层叠三层绘制,第一层绘制图片后通过OnAreaChange事件获取图片所在容器的坐标,从而确定二层、三层的Canvas画布所在位置。第二层绘制遮罩层实现裁剪区域与非裁剪区域区分显示。第三层绘制裁剪框,结合OnTouch事件实现可拖拽裁剪框,从而确定裁剪区域。

// 绘制背景图
async drawImage() {
  await this.initData('test.jpg')
  if (this.imageInfo != undefined) {
    this.canvasContext.drawImage(this.pixelMap, 0, 0, px2vp(this.imageInfo.size.width),
      px2vp(this.imageInfo.size.height));
    this.canvasContext.save();
  }
}

// 绘制蒙层
drawMask() {
  this.canvasContext3.clearRect(0, 0, this.imageInfo?.size.width, this.imageInfo?.size.height);
  this.canvasContext3.fillStyle = 'rgba(0,0,0,0.7)';
  this.canvasContext3.fillRect(0, 0, px2vp(this.imageInfo?.size.width), px2vp(this.imageInfo?.size.height));
  this.canvasContext3.clearRect(this.clipRect.x - this.initPosition.x, this.clipRect.y - this.initPosition.y,
    this.clipRect.width, this.clipRect.height);
}

// 绘制裁剪框
drawClipImage() {
  this.canvasContext2.clearRect(0, 0, this.clipRect.width, this.clipRect.height);
  this.canvasContext2.lineWidth = 6
  this.canvasContext2.strokeStyle = '#ffffff'
  this.canvasContext2.beginPath()
  this.canvasContext2.moveTo(0, 20)
  this.canvasContext2.lineTo(0, 0);
  this.canvasContext2.lineTo(20, 0);
  this.canvasContext2.moveTo(this.clipRect.width - 20, 0);
  this.canvasContext2.lineTo(this.clipRect.width, 0);
  this.canvasContext2.lineTo(this.clipRect.width, 20);
  this.canvasContext2.moveTo(0, this.clipRect.height - 20);
  this.canvasContext2.lineTo(0, this.clipRect.height);
  this.canvasContext2.lineTo(20, this.clipRect.height);
  this.canvasContext2.moveTo(this.clipRect.width - 20, this.clipRect.height);
  this.canvasContext2.lineTo(this.clipRect.width, this.clipRect.height);
  this.canvasContext2.lineTo(this.clipRect.width, this.clipRect.height - 20);
  this.canvasContext2.stroke()
  this.canvasContext2.beginPath();
  this.canvasContext2.lineWidth = 0.5;
  let height = Math.round(this.clipRect.height / 3);
  for (let index = 0; index <= 3; index++) {
    let y = index === 3 ? this.clipRect.height : height * index;
    this.canvasContext2.moveTo(0, y);
    this.canvasContext2.lineTo(this.clipRect.width, y);
  }
  let width = Math.round(this.clipRect.width / 3);
  for (let index = 0; index <= 3; index++) {
    let x = index === 3 ? this.clipRect.width : width * index;
    this.canvasContext2.moveTo(x, 0);
    this.canvasContext2.lineTo(x, this.clipRect.height);
  }
  this.canvasContext2.stroke();
}

// 裁剪图片
async clipImage() {
  let x = this.clipRect.x - this.initPosition.x;
  let y = this.clipRect.y - this.initPosition.y;
  console.info('x= ' + x + '  y = ' + y + 'height = ' + this.clipRect.height + 'width = ' + this.clipRect.width)
  await this.pixelMap?.crop({
    x: vp2px(x),
    y: vp2px(y),
    size: { height: vp2px(this.clipRect.height), width: vp2px(this.clipRect.width) }
  })
  this.cropImageInfo = await this.pixelMap?.getImageInfo();
  this.isCrop = true
  this.rotateOn = true
}

// 旋转图片
async rotateImage() {
  if (this.rotateOn) {
    await this.pixelMap?.rotate(90)
    const info = await this.pixelMap?.getImageInfo()
    this.cropImageInfo = info
    if (this.pixelMapChange) {
      this.pixelMapChange = false
    } else {
      this.pixelMapChange = true
    }
  }
}

5、如何访问沙盒路径?

Image组件不能直接传入应用沙箱路径,需要传入应用沙箱uri;

拿到文件的沙箱路径后,通过调用调用@ohos.file.fileuri模块的fileuri.getUriFromPath(file.path)将沙箱路径转化为沙箱uri,传入之后即可正常显示。

import { common } from '@kit.AbilityKit';
import { BusinessError, request } from '@kit.BasicServicesKit';
import { fileUri } from '@kit.CoreFileKit';

// 获取应用文件路径
let context = getContext(this) as common.UIAbilityContext;
let filesDir = context.filesDir;

@Entry
@Component
export struct Index11 {
  @State message: string = 'Hello World';
  @State urlImage: ResourceStr = ''
  build() {
    Row() {
      Column() {
        Text(this.message)
          .fontSize(50)
          .fontWeight(FontWeight.Bold)
        Button('展示图片')
          .onClick(() => {
            try {
              // 文件路径转成沙箱uri
              let filePath = filesDir + '/pic.jpg'
              this.urlImage = fileUri.getUriFromPath(filePath);
            } catch (error) {
              let err: BusinessError = error as BusinessError;
              console.error(`Invoke downloadTask downloadFile failed, code is ${err.code}, message is ${err.message}`);
            }
          })
          .width('100%')
        Image(this.urlImage)
      }
      .height('100%')
    }
  }}
相关推荐
小冷爱学习!23 分钟前
华为动态路由-OSPF-完全末梢区域
服务器·网络·华为
2501_904447741 小时前
华为发力中端,上半年nova14下半年nova15,大力普及原生鸿蒙
华为·智能手机·django·scikit-learn·pygame
MarkHD2 小时前
第十八天 WebView深度优化指南
华为·harmonyos
塞尔维亚大汉2 小时前
OpenHarmony(鸿蒙南向)——平台驱动开发【MIPI CSI】
harmonyos·领域驱动设计
别说我什么都不会3 小时前
鸿蒙轻内核M核源码分析系列十五 CPU使用率CPUP
操作系统·harmonyos
feiniao86514 小时前
2025年华为手机解锁BL的方法
华为·智能手机
塞尔维亚大汉4 小时前
OpenHarmony(鸿蒙南向)——平台驱动开发【I3C】
harmonyos·领域驱动设计
VVVVWeiYee4 小时前
BGP配置华为——路径优选验证
运维·网络·华为·信息与通信
今阳6 小时前
鸿蒙开发笔记-6-装饰器之@Require装饰器,@Reusable装饰器
android·app·harmonyos
余多多_zZ7 小时前
鸿蒙初学者学习手册(HarmonyOSNext_API14)_组件截图(@ohos.arkui.componentSnapshot (组件截图) )
学习·华为·harmonyos·鸿蒙·鸿蒙系统