Android Studio新手求助:实现相机拍照/选图添加至文档模板并打印
Hey there! Let's walk through building this photo document app step by step—this is a great beginner project that covers several core Android skills. I'll break it down into manageable chunks with code snippets to make it easy to follow:
First, create a new Android Studio project (Empty Activity is fine). Then we need to add the necessary permissions to access the camera and gallery:
- Add these permissions to your
AndroidManifest.xml:
<uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" /> <!-- For Android 13+ --> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="32" /> <!-- For older versions --> <uses-feature android:name="android.hardware.camera" android:required="false" />
- Next, implement dynamic permission request since camera and gallery permissions are dangerous permissions. Add this code to your MainActivity:
private val requestPermissionsLauncher = registerForActivityResult( ActivityResultContracts.RequestMultiplePermissions() ) { permissions -> val cameraGranted = permissions[android.Manifest.permission.CAMERA] ?: false val storageGranted = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { permissions[android.Manifest.permission.READ_MEDIA_IMAGES] ?: false } else { permissions[android.Manifest.permission.WRITE_EXTERNAL_STORAGE] ?: false } if (!cameraGranted || !storageGranted) { Toast.makeText(this, "Permissions are required to proceed", Toast.LENGTH_SHORT).show() } } // Call this when your activity starts or when the user taps a button fun requestNecessaryPermissions() { val permissions = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { arrayOf(android.Manifest.permission.CAMERA, android.Manifest.permission.READ_MEDIA_IMAGES) } else { arrayOf(android.Manifest.permission.CAMERA, android.Manifest.permission.WRITE_EXTERNAL_STORAGE) } requestPermissionsLauncher.launch(permissions) }
We'll need:
- Buttons to trigger camera capture and gallery selection
- A
WebView(instead of a plain ImageView) to display the formatted document—WebView makes it way easier to handle text, images, and layout than manually drawing bitmaps.
Here's a sample activity_main.xml:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:padding="16dp"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:gap="16dp"> <Button android:id="@+id/btnCapturePhoto" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="Capture Photo" /> <Button android:id="@+id/btnSelectFromGallery" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="Select from Gallery" /> </LinearLayout> <Button android:id="@+id/btnGenerateDocument" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Generate Document" android:layout_marginTop="16dp"/> <Button android:id="@+id/btnPrint" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Print Document" android:layout_marginTop="8dp" android:enabled="false"/> <WebView android:id="@+id/webViewDocument" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" android:layout_marginTop="16dp"/> </LinearLayout>
We'll use ActivityResultContracts (the modern replacement for old onActivityResult):
First, declare variables to hold the 3 selected images:
private val selectedImageUris = mutableListOf<Uri>() private lateinit var capturePhotoLauncher: ActivityResultContracts.StartActivityForResult private lateinit var selectGalleryLauncher: ActivityResultContracts.GetContent
Initialize the launchers in onCreate:
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) requestNecessaryPermissions() // Camera capture launcher capturePhotoLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> if (result.resultCode == RESULT_OK) { val imageBitmap = result.data?.extras?.get("data") as? Bitmap imageBitmap?.let { // Save bitmap to a temporary file to get a Uri val tempFile = createTempFile("camera_photo", ".jpg", cacheDir) val outputStream = FileOutputStream(tempFile) it.compress(Bitmap.CompressFormat.JPEG, 90, outputStream) outputStream.close() val imageUri = FileProvider.getUriForFile(this, "${packageName}.fileprovider", tempFile) addImageUri(imageUri) } } } // Gallery selection launcher selectGalleryLauncher = registerForActivityResult(ActivityResultContracts.GetContent()) { uri -> uri?.let { addImageUri(it) } } // Set button click listeners findViewById<Button>(R.id.btnCapturePhoto).setOnClickListener { val takePictureIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE) if (takePictureIntent.resolveActivity(packageManager) != null) { capturePhotoLauncher.launch(takePictureIntent) } else { Toast.makeText(this, "No camera app available", Toast.LENGTH_SHORT).show() } } findViewById<Button>(R.id.btnSelectFromGallery).setOnClickListener { selectGalleryLauncher.launch("image/*") } // ... rest of the code will go here } // Helper method to add image Uri and update UI private fun addImageUri(uri: Uri) { if (selectedImageUris.size < 3) { selectedImageUris.add(uri) Toast.makeText(this, "Added ${selectedImageUris.size}/3 photos", Toast.LENGTH_SHORT).show() if (selectedImageUris.size == 3) { findViewById<Button>(R.id.btnGenerateDocument).isEnabled = true } } else { Toast.makeText(this, "You can only add 3 photos", Toast.LENGTH_SHORT).show() } }
Note: Don't forget to add a FileProvider to your AndroidManifest.xml to share temporary camera files safely:
<application ...> <provider android:name="androidx.core.content.FileProvider" android:authorities="${applicationId}.fileprovider" android:exported="false" android:grantUriPermissions="true"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths" /> </provider> </application>
Then create res/xml/file_paths.xml:
<?xml version="1.0" encoding="utf-8"?> <paths xmlns:android="http://schemas.android.com/apk/res/android"> <cache-path name="camera_photos" path="." /> </paths>
We'll use an HTML template to build the document—this gives full control over layout (header, text, image grid, descriptions). Add this method to generate the HTML:
private fun generateDocumentHtml(): String { // Replace with your own header and text content val header = "My Photo Document" val introText = "Here are the three selected photos with their descriptions:" // You can let users input descriptions, but for simplicity, we'll use placeholder text val descriptions = listOf("Photo 1: A beautiful landscape", "Photo 2: Cute pet", "Photo 3: Fun moment") val imageHtml = StringBuilder() for (i in selectedImageUris.indices) { val uri = selectedImageUris[i] val description = descriptions[i] imageHtml.append(""" <div style="width: 30%; margin: 1%;"> <img src="content://$uri" style="width: 100%; border-radius: 8px;" /> <p style="text-align: center; margin-top: 8px; font-size: 14px;">$description</p> </div> """.trimIndent()) } return """ <html> <head> <style> body { padding: 16px; font-family: sans-serif; } h1 { text-align: center; color: #2c3e50; } .intro { font-size: 16px; margin: 16px 0; text-align: center; } .image-grid { display: flex; flex-wrap: wrap; justify-content: center; margin-top: 24px; } </style> </head> <body> <h1>$header</h1> <p class="intro">$introText</p> <div class="image-grid">$imageHtml</div> </body> </html> """.trimIndent() }
Then add a click listener for the "Generate Document" button to load this HTML into the WebView:
findViewById<Button>(R.id.btnGenerateDocument).setOnClickListener { val webView = findViewById<WebView>(R.id.webViewDocument) webView.settings.javaScriptEnabled = false // Not needed here webView.loadDataWithBaseURL(null, generateDocumentHtml(), "text/html", "UTF-8", null) findViewById<Button>(R.id.btnPrint).isEnabled = true }
Android's PrintManager makes printing WebView content straightforward. Add this method:
private fun printDocument() { val webView = findViewById<WebView>(R.id.webViewDocument) val printManager = getSystemService(Context.PRINT_SERVICE) as PrintManager val jobName = "${getString(R.string.app_name)} Document Print" val printAdapter = webView.createPrintDocumentAdapter(jobName) printManager.print(jobName, printAdapter, null) }
Then set the click listener for the Print button:
findViewById<Button>(R.id.btnPrint).setOnClickListener { printDocument() }
- Add input fields for users to edit the header, intro text, and photo descriptions
- Add image preview thumbnails so users can see selected photos before generating the document
- Handle edge cases: empty image selection, permission denial, low memory
- Add image cropping functionality if needed
内容的提问来源于stack exchange,提问作者Rima

