【每日学点鸿蒙知识】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%')
}
}}