打造一款图片选择控件《ImageSelectView》 原创
当我们做发布帖子或者评论的功能时,往往可以发布图片,这就需要我们开发从手机的相册选择图片的功能。先看效果。
一、图片选择控件ImageSelectView功能
(1)可以控制选择图片数量
(2)选择图片按钮一直处于图片最后一个,且图片数量达到最大,按钮消失
(3)删除已选某张图片
(4)替换某张已选图片
二、读取设备相册涉及的权限问题
一般应用获取“存储”权限后,就能读取到手机内部存储上所有的文件,包含所有的照片,这就产生了隐私风险:它就可以在用户毫无感知的情况下,分析用户的文件和图片,将隐私“偷走“。
但是,用户通常不希望授予应用对其所有照片和视频的访问权限,因此,HarmonyOS在API9以后引入了Picker选择器,在保证用户正常的数据访问述求的同时,最小化减少应用的数据泄露。避免全量数据的授权,降低授权的颗粒度,例如用户在发送图片时,只想让应用访问到用户想要发送的。
所以我们使用系统的PhotoViewPicker来作为图片选择的核心组件,这样既安全也省去了申请权限的步骤。
三、核心逻辑实现
(1)配置可选图片数量
使用状态变量来控制每次选择图片最多选择的数量。以默认最大为9张为例,每次选择图片后,下次可选择图片数量为9减去已选择图片的数量。
@State maxSelectNumber: number = 9 //做多选择n张图片,默认9张
(2)当选择图片数量到达最大时,添加按钮需要消失掉
使用数组来临时存储已选图片的路径,其中最后一个元素为默认的“选择”按钮。我们通过@Watch监听此数组的实时变化。
@State @Watch("onImagesChange") images: string[] = [ADD] //所选图片
(3)如何删除已选图片
使用数组的splice方法,将指定坐标的元素删除
this.images.splice(index, 1)
(4)已选的图片如何将某张图片进行重新选择?
依然使用PhotoViewPicker组件进行图片的选择,将回调中的图片路径替换到指定坐标的元素。
const photoSelectOptions = new photoAccessHelper.PhotoSelectOptions();
photoSelectOptions.MIMEType = photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE; // 过滤选择媒体文件类型为IMAGE
photoSelectOptions.maxSelectNumber = 1
const photoViewPicker = new photoAccessHelper.PhotoViewPicker();
photoViewPicker.select(photoSelectOptions).then(async (photoSelectResult: photoAccessHelper.PhotoSelectResult) => {
if (photoSelectResult.photoUris.length) {
photoSelectResult.photoUris.forEach(e => {
this.images[index] = e//替换指定图片
});
}
}).catch((err: BusinessError) => {
})
}
四、完整代码实现
import { BusinessError } from "@kit.BasicServicesKit"
import { photoAccessHelper } from "@kit.MediaLibraryKit"
let ADD: string = "add"
/**
* 图片选择
*/
@Component
export struct ImageSelectView {
@State @Watch("onImagesChange") images: string[] = [ADD] //所选图片
@State maxSelectNumber: number = 9 //做多选择n张图片,默认9张
@State itemWidth: number = 100
@State itemHeight: number = 100
onSelected?: (images: string[]) => void //选中图片回调方法
onImagesChange() {
if (this.images.length > 9) {
if (this.images.includes(ADD)) {
this.images.pop()
}
}
if (this.images.length < 9) {
if (!this.images.includes(ADD)) {
this.images.push(ADD)
}
}
this.onSelected && this.onSelected(this.images.filter(item => item !== ADD))
}
build() {
Column() {
Grid() {
ForEach(this.images, (e: string, index: number) => {
GridItem() {
Stack() {
Image(e === ADD ? $r("app.media.img_add") : e)
.width(this.itemWidth)
.height(this.itemHeight)
.border({ width: 1, color: e === ADD ? Color.Transparent : Color.Gray, radius: 8 })
.onClick(() => {
if (e === ADD) {
this.pick()
} else {
this.change(index)
}
})
if (e !== ADD) {
Image($r("app.media.delete"))
.width(25)
.height(25)
.backgroundColor(Color.White)
.borderRadius(90)
.onClick(() => {
//删除指定坐标图片
this.images.splice(index, 1)
this.maxSelectNumber++ //删除一张,就可以再选一张
})
}
}.alignContent(Alignment.TopEnd)
}
})
}
// .columnsTemplate('1fr 1fr 1fr')
.columnsGap(5)
.rowsGap(5)
.width("100%")
}.width("100%")
}
pick() {
const photoSelectOptions = new photoAccessHelper.PhotoSelectOptions();
photoSelectOptions.MIMEType = photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE; // 过滤选择媒体文件类型为IMAGE
photoSelectOptions.maxSelectNumber = this.maxSelectNumber; // 选择媒体文件的最大数目
const photoViewPicker = new photoAccessHelper.PhotoViewPicker();
photoViewPicker.select(photoSelectOptions).then(async (photoSelectResult: photoAccessHelper.PhotoSelectResult) => {
if (photoSelectResult.photoUris.length) {
photoSelectResult.photoUris.forEach(e => {
this.images.splice(this.images.length - 1, 0, e)
this.maxSelectNumber--
});
}
}).catch((err: BusinessError) => {
})
}
/**
* 将已选中的某张图片替换
* @param index
*/
change(index: number) {
const photoSelectOptions = new photoAccessHelper.PhotoSelectOptions();
photoSelectOptions.MIMEType = photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE; // 过滤选择媒体文件类型为IMAGE
photoSelectOptions.maxSelectNumber = 1
const photoViewPicker = new photoAccessHelper.PhotoViewPicker();
photoViewPicker.select(photoSelectOptions).then(async (photoSelectResult: photoAccessHelper.PhotoSelectResult) => {
if (photoSelectResult.photoUris.length) {
photoSelectResult.photoUris.forEach(e => {
this.images[index] = e
});
}
}).catch((err: BusinessError) => {
})
}
}
五、如何使用
import { ImageSelectView } from '../ImageSelectView'
@Entry
@Component
struct ImagePage {
build() {
Column() {
//图片选择控件
ImageSelectView({ maxSelectNumber: 9 })
}.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
.height('100%')
.width('100%')
}
}