HarmonyOS基于ArkTS和ArkUI开发用户从相册选择照片并更换头像

1,244 阅读5分钟

HarmonyOS基于ArkTS和ArkUI开发用户从相册选择照片并更换头像

引言

本文主要讲述在鸿蒙系统中,实现用户选择相册中的照片并更换应用头像业务。在相册中选择图片不能直接更换为头像,需要把用户选择的图片放到缓存中,系统再从缓存中拷贝图片到服务器,最后才能完成头像的更新。把相册中的图片更换为头像看似简单,实则不允许应用程序直接去相册中拿图片,因为相册是用户的隐私,应用程序是被禁止访问用户的隐私的!而且只有把图片数据上传到服务器才能实现持久化保存和数据同步。所以就有了以下三步来实现更换头像的业务,分别是:

  • 申请打开相册应用并让用户选择一张图片
  • 把用户选择的图片拷贝到缓存目录
  • 上传图片到服务器并更新用户头像

getavatar.gif

第一步. 申请打开相册应用并让用户选择一张图片,

第一步主要分为两部分,首选需要设置选择规则。

  //创建选择器对象
  const opt = new picker.PhotoSelectOptions()
  //指定选择的类型是图片
  opt.MIMEType = picker.PhotoViewMIMETypes.IMAGE_TYPE
  //指定选择的数量,1这里指只能选择张图片
  opt.maxSelectNumber = 1

选择器对象主要有两个属性控制选择(MIMEType和maxSelectNumbe),分别控制着选择器选择的是 图片/视频 和选择的数量。

官方文档picker

  //创建选择相册图片对象
  const pickView = new picker.PhotoViewPicker()
  // 调用select方法选择图片后获得图片数据
  let photo = await pickView.select(opt)
  //photo.photoUris是个数组,里面存的是选择的图片在系统中的位置
  return photo.photoUris

这部分代码是打开系统相册并按照选择器对象的规则让用户选择图片。

第一部分封装成函数代码如下:

async selectImage(maxNum:number) {
  const opt = new picker.PhotoSelectOptions()
  opt.MIMEType = picker.PhotoViewMIMETypes.IMAGE_TYPE
  opt.maxSelectNumber = maxNum
  const pickView = new picker.PhotoViewPicker()
  let photo = await pickView.select(opt)
  return photo.photoUris
}

函数的形参maxNum用来确定要选择的图片数量,async和await组合使用可以直接拿到返回的Promise对象避免了使用then和catch解析,最后返回系统中图片的路径用于下面的操作。

第二步.把用户选择的图片拷贝到缓存目录

  //获取应用程序缓存
  let cacheDir = getContext().cacheDir
  // 图片扩展名
  let fileType = 'jpg'
  // 用时间戳生成文件名,保证名字不重复
  let filename = Date.now() + '.' + fileType
  // 拼接成完整的应用缓存路径
  let fullpath = cacheDir + '/' + filename

拼接出缓存的目录,用于拷贝用户选择的图片。

    //读取系统图片目录
    const file = fs.openSync(photoUrl,fs.OpenMode.READ_ONLY)
    //拷贝图片到缓存
    fs.copyFileSync(file.fd,fullpath)

通过fs这个api中的方法完成图片拷贝工作,将相册中的图片拷贝到应用程序缓存目录中。 官方文档fs

第二部分封装成函数代码如下:

    copyImageToCache(photoUrl:string){
      let cacheDir = getContext().cacheDir
      let fileType = 'jpg'
      let filename = Date.now() + '.' + fileType
      let fullpath = cacheDir + '/' + filename
      const file = fs.openSync(photoUrl,fs.OpenMode.READ_ONLY)
      fs.copyFileSync(file.fd,fullpath)
      return [filename,fileType]
    }

photoUrl是第一步中返回系统图片目录,返回缓存的图片名字和图片的扩展名,用于第三步发送请求时使用。

拷贝完成后可以在编译器的缓存目录中查看缓存的图片

Snipaste_2024-09-03_11-58-20.png

注:第一步返回的是一个数组,但是选择图片就选择一张,所以在这里传参的时候传数组中的第一个就行。

第三步.上传图片到服务器并更新用户头像

