這學期補修了Android這門課,短短的八次課讓我對Android有了初步的了解。作為結課項目,老師讓我們用Camera/Surfaceview完成相機功能。現將學習的心得記錄下來。
整個相機程序實現的思路是,使用Camera實例,設置好參數后,得到攝像頭傳回的圖像數據,將這些數據在Surfaceview實例中進行展示,實現預覽功能。在Surfaceview下,設置click button,當單擊click button后,調用Camera.takePicture方法,完成照相動作,將圖片保存到手機本地。
整個工程的核心是實現了SurfaceHolder.Callback接口,重寫接口中的幾個方法,而這幾個方法,都是Surfaceview實例將要調用的關鍵方法。他們分別是:surfaceCreated/surfaceDestroyed/surfaceChanged。然后可以通過Surfaceview.getHolder().addCallback(Callback)方法,將這個Callback與前端layout中的surfaceview相關聯起來。
下面來看下Callback中方法具體代碼:
1 @Override 2 public void surfaceChanged(SurfaceHolder holder, int format, int width, 3 int height) { 4 Log.e("tag", " surfaceChanged"); 5 mParameters = camera.getParameters(); 6 mParameters.setPictureFormat(PixelFormat.JPEG); 7 Log.e("tag", 8 "parameters.getPictureSize()" 9 + mParameters.getPictureSize().width); 10 setPictureSize(mParameters); 11 Log.i("tag", "holder width:" + width + " height:" + height); 12 // parameters.setPreviewSize(width, height);//需要判斷支持的預覽 13 14 camera.setParameters(mParameters); 15 16 camera.startPreview(); 17 } 18 19 private void setPictureSize(Parameters parameters) { 20 List<Size> sizes = parameters.getSupportedPictureSizes(); 21 if (sizes == null) { 22 return; 23 } 24 int maxSize = 0; 25 int width = 0; 26 int height = 0; 27 for (int i = 0; i < sizes.size(); i++) { 28 Size size = sizes.get(i); 29 int pix = size.width * size.height; 30 if (pix > maxSize) { 31 maxSize = pix; 32 width = size.width; 33 height = size.height; 34 } 35 } 36 Log.i("tag", "圖片的大小:" + width + " height:" + height); 37 parameters.setPictureSize(width, height); 38 }
上段代碼重寫了surfaceChanged方法。該方法中完成了設置相機的參數,包括圖片格式,預覽尺寸,並開始預覽功能,即在surfaceview中實時顯示攝像頭中捕捉的畫面。其中setPicturesize方法使用了相對的圖片大小設置,如果將圖片大小設置成絕對值,可能會造成不同尺寸屏幕的機器不能正常運行程序的異常發生。
在另外兩個方法中分別完成了camera實例的獲取和銷毀工作,具體代碼如下:
@Override public void surfaceDestroyed(SurfaceHolder holder) { Log.i("tag", " surfaceDestroyed"); if (camera != null) { camera.stopPreview(); camera.release(); camera = null; } } @Override public void surfaceCreated(SurfaceHolder holder) { try { if (camera == null) { camera = Camera.open(); } camera.setPreviewDisplay(holder); Log.i("", "camera created"); //WindowManager wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE); //Display display = wm.getDefaultDisplay(); // Camera.Parameters parameters = camera.getParameters(); // //parameters.setPreviewSize(display.getWidth(), display.getHeight());//設置預覽照片的大小 // parameters.setPreviewFrameRate(3);//每秒3幀 // parameters.setPictureFormat(PixelFormat.JPEG);//設置照片的輸出格式 // parameters.set("jpeg-quality", 100);//照片質量 // //parameters.setPictureSize(display.getWidth(), display.getHeight());//設置照片的大小 // camera.setParameters(parameters); // camera.setPreviewDisplay(surfaceView.getHolder());//通過SurfaceView顯示取景畫面 // camera.startPreview();//開始預覽 // preview = true; } catch (IOException e) { if (camera != null) { camera.stopPreview(); camera.release(); camera = null; } e.printStackTrace(); } }
下面將整個項目的代碼貢獻出來,包括實現自動對焦,手動調焦距,設置閃光燈等功能。
共包含一個mainActivity和CameraCallback兩個類
mainActivity的代碼如下:
import android.app.Activity; import android.hardware.Camera; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.view.MotionEvent; import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.View; import android.view.View.OnClickListener; import android.view.View.OnTouchListener; import android.widget.Button; import android.widget.SeekBar; import android.widget.Toast; import android.widget.SeekBar.OnSeekBarChangeListener; public class MainActivity extends Activity implements OnClickListener { private Camera mCamera; private SurfaceView mSurfaceView; private SurfaceHolder mHolder; private CameraCallback mCallback; private Button mTakePicButton; private Button mSwitchButton; private Button mflashButton; private boolean saved = true; private boolean isFrontCamera; public static final int MESSAGE_SVAE_SUCCESS = 0; public static final int MESSAGE_SVAE_FAILURE = 1; private final int FLASH_MODE_AUTO = 0; private final int FLASH_MODE_ON = 1; private final int FLASH_MODE_OFF = 2; private int mFlashMode = 0; private Handler mHandler = new Handler() { public void handleMessage(Message msg) { saved = true; switch (msg.what) { case MESSAGE_SVAE_SUCCESS: Toast.makeText(MainActivity.this, "保存成功", Toast.LENGTH_SHORT) .show(); break; case MESSAGE_SVAE_FAILURE: Toast.makeText(MainActivity.this, "保存失敗", Toast.LENGTH_SHORT) .show(); break; } }; }; private SeekBar mZoomBar; private View mZoomLayout; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); initView(); } private void initView() { mTakePicButton = (Button) findViewById(R.id.camera_take_btn); mTakePicButton.setOnClickListener(this); mSwitchButton = (Button) findViewById(R.id.camera_switch_btn); mSwitchButton.setOnClickListener(this); mflashButton = (Button) findViewById(R.id.flashMode); mflashButton.setOnClickListener(this); mZoomLayout = findViewById(R.id.zoomLayout); mZoomBar = (SeekBar) findViewById(R.id.seekBar1); mZoomBar.setOnSeekBarChangeListener(new OnSeekBarChangeListener() { @Override public void onStopTrackingTouch(SeekBar seekBar) { } @Override public void onStartTrackingTouch(SeekBar seekBar) { } @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { mCallback.setZoom(progress); } }); initSurfaceView(); } private void initSurfaceView() { mSurfaceView = (SurfaceView) findViewById(R.id.surfaceView1); mHolder = mSurfaceView.getHolder(); mCallback = new CameraCallback(this); mHolder.addCallback(mCallback); mSurfaceView.setOnTouchListener(new OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { if (event.getAction() == MotionEvent.ACTION_UP && !isFrontCamera) {// 前置攝像頭取消觸摸自動聚焦功能 View view = findViewById(R.id.RelativeLayout1); mCallback.autoFocus(view, event); } return true; } }); // 判斷是否支持前置攝像頭 int cameras = mCallback.getNumberOfCameras(); if (cameras <= 1) { mSwitchButton.setVisibility(View.GONE); } // 是否支持閃關燈 if (!mCallback.isSupportedFlashMode()) { mflashButton.setVisibility(View.GONE); } if (mCallback.isSupportedZoom()) { mZoomBar.setMax(mCallback.getMaxZoom()); } else { mZoomBar.setVisibility(View.GONE); } } @Override public void onClick(View v) { switch (v.getId()) { case R.id.camera_take_btn: if (saved) { saved = false; mCallback.takePicture(mHandler); } break; case R.id.camera_switch_btn: isFrontCamera = !isFrontCamera; if (isFrontCamera) { mSwitchButton.setText("打開后攝像頭"); mflashButton.setVisibility(View.GONE); mZoomLayout.setVisibility(View.GONE); } else { mSwitchButton.setText("打開前攝像頭"); mflashButton.setVisibility(View.VISIBLE); mZoomLayout.setVisibility(View.VISIBLE); } mCallback.switchCamera(mSurfaceView, isFrontCamera); break; case R.id.flashMode: mFlashMode = (mFlashMode + 1) % 3; switch (mFlashMode) { case FLASH_MODE_AUTO: mflashButton.setText("flash_auto"); break; case FLASH_MODE_ON: mflashButton.setText("flash_on"); break; case FLASH_MODE_OFF: mflashButton.setText("flash_off"); break; default: break; } mCallback.SetFlashMode(mFlashMode); break; default: break; } } }
CameraCallback類代碼如下:
import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.lang.reflect.Method; import java.util.List; import android.app.Activity; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.PixelFormat; import android.graphics.Bitmap.CompressFormat; import android.hardware.Camera; import android.hardware.Camera.AutoFocusCallback; //import android.hardware.Camera.CameraInfo; import android.hardware.Camera.Parameters; import android.hardware.Camera.PictureCallback; import android.hardware.Camera.Size; import android.os.Environment; import android.os.Handler; import android.util.Log; import android.view.MotionEvent; import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.View; import android.view.animation.Animation; import android.view.animation.ScaleAnimation; import android.view.animation.Animation.AnimationListener; import android.widget.ImageView; import android.widget.RelativeLayout; import android.widget.RelativeLayout.LayoutParams; public class CameraCallback implements SurfaceHolder.Callback { private Context mContext; private Camera mCamera; private boolean isShowFrame; private SurfaceHolder mHolder; // 在2.3的Camera.CameraInfo類中 // CAMERA_FACING_BACK常量的值為0,CAMERA_FACING_FRONT為1 private static final int CAMERA_FACING_BACK = 0; private static final int CAMERA_FACING_FRONT = 1; private final int FLASH_MODE_AUTO = 0; private final int FLASH_MODE_ON = 1; private final int FLASH_MODE_OFF = 2; private Parameters mParameters; public CameraCallback(Context context) { this.mContext = context; } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { Log.e("tag", " surfaceChanged"); mParameters = mCamera.getParameters(); if (isSupportedFlashMode()) {// 需要判斷是否支持閃光燈 mParameters.setFlashMode(Parameters.FLASH_MODE_AUTO); } mParameters.setPictureFormat(PixelFormat.JPEG); Log.e("tag", "parameters.getPictureSize()" + mParameters.getPictureSize().width); setPictureSize(mParameters); // parameters.setPreviewFormat(PixelFormat.JPEG);// Log.i("tag", "holder width:" + width + " height:" + height); // parameters.setPreviewSize(width, height);//需要判斷支持的預覽 mCamera.setParameters(mParameters); mCamera.startPreview(); } @Override public void surfaceCreated(SurfaceHolder holder) { mHolder = holder; Log.e("tag", " surfaceCreated"); try { if (mCamera == null) { mCamera = Camera.open(); } setDisplayOrientation(mCamera); mCamera.setPreviewDisplay(holder); Log.i("", "mCamera 2"); } catch (IOException e) { if (mCamera != null) { mCamera.stopPreview(); mCamera.release(); mCamera = null; } e.printStackTrace(); } } @Override public void surfaceDestroyed(SurfaceHolder holder) { Log.e("tag", " surfaceDestroyed"); if (mCamera != null) { mCamera.stopPreview(); mCamera.release(); mCamera = null; } } public int getNumberOfCameras() { try { Method method = Camera.class.getMethod("getNumberOfCameras", null); if (method != null) { Object object = method.invoke(mCamera, null); if (object != null) { return (Integer) object; } } } catch (Exception e) { e.printStackTrace(); } return 0; } private void setDisplayOrientation(Camera camera) { try { Method method = Camera.class.getMethod("setDisplayOrientation", int.class); if (method != null) { method.invoke(camera, 90); } Log.i("tag", "方法名:" + method.getName()); } catch (Exception e) { e.printStackTrace(); } } public Camera open(int i) { try { Method method = Camera.class.getMethod("open", int.class); if (method != null) { Object object = method.invoke(mCamera, i); if (object != null) { return (Camera) object; } } } catch (Exception e) { e.printStackTrace(); } return null; } // 設置圖片大小 private void setPictureSize(Parameters parameters) { List<Size> sizes = parameters.getSupportedPictureSizes(); if (sizes == null) { return; } int maxSize = 0; int width = 0; int height = 0; for (int i = 0; i < sizes.size(); i++) { Size size = sizes.get(i); int pix = size.width * size.height; if (pix > maxSize) { maxSize = pix; width = size.width; height = size.height; } } Log.i("tag", "圖片的大小:" + width + " height:" + height); parameters.setPictureSize(width, height); } public Camera getCamera() { return mCamera; } // 自動對焦 public void autoFocus(View v, MotionEvent event) { if (isShowFrame) { return; } mCamera.autoFocus(new AutoFocusCallback() { @Override public void onAutoFocus(boolean success, Camera camera) { } }); RelativeLayout layout = (RelativeLayout) v; final ImageView imageView = new ImageView(mContext); Bitmap bitmap = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.lcamera_focus_frame1); imageView.setImageBitmap(bitmap); LayoutParams params = new RelativeLayout.LayoutParams( bitmap.getWidth(), bitmap.getHeight()); // imageView.setLayoutParams(params); Log.e("tag", "bitmap.getWidth:" + bitmap.getWidth()); params.leftMargin = (int) (event.getX() - bitmap.getWidth() / 2); params.topMargin = (int) (event.getY() - bitmap.getHeight() / 2); layout.addView(imageView, params); imageView.setVisibility(View.VISIBLE); ScaleAnimation animation = new ScaleAnimation(1, 0.5f, 1, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); animation.setDuration(300); animation.setFillAfter(true); animation.setAnimationListener(new AnimationListener() { @Override public void onAnimationStart(Animation animation) { } @Override public void onAnimationRepeat(Animation animation) { } @Override public void onAnimationEnd(final Animation animation) { imageView.setImageResource(R.drawable.lcamera_focus_frame2); new Thread() { public void run() { try { Thread.sleep(400); ((Activity) (mContext)) .runOnUiThread(new Runnable() { @Override public void run() { imageView .setImageResource(R.drawable.lcamera_focus_frame3); } }); Thread.sleep(200); ((Activity) (mContext)) .runOnUiThread(new Runnable() { @Override public void run() { imageView.clearAnimation(); imageView.setVisibility(View.GONE); isShowFrame = false; } }); } catch (InterruptedException e) { e.printStackTrace(); } }; }.start(); } }); imageView.startAnimation(animation); isShowFrame = true; } // 拍照 public void takePicture(final Handler handler) { mCamera.takePicture(null, null, new PictureCallback() { @Override public void onPictureTaken(byte[] data, Camera camera) { FileOutputStream fos = null; try { File directory; if (Environment.getExternalStorageState().equals( Environment.MEDIA_MOUNTED)) { directory = new File(Environment .getExternalStorageDirectory(), "camera"); } else { directory = new File(mContext.getCacheDir(), "camera"); } if (!directory.exists()) { directory.mkdir(); } File file = new File(directory, System.currentTimeMillis() + ".jpg"); Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length); fos = new FileOutputStream(file); boolean compress = bitmap.compress(CompressFormat.JPEG, 100, fos); if (compress) { handler.sendEmptyMessage(MainActivity.MESSAGE_SVAE_SUCCESS); } else { handler.sendEmptyMessage(MainActivity.MESSAGE_SVAE_FAILURE); } mCamera.startPreview(); Log.i("tag", " 保存是否成功:" + compress + " file.exists:" + file.exists()); } catch (FileNotFoundException e) { e.printStackTrace(); } finally { if (fos != null) { try { fos.close(); } catch (IOException e) { } } } } }); } // 多鏡頭切換 public void switchCamera(SurfaceView surfaceView, boolean isFrontCamera) { if (mCamera != null) { mCamera.stopPreview(); mCamera.release(); mCamera = null; } int cameraId = isFrontCamera ? CAMERA_FACING_FRONT : CAMERA_FACING_BACK;// CAMERA_FACING_FRONT為前置攝像頭 mCamera = open(cameraId); Parameters parameters = mCamera.getParameters(); Log.e("tag", "parameters.getPictureSize()" + parameters.getPictureSize().width); setPictureSize(parameters); parameters.setPictureFormat(PixelFormat.JPEG); mCamera.setParameters(parameters); Log.e("tag", "2 parameters.getPictureSize()" + parameters.getPictureSize().width); setDisplayOrientation(mCamera); try { mCamera.setPreviewDisplay(mHolder); mCamera.startPreview(); } catch (IOException e) { e.printStackTrace(); } } public boolean isSupportedZoom() { if (mCamera != null) { Parameters parameters = mCamera.getParameters(); return parameters.isZoomSupported(); } return false; } public int getMaxZoom() { if (mCamera == null) { mCamera = Camera.open(); } mParameters = mCamera.getParameters(); return mParameters.getMaxZoom(); } // 設置Zoom public void setZoom(int value) { Log.i("tag", "value:" + value); mParameters.setZoom(value); mCamera.setParameters(mParameters); mCamera.startPreview(); } public boolean isSupportedFlashMode() { if (mCamera == null) { mCamera = Camera.open(); } Parameters parameters = mCamera.getParameters(); List<String> modes = parameters.getSupportedFlashModes(); if (modes != null && modes.size() != 0) { boolean autoSupported = modes.contains(Parameters.FLASH_MODE_AUTO); boolean onSupported = modes.contains(Parameters.FLASH_MODE_ON); boolean offSupported = modes.contains(Parameters.FLASH_MODE_OFF); return autoSupported && onSupported && offSupported; } return false; } // 設置閃光燈模式 public void SetFlashMode(int flashMode) { switch (flashMode) { case FLASH_MODE_AUTO: mParameters.setFlashMode(Parameters.FLASH_MODE_AUTO); break; case FLASH_MODE_ON: mParameters.setFlashMode(Parameters.FLASH_MODE_ON); break; case FLASH_MODE_OFF: mParameters.setFlashMode(Parameters.FLASH_MODE_OFF); break; } mCamera.setParameters(mParameters); mCamera.startPreview(); } }
layout中main.xml聲明相應的surfaceview和相應的button/seekbar
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/RelativeLayout1" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical" > <SurfaceView android:id="@+id/surfaceView1" android:layout_width="fill_parent" android:layout_height="fill_parent" android:layout_marginBottom="55dp" /> <RelativeLayout android:id="@+id/camera_bottom" android:layout_width="fill_parent" android:layout_height="55.0dip" android:layout_alignParentBottom="true" android:layout_alignParentRight="true" android:background="#88ffffff" android:orientation="vertical" > <Button android:id="@+id/camera_take_btn" android:layout_width="60dp" android:layout_height="wrap_content" android:layout_centerInParent="true" android:layout_gravity="center" android:layout_marginLeft="3.0dip" android:text="拍照" /> </RelativeLayout> <Button android:id="@+id/camera_switch_btn" android:layout_width="120dp" android:layout_height="wrap_content" android:layout_alignParentTop="true" android:layout_centerHorizontal="true" android:layout_marginTop="5dp" android:gravity="center" android:text="打開前置攝像頭" > </Button> <Button android:id="@+id/flashMode" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:layout_alignParentTop="true" android:layout_marginTop="5dp" android:text="flash_auto" /> <LinearLayout android:id="@+id/zoomLayout" android:layout_width="wrap_content" android:layout_height="20dp" android:layout_above="@+id/camera_bottom" android:layout_centerHorizontal="true" android:layout_marginBottom="5dp" android:orientation="horizontal" > <TextView android:id="@+id/textView1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text=" - " android:textColor="#ffffff" /> <SeekBar android:id="@+id/seekBar1" android:layout_width="250dp" android:layout_height="13dp" android:layout_gravity="center_vertical" android:maxHeight="6dp" android:progressDrawable="@drawable/seekbar_progress" android:thumb="@drawable/camera_seekbar_progress_ball" android:thumbOffset="0dp" /> <TextView android:id="@+id/textView2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="top" android:gravity="top" android:text=" + " android:textColor="#ffffff" /> </LinearLayout> </RelativeLayout>
在AndroidManifest中做如下權限聲明
<uses-sdk android:minSdkVersion="8" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.CAMERA" /> <uses-feature android:name="android.hardware.camera" /> <uses-feature android:name="android.hardware.camera.autofocus" />