基于ArkTS语言的OpenHarmony APP应用开发:图片处理 原创
1、程序介绍
本案例使用TextArea实现多文本输入,使用mediaLibrary实现在相册中获取图片,使用image生成pixelMap,使用pixelMap的scale(),crop(),rotate()接口实现对图片的缩放,裁剪,旋转功能。
案例说明:
-
发表评价页面点击添加图片/照片,页面跳转到图片选择页面。
-
进入图片选择页面后,选择需要显示的图片,最多选择6张图片。
-
选中图片后点击下一步,页面会跳转到图片编辑页面,点击缩放,页面会显示缩小,放大按钮,点击按钮,可对图片进行缩小,放大操作。点击裁剪,页面会跳出裁剪比例,点击想要裁剪的比例可以对图片进行裁剪。点击旋转可对图片进行旋转。
-
图片编辑完成后,点击确认,页面会跳转到发表评价页面,显示相关照片。点击添加图片/照片可以对图片进行重新选择。
-
点击返回按钮,退出应用。
本案例已在OpenHarmony凌蒙派-RK3568开发板验证通过,具体代码可参考:https://gitee.com/Lockzhiner-Electronics/lockzhiner-rk3568-openharmony/tree/master/samples/d07_Image
API接口:9
2、知识准备
2.1、TextArea
多行文本输入框组件,当输入的文本内容超过组件宽度时会自动换行显示。
function TextArea(value?:{placeholder?: ResourceStr, text?: ResourceStr, controller?: TextAreaController})
其中,参数定义如下:
参数名 | 参数类型 | 必填 | 参数描述 |
---|---|---|---|
placeholder | ResourceStr | 否 | 设置无输入时的提示文本 |
text | ResourceStr | 否 | 设置输入框当前的文本内容 |
controller | TextAreaController | 否 | 设置TextArea控制器 |
其中,属性可支持如下:
名称 | 参数类型 | 描述 |
---|---|---|
placeholderColor | ResourceColor | 设置placeholder文本颜色 |
placeholderFont | Font | 设置plaoceholder文本样式 |
textAlign | TextAlign | 设置文本在输入框中的水平对齐式<br>默认值:TextAlign.Start |
caretColor | ResourceColor | 设置输入框光标颜色 |
inputFilter | {<br>value: ResourceStr,<br>error?: (value: string) => void<br>} | 通过正则表达式设置输入过滤器。匹配表达式的输入允许显示,不匹配的输入将被过滤。仅支持单个字符匹配,不支持字符串匹配。<br/>- value:设置正则表达式。<br/>- error:正则匹配失败时,返回被过滤的内容。 |
copyOption | CopyOptions | 设置输入的文本是否可复制。<br/>设置CopyOptions.None时,当前TextArea中的文字无法被复制或剪切,仅支持粘贴。 |
2.2、mediaquery
ohos.mediaquery提供媒体查询,提供根据不同媒体类型定义不同的样式。
2.2.1、导入模块
import mediaquery from '@ohos.mediaquery'
2.2.2、 mediaquery.matchMediaSync
function matchMediaSync(condition: string): MediaQueryListener
设置媒体查询的查询条件,并返回对应的监听句柄。
参数定义如下所示:
参数名 | 类型 | 必填 | 说明 |
---|---|---|---|
condition | string | 是 | 媒体事件的匹配事件 |
返回值定义如下所示:
类型 | 说明 |
---|---|
MediaQueryListener | 媒体事件监听句柄,用于注册和销毁监听回调 |
2.2.3、MediaQueryListener
媒体查询的句柄,并包含了申请句柄时的首次查询结果。
系统能力: SystemCapability.ArkUI.ArkUI.Full
(1)属性
名称 | 类型 | 可读 | 可写 | 说明 |
---|---|---|---|---|
matches | boolean | 是 | 否 | 是否符合匹配条件 |
media | string | 是 | 否 | 媒体事件的匹配条件 |
(2)on函数
function on(type: 'change', callback: Callback<MediaQueryResult>): void
通过句柄向对应的查询条件注册回调,当媒体属性发生变更时会触发该回调。
系统能力: SystemCapability.ArkUI.ArkUI.Full
参数:
参数名 | 类型 | 必填 | 说明 |
---|---|---|---|
type | string | 是 | 必须填写字符串change |
callback | Callback<MediaQueryResult> | 是 | 向媒体查询注册的回调 |
(3)off函数
function off(type: 'change', callback?: Callback<MediaQueryResult>): void
通过句柄向对应的查询条件去注册回调,当媒体属性发生变更时不在触发指定的回调。
系统能力: SystemCapability.ArkUI.ArkUI.Full
参数:
参数名 | 类型 | 必填 | 说明 |
---|---|---|---|
type | string | 是 | 必须填写字符串change |
callback | Callback<MediaQueryResult> | 否 | 需要去注册的回调,如果参数缺省则去注册该句柄下所有的回调 |
2.2.4、MediaQueryResult
用于执行媒体查询操作。
系统能力: SystemCapability.ArkUI.ArkUI.Full
(1)属性
名称 | 类型 | 可读 | 可写 | 说明 |
---|---|---|---|---|
matches | boolean | 是 | 否 | 是否符合匹配条件 |
media | string | 是 | 否 | 媒体事件的匹配条件 |
2.3、mediaLibrary
MediaLibrary支持能力列举如下:
- 查询音频、视频和图片文件元数据信息
- 查询图片和视频相册
- 媒体文件操作如创建、重命名、拷贝和删除
- 相册操作如创建、重命名和删除
MediaLibrary整体框架,如下所示:
详细请参考:官方文档
2.4、Grid
网格容器,由“行”和“列”分割的单元格所组成,通过指定“项目”所在的单元格做出各种各样的布局。
详细请参考:官方文档
2.5、@ohos.multimedia.image
本模块提供图片处理效果,包括通过属性创建PixelMap、读取图像像素数据、读取区域内的图片数据等。
详细请参考:官方文档
2.6、@ohos.router
本模块提供通过不同的url访问不同的页面,包括跳转到应用内的指定页面、用应用内的某个页面替换当前页面、返回上一页面或指定的页面等。
详细请参考:官方文档
3、程序解析
3.1、申请设备权限
配置文件module.json5中声明权限,申请应用读取、写入用户外部存储中的媒体文件信息以及允许应用访问用户媒体文件中的地理位置信息。
"requestPermissions": [
{
"name": "ohos.permission.READ_MEDIA",
"reason": "$string:media_read_permission",
"usedScene": {
"abilities": [
"MainAbility"
],
"when": "inuse"
}
},
{
"name": "ohos.permission.WRITE_MEDIA",
"reason": "$string:media_write_permission",
"usedScene": {
"abilities": [
"MainAbility"
],
"when": "inuse"
}
},
{
"name": "ohos.permission.MEDIA_LOCATION",
"reason": "$string:media_location_permission",
"usedScene": {
"abilities": [
"MainAbility"
],
"when": "inuse"
}
}
]
3.2、Index.ets
首先,声明1个pixelMaps数组,用于存放照片或视频。
@StorageLink('pixelMaps') pixelMaps: Array<image.PixelMap> = []
注意:@StorageLink('pixelMaps')
表明通过使用@StorageLink(key)装饰的状态变量,与AppStorage建立双向数据绑定。
其次,在build()
调用之前运行aboutToAppear()
,负责获取AppStorage的pixelMaps属性,然后往当前页传入isShowCamera
参数。
aboutToAppear() {
logger.info(TAG, 'enter aboutToAppear')
// 与AppStorage建立双向数据绑定,获取pixelMaps属性
if (AppStorage.Get('pixelMaps')) {
this.pixelMaps.push(undefined)
}
// 获取发起跳转的页面往当前页传入的参数
if (router.getParams() && router.getParams()['isShowCamera']) {
this.isShowCamera = true
}
}
最后,build()
显示整个页面。
其中,已选图片通过Grid和GridItem控件排列显示。
// 使用网格容器将已选照片显示出现
Grid() {
// 轮流显示成1行照片
ForEach(this.pixelMaps, (item, index) => {
GridItem() {
if (index < this.pixelMaps.length - 1) {
Image(item)
.width('100%')
.height(100)
.borderRadius(10)
} else {
// 最后一个为"添加图片/照片"
Column() {
Image($r('app.media.photo'))
.height(40)
.width(40)
.onClick(() => {
router.push({
url: 'pages/ChoicePhotos'
})
})
Text($r('app.string.add_picture'))
.fontSize(13)
}
.alignItems(HorizontalAlign.Center)
.justifyContent(FlexAlign.Center)
.width('100%')
.height(100)
.borderRadius(10)
.backgroundColor('#F1F3F5')
}
}
})
}
.columnsTemplate('1fr 1fr 1fr 1fr')
.columnsGap(8)
.rowsGap(8)
.margin({ top: 8 })
.width('94%')
.height(105 * (this.pixelMaps.length > 4 ? 2 : 1))
3.3、ChoicePhotos.ets
该页面为选择照片页面。该页面通过调用ChoicePhoto()
显示页面。
@Entry @Component struct ChoicePhotos {
build() {
Column() {
ChoicePhoto()
}
}
}
ChoicePhoto()
主要负责显示照片视频以及添加等功能的界面。
首先,声明1个MediaLibrary类对象和监听句柄listener,用于查询音频、视频和图片文件元数据信息。
private mediaLibraryInstance: mediaLibrary.MediaLibrary = mediaLibrary.getMediaLibrary(getContext(this) as any)
private listener = mediaQuery.matchMediaSync('screen and (min-aspect-ratio: 1.5) or (orientation: landscape)')
其次,调用build()之前做初始化工作,主要是开启监听句柄listener的change事件,并获取设备端保存的图片资源。
async aboutToAppear() {
logger.info(TAG, `aboutToAppear`)
this.listener.on('change', this.onLand)
this.nextText = await this.convertResourceToString($r('app.string.next'))
// 获取图片的文件信息
this.getFileAssetsFromType(mediaLibrary.MediaType.IMAGE)
}
其中,getFileAssetsFromType()获取设备端所有的图片或视频的文件信息。
async getFileAssetsFromType(mediaType: mediaLibrary.MediaType) {
logger.info(TAG, `getFileAssetsFromType`)
let context = getContext(this) as any
// 获取媒体库的实例,用于访问和修改用户等个人媒体数据信息(如音频、视频、图片、文档等)
let mediaLibraryInstance = mediaLibrary.getMediaLibrary(context)
if (!mediaLibraryInstance) {
return
}
logger.info(TAG, `mediaLibraryInstance = ${JSON.stringify(mediaLibraryInstance)}`)
// 创建文件获取选项,此处参数为获取image类型的文件资源
let fileKeyObj = mediaLibrary.FileKey
let fetchOp = {
selections: `${fileKeyObj.MEDIA_TYPE}=?`,
selectionArgs: [`${mediaType}`],
}
// 获取文件资源
let fetchFileResult = await mediaLibraryInstance.getFileAssets(fetchOp)
logger.info(TAG, `fetchFileResult = ${JSON.stringify(fetchFileResult)} , ${fetchFileResult.getCount()}`)
if (fetchFileResult && fetchFileResult.getCount() > 0) {
this.medias = await fetchFileResult.getAllObject()
}
logger.info(TAG, `this.medias = ${JSON.stringify(this.medias)}`)
}
再次,使用Grid和GridItem控件显示图片或视频。
Grid() {
ForEach(this.medias, (item, index) => {
GridItem() {
Stack({ alignContent: Alignment.TopEnd }) {
Image(item.uri)
.width('100%')
.height('100%')
.borderRadius(10)
.objectFit(ImageFit.Fill)
if (this.isShowChoices[index]) {
this.showChoiceBuild('#e92f4f', this.choiceMedias.indexOf(item) + 1)
} else {
this.showChoiceBuild('#ffb7b4b4', 0)
}
}
.width('100%')
.height('100%')
.onClick(() => {
this.isShowChoices[index] = !this.isShowChoices[index]
if (this.isShowChoices[index]) {
if (this.choiceMedias.length > 5) {
prompt.showDialog({ message: $r('app.string.choice_number') })
this.isShowChoices[index] = !this.isShowChoices[index]
return
}
this.choiceMedias.push(item)
} else {
if (this.choiceMedias.indexOf(item) != -1) {
this.choiceMedias.splice(this.choiceMedias.indexOf(item), 1)
}
}
})
}
.aspectRatio(1)
})
}
最后,定义1个按键事件。一旦选中某个图片,可进入图片编辑页面。
Button(`${this.nextText} ${this.isChoice === true ? `(${this.choiceMedias.length})` : ''}`)
.fontSize(20)
.height(32)
.backgroundColor(this.isChoice === true ? '#E92F4F' : '#fffa8e8e')
.margin({ right: 10 })
.borderRadius(20)
.onClick(() => {
if (this.isChoice === false) {
return
}
this.mediaUris = this.choiceMedias.map((item) => {
return item.uri
})
// 跳转到图片编辑页面
router.push({
url: 'pages/EditPages',
params: { mediaUris: this.mediaUris, isLand: this.isLand }
})
})
4、项目编译
4.1、打开项目
打开DevEco Studio,再打开自定义通知项目。
4.2、编译程序
点击菜单栏上的“Build” -> “Rebuild Project”。如果出现无法编译,则注意查看Event Log界面。如下所示:
点击Run 'npm install'
,让DevEco Studio安装相关依赖包。
重新点击菜单栏上的“Build” -> “Rebuild Project”。出现如下错误:
点击上图红色框部分,安装相关服务。
重新点击菜单栏上的“Build” -> “Rebuild Project”,编译成功。
4.3、安装程序
点击“entry”按钮,将项目程序安装到设备端。如下图所示:
如果出现下述报错,表示无法安装。如图所示:
点击上图红色框的蓝色字体,弹出"Project Structure"对话框,点击"Apply",再点击"OK"。如图所示:
重新点击“entry”按钮,将项目程序安装到设备端。