let uploader = await request.uploadFile(getContext(), {
  url: 'https://hmajax.itheima.net/api/uploadimg',//请求url
  method: 'POST',//请求方式
  header: {
    'Content-Type': 'multipart/form-data'//数据上传格式
  },
  files: [{
    filename: filename, // 文件名,例如:122342.jpg
    type: fileType, //扩展名,例如 jpg
    name: 'img', //上传接口的参数名称
    uri: `internal://cache/${filename}`  // 缓存目录中的要上传给服务器的图片路径
  }],
  data: []
})

通过request对象的uploadFile方法把缓存中的图片上传到服务器。

官方文档request.uploadFile()

封装成函数代码如下:

async uploadImage(filename: string, fileType: string) {
  let uploader = await request.uploadFile(getContext(), {
    url: 'https://hmajax.itheima.net/api/uploadimg',
    method: 'POST',
    header: {
      'Content-Type': 'multipart/form-data'
    },
    files: [{
      filename: filename, // 文件名,例如:122342.jpg
      type: fileType, //扩展名,例如 jpg
      name: 'img', //上传接口的参数名称
      uri: `internal://cache/${filename}`  // 缓存目录中的要上传给服务器的图片路径
    }],
    data: []
  })
  //获取服务器返回来的数据
  uploader.on('headerReceive', res => {
    //imgPath是状态变量
    //控制用户头像
    this.imgPath = JSON.parse(res['body'])['data']['url']
  })
}

如果一个数据的类型是object类型,我们可以直接使用 [] 方法,传入你想从这个对象中获取到的属性名称即可,这种取值方法可以省略interface的定义,简单来讲就是可以偷懒。

res是服务器返回的数据,数据里有headers和body的JSON字符串对象,通过 [] 选中body再通过JSON.parse()方法把JSON字符串对象转换成JSON对象如下图。

image.png

最后再通过 [] 选取data和其中的url即可获取服务器中的图片。

注:要使模拟器可以使用网络需要在entry/src/main/module.json5中添加如下配置

"requestPermissions": [{
  "name": "ohos.permission.INTERNET"
}],

详细位置可看下图

image.png

整体项目代码如下:
import { picker } from '@kit.CoreFileKit'
import fs from '@ohos.file.fs';
import { request } from '@kit.BasicServicesKit';

@Entry
@Component
struct page1 {
  @State imgPaht: string = 'https://pic.rmb.bdstatic.com/bjh/down/3020ded00363bdfc985230c637d8c0e7.png'

  async selectImage(maxNum:number) {
    const opt = new picker.PhotoSelectOptions()
    opt.MIMEType = picker.PhotoViewMIMETypes.IMAGE_TYPE
    opt.maxSelectNumber = maxNum
    const pickView = new picker.PhotoViewPicker()
    let photo = await pickView.select(opt)
    return photo.photoUris
  }
  copyImageToCache(photoUrl:string){
    let cacheDir = getContext().cacheDir
    let fileType = 'jpg'
    let filename = Date.now() + '.' + fileType
    let fullpath = cacheDir + '/' + filename
    const file = fs.openSync(photoUrl,fs.OpenMode.READ_ONLY)
    fs.copyFileSync(file.fd,fullpath)
    return [filename,fileType]
  }
  async uploadImage(filename:string,fileType:string){
    let uploader = await request.uploadFile(getContext(),{
      url: 'https://hmajax.itheima.net/api/uploadimg',
      method: 'POST',
      header: {
        'Content-Type': 'multipart/form-data'
      },
      files: [{
        filename: filename, // 文件名,例如:122342.jpg
        type: fileType, //扩展名,例如 jpg
        name: 'img', //上传接口的参数名称
        uri: `internal://cache/${filename}`  // 缓存目录中的要上传给服务器的图片路径
      }],
      data: []
    })
    uploader.on('headerReceive',res=>{
      AlertDialog.show({
        message:JSON.stringify(JSON.parse(res['body'])['data'],null,2)
        // offset:{dx:0,dy:-150}
      })
      // let obj = JSON.parse(res['body'])['data']
      this.imgPaht = JSON.parse(res['body'])['data']['url']
    })
  }

  build() {
    Column() {
      Image(this.imgPaht)
        .width(150)
        .height(150)
        .borderRadius(75)
        .onClick(async () => {
          let urls = await this.selectImage(1)
          let fileArr = this.copyImageToCache(urls[0])
          this.uploadImage(fileArr[0],fileArr[1])
        })
    }
    .width('100%')
    .height('100%')
  }
}