Android自定义相机开发(类似OCR扫描相机)

Android自定义相机开发(类似OCR扫描相机):拍照与相册选择全解析

本文将详细介绍如何实现一个功能完整的Android自定义相机,包含拍照、相册选择、图片裁剪、压缩和上传等核心功能

一、背景与需求

在电商类App中,经常需要用户拍摄订单信息。系统相机体验不统一且无法定制,因此我们需要开发一个自定义相机,满足以下需求:

  • ✅ 自定义相机界面与取景框
  • ✅ 支持拍照和相册选择
  • ✅ 支持图片裁剪和压缩功能
  • ✅ 权限动态申请
  • ✅ 多语言适配
  • ✅ 图片上传服务

二、实现效果预览

拍照界面 相册选择 预览确认

三、核心实现代码

1. 相机初始化与预览

kotlin 复制代码
private fun startCameraPreview() {
    try {
        releaseCamera()
        camera = Camera.open(Camera.CameraInfo.CAMERA_FACING_BACK).apply {
            val parameters = parameters.apply {
                // 设置连续对焦模式
                focusMode = Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE
            }
            setParameters(parameters)
            
            // 根据设备旋转设置预览方向
            setDisplayOrientation(90)
            setPreviewDisplay(surfaceView?.holder)
            startPreview()
        }
    } catch (e: IOException) {
        Log.e(TAG, "Camera preview failed", e)
        showToast("无法启动相机预览")
    } catch (e: RuntimeException) {
        Log.e(TAG, "Camera unavailable", e)
        showToast("相机不可用或已被占用")
    }
}

2. 权限动态申请

kotlin 复制代码
private fun checkPermissions() {
    val permissionsNeeded = mutableListOf<String>()
    
    if (!checkCameraPermission()) {
        permissionsNeeded.add(Manifest.permission.CAMERA)
    }
    
    if (!checkStoragePermission()) {
        permissionsNeeded.add(Manifest.permission.WRITE_EXTERNAL_STORAGE)
    }
    
    if (permissionsNeeded.isNotEmpty()) {
        ActivityCompat.requestPermissions(
            this,
            permissionsNeeded.toTypedArray(),
            CAMERA_PERMISSION_REQUEST
        )
    } else {
        initCamera()
    }
}

override fun onRequestPermissionsResult(
    requestCode: Int,
    permissions: Array<out String>,
    grantResults: IntArray
) {
    when (requestCode) {
        CAMERA_PERMISSION_REQUEST -> {
            if (grantResults.isNotEmpty() && 
                grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                initCamera()
            } else {
                showToast("需要相机权限才能使用此功能")
                finish()
            }
        }
        // 处理其他权限...
    }
}

3. 图片压缩算法(智能压缩)

kotlin 复制代码
private fun compressImageFile(originalFile: File): File {
    // 1. 小于1MB不压缩
    if (originalFile.length() < 1024 * 1024) return originalFile
    
    // 2. 获取图片尺寸
    val options = BitmapFactory.Options().apply {
        inJustDecodeBounds = true
    }
    BitmapFactory.decodeFile(originalFile.absolutePath, options)
    
    // 3. 计算缩放比例 (目标分辨率1280px)
    val (width, height) = options.run { outWidth to outHeight }
    val scale = calculateScaleFactor(width, height, 1280f)
    
    // 4. 解码时进行尺寸压缩
    val decodeOptions = BitmapFactory.Options().apply {
        inSampleSize = scale
        inPreferredConfig = Bitmap.Config.RGB_565
    }
    
    // 5. 质量压缩 (80% → 50% 阶梯式)
    return BitmapFactory.decodeFile(originalFile.absolutePath, decodeOptions)?.run {
        val compressedFile = File.createTempFile("compressed_", ".jpg", cacheDir)
        var quality = 80
        do {
            ByteArrayOutputStream().use { baos ->
                compress(Bitmap.CompressFormat.JPEG, quality, baos)
                if (baos.size() < 1024 * 1024) {
                    compressedFile.outputStream().use { fos ->
                        fos.write(baos.toByteArray())
                    }
                    return compressedFile
                }
            }
            quality -= 10
        } while (quality >= 50)
        
        recycle()
        originalFile
    } ?: originalFile
}

private fun calculateScaleFactor(width: Int, height: Int, maxSize: Float): Int {
    val scale = when {
        width > height -> width / maxSize
        else -> height / maxSize
    }
    return when {
        scale <= 1 -> 1
        scale <= 2 -> 2
        scale <= 4 -> 4
        scale <= 8 -> 8
        else -> 8
    }
}

4. 图片裁剪实现

kotlin 复制代码
private fun getCroppedBitmap(imageView: TouchImageView, container: View): Bitmap {
    // 1. 获取裁剪框位置
    val location = IntArray(2)
    container.getLocationOnScreen(location)
    val (left, top) = location
    val right = left + container.width
    val bottom = top + container.height

    // 2. 创建裁剪Bitmap
    val bitmap = Bitmap.createBitmap(
        container.width,
        container.height,
        Bitmap.Config.ARGB_8888
    )

    // 3. 绘制裁剪区域
    val canvas = Canvas(bitmap)
    canvas.translate(
        -imageView.scrollX.toFloat() - left,
        -imageView.scrollY.toFloat() - top
    )
    imageView.draw(canvas)

    return bitmap
}

