常言道,眼睛是心灵的窗户,那么相机便是手机的窗户了,主打美颜相机功能的拍照手机大行其道,可见对于手机App来说,如何恰如其分地运用相机开发至关重要。
Android的SDK一开始就自带了相机工具Camera,从Android5.0开始又推出了升级版的camera2,然而不管是初代的Camera还是二代的camera2,编码过程都比较繁琐,对于新手而言有点艰深。为此谷歌公司在Jetpack库中集成了增强的相机库CameraX,想让相机编码(包括拍照和录像)变得更加方便。CameraX基于camera2开发,它提供一致且易用的API接口,还解决了设备兼容性问题,从而减少了编码工作量。
不管是拍照还是录像,都要在AndroidManifest.xml中添加相机权限,还要添加存储卡访问权限,如下所示:

<!-- 相机 -->
<uses-permission android:name="android.permission.CAMERA" />
<!-- 存储卡读写 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

由于CameraX来自Jetpack库,因此要修改模块的build.gradle,往dependencies节点添加以下几配置,表示导入指定版本的camerax库:

// camerax库各版本见 https://mvnrepository.com/artifact/androidx.camera/camera-core
implementation 'androidx.camera:camera-core:1.0.1'
// camerax库各版本见 https://mvnrepository.com/artifact/androidx.camera/camera-camera2
implementation 'androidx.camera:camera-camera2:1.0.1'
// camerax库各版本见 https://mvnrepository.com/artifact/androidx.camera/camera-lifecycle
implementation 'androidx.camera:camera-lifecycle:1.0.1'
// camerax库各版本见 https://mvnrepository.com/artifact/androidx.camera/camera-view
implementation 'androidx.camera:camera-view:1.0.0-alpha28'

使用CameraX拍照之前要先初始化相机,包括界面预览以及参数设定等等,具体的初始化步骤说明如下。
1、准备一个预览视图对象PreviewView,并添加至当前界面;
2、获取相机提供器对象ProcessCameraProvider;
3、构建预览对象Preview,指定预览的宽高比例;
4、构建摄像头选择器对象CameraSelector,指定使用前置摄像头还是后置摄像头;
5、构建图像捕捉器对象ImageCapture,分别设置捕捉模式、旋转角度、宽高比例、闪光模式等拍照参数;
6、调用相机提供器对象的bindToLifecycle方法,把相机选择器、预览视图、图像捕捉器绑定到相机提供器;
7、调用预览视图对象的setSurfaceProvider方法,设置预览视图的表面提供器;
把上述的初始化步骤串起来,写到一个自定义的相机视图控件中,形成了以下的CameraX初始化代码:

