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

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:

1. Project Setup & Permissions First

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)
}
2. Design the UI Layout

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>
4. Generate & Display the Document Template

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
}
5. Add Printing Functionality

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()
}
6. Optional Polish & Enhancements
  • 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.21 08:18:27