拍照或者拍视频应该是大多数APP都需要去实现的功能,可以说这是既常用又容易出错的地方。比如在Android 6.0之后需要动态申请权限,Android 7.0之后将文件路径由file://修改为content://等等。特别是Android自身对相机都设计了两套API,android.hardware.Camera和android.hardware.camera2,由此可见需要对相机功能做一个总结,两篇文章分别用android.hardware.Camera和android.hardware.camera2去实现调用系统相机拍照和拍视频已经自定相机拍照和拍视频。
不管是使用系统相机还是自定义相机,都需要取得相机使用的权限,如果需要保存拍下的图片还需要申请存储权限,权限申请如下:
1. AndroidManifest.xml加入:
... >
"android.hardware.camera"
android:required="true" />
"android.permission.WRITE_EXTERNAL_STORAGE" />
...
Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
if (takePictureIntent.resolveActivity(getPackageManager()) != null) {
startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE);
}
在startActivityForResult()方法之前调用了resolveActivity(),这个方法返回第一个可以处理这个Intent的Activity,如果不做判断,没有Activity处理的话APP有可能会挂掉。
系统相机返回的数据依然在onActivityResult(int requestCode, int resultCode, Intent data)回调方法里面,下面拿到这个拍照返回数据并显示在ImageView上面:
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_IMAGE_CAPTURE && resultCode == RESULT_OK) {
Bundle extras = data.getExtras();
Bitmap imageBitmap = (Bitmap) extras.get("data");
mImageView.setImageBitmap(imageBitmap);
}
}
效果如图:
可以看到拍照得到的图片很小,因为没做任何配置,所以这种拍照方式系统默认返回的图片只适合做一个ICON。如果希望得到一个全尺寸的图片需要在前面拍照的基础上做一些修改。
1、首先修改调用系统相机的Intent
Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
if (takePictureIntent.resolveActivity(getPackageManager()) != null) {
File photoFile = null;
try {
photoFile = createImageFile();
} catch (IOException ex) {
}
if (photoFile != null) {
//注意这里的com.example.wangc.myapplication.fileprovider一定要和Manifest文件中provider配置的一致
Uri photoURI = FileProvider.getUriForFile(this,
"com.example.wangc.myapplication.fileprovider",
photoFile);
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI);
startActivityForResult(takePictureIntent, REQUEST_TAKE_PHOTO);
}
}
2、然后在AndroidManifest.xml中加入FileProvider配置
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.example.wangc.myapplication.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths">meta-data>
provider>
3、再新建file_paths文件
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="my_images" path="Android/data/com.example.wangc.myapplication/files/Pictures" />
paths>
这里就涉及到了Android 7.0之后的文件系统修改,不熟悉的可以看官网。
4、最后依然是在onActivityResult中获取拍照得到的图片
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_TAKE_PHOTO && resultCode == RESULT_OK) {
Bitmap bitmap = BitmapFactory.decodeFile(mCurrentPhotoPath);
mImageView.setImageBitmap(bitmap);
}
}
效果如图:
调用系统相机拍视频
Intent takeVideoIntent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
if (takeVideoIntent.resolveActivity(getPackageManager()) != null) {
startActivityForResult(takeVideoIntent, REQUEST_VIDEO_CAPTURE);
}
获取返回的视频
if (requestCode == REQUEST_VIDEO_CAPTURE && resultCode == RESULT_OK){
Uri videoUri = data.getData();
mVideoView.setVideoURI(videoUri);
mVideoView.start();
}
视频文件太大不上传效果图了。
当需要在拍照预览界面添加上一些项目自身界面元素的时候系统相机就不能满足需要了,这是就需要用到自定义相机拍照。
要实现自定义相机总的要完成以下几个步骤:
1、监测设备是否有相机可使用
2、利用SurfaceView创建拍照时预览界面
3、完成拍照
4、保存拍照文件
5、释放相机资源
private boolean checkCameraHardware(Context context) {
if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA)){
// this device has a camera
return true;
} else {
// no camera on this device
return false;
}
}
public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback {
private SurfaceHolder mHolder;
private Camera mCamera;
public CameraPreview(Context context, Camera camera) {
super(context);
mCamera = camera;
mHolder = getHolder();
mHolder.addCallback(this);
mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
}
public void surfaceCreated(SurfaceHolder holder) {
try {
mCamera.setPreviewDisplay(holder);
mCamera.startPreview();
} catch (IOException e) {
Log.d("+++", "Error setting camera preview: " + e.getMessage());
}
}
public void surfaceDestroyed(SurfaceHolder holder) {
}
public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
if (mHolder.getSurface() == null){
return;
}
try {
mCamera.stopPreview();
} catch (Exception e){
}
try {
mCamera.setPreviewDisplay(mHolder);
mCamera.startPreview();
} catch (Exception e){
Log.d("+++", "Error starting camera preview: " + e.getMessage());
}
}
}
//获取相机实例
mCamera = getCameraInstance();
mPreview = new CameraPreview(this, mCamera);
FrameLayout preview = findViewById(R.id.camera_preview);
preview.addView(mPreview);
Button captureButton = findViewById(R.id.button_capture);
captureButton.setOnClickListener(
new View.OnClickListener() {
@Override
public void onClick(View v) {
mCamera.takePicture(null, null, mPicture);
}
}
);
/**
* 拍照回调
*/
private Camera.PictureCallback mPicture = new Camera.PictureCallback() {
@Override
public void onPictureTaken(byte[] data, Camera camera) {
//保存返回的文件
File pictureFile = getOutputMediaFile(MEDIA_TYPE_IMAGE);
if (pictureFile == null){
Log.d("+++", "Error creating media file, check storage permissions: " );
return;
}
try {
FileOutputStream fos = new FileOutputStream(pictureFile);
fos.write(data);
fos.close();
} catch (FileNotFoundException e) {
Log.d("+++", "File not found: " + e.getMessage());
} catch (IOException e) {
Log.d("+++", "Error accessing file: " + e.getMessage());
}
}
};
/**
*保存文件
* @return
*/
private static File getOutputMediaFile(int type){
File mediaStorageDir = new File(Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_PICTURES), "MyCameraApp");
if (! mediaStorageDir.exists()){
if (! mediaStorageDir.mkdirs()){
Log.d("MyCameraApp", "failed to create directory");
return null;
}
}
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
File mediaFile;
if (type == MEDIA_TYPE_IMAGE){
mediaFile = new File(mediaStorageDir.getPath() + File.separator +
"IMG_"+ timeStamp + ".jpg");
} else if(type == MEDIA_TYPE_VIDEO) {
mediaFile = new File(mediaStorageDir.getPath() + File.separator +
"VID_"+ timeStamp + ".mp4");
} else {
return null;
}
return mediaFile;
}
private void releaseCamera(){
if (mCamera != null){
mCamera.release(); // release the camera for other applications
mCamera = null;
}
}
效果如图:
这里只是完成了简单的自定义拍照流程,还有很多细节需要注意,比如要先通过getSupportedPreviewSizes()获取相机最佳预览尺寸,然后通过setPreviewSize()方法设置,不然就会出现预览模糊或者变形的情况。