private Context mContext; // 声明一个上下文对象
private PreviewView mCameraPreview; // 声明一个预览视图对象
private CameraSelector mCameraSelector; // 声明一个摄像头选择器
private Preview mPreview; // 声明一个预览对象
private ProcessCameraProvider mCameraProvider; // 声明一个相机提供器
private ImageCapture mImageCapture; // 声明一个图像捕捉器
private VideoCapture mVideoCapture; // 声明一个视频捕捉器
private ExecutorService mExecutorService; // 声明一个线程池对象
private LifecycleOwner mOwner; // 声明一个生命周期拥有者
private int mCameraType = CameraSelector.LENS_FACING_BACK; // 摄像头类型
private int mAspectRatio = AspectRatio.RATIO_16_9; // 宽高比例
private int mFlashMode = ImageCapture.FLASH_MODE_AUTO; // 闪光灯模式
private String mMediaDir; // 媒体保存目录public CameraXView(Context context, AttributeSet attrs) {super(context, attrs);mContext = context;mCameraPreview = new PreviewView(mContext); // 创建一个预览视图ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);mCameraPreview.setLayoutParams(params);addView(mCameraPreview); // 把预览视图添加到界面上mExecutorService = Executors.newSingleThreadExecutor(); // 创建一个单线程线程池mMediaDir = mContext.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS).toString();
}// 打开相机
public void openCamera(LifecycleOwner owner, int cameraMode, OnStopListener sl) {mOwner = owner;mCameraMode = cameraMode;mStopListener = sl;mHandler.post(() ->  initCamera()); // 初始化相机
}// 初始化相机
private void initCamera() {ListenableFuture future = ProcessCameraProvider.getInstance(mContext);future.addListener(() -> {try {mCameraProvider = (ProcessCameraProvider) future.get();resetCamera(); // 重置相机} catch (Exception e) {e.printStackTrace();}}, ContextCompat.getMainExecutor(mContext));
}// 重置相机
private void resetCamera() {int rotation = mCameraPreview.getDisplay().getRotation();// 构建一个摄像头选择器mCameraSelector = new CameraSelector.Builder().requireLensFacing(mCameraType).build();// 构建一个预览对象mPreview = new Preview.Builder().setTargetAspectRatio(mAspectRatio) // 设置宽高比例.build();// 构建一个图像捕捉器mImageCapture = new ImageCapture.Builder().setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY) // 设置捕捉模式.setTargetRotation(rotation) // 设置旋转角度.setTargetAspectRatio(mAspectRatio) // 设置宽高比例.setFlashMode(mFlashMode) // 设置闪光模式.build();bindCamera(MODE_PHOTO); // 绑定摄像头// 设置预览视图的表面提供器mPreview.setSurfaceProvider(mCameraPreview.getSurfaceProvider());
}// 绑定摄像头
private void bindCamera(int captureMode) {mCameraProvider.unbindAll(); // 重新绑定前要先解绑try {if (captureMode == MODE_PHOTO) { // 拍照// 把相机选择器、预览视图、图像捕捉器绑定到相机提供器的生命周期Camera camera = mCameraProvider.bindToLifecycle(mOwner, mCameraSelector, mPreview, mImageCapture);}} catch (Exception e) {e.printStackTrace();}
}// 关闭相机
public void closeCamera() {mCameraProvider.unbindAll(); // 解绑相机提供器mExecutorService.shutdown(); // 关闭线程池
}

初始化相机之后,即可调用图像捕捉器的takePicture方法拍摄照片了,拍照代码示例如下:

private String mPhotoPath; // 照片保存路径
// 获取照片的保存路径
public String getPhotoPath() {return mPhotoPath;
}// 开始拍照
public void takePicture() {mPhotoPath = String.format("%s/%s.jpg", mMediaDir, DateUtil.getNowDateTime());ImageCapture.Metadata metadata = new ImageCapture.Metadata();// 构建图像捕捉器的输出选项ImageCapture.OutputFileOptions options = new ImageCapture.OutputFileOptions.Builder(new File(mPhotoPath)).setMetadata(metadata).build();// 执行拍照动作mImageCapture.takePicture(options, mExecutorService, new ImageCapture.OnImageSavedCallback() {@Overridepublic void onImageSaved(ImageCapture.OutputFileResults outputFileResults) {mStopListener.onStop("已完成拍摄,照片保存路径为"+mPhotoPath);}@Overridepublic void onError(ImageCaptureException exception) {mStopListener.onStop("拍摄失败,错误信息为:"+exception.getMessage());}});
}

然后在App代码中集成新定义的增强相机控件,先在布局文件中添加CameraXView节点,如下所示。

    <com.example.chapter14.widget.CameraXViewandroid:id="@+id/cxv_preview"android:layout_width="match_parent"android:layout_height="wrap_content" />

再给Java代码补充CameraXView对象的初始化以及拍照动作,其中关键代码示例如下:

private CameraXView cxv_preview; // 声明一个增强相机视图对象
private View v_black; // 声明一个视图对象
private ImageView iv_photo; // 声明一个图像视图对象
private final Handler mHandler = new Handler(Looper.myLooper()); // 声明一个处理器对象// 初始化相机
private void initCamera() {// 打开增强相机,并指定停止拍照监听器cxv_preview.openCamera(this, CameraXView.MODE_PHOTO, (result) -> {runOnUiThread(() -> {iv_photo.setEnabled(true);Toast.makeText(this, result, Toast.LENGTH_SHORT).show();});});
}// 处理拍照动作
private void dealPhoto() {iv_photo.setEnabled(false);v_black.setVisibility(View.VISIBLE);cxv_preview.takePicture(); // 拍摄照片mHandler.postDelayed(() -> v_black.setVisibility(View.GONE), 500);
}

运行测试App,点击拍照图标,观察到增强相机的拍照效果如下面两图所示,其中第一张图为准备拍照时的预览界面,第二张图为拍照结束后的观赏界面。

点此查看Android开发笔记的完整目录

Android开发笔记(一百八十一)使用CameraX拍照相关推荐

  1. Android开发笔记(八十一)屏幕规格适配

    Configuration 适配各种屏幕规格,首先要取到系统对于屏幕的配置信息,这些配置可从工具类Configuration获得.Configuration对象在Activity中通过调用getRes ...

  2. Android开发笔记(六十一)文件下载管理DownloadManager

    下载管理DownloadManager 文件下载其实是网络数据访问的一种特殊形式,使用普通的http请求也能完成,就是实现起来会繁琐一些.因为下载功能比较常用,而且业务功能相对统一,所以从Androi ...

  3. Android开发笔记(八十六)几个特殊的类

    接口interface interface是一些功能的集合,但它只定义了对象必须实现的成员,而不包含成员的实现代码,成员的具体代码由实现接口的类提供.Android对接口的使用场景主要有三类:事件监听 ...

  4. Android开发笔记(五十一)通过Messenger实现进程间通信

    进程间通信IPC IPC是"Inter-Process Communication"的缩写,即进程间通信.Android为APP提供了多进程工作模式,这是因为多线程存在若干局限: ...

  5. Android开发笔记(八十九)单例模式

    基本概念 单例模式是一种常用的软件设计模式,它确保一个类只有一个实例,从而方便对实例个数的控制并节约系统资源. 单例模式有三个特点: 1.某个类只能有一个实例: 2.它要自行创建这个实例: 3.它只有 ...

  6. Android开发笔记(八十八)同步与加锁

    同步synchronized 同步方法 synchronized可用来给方法或者代码块加锁,当它修饰一个方法或者一个代码块的时候,同一时刻最多只有一个线程执行这段代码.这就意味着,当两个并发线程同时访 ...

  7. Android开发笔记(八十七)几个修饰关键字

    原生native native是方法修饰符,表示该方法是由其他一种语言(如C/C++)实现的原生方法.其实native只在JNI接口中使用,java代码中只有原生方法的定义,具体的实现代码在其他语言( ...

  8. Android开发笔记(八十)运行状态检查

    大家都知道刻舟求剑的寓言故事,说的是事物是发展变化着的,如果拘泥于原来的情况,那随着情况的改变,就不会得到预期的结果.同样,影响app运行的因素,并不只是外部环境(如硬件.系统.权限等等),还包括ap ...

  9. Android开发笔记(二十一)横幅轮播页Banner

    ViewPager ViewPager的概念 在前面的博文< Android开发笔记(十九)底部标签栏TabBar>中,我们提到可以在一个主页面里通过选项卡方式,切换到不同的子页面.那么在 ...

  10. Android开发笔记(八十五)手机数据库Realm

    Realm应用背景 Android自带的SQLite数据库,在多数场合能够满足我们的需求,但随着app广泛使用,SQLite也暴露了几个不足之处: 1.开发者编码比较麻烦,而且还要求开发者具备SQL语 ...

最新文章

  1. 请问大数据有没有速成的方法?嗯 真的没有
  2. 创建私有CA详细图解
  3. 空间金字塔Spatial Pyramid的BOW和Pyramid HOG的多核
  4. 牛客 - 王国(虚树+树的直径)
  5. Abp vNext 切换MySql数据库
  6. ssl1202-滑雪【记忆化搜索法】
  7. linux下查看进度命令,在Linux系统中使用Coreutils Viewer显示命令运行进度
  8. Linux第三方软件仓库
  9. redis rdb aof区别_聊一聊RDB、AOF在redis持久化里的底层原理
  10. 人工智能——数据、信息与知识
  11. Java 源码 —— List
  12. 泰山游记:所为非风光,为历史尔
  13. ActiveMQ(19):高级特性之独有消费者(Exclusive Consumer)
  14. 计算机驱动程序属于系统还是软件,什么是VGA驱动程序?
  15. mac下如何设置excel下拉表格
  16. 你现在还在自己洗碗?教你制作单片机的洗碗机控制器
  17. 微信小程序开发(第一篇 开发环境的准备+demo获取微信用户信息)
  18. 深入电子元器件行业产业场景,在线采购商城系统加速电子元器件交易数字化
  19. ps导出发生未知错误,怎么办?
  20. 高通平台gpio调试

热门文章

  1. 软件测试方法_边界值分析法
  2. 小米组织变革:新设三大部门,推进“手机X AIOT”战略落地
  3. 唐门暗器之私有云排名
  4. Jupyter Notebook 工作环境配置
  5. 高项、高级项目管理师论文-干系人管理
  6. IOS 苹果手机 使用重力加速度,js web devicemotion,deviceorientation事件
  7. ZZM区块链全球区块文化娱乐相结合的新型网站源码
  8. ​杭州阿里、海康、网易等组成 HR 联盟,以后你还敢跳槽吗?
  9. 32单片机与迪文屏通信的开发学习
  10. 分析C++软件异常需要掌握的汇编知识汇总