[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')
}
}
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;
})
)
}
}
}
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%')
}
}
4. How to implement image cropping and rotation?
Use Canvas combined with media image processing. Layer three Canvases:
- Draw the original image and get its container coordinates via
OnAreaChange
. - Draw a mask to distinguish the cropping area.
- 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
}
}
}
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%')
}
}}
Top comments (0)