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

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

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


打造一款图片选择控件《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%')
  }
}

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
收藏
回复
举报
回复
    相关推荐