Android实现自定义相机
前言
开发安卓过程中,经常会用到相机,有时候系统相机不能满足开发者的需要,这时候就需要自定义相机来实现。(注意:本文只是提供思路,有关键代码,但是代码不全)
相机说明
自定义相机Activity需要用到Camera这个安卓原生的相机类,需要一个surfaceView来预览,如果要拍照声音还需要一个ToneGenerator类,如果需要监听屏幕旋转角度,需要实现SensorEventListener这个监听,还需要感应重力的Sensor以及SensorManager。
SurfaceHolder以及SurfaceHolder.Callback
surfaceHolder它是一个用于控制surface的接口,它提供了控制surface 的大小,格式,上面的像素,即监视其改变的。SurfaceHolder.Callback在底层的Surface状态发生变化的时候通知View。
相机启动
启动相机是在surfaceHolder的回调函数surfaceCreated里面做,需要注意启动相机前要做一些配置:
配置文件
surfaceview.getHolder()
.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);//设置surfaceview不维护自己的缓冲区,而是等待屏幕的渲染引擎将内容推送到用户面前
surfaceview.getHolder().setFixedSize(width, height);
surfaceview.getHolder().setKeepScreenOn(true);// 屏幕常亮
surfaceview.getHolder().addCallback(new SurfaceCallback());// 为SurfaceView的句柄添加一个回数调函
sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
orientation_SensorListener = new OrientationSensorListener(
sensor_handler);
sensorManager.registerListener(orientation_SensorListener, sensor,
SensorManager.SENSOR_DELAY_UI);
启动相机的方法
/**
* 启动相机
*/
private void startCamera() {
try {
if (null == mCamera) {
// 获得Camera对象
mCamera = Camera.open();
// 设置用于显示拍照摄像的SurfaceHolder对象
if (mCamera != null) {
SurfaceHolder mSurfaceHolder =surfaceview.getHolder();
mCamera.setPreviewDisplay(mSurfaceHolder);
mCamera.setErrorCallback(new CameraonError());//记录相机错误的回调函数
mCamera.startPreview();
}
}
} catch (Exception e) {
e.printStackTrace();
// 释放手机摄像头
Toast.makeText(PersonPhotoActivity.this, "相机启动失败请重新启动",Toast.LENGTH_SHORT).show();
if (mCamera != null) {
mCamera.release();
mCamera = null;
}
finish();
} finally {
is_takingPhoto = false;//防止连拍的标志
}
}
surfaceHolder的回调函数
private final class SurfaceCallback implements Callback {
// 预览变化时调用该方法
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width,int height) {
try {
Camera.Parameters parameters = mCamera.getParameters();
parameters.setPictureFormat(PixelFormat.JPEG);
parameters.setPictureSize(1920, 1080);//设置照片分辨率16:9
mCamera.setErrorCallback(new CameraonError());
mCamera.setParameters(parameters);
mCamera.startPreview();
} catch (Exception e) {
}
}
// 开始预览时调用该方法
@Override
public void surfaceCreated(SurfaceHolder holder) {
startCamera();
}
// 停止预览时调用该方法
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
if (mCamera != null) {
mCamera.release(); // 释放照相机
mCamera = null;
}
}
}
相机拍照
点击拍照按钮,拍照时调用自动对焦(无法自动对焦时怎么做?)
public void takePicture() {
if (!is_takingPhoto) {
is_takingPhoto = true;//防止多次调用
Toast.makeText(getApplication(), "点击拍照按钮后请不要晃动,以免影响图片拍摄效果!",Toast.LENGTH_SHORT).show();
if (mCamera == null) {
Reset();
startCamera();
} else {
// 自动对焦
mCamera.autoFocus(new AutoFocusCallback() {
@Override
public void onAutoFocus(boolean success, Camera camera) {
// 自动对焦成功后才拍摄
if (success) {
img_focus.setImageResource(R.drawable.focus2);
sensorManag.unregisterListener(
orientation_SensorListener);
mCamera.takePicture(shutterCallback, null,new MyPictureCallback());
mCamera.cancelAutoFocus();//有人说加这行可以使无法对焦的机器去自动对焦,在小米手机上试了好用,但是不知道其他机器
} else {
img_focus.setImageResource(R.drawable.focus1);
Toast.makeText(getApplication(), "对焦失败,请重拍!",
Toast.LENGTH_LONG).show();
is_takingPhoto = false;
}
}
});
}
}
}
照片捕获成功时候的处理
private final class MyPictureCallback implements PictureCallback {
@Override
public void onPictureTaken(byte[] data, Camera camera) {
try {
BitmapFactory.Options options = new BitmapFactory.Options();
// options.inSampleSize = 2;
if (data != null) {
if (data.length / 1024 > 350
&& data.length / 1024 < 450) {
options.inSampleSize = 3;
} else if (data.length / 1024 > 450) {
options.inSampleSize = 4;
}
datas = data;//字节流(保存图片时候使用)
}
options.inPurgeable = true;//内存不足时可被回收
options.inInputShareable = true;
mBitmap = BitmapFactory.decodeByteArray(data, 0, data.length,options);//此bitmap作为预览时候使用,用完一定注意回收
BitmapDrawable bitmapDrawable = new BitmapDrawable(getResources(), mBitmap);
img_background.setBackgroundDrawable(bitmapDrawable);
img_background.setVisibility(View.VISIBLE);
//以下全是控件状态的变化
//surfaceview.setVisibility(View.GONE);
//bt_remake.setVisibility(View.VISIBLE);
//bt_photograph.setVisibility(View.VISIBLE);
//photograph_btn.setVisibility(View.GONE);
//lin_takephoto.setVisibility(View.GONE);
//btn_cancle.setVisibility(View.GONE);
} catch (Exception e) {
e.printStackTrace();
} finally {
is_takingPhoto = false;
}
}
}
保存图片返回本地地址url
public String savephoto(String path, String name, byte[] imagedata) {
String msgPath = path + name;
try {
//判断目录是否可用
if (!Environment.getExternalStorageState().equals(
Environment.MEDIA_MOUNTED)) {
return null;
}
File file = new File(path);//创建文件
if (!file.exists()) {
file.mkdirs();
}
// file.createNewFile();
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 4;
options.inPurgeable = true;
options.inInputShareable = true;
Bitmap bitmap = BitmapFactory.decodeByteArray(imagedata, 0,imagedata.length,options);
FileOutputStream fi = new FileOutputStream(path + name);
bitmap.compress(Bitmap.CompressFormat.JPEG, 90, fi);//压缩质量
fi.close();
fi.flush();
bitmap.recycle();//bitmap释放
bitmap = null;
} catch (Exception e) {
e.printStackTrace();
msgPath = "error";//保存出错
}
return msgPath;
}
保存图片调用
bt_photograph.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
String path = ImageUtils.getInstance().savephoto(filePath,fileName, datas);
is_takingPhoto = false;
if (path.equals("error") || path == null) {
Toast.makeText(PersonPhotoActivity.this, "图片保存失败,请重新拍照!", 0).show();
Reset();//保存失败重置相机
} else {
img_focus.setImageResource(R.drawable.focus1);//对焦图片设置
img_focus.setVisibility(View.VISIBLE);
Intent intent = new Intent();
if (degree == -1) {
Log.d("toast", "未捕获旋转角度");
} else {
intent.putExtra("degree", degree);
}
intent.putExtra("url", path);
setResult(10, intent);//成功
PersonPhotoActivity.this.finish();
}
}
});
相机重置
重置状态,重置bitmap ,重置图标
private void reset() {
img_focus.setImageResource(R.drawable.focus1);
img_focus.setVisibility(View.VISIBLE);
img_background.setBackgroundDrawable(null);
img_background.setVisibility(View.GONE);
surfaceview.setVisibility(View.VISIBLE);//预览关闭
bt_remake.setVisibility(View.GONE);
bt_photograph.setVisibility(View.GONE);
photograph_btn.setVisibility(View.VISIBLE);
lin_takephoto.setVisibility(View.VISIBLE);
btn_cancle.setVisibility(View.VISIBLE);
if(mBitmap != null){
mBitmap.recycle();
mBitmap = null;
}
if(datas != null ){
datas = null;
}
if (null != mCamera) {
mCamera.startPreview();//重置相机
} else {
startCamera();
}
}
其他功能
拍照声音的实现
private ShutterCallback shutterCallback = new ShutterCallback() {
public void onShutter() {
if (tone == null)
// 发出提示用户的声音
tone = new ToneGenerator(
AudioManager.AUDIOFOCUS_REQUEST_GRANTED,
ToneGenerator.MIN_VOLUME);
tone.startTone(ToneGenerator.TONE_PROP_BEEP);
}
};
开关闪光灯
相机闪光灯是通过parameter来配置。
private void turnLightOn() {
if (mCamera == null) {
return;
}
Parameters parameters = mCamera.getParameters();
if (parameters == null) {
return;
}
List<String> flashModes = parameters.getSupportedFlashModes();
// 是否支持闪光灯
if (flashModes == null) {
return;
}
String flashMode = parameters.getFlashMode();
if (!Parameters.FLASH_MODE_TORCH.equals(flashMode)) {
// 开启闪光灯
if (flashModes.contains(Parameters.FLASH_MODE_TORCH)) {
parameters.setFlashMode(Parameters.FLASH_MODE_TORCH);
mCamera.setParameters(parameters);//为相机开启闪光灯
}
}
}
关闭闪光灯,跟开启闪光灯判断一样,只是配置不同,关键代码
//支持关闭闪光灯模式
if (!Parameters.FLASH_MODE_OFF.equals(flashMode)) {
if (flashModes.contains(Parameters.FLASH_MODE_OFF)) {
parameters.setFlashMode(Parameters.FLASH_MODE_OFF);
mCamera.setParameters(parameters);
}
}
捕获屏幕旋转角度
private Handler sensor_handler= new Handler() {
@Override
public void handleMessage(android.os.Message msg) {
if (msg.what == 888) {
int orientation = msg.arg1;
if (orientation > 45 && orientation < 135) {
degree = 180;
Log.e("180", "右横");
} else if (orientation > 135 && orientation < 225) {
degree = 270;
Log.e("270", "倒屏");
} else if (orientation > 225 && orientation < 315) {
degree = 0;
Log.e("0", "左横");
} else if ((orientation > 315 && orientation < 360)
|| (orientation > 0 && orientation < 45)) {
degree = 90;
Log.e("90", "竖屏");
}
}
super.handleMessage(msg);
}
重力感应改变的监听(谷歌提供)
public synchronized void onSensorChanged(SensorEvent event) {
float[] values = event.values;
int orientation = ORIENTATION_UNKNOWN;
float X = -values[_DATA_X];
float Y = -values[_DATA_Y];
float Z = -values[_DATA_Z];
float magnitude = X*X + Y*Y;
// Don't trust the angle if the magnitude is small compared to the y value
if (magnitude * 4 >= Z*Z) {
float OneEightyOverPi = 57.29577957855f;
float angle = (float)Math.atan2(-Y, X) * OneEightyOverPi;
orientation = 90 - (int)Math.round(angle);
// normalize to 0 - 359 range
while (orientation >= 360) {
orientation -= 360;
}
while (orientation < 0) {
orientation += 360;
}
}
if (rotateHandler!=null) {
rotateHandler.obtainMessage(888, orientation, 0).sendToTarget();//发送消息通知改变
}
}
类的销毁
@Override
protected void onDestroy() {
if (mBitmap != null) {
mBitmap.recycle();
mBitmap = null;
}
if(datas != null){
datas = null;
}
super.onDestroy();
}
小结
1.为什么保存要压缩图片?
因为每个型号手机相机配置不一样,相机非常好的手机拍摄一张图片有可能是6-12m,如果不做处理直接放到bitmap中,展示的时候就会内存溢出,可以根据自己需要,改变压缩保存图片的方法。
2.为什么要释放bitmap?
相机启动本身就是一件耗费资源的事情,如果bitmap一直存在activity中不释放,当activity销毁的时候内存回收机制不会立即执行,可能导致内存溢出。
3.为何要加入重力感应?
相机是有角度的,不监听角度改变,拍摄的照片就会按照相机拍摄角度显示,有感应之后放置照片按角度放置就可以。