如何从系统相册选择多张图片?原生实现方案求助
嘿,我来帮你搞定这个问题!首先咱们先解决多选图片的核心问题,然后再针对不同平台(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
相关产品推荐
相关产品推荐

