DEV Community

Cover image for [Daily HarmonyOS Next Knowledge] Video Playback Failure, Toggle Dragging, Image Cropping/Rotation, etc.
kouwei qing
kouwei qing

Posted on

[Daily HarmonyOS Next Knowledge] Video Playback Failure, Toggle Dragging, Image Cropping/Rotation, etc.

[Daily HarmonyOS Next Knowledge] Video Playback Failure, Toggle Dragging, Image Cropping/Rotation, etc.

1. How to change the size, color, and position of the passwordIcon in TextInput's password input mode?

Use a Stack container as the parent and an Image component as the child to customize the passwordIcon. This allows you to adjust the Image's position, size, and color.

@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) // Set padding to prevent input from overlapping the icon
            .padding({
              right: 50
            })
            .onChange((value: string) => {
              this.text = value
            })
          // Override passwordIcon with custom Image
          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')
  }
}
Enter fullscreen mode Exit fullscreen mode

2. How to disable the default click gesture when dragging the Toggle component?

Manually control the Toggle's state by creating a custom boolean variable (e.g., toggleIsOn) and updating it in the onChange callback.

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 callback fires before 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;
          })
      )
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

3. Why does Video fail to play immediately after setting a local video source?

There is a loading delay between setting the video source and being able to play. Playback should be triggered in the onPrepared callback after the video is ready.

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(`Current playback time: ${this.currentTime}`)

      Button('Play immediately')
        .onClick(()=>{
          if(this.videoUrl === '') {
            // Set URL to local video path in app sandbox
            this.videoUrl = 'file:///data/storage/el2/base/files/xxxx.mp4';
            // Problem: Calling start() immediately after setting source fails to play
            if (this.prepared === true) {
              this.videoVC.start();
            }
          }
        })

      Button('Play after 100ms delay')
        .onClick(()=>{
          if(this.videoUrl === '') {
            this.videoUrl = 'file:///data/storage/el2/base/files/xxxx.mp4';
            // Delay starting playback slightly (100ms) after setting source
            setTimeout(()=>{
              this.videoVC.start();
            }, 100)
          }
        })


      Button('Reset playback')
        .fontSize(10)
        .fontColor(Color.White)
        .onClick(()=>{
          this.videoVC.stop();
          this.videoUrl = '';
        })
    }
    .width('100%')
    .height('100%')
  }
}
Enter fullscreen mode Exit fullscreen mode

4. How to implement image cropping and rotation?

Use Canvas combined with media image processing. Layer three Canvases:

  1. Draw the original image and get its container coordinates via OnAreaChange.
  2. Draw a mask to distinguish the cropping area.
  3. Draw a resizable crop frame with drag gestures to define the crop area.
// Draw background image
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();
  }
}

// Draw mask layer
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);
}

// Draw crop frame
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();
}

// Crop image
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
}

// Rotate image
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
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

5. How to access sandbox paths?

The Image component requires a sandbox URI instead of a direct sandbox path. Convert the path using fileuri.getUriFromPath().

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

// Get app file directory
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('Show Image')
          .onClick(() => {
            try {
              // Convert file path to sandbox 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%')
    }
  }}
Enter fullscreen mode Exit fullscreen mode

Top comments (0)