You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何从系统相册选择多张图片?原生实现方案求助

嘿,我来帮你搞定这个问题!首先咱们先解决多选图片的核心问题,然后再针对不同平台(Web/移动端)给出纯原生的PDF生成方案,完全不用第三方库~

纯原生实现多选图片并生成PDF方案

一、先解决「无法多选图片」的问题

不管是Web还是移动端,原生组件本身就支持多选,大概率是你没开启对应的配置:

Web端

原生HTML的input[type="file"]只需要加个multiple属性,再配合accept="image/*"限制只选图片,就能实现多选:

<input type="file" id="imagePicker" multiple accept="image/*">

然后用JS监听change事件获取所有选中的图片文件:

const imagePicker = document.getElementById('imagePicker');
let selectedImages = [];

imagePicker.addEventListener('change', (e) => {
  selectedImages = Array.from(e.target.files);
  // 可选:给用户做图片预览
  selectedImages.forEach(file => {
    const reader = new FileReader();
    reader.onload = (event) => {
      const img = document.createElement('img');
      img.src = event.target.result;
      img.style.maxWidth = '200px';
      document.body.appendChild(img);
    };
    reader.readAsDataURL(file);
  });
});

移动端原生

  • iOS(Swift):用PHPickerViewController(iOS14+推荐),设置selectionLimit = 0(0表示不限数量)就能多选;旧版本可以用UIImagePickerController并开启allowsMultipleSelection = true。
  • Android(Kotlin):启动选择器时,给Intent加intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)参数,就能支持多选。

二、纯原生生成PDF(分平台)

Web端纯原生方案

Web没有自带的PDF生成API,但我们可以用Canvas绘制图片,再手动构建PDF的基础结构(利用PDF的底层语法)来生成,全程不用第三方库:

// 主函数:将选中的图片生成PDF并下载
async function generatePDFFromImages(images) {
  const canvas = document.createElement('canvas');
  const ctx = canvas.getContext('2d');
  const pageImages = [];

  // 把每张图片转成Canvas绘制后的base64数据
  for (const imgFile of images) {
    const img = await loadImage(imgFile);
    canvas.width = img.width;
    canvas.height = img.height;
    ctx.drawImage(img, 0, 0);
    // 提取base64的二进制部分
    const imageData = canvas.toDataURL('image/jpeg', 0.9).split(',')[1];
    pageImages.push(imageData);
  }

  // 构建PDF内容(简化版,支持多页JPEG图片)
  const pdfContent = buildPDFContent(pageImages, canvas.width, canvas.height);
  // 转成Blob并触发下载
  const pdfBlob = new Blob([pdfContent], { type: 'application/pdf' });
  const downloadLink = document.createElement('a');
  downloadLink.href = URL.createObjectURL(pdfBlob);
  downloadLink.download = 'my-images.pdf';
  downloadLink.click();
}

// 辅助:把File对象转成可绘制的Image元素
function loadImage(file) {
  return new Promise((resolve, reject) => {
    const img = new Image();
    img.onload = () => resolve(img);
    img.onerror = reject;
    img.src = URL.createObjectURL(file);
  });
}

// 核心:手动构建PDF的底层结构
function buildPDFContent(pageImages, pageWidth, pageHeight) {
  const numPages = pageImages.length;
  let pdf = '%PDF-1.7\n';
  
  // 1. PDF目录对象
  pdf += '1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n';
  // 2. 页面集合对象
  pdf += '2 0 obj\n<< /Type /Pages /Kids [';
  for (let i = 0; i < numPages; i++) pdf += `${i+3} 0 R `;
  pdf += '] /Count ' + numPages + ' >>\nendobj\n';
  
  // 3. 逐个添加图片和页面
  pageImages.forEach((imageData, index) => {
    const imgObjNum = index + 3 + numPages;
    const pageObjNum = index + 3;
    const contentObjNum = index + 3 + 2*numPages;
    
    // 图片对象
    pdf += `${imgObjNum} 0 obj\n<< /Type /XObject /Subtype /Image /Width ${pageWidth} /Height ${pageHeight} /ColorSpace /DeviceRGB /BitsPerComponent 8 /Filter /DCTDecode /Length ${imageData.length} >>\nstream\n${atob(imageData)}\nendstream\nendobj\n`;
    // 页面对象
    pdf += `${pageObjNum} 0 obj\n<< /Type /Page /Parent 2 0 R /Resources << /XObject << /Img ${imgObjNum} 0 R >> >> /MediaBox [0 0 ${pageWidth} ${pageHeight}] /Contents ${contentObjNum} 0 R >>\nendobj\n`;
    // 页面内容(绘制图片)
    pdf += `${contentObjNum} 0 obj\n<< /Length 44 >>\nstream\nq ${pageWidth} 0 0 ${pageHeight} 0 0 cm /Img Do Q\nendstream\nendobj\n`;
  });
  
  // 4. 交叉引用表和尾部
  pdf += 'xref\n0 ' + (3 + 3*numPages) + '\n0000000000 65535 f \n';
  let offset = pdf.length - 1;
  for (let i = 1; i <= 3 + 3*numPages; i++) {
    const line = pdf.slice(offset);
    offset += line.length;
    pdf += `${String(offset - line.length).padStart(10, '0')} 00000 n \n`;
  }
  pdf += 'trailer\n<< /Size ' + (3 + 3*numPages) + ' /Root 1 0 R >>\nstartxref\n' + (pdf.length - pdf.indexOf('startxref') - 9) + '\n%%EOF';
  
  return pdf;
}

