今天碰到了一个需求,项目中调用系统相机拍摄时要给定一个拍摄位置范围,想了想,就决定使用camera+SurfaceView来实现。
第一步,权限
<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera" />
首先给出我的拍照布局文件
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
android:keepScreenOn="true" >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal" >
<SurfaceView
android:id="@+id/my_surfaceView"
android:layout_width="match_parent"
android:layout_height="match_parent" >
</SurfaceView>
</LinearLayout>
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="40dp"
android:src="@drawable/photo_flame" />
<ImageView
android:id="@+id/take_photo"
android:layout_width="80dp"
android:layout_height="80dp"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:background="@drawable/states_btn_capture"
android:clickable="true" />
</RelativeLayout>
主要有一个用于展示的SurfaceView,一个白色的边框,一个点击拍照的按钮
我总体的思路是在需要拍照是点击按钮,以startActivityForResult的方式启动这个拍照Activity,拍摄成功后回传回去。
好了,开始今天的旅程
step1,在Actvity的onCreate()生命周期方法内完成各种初始化
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test_camera);
startOrientationChangeListener(); // 启动设备方向监听器
mTakePhoto = (ImageView) findViewById(R.id.take_photo);
mSurfaceView = (SurfaceView) findViewById(R.id.my_surfaceView);
holder = mSurfaceView.getHolder();
holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); //必须设置,不然会报错
holder.addCallback(this); // 回调接口
}
这里面有个设备方向监听的方法,主要是为了监听当前屏幕在拍摄时是横屏还是竖屏,方便我们旋转角度去保存图片
private final void startOrientationChangeListener() {
mOrEventListener = new OrientationEventListener(this) {
@Override
public void onOrientationChanged(int rotation) {
if (((rotation >= 0) && (rotation <= 45)) || (rotation >= 315)
|| ((rotation >= 135) && (rotation <= 225))) {// portrait
mCurrentOrientation = true;
} else if (((rotation > 45) && (rotation < 135))
|| ((rotation > 225) && (rotation < 315))) {// landscape
mCurrentOrientation = false;
}
}
};
mOrEventListener.enable();
}
step2 重新SurfaceHolder.Callback 的三个方法,简单完成我们的拍照功能
surfaceCreated(SurfaceHolder holder)
public void surfaceCreated(SurfaceHolder holder) {
try {
if (null == mCamera) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) {
mCamera = Camera.open(0);
} else {
mCamera = Camera.open();// 开启相机,不能放在构造函数中,不然不会显示画面.
}
}
mCamera.setDisplayOrientation(90);
} catch (Exception e) {
}
}
完成我们camera的初始化,这里需要判断版本,因为低版本没有前置摄像头,还需要将自己的view旋转90度
surfaceChanged(SurfaceHolder holder, int format, int width, int height)
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
if (mCamera != null) {
Parameters parameters = mCamera.getParameters();// 获取mCamera的参数对象
Size largestSize = getBestSupportedSize(parameters
.getSupportedPreviewSizes());
if (largestSize != null) {
parameters
.setPreviewSize(largestSize.width, largestSize.height);// 设置预览图片尺寸
parameters
.setPictureSize(largestSize.width, largestSize.height);// 设置输出图片尺寸
if (parameters.getSupportedFocusModes().contains(
parameters.FOCUS_MODE_AUTO)) {
parameters.setFocusMode(parameters.FOCUS_MODE_AUTO);
}
parameters.setPictureFormat(PixelFormat.JPEG); // 图片格式
parameters.setExposureCompensation(0); // 曝光率
mCamera.setParameters(parameters);
try {
mCamera.setPreviewDisplay(holder);//连接预览
mCamera.startPreview();//开始预览
} catch (Exception e) {
if (mCamera != null) {
mCamera.release();
mCamera = null;
}
}
}
}
}
当它变化时,给camera设置各种配置,连接并开始预览
surfaceDestroyed(SurfaceHolder holder)
public void surfaceDestroyed(SurfaceHolder holder) {
synchronized (this) {
try {
if (mCamera != null) {
mCamera.setPreviewCallback(null);
mCamera.stopPreview();
mCamera.release();
mCamera = null;
}
} catch (Exception e) {
}
}
}
当它销毁时,记得释放资源,否则可能会导致再次调用相机时发现相机被占用
到这里就基本把重点说大半了,想要实现我们的功能,我们就要点击拍照按钮,取得图片成功后将图片地址回传回去就ok了。
step3,给我们的拍摄按钮绑定点击时间
public void onClick(View v) {
// 点击拍照
switch (v.getId()) {
case R.id.take_photo:
mCamera.takePicture(mShutter, null, mJpeg);
mTakePhoto.setClickable(false);
break;
default:
break;
}
}
这里我设置了点击一次后就不能再点击了,防止多次点击,还有如果在libiary里面使用的话,因为无法使用id,那时候我们可能需要将我们的点击写成内部类的形式
mCamera.takePicture(mShutter, null, mJpeg);
有三个参数,第一个是图像数据处理还未完成时的回调函数,音视频数据处理完成后的回调函数 ,图像数据处理完成后的回调函数
private Camera.ShutterCallback mShutter = new Camera.ShutterCallback() {
@Override
public void onShutter() {
// 一般显示进度条
}
};
private Camera.PictureCallback mJpeg = new Camera.PictureCallback() {
@Override
public void onPictureTaken(byte[] data, Camera camera) {
// 保存图片
mFileName = UUID.randomUUID().toString() + ".jpg";
FileOutputStream out = null;
try {
out = openFileOutput(mFileName, Context.MODE_PRIVATE);
byte[] newData = null;
if (mCurrentOrientation) {
// 竖屏时,旋转图片再保存
Bitmap oldBitmap = BitmapFactory.decodeByteArray(data, 0,
data.length);
Matrix matrix = new Matrix();
matrix.setRotate(90);
Bitmap newBitmap = Bitmap.createBitmap(oldBitmap, 0, 0,
oldBitmap.getWidth(), oldBitmap.getHeight(),
matrix, true);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
newBitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos);
newData = baos.toByteArray();
out.write(newData);
} else {
out.write(data);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (out != null)
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
closeCamera();
Intent intent = new Intent();
intent.putExtra("url", mFileName);
setResult(RESULT_OK, intent);
finish();
}
};
在图片处理完成的函数中,我们需要根据拍摄时的横竖屏情况来调整我们保持图片的角度。
成功后释放camera资源,回传图片地址,结束当前的界面
public void closeCamera() {
synchronized (this) {
try {
if (mCamera != null) {
mCamera.setPreviewCallback(null);
mCamera.stopPreview();
mCamera.release();
mCamera = null;
}
} catch (Exception e) {
}
}
}
与销毁时处理方式相似,主要就是释放资源
下面做些优化,android存在物理键盘,所以我们需要处理物理返回事件
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK && event.getRepeatCount() == 0) {
closeCamera();
finish();
return true;
}
return super.onKeyDown(keyCode, event);
}
到这里,基本上就结束了。
我们还可以加上聚焦的功能,你可以采用定时聚焦,这里我采用的是,触摸屏幕聚焦,就是给surfaceView增加触摸事件,在回调方法中去聚焦
mSurfaceView.setOnTouchListener(this);
@Override
public boolean onTouch(View v, MotionEvent event) {
autoFocus();
return false;
}
public void autoFocus() {
if (mCamera != null) {
synchronized (mCamera) {
try {
if (mCamera.getParameters().getSupportedFocusModes() != null
&& mCamera.getParameters()
.getSupportedFocusModes()
.contains(Camera.Parameters.FOCUS_MODE_AUTO)) {
mCamera.autoFocus(new AutoFocusCallback() {
public void onAutoFocus(boolean success,
Camera camera) {
}
});
} else {
Toast.makeText(this, "无对焦功能", Toast.LENGTH_LONG).show();
}
} catch (Exception e) {
e.printStackTrace();
mCamera.stopPreview();
mCamera.startPreview();
Toast.makeText(this, "对焦失败", Toast.LENGTH_SHORT).show();
}
}
}
}
这里你需要判断你的手机是否支持聚焦的功能。好了,就这样。
相机camera.setParameters(parameters)方法出现java.lang.RuntimeException: setParameters failed
这个错误是和调用相机摄像头相关的。产生这个错误的原因主要在于代码控制分辨率的显示和真机测试分辨率不一样
一:解决办法
WindowManager wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
Display display = wm.getDefaultDisplay();
Camera.Parameters parameters = camera.getParameters();// 得到摄像头的参数
parameters.setPreviewSize(display.getWidth(),display.getHeight());注释掉这两句
parameters.setPictureSize(display.getHeight(),display.getWidth());注释掉这两句
二:有可能你的真机是属于定制机,或者深度开发过,对camera对了不少的改动。
camera.setParameters(parameters);//导致不能使用这个方法了,注释掉这一行吧。
在保持图片时,可能会报OOM异常,
Bitmap oldBitmap = BitmapFactory.decodeByteArray(data, 0,data.length);
这句的问题,优化方法
public Bitmap byteToBitmap(byte[] imgByte) {
InputStream input = null;
Bitmap bitmap = null;
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 8;
input = new ByteArrayInputStream(imgByte);
SoftReference softRef = new SoftReference(BitmapFactory.decodeStream(
input, null, options));
bitmap = (Bitmap) softRef.get();
if (imgByte != null) {
imgByte = null;
}
try {
if (input != null) {
input.close();
}
} catch (IOException e) {
e.printStackTrace();
}
return bitmap;
}
下面给上里面的链接
http://download.youkuaiyun.com/detail/rui_yi/9548251