最近观看视频学习的时候,看见慕课网上又关于Camera实现自定义拍照的功能,特此写下笔记记录一下!
本文知识点
- Android实现基本的拍照功能
- Camera结合SurfaceView实现简单的自定义拍照功能
Android实现基本的拍照功能
在Android中实现最基本的拍照功能,无非就是调用系统的相机,并成功获取到相应拍摄的照片显示到指定的位置.这里代码描述一下:
1.跳转到系统相机页面
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
Uri photoUri = Uri.fromFile(new File(mPath));
intent.putExtra(MediaStore.EXTRA_OUTPUT, photoUri);
startActivityForResult(intent, 1);
这里说明几点问题:
- 首先Action是指定的,这里指定的是去系统相机页面;
- 你可以指定照片保存的相应路径,也就是第二行的URI的内容,将路径转成相应的URI传入到系统相机页面,照相完成后就保存到你指定的页面了,为社么这里要指定路径呢?因为如果你没有指定路径的话,你从onActivityResult()中返回的Intent中的"data"中获取到相应的图片数据,这里可以直接强转成Bitmap对象,但是这个数据返回的图片是压缩了的(很不清楚).但是指定了相应的路径之后,你就可以直接从相应的路径去获取图片,这样获取到的图片就很清楚了.
2.获取相应的图片
这里获取图片分为两种:
-
没有指定图片的路径的获取方法(但是这里取出来的是一张缩略图,不是很清楚)
Bundle bundle = data.getExtras(); /*这里取出的是一张缩略图*/ Bitmap bitmap = (Bitmap) bundle.get("data"); mIv.setImageBitmap(bitmap);//设置图片的方法
-
指定了图片的路径的获取方法(这里其实就是相应的文件读写了)
FileInputStream fis = null; try { fis = new FileInputStream(mPath); Bitmap bitmap = BitmapFactory.decodeStream(fis); mIv.setImageBitmap(bitmap);//设置图片的方法 } catch (FileNotFoundException e) { e.printStackTrace(); } finally { if (fis != null) { try { fis.close(); } catch (IOException e) { e.printStackTrace(); } } }
通过以上方式就能获取相应的图片进行设置了.
Camera结合SurfaceView实现简单的自定义拍照功能
其实这个功能最主要的是Camera和SurfaceView控件的结合,其次就是Camera给我们提供了相当丰富的API,可以让我们在只调用相应API的情况下就能实现拍照功能,但是要考虑的细节还是很多的,后面我也会相应的讲解.
1.布局文件创建相应的SurfaceView提供相应的预览页面
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.jinlong.kehai.CustomCameraActivity">
<SurfaceView
android:id="@+id/sv"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:layout_marginBottom="20dp"
android:onClick="StartCamera"
android:text="拍照"
android:id="@+id/button"/>
</RelativeLayout>
这里其实就创建了一个SurfaceView的全屏布局,然后创建了一个相应的拍照按钮,没有什么可说的.
2.创建相应的Camera并控制其生命周期
关于Camera的生命周期的问题很重要,为什么这么说呢?设想一下,当你按Home键退出的时候,如果你不对相应的生命周期进行处理,可能在返回的时候就会出现相应的问题.所以这里说相应的生命周期是很重要的问题.
2.1获取相应的Camera对象
public Camera getCamera() {
Camera camera;
try {
camera = Camera.open();
} catch (Exception e) {
e.printStackTrace();
camera = null;
}
return camera;
}
Camera对象是通过open()方法进行创建的.
2.2获取相应的SufraceView的对象
在Camera有提供预览的方法,但是这个方法需要一个相应的SurfaceHolder对象,所以我们必须通过SurfaceView中获取到相应的SurfaceHolder对象,这里就很简单了.看下面的具体代码
SurfaceView mSurfaceView = (SurfaceView) findViewById(R.id.sv);
mSurfaceHolder = mSurfaceView.getHolder();
mSurfaceHolder.addCallback(this);
这里面的callBack是SurfaceHolder相应的回调.
@Override
public void surfaceCreated(SurfaceHolder holder) {
/*创建*/
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
/*改变 */
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
/*销毁*/
}
相应的内容我已经写在注释中了,这里其实在相应的方法中也应该关注Camera的生命周期处理.然后代码边长这个样子
@Override
public void surfaceCreated(SurfaceHolder holder) {
Log.e(TAG, "surfaceCreated: ");
/*创建*/
setStartPreview(mCamera, holder);
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
Log.e(TAG, "surfaceChanged: ");
/*改变,重启整个预览功能*/
mCamera.stopPreview();
setStartPreview(mCamera, holder);
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
Log.e(TAG, "surfaceDestroyed: ");
/*销毁*/
releaseCamera();
}
这里用到的相应的方法,会在后面提供.
2.3Camera生命周期的处理
其实关于生命周期的处理无非就是在相应Activity失去焦点的时候结束掉相应的Camera,然后在Activity获取焦点的时候创建Camera.其次就是在surfaceHolder回调中处理一下相应的生命周期就可以了.这里我选择的是在相应的onResume()中去启动相应的Camera.然后在onPause()中释放相应的资源.相应的代码如下:
@Override
protected void onResume() {
super.onResume();
if (mCamera == null) {
mCamera = getCamera();
if (mSurfaceHolder != null) {
Log.e(TAG, "onResume: ");
setStartPreview(mCamera, mSurfaceHolder);
}
}
}
@Override
protected void onPause() {
super.onPause();
releaseCamera();
}
/*设置预览内容,开始预览*/
public void setStartPreview(Camera camera, SurfaceHolder holder) {
/*camera和SurfaceHolder进行关联*/
try {
camera.setPreviewDisplay(holder);
/*系统默认camera是横屏的*/
/*设置角度 TODO 这里很重要的*/
camera.setDisplayOrientation(90);
camera.startPreview();
Log.e(TAG, "setStartPreview: ");
} catch (IOException e) {
e.printStackTrace();
}
}
/*释放资源*/
private void releaseCamera() {
if (mCamera != null) {
mCamera.setPreviewCallback(null);/*这个是预览的回调,里面会返回一个Byte[]和相应的Camera*/
mCamera.stopPreview();/*取消预览功能*/
mCamera.release();
mCamera = null;
}
}
仔细讲解一下上面的内容,这些内容也算是本节的重点了.
- 首先在onResume中我创建了一个相应的Camera,通过自己的方法setStartPreview()开启了预览
- setStartPreview()中通过Camera的staetPreview()开启了预览, 这里面的holder是相应的SurfaceHolder,让SurfaceView提供相应的预览,这里其实有一个很意思的事情,就是你要是不设置相应的SurfaceHolder的话,拍照是可以进行的,而且还会有相应的图像,但是你不会看见相应的预览内容.
- 这里还有一个问题很重要,预览默认是横屏的,所以这里应该设置预览是垂直于屏幕的.通过setDisplayOrientation();设置成90度就能保证预览是正确的内容.
- Camera.setPreviewCallback这个是相应的预览时候回调的方法,这里会一直回调,这里面会回调一个相应的Byte[]和相应的Camera对象
通过上面的内容,你还是不能成功的预览,为什么呢?权限这个问题当时困扰我很久,
<uses-permission android:name="android.permission.CAMERA"/>
这样就可以完成相应的预览了,是不是很神奇.后来我仔细想了想,其实就是Camera获取到相应的数据,然后通过SurfaceView进行显示罢了,感觉就应该是这样,感觉一些相机的应用添加美颜的话,应该就是在相应的预览的哪个回调中添加了,但是具体还没有去尝试,但是我觉得应该是这....
接下来我们说说如何保存相应的拍照的图片.
2.4保存相应的图片
当你点击相应的拍照按钮的时候其实就是处理相应的保存操作的,这里一般都是通过保存到指定的路径来保存相应的图片.具体逻辑说明一下:
- 设置相应的参数
- 当在对焦完成的时候保存图片
- 修正图片的角度
2.4.1 设置相应的参数
设置参数其实是通过Camera中的一个静态内部类(Parameters)完成的
/*参数的一些设置*/
Camera.Parameters parameters = mCamera.getParameters();
/*格式*/
parameters.setPictureFormat(ImageFormat.JPEG);
/*大小*/
parameters.setPreviewSize(800, 400);
/*对焦,这里设置的是自动对焦*/
parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
这个静态内部类能设置很多参数,具体内容我也不太清楚,感觉查查API应该可以解决,所以这里就不深入研究了,等到有时间的时候在好好研究把!
2.4.2当对焦完成的时候进行保存图片
其实这里就是一个相应的对焦回调,然后直接保存图片就行了,这里千万别忘了添加写入SD卡的权限,没有权限是不行的!!!
/*对焦最准确的时候的回调*/
mCamera.autoFocus(new Camera.AutoFocusCallback() {
@Override
public void onAutoFocus(boolean success, Camera camera) {
if (success) {
/*最后一个回调比较重要*/
mCamera.takePicture(null, null, mPictureCallback);
}
}
});
autoFocus是一个相应的回调,这个回调只有一个方法,其实这里面就是一个boolean的参数和相机的对象,boolean的参数你返回成功的时候,你可以理解为对焦完成了,然后调用Camera的takePicture()完成拍照,但是这里面有相应的三个参数,其实这三个参数的含义大概是这样的:
- 参数一:应该是从按下快门到捕获前这段时间,这里可以处理快门的声音提示,告知用户已经捕获完成了
- 参数二:生成的最原始的图片的回调
- 参数三:生成JPEG图片的回调
所以这里我们只是处理了生成JPEG的图片的回调,这个回调才是实际保存图片的地方,代码是这样的:
private Camera.PictureCallback mPictureCallback = new Camera.PictureCallback() {
@Override
public void onPictureTaken(byte[] data, Camera camera) {
/*data存储了整个图片的数据
这里应该保存到指定的路径中去*/
File tempFile = new File(Environment.getExternalStorageDirectory().getPath() + "/image.png");
FileOutputStream fos = null;
try {
fos = new FileOutputStream(tempFile);
fos.write(data);
/*保存指定路径,之后就是传递值的问题了,这里是保存了相应的内容跳转到一个Activity*/
Intent intent = new Intent(CustomCameraActivity.this, ResultActivity.class);
intent.putExtra("path", tempFile.getPath());
startActivity(intent);
finish();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
};
这里其实就是把相应的byte[]的内容写到相应的文件中去,有一段内容是跳转到相应的预览页面的.这里只是传过去一个相应的图片的位置,这里关于图片角度的处理就不去写了,不在本文讲述的内容中,这里只是把代码贴出来,感兴趣的同学自己去研究把!
/*调整图片的角度*/
File file = new File(path);
FileInputStream fis = null;
try {
fis = new FileInputStream(file);
Bitmap bitmap = BitmapFactory.decodeStream(fis);
Matrix matrix = new Matrix();/*添加矩阵*/
matrix.setRotate(90);
bitmap = Bitmap.createBitmap(bitmap, 0, 0,
bitmap.getWidth(), bitmap.getHeight(), matrix, true);
iv.setImageBitmap(bitmap);
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
以上内容就是简单的定义了一个拍照的页面,其实还有很多细节需要进行优化,这里没有讲的那么细致,因为我只是感兴趣,所以才研究了一下,毕竟我们公司不是做拍照的,所以有什么问题希望大家提出来,我在去研究,这样都能相互进步!如果有什么写的不对的,也希望您能提出来!感谢您的宝贵意见....
网友评论