注意:这个是简化版实现,只支持JPEG图片,页面尺寸和图片一致。如果需要支持PNG或固定A4尺寸,需要调整PDF结构和Canvas绘制逻辑。

iOS原生方案(Swift)

用苹果自带的PDFKit框架(属于系统原生,不算第三方库),直接把图片转成PDF页面:

// 多选图片(PHPicker示例)
func showImagePicker() {
  var config = PHPickerConfiguration()
  config.selectionLimit = 0 // 不限选中数量
  config.filter = .images
  let picker = PHPickerViewController(configuration: config)
  picker.delegate = self
  present(picker, animated: true)
}

// 生成并保存PDF
func generatePDF(from images: [UIImage]) {
  let pdfDoc = PDFDocument()
  for image in images {
    guard let pdfPage = PDFPage(image: image) else { continue }
    pdfDoc.insert(pdfPage, at: pdfDoc.pageCount)
  }
  // 保存到Documents目录
  let docsDir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
  let pdfURL = docsDir.appendingPathComponent("images.pdf")
  pdfDoc.write(to: pdfURL)
  // 可以提示用户分享或打开文件
}

// PHPicker代理方法,获取选中的图片
extension YourViewController: PHPickerViewControllerDelegate {
  func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
    picker.dismiss(animated: true)
    var selectedImages = [UIImage]()
    for result in results {
      result.itemProvider.loadObject(ofClass: UIImage.self) { obj, error in
        if let image = obj as? UIImage {
          selectedImages.append(image)
          if selectedImages.count == results.count {
            DispatchQueue.main.async {
              self.generatePDF(from: selectedImages)
            }
          }
        }
      }
    }
  }
}

Android原生方案(Kotlin)

用系统自带的PdfDocument类,通过Canvas绘制图片生成PDF:

private val REQUEST_PICK_IMAGES = 1001

// 打开图片选择器(支持多选)
fun openImagePicker() {
  val intent = Intent(Intent.ACTION_GET_CONTENT).apply {
    type = "image/*"
    putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
    addCategory(Intent.CATEGORY_OPENABLE)
  }
  startActivityForResult(intent, REQUEST_PICK_IMAGES)
}

// 处理选中的图片并生成PDF
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
  super.onActivityResult(requestCode, resultCode, data)
  if (requestCode == REQUEST_PICK_IMAGES && resultCode == RESULT_OK) {
    val selectedImages = mutableListOf<Bitmap>()
    // 处理单张或多张选中的图片
    data?.clipData?.let { clipData ->
      for (i in 0 until clipData.itemCount) {
        val uri = clipData.getItemAt(i).uri
        val bitmap = MediaStore.Images.Media.getBitmap(contentResolver, uri)
        selectedImages.add(bitmap)
      }
    } ?: data?.data?.let { uri ->
      val bitmap = MediaStore.Images.Media.getBitmap(contentResolver, uri)
      selectedImages.add(bitmap)
    }
    // 生成PDF
    generatePDF(selectedImages)
  }
}

// 生成PDF并保存
fun generatePDF(images: List<Bitmap>) {
  val pdfDoc = PdfDocument()
  for (bitmap in images) {
    // 创建和图片尺寸一致的页面
    val pageInfo = PdfDocument.PageInfo.Builder(bitmap.width, bitmap.height, 1).create()
    val page = pdfDoc.startPage(pageInfo)
    val canvas = page.canvas
    // 绘制图片到页面
    canvas.drawBitmap(bitmap, 0f, 0f, null)
    pdfDoc.finishPage(page)
  }
  // 保存到外部存储(需要权限)
  val file = File(getExternalFilesDir(null), "images.pdf")
  pdfDoc.writeTo(FileOutputStream(file))
  pdfDoc.close()
  // 提示用户文件已保存
  Toast.makeText(this, "PDF已保存到: ${file.path}", Toast.LENGTH_LONG).show()
}

内容的提问来源于stack exchange,提问作者Rohan Patel

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.29 08:05:32