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!如有任何问题,欢迎在评论区交流讨论~

相关推荐
雨白2 小时前
Jetpack系列(二):Lifecycle与LiveData结合,打造响应式UI
android·android jetpack
kk爱闹3 小时前
【挑战14天学完python和pytorch】- day01
android·pytorch·python
每次的天空5 小时前
Android-自定义View的实战学习总结
android·学习·kotlin·音视频
恋猫de小郭5 小时前
Flutter Widget Preview 功能已合并到 master,提前在体验毛坯的预览支持
android·flutter·ios
随心最为安6 小时前
Android Library Maven 发布完整流程指南
android
岁月玲珑6 小时前
【使用Android Studio调试手机app时候手机老掉线问题】
android·ide·android studio
还鮟11 小时前
CTF Web的数组巧用
android
小蜜蜂嗡嗡12 小时前
Android Studio flutter项目运行、打包时间太长
android·flutter·android studio
aqi0012 小时前
FFmpeg开发笔记(七十一)使用国产的QPlayer2实现双播放器观看视频
android·ffmpeg·音视频·流媒体