5. 多语言适配实现

kotlin 复制代码
override fun attachBaseContext(newBase: Context?) {
    super.attachBaseContext(LocalManageUtil.setLocal(newBase))
}

override fun getResources(): Resources {
    return LocalManageUtil.setLocal(baseContext).resources
}

// 在布局中使用资源ID
<TextView
    android:id="@+id/btn_cancel"
    android:text="@string/cancle"
    ... />

四、布局文件核心设计

xml 复制代码
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/black">
    
    <!-- 相机预览区域 -->
    <RelativeLayout
        android:id="@+id/container"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_gravity="center"
        android:layout_marginHorizontal="30dp">
        
        <SurfaceView
            android:id="@+id/camera_preview"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
        
        <!-- 双预览方案:拍照使用ImageView,相册使用TouchImageView -->
        <ImageView
            android:id="@+id/preview_image"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:visibility="gone" />
        
        <com.riven.ui.TouchImageView
            android:id="@+id/preview_touch_image"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:visibility="gone"/>
        
        <!-- 取景框装饰元素 -->
        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent">
            <ImageView android:src="@mipmap/icon_left_top" ... />
            <ImageView android:src="@mipmap/icon_right_top" ... />
            <!-- 其他装饰元素... -->
        </RelativeLayout>
    </RelativeLayout>
    
    <!-- 操作按钮区域 -->
    <RelativeLayout
        android:id="@+id/rl_bottom_take"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom">
        
        <TextView
            android:id="@+id/btn_cancel"
            android:text="@string/cancle" />
        
        <Button
            android:id="@+id/btn_capture"
            android:background="@mipmap/icon_camera_take" />
        
        <TextView
            android:id="@+id/btn_album"
            android:text="Gallery" />
    </RelativeLayout>
</FrameLayout>

五、性能优化要点

  1. 内存管理

    • 及时回收Bitmap:bitmap.recycle()
    • 使用RGB_565配置减少内存占用
    • 在onDestroy中释放资源
  2. 相机资源释放

    kotlin 复制代码
    override fun onPause() {
        super.onPause()
        releaseCamera()
    }
    
    private fun releaseCamera() {
        camera?.apply {
            stopPreview()
            release()
        }
        camera = null
    }
  3. 异步处理

    kotlin 复制代码
    Observable.fromCallable {
        // 耗时操作:图片压缩
        compressImageFile(file)
    }
    .subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe({ compressedFile ->
        // 更新UI
    }, { error ->
        // 错误处理
    })
  4. 大图处理策略

    • 使用inSampleSize进行采样压缩
    • 分步加载:先加载低分辨率预览图
    • 使用ViewStub延迟加载

六、总结与踩坑经验

开发中常见问题

  1. 相机方向问题

    • 解决方案:根据设备旋转动态设置setDisplayOrientation()
    • 注意前置/后置摄像头方向差异
  2. 内存溢出(OOM)

    • 使用BitmapFactory.Options进行采样压缩
    • 及时回收不再使用的Bitmap
    • 使用WeakReference引用大图对象
  3. 权限兼容性

    • Android 6.0+需要动态申请权限
    • 处理用户拒绝权限的场景
    • 适配Android 10的存储权限变更
  4. 图片旋转问题

    • 从相册选择的图片需要读取EXIF信息
    • 使用Matrix进行旋转校正

后续扩展功能

  1. 增加高级功能

    • 手势缩放对焦
    • 人脸识别框
    • HDR模式支持
    • 美颜滤镜
  2. 性能优化方向

    • 使用Camera2 API替代已废弃的Camera API
    • 实现图片缓存池
    • 使用Glide/Picasso等专业库管理图片
  3. 用户体验优化

    • 添加拍照动画
    • 实现连拍功能
    • 添加网格线辅助构图

完整项目源码已开源:GitHub链接

欢迎Star & Fork!如有任何问题,欢迎在评论区交流讨论~

相关推荐
一笑的小酒馆3 小时前
Android12去掉剪贴板复制成功的Toast
android
一笑的小酒馆3 小时前
Android12App启动图标自适应
android
程序员江同学4 小时前
Kotlin 技术月报 | 2025 年 7 月
android·kotlin
某空m6 小时前
【Android】内容提供器
android
Greenland_126 小时前
Android 编译报错 Null extracted folder for artifact: xxx activity:1.8.0
android
ZhuYuxi3337 小时前
【Kotlin】const 修饰的编译期常量
android·开发语言·kotlin
Bryce李小白7 小时前
Kotlin 实现 MVVM 架构设计总结
android·开发语言·kotlin
Kiri霧7 小时前
Kotlin位运算
android·开发语言·kotlin
xjdkxnhcoskxbco7 小时前
kotlin基础【3】
android·开发语言·kotlin
thginWalker8 小时前
MySQL图解索引篇
android·mysql·adb