主要有Surface,SurfaceView及SurfaceHolder三个类。Surface对象代表着原始像素数据的缓冲区.SurfaceHolder是与Surface联系的纽带,而SurfaceView内部封装了一个专门用于绘制的Surface对象。(Provides a dedicated drawing surface embedded inside of a view hierarchy)。
Surface对象也是有生命周期的,当SurfaceView出现在屏幕上时会创建Surface,当SurfaceView从屏幕上消失时,Surface会销毁。为了正确把握Surface的生命周期,可以通过SurfaceHolder.Callback接口实现。
SurfaceView是View的特殊子类,用于在视图层级结构中提供一个专门用来绘图的Surface,其目的是将这个绘图Surface提供给另一个线程(非UI线程),这样系统就不必等到视图层级结构做好绘制准备后再进行绘制。相反,另一个线程执有Surface的引用,它可以根据自己的步调向自身的canvas中进行绘制 。
我们向应用程序添加的UI组件或者自定义控件,都会被加入到视图层级结构中,完整的视图树是在UI线程中进行绘制的。而SurfaceView是在自己的线程中进行绘制的,并不会用到UI线程。
SurfaceView与其他View不相同的地方在于,它不会进行自我绘制内容。把想将内容绘制到Surface缓冲区的对象,叫做Surface的客户端。
/** * 打开相机,将图片存储到指定的文件处 */ public static void openCamera(Activity activity, int requestCode, File file) { if (file == null) { return; } Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(file)); if (requestCode != 0) { activity.startActivityForResult(intent, requestCode); } else { activity.startActivity(intent); } }如果将上面的隐匿intent的action设置为MediaStore.ACTION_VID E O_CAPTURE就会启动录像功能。
//通知系统扫描 MediaScannerConnection.scanFile(this, new String[]{AppUtils.getCaptureImageFile(this).getAbsolutePath()}, null, null); sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.fromFile(captureImageFile)));其中captureImageFile是用来存储拍照的图片,而AppUtils.getCaptureImageFile()返回的是图片所在的文件夹。也就是说该方法只能刷新指定的文件,而不是刷新一个文件夹。
Camera提供了对设备相机硬件级别的调用。相机是一种独占性资源,一次只能有一个activity调用相机,因此使用相机很关键的一点:需要时使用,使用完毕之后立即释放。如果忘记释放,除非重启设备,否则其他应用将无法使用相机。
如果使用相机,需要在清单文件中添加相应的权限及<uses-feature>。<uses-feature>是用来指定应用使用了设备的某项特色功能,它可以保证只有那些具备了该功能的设备才能使用该应用。具体如下:
<uses-permission android:name="android.permission.CAMERA" /> <uses-feature android:name="android.hardware.camera" android:required="true" />
在onResume()和onPause()回调方法中获取和释放相机资源。因为这两个方法是用户可以和视图交互的时间边界,只有用户可以与视图交互时才需要使用相机。获取和释放相机的代码如下:
@Override protected void onResume() {//获取相机资源 super.onResume(); mCamera = Camera.open(); } @Override protected void onPause() {//释放相机资源 if (mCamera != null) { mCamera.release(); mCamera = null; } super.onPause(); }
使用相机时必须进行非空判断,这是必须的。因为即使请求获取相机资源,也有可能获取不到。
open():打开后置摄像头。一个重载方法open(int),它是在api9中添加的,系统会根据传入的值打开对应的相机。一般来说,具体使用如下(open()源码):
public static Camera open() { int numberOfCameras = getNumberOfCameras(); CameraInfo cameraInfo = new CameraInfo(); for (int i = 0; i < numberOfCameras; i++) { getCameraInfo(i, cameraInfo); if (cameraInfo.facing == CameraInfo.CAMERA_FACING_BACK) { return new Camera(i); } } return null; }遍历每一个摄像头,然后根据前后位置判断要打开哪个。
setDisplayOrientation():设置预览方向。默认时预览为横屏的,调用setDisplayOrientation(90)后,才会变成竖屏的。
getParameters():获取相机的参数。
setParameters():这相机设置参数。
autoFocus():自动对焦。参数为自动对焦后的回调。
setPreviewCallback():设置预览的回调。在该加高中,可以将预览的数据保存。
具体代码如下:
/** * @return true代表有相机,false代表无相机 */ private boolean hasCamera() { PackageManager pm = getPackageManager(); return pm.hasSystemFeature(PackageManager.FEATURE_CAMERA) || pm.hasSystemFeature(PackageManager.FEATURE_CAMERA_FRONT) || Camera.getNumberOfCameras() > 0; }
SurfaceView sv = (SurfaceView) findViewById(R.id.btn1); SurfaceHolder holder = sv.getHolder(); holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); holder.addCallback(new Callback() { public void surfaceCreated(SurfaceHolder holder) { try { if (mCamera != null)//设置预览 mCamera.setPreviewDisplay(holder); } catch (IOException e) { e.printStackTrace(); } } public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { if (mCamera == null) return; //设置相机的预览尺寸 Parameters parameters = mCamera.getParameters(); List<Size> sizes = parameters.getSupportedPreviewSizes(); Size s = sizes.get(0); int ar = s.width * s.height; for (Size ts : sizes) { int ta = ts.width * ts.height; if (ta > ar) { s = ts; ar = ta; } } parameters.setPreviewSize(s.width, s.height); mCamera.setParameters(parameters); //开始预览。如果出异常,就要释放相机资源 try { mCamera.startPreview(); } catch (Exception e) { mCamera.release(); mCamera = null; } } public void surfaceDestroyed(SurfaceHolder holder) { if (mCamera != null) mCamera.stopPreview(); } });
在设置预览之前,调用了SurfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS),为了在低版本上运行这一点是必须有的。
在上面代码中,在surfaceCreated()中调用Camera.setPreviewDisplay(),在surfaceChanged()中调用Camera.startPreview(),在surfaceDestroyed()中调用Camera.stopPreview()。
主要通过setPreCallback()中的回调进行。如下:
camera.startPreview(); camera.setPreviewCallback(new Camera.PreviewCallback() { @Override public void onPreviewFrame(byte[] data, Camera camera) { takePicture(data); } });takePicture是将参数转为bitmap的方法,如下:
private void takePicture(byte[] data) { Camera.Size size = camera.getParameters().getPreviewSize(); try { YuvImage image = new YuvImage(data, ImageFormat.NV21, size.width, size.height, null); ByteArrayOutputStream stream = new ByteArrayOutputStream(); image.compressToJpeg(new Rect(0, 0, size.width, size.height), 80, stream); Bitmap bmp = BitmapFactory.decodeByteArray(stream.toByteArray(), 0, stream.size());//获取预览的bitmap //do sth stream.close(); } catch (Exception ex) { Log.e("Sys", "Error:" + ex.getMessage()); } }该方法主要是利用系统自带的YuvImage类进行。其中第二个参数只能是ImageFormat.NV21或者ImageFormat.YUY2。
代码来源于apidemo
private Camera.Size getOptimalPreviewSize(List<Camera.Size> sizes, int w, int h) { final double ASPECT_TOLERANCE = 0.1; double targetRatio = (double) w / h; if (sizes == null) return null; Camera.Size optimalSize = null; double minDiff = Double.MAX_VALUE; // Try to find an size match aspect ratio and size for (Camera.Size size : sizes) { double ratio = (double) size.width / size.height; if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE) continue; if (Math.abs(size.height - h) < minDiff) { optimalSize = size; minDiff = Math.abs(size.height - h); } } // Cannot find the one match the aspect ratio, ignore the requirement if (optimalSize == null) { minDiff = Double.MAX_VALUE; for (Camera.Size size : sizes) { if (Math.abs(size.height - h) < minDiff) { optimalSize = size; minDiff = Math.abs(size.height - h); } } } return optimalSize; }
if (mCamera != null) { mCamera.takePicture(new ShutterCallback() { public void onShutter() { System.out.println("咔咔响"); } }, null, new PictureCallback() {//将数据存储成JPG格式 public void onPictureTaken(byte[] data, Camera camera) { File directory = Environment .getExternalStorageDirectory(); File f = new File(directory, "me.jpg"); FileOutputStream fo = null; try { fo = new FileOutputStream(f); fo.write(data); } catch (Exception e) { e.printStackTrace(); } finally { if (fo != null) { try { fo.close(); } catch (IOException e1) { e1.printStackTrace(); } } } finish(); } }); }
使用Camera进行拍照,在预览图上有边框,拍摄的图片是边框中的部分。
Camera实现拍照,必须使用一个SurfaceView做为屏幕,用来显示预览图片。而边框可以使用另一个Surfaceview,两个SurfaceView采用帧布局。把底部的SurfaceView设置为Camera的预览区域,顶部是用来进行边框显示。
public class MySurfaceView extends SurfaceView implements SurfaceHolder.Callback{ private SurfaceHolder holder; public MySurfaceView(Context context, AttributeSet attrs) { super(context, attrs); holder = getHolder(); holder.addCallback(this); holder.setFormat(PixelFormat.TRANSPARENT); setZOrderOnTop(true); } @Override public void surfaceCreated(SurfaceHolder holder) { } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { } @Override public void surfaceDestroyed(SurfaceHolder holder) { } public void draw(){ Canvas canvas = holder.lockCanvas(); canvas.drawColor(Color.TRANSPARENT); Paint p = new Paint(); p.setStrokeWidth(4); p.setColor(Color.RED); p.setStyle(Paint.Style.STROKE); p.setAntiAlias(true); canvas.drawRect(100, 300, 400, 600, p);//在顶部画一个矩形,可以根据surfaceview的位置进行画 holder.unlockCanvasAndPost(canvas); } }最后在activity中将拍照获取的图片截取框中的部分即可。