打造一款图片选择控件《ImageSelectView》 原创

一路向北545
发布于 2024-12-7 16:31
1.8w浏览
0收藏

当我们做发布帖子或者评论的功能时,往往可以发布图片,这就需要我们开发从手机的相册选择图片的功能。先看效果。


打造一款图片选择控件《ImageSelectView》-鸿蒙开发者社区


一、图片选择控件ImageSelectView功能

(1)可以控制选择图片数量

(2)选择图片按钮一直处于图片最后一个,且图片数量达到最大,按钮消失

(3)删除已选某张图片

(4)替换某张已选图片


二、读取设备相册涉及的权限问题

一般应用获取“存储”权限后,就能读取到手机内部存储上所有的文件,包含所有的照片,这就产生了隐私风险:它就可以在用户毫无感知的情况下,分析用户的文件和图片,将隐私“偷走“。

但是,用户通常不希望授予应用对其所有照片和视频的访问权限,因此,HarmonyOS在API9以后引入了Picker选择器,在保证用户正常的数据访问述求的同时,最小化减少应用的数据泄露。避免全量数据的授权,降低授权的颗粒度,例如用户在发送图片时,只想让应用访问到用户想要发送的。

所以我们使用系统的PhotoViewPicker来作为图片选择的核心组件,这样既安全也省去了申请权限的步骤。


三、核心逻辑实现

(1)配置可选图片数量

使用状态变量来控制每次选择图片最多选择的数量。以默认最大为9张为例,每次选择图片后,下次可选择图片数量为9减去已选择图片的数量。


@State maxSelectNumber: number = 9 //做多选择n张图片,默认9张
  • 1.


(2)当选择图片数量到达最大时,添加按钮需要消失掉

使用数组来临时存储已选图片的路径,其中最后一个元素为默认的“选择”按钮。我们通过@Watch监听此数组的实时变化。


@State @Watch("onImagesChange") images: string[] = [ADD] //所选图片
  • 1.


(3)如何删除已选图片

使用数组的splice方法,将指定坐标的元素删除


this.images.splice(index, 1)
  • 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) => {
  })
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.


四、完整代码实现

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) => {
    })
  }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95.
  • 96.
  • 97.
  • 98.
  • 99.
  • 100.
  • 101.
  • 102.
  • 103.
  • 104.
  • 105.


五、如何使用

import { ImageSelectView } from '../ImageSelectView'
@Entry
@Component
struct ImagePage {
  build() {
    Column() {
    //图片选择控件
      ImageSelectView({ maxSelectNumber: 9 })
    }.justifyContent(FlexAlign.Center)
    .alignItems(HorizontalAlign.Center)
    .height('100%')
    .width('100%')
  }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
收藏
回复
举报
1
1条回复
按时间正序
/
按时间倒序
wx67bede46c47e0
wx67bede46c47e0

我优化了一些内容


import { BusinessError } from "@kit.BasicServicesKit"import { photoAccessHelper } from "@kit.MediaLibraryKit"
let ADD: string = "add"
/** * 图片选择
 */@Componentexport struct ImageSelectView {  @State images: string[] = [] //所选图片  @State maxSelectNumber: number = 9 //做多选择n张图片,默认9张  @State itemWidth: number = 100  @State itemHeight: number = 100  @State private isAdd: boolean = true
  build() {    Column() {      Grid() {        ForEach(this.images, (e: string, index: number) => {          GridItem() {            Stack() {              Image(e)                .width(this.itemWidth)                .height(this.itemHeight)                .border({ width: 1, color: e === ADD ? Color.Transparent : Color.Gray, radius: 8 })                .onClick(() => {                  this.change(index)                })              Image($r("app.media.icon_upload_close"))                .width(25)                .height(25)                .backgroundColor(Color.White)                .borderRadius({topRight: 8, bottomLeft: 12})                .onClick(() => {                  //删除指定坐标图片                  this.images.splice(index, 1)                  if (this.isAdd == false) {                    this.isAdd = true                  }                })            }.alignContent(Alignment.TopEnd)          }        })
        if (this.isAdd) {          GridItem() {            Stack() {              Image($r('app.media.icon_upload'))                .width(this.itemWidth)                .height(this.itemHeight)                .border({ width: 1, color: Color.Transparent, radius: 8 })                .onClick(() => {                  this.pick()                })            }.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-this.images.length; // 选择媒体文件的最大数目    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)        });        if (this.images.length == this.maxSelectNumber) {          this.isAdd = false        }else {          this.isAdd = true        }      }    }).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) => {    })  }}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
回复
2025-2-26 18:24:17


回复
    相关推荐