好久沒寫了,有些東西做過都快忘了,趕緊記一下。
現在來實現一個簡單的相機程序。
原文地址http://www.cnblogs.com/rossoneri/p/4246134.html
當然需要的話可以直接調用系統的camera程序,但自己實現會使用更自由。
吶,既然要用實現相機,那就需要先了解一下調用camera的類android.hardware.camera

android.hardware.Camera The Camera class is used to set image capture settings, start/stop preview, snap pictures, and retrieve frames for encoding for video. This class is a client for the Camera service, which manages the actual camera hardware. To access the device camera, you must declare the android.Manifest.permission.CAMERA permission in your Android Manifest. Also be sure to include the <uses-feature> manifest element to declare camera features used by your application. For example, if you use the camera and auto-focus feature, your Manifest should include the following: <uses-permission android:name="android.permission.CAMERA" /> <uses-feature android:name="android.hardware.camera" /> <uses-feature android:name="android.hardware.camera.autofocus" /> To take pictures with this class, use the following steps: Obtain an instance of Camera from open(int). Get existing (default) settings with getParameters(). If necessary, modify the returned Camera.Parameters object and call setParameters(Camera.Parameters). If desired, call setDisplayOrientation(int). Important: Pass a fully initialized SurfaceHolder to setPreviewDisplay(SurfaceHolder). Without a surface, the camera will be unable to start the preview. Important: Call startPreview() to start updating the preview surface. Preview must be started before you can take a picture. When you want, call takePicture(Camera.ShutterCallback, Camera.PictureCallback, Camera.PictureCallback, Camera.PictureCallback) to capture a photo. Wait for the callbacks to provide the actual image data. After taking a picture, preview display will have stopped. To take more photos, call startPreview() again first. Call stopPreview() to stop updating the preview surface. Important: Call release() to release the camera for use by other applications. Applications should release the camera immediately in android.app.Activity.onPause() (and re-open() it in android.app.Activity.onResume()). To quickly switch to video recording mode, use these steps: Obtain and initialize a Camera and start preview as described above. Call unlock() to allow the media process to access the camera. Pass the camera to android.media.MediaRecorder.setCamera(Camera). See android.media.MediaRecorder information about video recording. When finished recording, call reconnect() to re-acquire and re-lock the camera. If desired, restart preview and take more photos or videos. Call stopPreview() and release() as described above. This class is not thread-safe, and is meant for use from one event thread. Most long-running operations (preview, focus, photo capture, etc) happen asynchronously and invoke callbacks as necessary. Callbacks will be invoked on the event thread open(int) was called from. This class's methods must never be called from multiple threads at once. Caution: Different Android-powered devices may have different hardware specifications, such as megapixel ratings and auto-focus capabilities. In order for your application to be compatible with more devices, you should not make assumptions about the device camera specifications.
另外補充一下,實現android的video也是使用的Camera API,用到相關的類為Camera,SurfaceView,MediaRecorder,Intent(MediaStore.ACTION_IMAGE_CAPTURE, MediaStore.ACTION_VEDIO_CAPTURE)
好,根據camera的說明,在開始編寫程序之前需要確認manifest中添加關於使用攝像設備的適當的權限聲明,如果使用camera API必須加上下段說明:
<uses-permission android:name="android.permission.CAMERA" />
當然,程序也需要聲明使用camera的特性:
<uses-feature android:name="android.hardware.camera" />

我就不翻譯了,應該不難懂 <uses-feature android:name="android.hardware.camera" /> The application uses the device's camera. If the device supports multiple cameras, the application uses the camera that facing away from the screen. <uses-feature android:name="android.hardware.camera.autofocus" /> Subfeature. The application uses the device camera's autofocus capability. <uses-feature android:name="android.hardware.camera.flash" /> Subfeature. The application uses the device camera's flash. <uses-feature android:name="android.hardware.camera.front" /> Subfeature. The application uses a front-facing camera on the device. <uses-feature android:name="android.hardware.camera.any" /> The application uses at least one camera facing in any direction, or an external camera device if one is connected. Use this in preference to android.hardware.camera if a back-facing camera is not required.
如果需要其他特性,在列表里選擇性添加就好,比如一會兒我還需要自動對焦就要添加相關代碼到manifest。添加特性代碼就是為了防止你的程序被安裝到沒有攝像頭或者不支持你要的功能的設備上去(prevent your application from being installed to devices that do not include a camera or do not support the camera features you specify. )
如果你的程序能通過適當的操作使用camera或一些特性,但並不特別需要它,可以增加required屬性為false:
<uses-feature android:name="android.hardware.camera" android:required="false" />
Ok,前面說的有點多,下面說下使用camera的流程:
- 整體流程
- 檢測camera的存在並訪問camera
- 繼承SurfaceView並添加SurfaceHolder接口以顯示預覽畫面
- 為預覽畫面添加你需要的布局和控件
- 增加對拍照事件的監聽
- 使用拍照功能並保存照片
- 最后要釋放camera
- 流程細節
- 通過open(int)方法獲取camera的實例,int為camera的id
- 使用getParameters()獲取相機當前的設置,包括預覽尺寸,拍照尺寸等等參數
- 如果修改了相關設置,調用setParameters(Camera.Parameters)將更改的信息重新生效
- 有需要的話使用setDisplayOrientation(int)來改變預覽畫面的方向
- 使用setPreviewDisplay(SurfaceHolder)傳遞一個完整初始化的SurfaceHolder,沒有surface,就沒法啟動預覽畫面
- 在拍照之前先調用startPreview()來更新預覽畫面
- 調用takePicture(Camera.ShutterCallback, Camera.PictureCallback, Camera.PictureCallback, Camera.PictureCallback)進行拍照,在回調函數中獲得照片對象並做處理
- 預覽畫面會在拍照后關閉,如果還需要拍照,記得先startPreview()
- 用stopPreview()來關閉預覽畫面
- camera使用之后一定要調用release()釋放掉
下面跟着流程,開始編碼
先在主界面添加一個按鈕,用來打開相機,效果如下:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.example.demo_camera.MainActivity" > <Button android:id="@+id/main_btn" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerHorizontal="true" android:layout_centerVertical="true" android:text="@string/main_btn_open_camera" /> <include android:id="@+id/camera_layout" android:layout_width="match_parent" android:layout_height="match_parent" layout="@layout/view_camera" android:visibility="gone" /> </RelativeLayout>
設計拍照界面,一個surfaceview用來顯示預覽畫面,兩個button進行拍照和返回

<?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/camera_view" android:layout_width="match_parent" android:layout_height="match_parent" > <SurfaceView android:id="@+id/cameraSurfaceView" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_gravity="center" /> <RelativeLayout android:id="@+id/cameraButtonLayout" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@android:color/transparent" android:orientation="horizontal" > <Button android:id="@+id/cameraTakePicCancle" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:layout_alignParentEnd="true" android:layout_alignParentRight="true" android:background="@color/RoyalBlue" android:text="@string/camera_btn_back" /> <Button android:id="@+id/cameraTakePic" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:layout_marginEnd="10dp" android:layout_marginRight="10dp" android:layout_toLeftOf="@id/cameraTakePicCancle" android:layout_toStartOf="@id/cameraTakePicCancle" android:background="@color/RoyalBlue" android:text="@string/camera_btn_takephoto" /> </RelativeLayout> </FrameLayout>
編寫camera操作的代碼,增加了自動對焦,分辨前后攝像頭等內容,為了代碼看起來連貫,具體說明放在注釋里

1 package com.example.demo_camera; 2 3 import java.util.List; 4 5 import android.app.Activity; 6 import android.graphics.PixelFormat; 7 import android.hardware.Camera; 8 import android.hardware.Camera.AutoFocusCallback; 9 import android.hardware.Camera.CameraInfo; 10 import android.hardware.Camera.Size; 11 import android.view.SurfaceHolder; 12 import android.view.SurfaceView; 13 import android.view.View; 14 import android.view.View.OnClickListener; 15 import android.view.ViewGroup; 16 import android.widget.Button; 17 18 public class CameraView { 19 // Private Constants /////////////////////////////////////////////////////// 20 21 // Public Variables //////////////////////////////////////////////////////// 22 23 // Member Variables //////////////////////////////////////////////////////// 24 private ViewGroup mView; 25 private Activity mActivity; 26 27 private Button mBtnTakePhoto; 28 private Button mBtnBack; 29 private SurfaceView mSurfaceView; 30 31 private Camera mCamera; 32 private Camera.Parameters mParameters; 33 private CameraInfo mCameraInfo; 34 35 private int mDegree; 36 private int mScreenWidth; 37 private int mScreenHeight; 38 39 // Constructors //////////////////////////////////////////////////////////// 40 public CameraView(Activity mActivity, ViewGroup mView) { 41 this.mActivity = mActivity; 42 this.mView = mView; 43 initCameraView(); 44 initCameraEvent(); 45 } 46 47 // Class Methods /////////////////////////////////////////////////////////// 48 49 // Private Methods ///////////////////////////////////////////////////////// 50 private void initCameraView() { 51 mBtnTakePhoto = (Button) mView.findViewById(R.id.cameraTakePic); 52 mBtnBack = (Button) mView.findViewById(R.id.cameraTakePicCancle); 53 mSurfaceView = (SurfaceView) mView.findViewById(R.id.cameraSurfaceView); 54 55 } 56 57 private void initCameraEvent() { 58 59 mSurfaceView.getHolder().setKeepScreenOn(true);// 屏幕常亮 60 mSurfaceView.getHolder().addCallback(new SurfaceCallback());// 為surfaceHolder添加回調 61 mSurfaceView.getHolder().setFormat(PixelFormat.TRANSPARENT); 62 63 mBtnTakePhoto.setOnClickListener(new OnClickListener() { 64 65 @Override 66 public void onClick(View v) { 67 // TODO Auto-generated method stub 68 // if (mCamera != null) { 69 } 70 }); 71 72 mBtnBack.setOnClickListener(new OnClickListener() { 73 74 @Override 75 public void onClick(View v) { 76 // TODO Auto-generated method stub 77 78 } 79 }); 80 } 81 82 private final class SurfaceCallback implements SurfaceHolder.Callback { // 回調中包含三個重寫方法,看方法名即可知道是干什么的 83 84 @Override 85 public void surfaceCreated(SurfaceHolder holder) { // 創建預覽畫面處理 86 // TODO Auto-generated method stub 87 int nNum = Camera.getNumberOfCameras(); // 根據攝像頭的id找前后攝像頭 88 if (nNum == 0) { 89 // 沒有攝像頭 90 return; 91 } 92 93 for (int i = 0; i < nNum; i++) { 94 CameraInfo info = new CameraInfo(); // camera information 對象 95 Camera.getCameraInfo(i, info);// 獲取information 96 if (info.facing == CameraInfo.CAMERA_FACING_BACK) { // 后攝像頭 97 startPreview(info, i, holder); // 設置preview的顯示屬性 98 return; 99 } 100 } 101 102 } 103 104 @Override 105 public void surfaceChanged(SurfaceHolder holder, int format, int width, 106 int height) { // 預覽畫面有變化時進行如下處理 107 // TODO Auto-generated method stub 108 if (mCamera == null) { 109 return; 110 } 111 112 mCamera.autoFocus(new AutoFocusCallback() { // 增加自動對焦 113 @Override 114 public void onAutoFocus(boolean success, Camera camera) { 115 // TODO Auto-generated method stub 116 if (success) { // 如果自動對焦成功 117 mCamera.cancelAutoFocus(); // 關閉自動對焦,下次有變化時再重新打開自動對焦,這句不能少 118 } 119 } 120 }); 121 } 122 123 @Override 124 public void surfaceDestroyed(SurfaceHolder holder) { // surfaceView關閉處理以下方法 125 // TODO Auto-generated method stub 126 if (mCamera != null) { 127 mCamera.stopPreview(); 128 mCamera.release(); // 釋放照相機 不能少 129 mCamera = null; 130 mCameraInfo = null; 131 } 132 } 133 134 } 135 136 private Size getOptimalSize(int nDisplayWidth, int nDisplayHeight, 137 List<Size> sizes, double targetRatio) { // 這里是我的一個計算顯示尺寸的方法,可以自己去設計 138 final double ASPECT_TOLERANCE = 0.001; 139 if (sizes == null) 140 return null; 141 142 Size optimalSize = null; 143 double minDiff = Double.MAX_VALUE; 144 145 int targetHeight = Math.min(nDisplayWidth, nDisplayHeight); 146 for (Size size : sizes) { 147 double ratio = (double) size.width / size.height; 148 if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE) 149 continue; 150 if (Math.abs(size.height - targetHeight) < minDiff) { 151 optimalSize = size; 152 minDiff = Math.abs(size.height - targetHeight); 153 } 154 } 155 if (optimalSize == null) { 156 minDiff = Double.MAX_VALUE; 157 for (Size size : sizes) { 158 if (Math.abs(size.height - targetHeight) < minDiff) { 159 optimalSize = size; 160 minDiff = Math.abs(size.height - targetHeight); 161 } 162 } 163 } 164 return optimalSize; 165 } 166 167 // Public Methods ////////////////////////////////////////////////////////// 168 public void show() { 169 mView.setVisibility(View.VISIBLE); 170 mSurfaceView.setVisibility(View.VISIBLE); // 171 } 172 173 public void hideCamera() { 174 mView.setVisibility(View.GONE); 175 mSurfaceView.setVisibility(View.GONE); // 176 } 177 178 public final ViewGroup getViewGroup() { 179 return mView; 180 } 181 182 public void initScreenSize(int nWidth, int nHeight) { // 設置屏幕的寬與高 183 ViewGroup.LayoutParams lp = mSurfaceView.getLayoutParams(); 184 lp.width = nWidth; 185 lp.height = nHeight; 186 mSurfaceView.setLayoutParams(lp); 187 188 mScreenWidth = nWidth; 189 mScreenHeight = nHeight; 190 } 191 192 public void startPreview(CameraInfo info, int cameraId, SurfaceHolder holder) {// 在回調中調用設置預覽的屬性 193 try { 194 195 mCameraInfo = info; 196 197 mCamera = Camera.open(cameraId); 198 199 mCamera.setPreviewDisplay(holder); // 設置用於顯示拍照影像的SurfaceHolder對象 200 mCamera.setDisplayOrientation(90); // 設置顯示的方向,這里手機是豎直為正向90度,可以自己寫個方法來根據屏幕旋轉情況獲取到相應的角度 201 202 { 203 mParameters = mCamera.getParameters(); 204 205 // PictureSize 獲取支持顯示的尺寸 因為支持的顯示尺寸是和設備有關,所以需要獲取設備支持的尺寸列表 206 // 另外因為是預覽畫面是全屏顯示,所以顯示效果也和屏幕的分辨率也有關系,為了最好的適應屏幕,建議選取 207 // 與屏幕最接近的寬高比的尺寸 208 List<Size> listPictureSizes = mParameters 209 .getSupportedPictureSizes(); 210 211 Size sizeOptimalPicture = getOptimalSize(mScreenWidth, 212 mScreenHeight, listPictureSizes, (double) mScreenWidth 213 / mScreenHeight); 214 mParameters.setPictureSize(sizeOptimalPicture.width, 215 sizeOptimalPicture.height); 216 217 // PreviewSize 218 List<Camera.Size> ListPreviewSizes = mParameters 219 .getSupportedPreviewSizes(); 220 221 Size sizeOptimalPreview = getOptimalSize( 222 sizeOptimalPicture.width, sizeOptimalPicture.height, 223 ListPreviewSizes, (double) sizeOptimalPicture.width 224 / sizeOptimalPicture.height); 225 mParameters.setPreviewSize(sizeOptimalPreview.width, 226 sizeOptimalPreview.height); 227 228 // 這里就是有的設備不支持Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE連續自動對焦這個字段,所以做個判斷 229 List<String> lstFocusModels = mParameters 230 .getSupportedFocusModes(); 231 for (String str : lstFocusModels) { 232 if (str.equals(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) { 233 mParameters 234 .setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE); 235 break; 236 } 237 } 238 239 mCamera.setParameters(mParameters); 240 } 241 242 mCamera.startPreview(); // 開始預覽 243 244 } catch (Exception e) { 245 e.printStackTrace(); 246 } 247 } 248 }
在MainActivity中添加下面主要代碼

1 package com.example.demo_camera; 2 3 import android.app.Activity; 4 import android.os.Bundle; 5 import android.util.DisplayMetrics; 6 import android.view.Menu; 7 import android.view.MenuItem; 8 import android.view.View; 9 import android.view.Window; 10 import android.view.View.OnClickListener; 11 import android.view.ViewGroup; 12 import android.view.WindowManager; 13 import android.widget.Button; 14 15 public class MainActivity extends Activity { 16 17 Button mBtnCamera; 18 ViewGroup mVgCamera; 19 CameraView mCameraView; 20 21 @Override 22 protected void onCreate(Bundle savedInstanceState) { 23 super.onCreate(savedInstanceState); 24 25 // 全屏顯示要隱藏標題欄狀態欄 26 // hide title bar 27 requestWindowFeature(Window.FEATURE_NO_TITLE); 28 // hide status bar 29 int flag = WindowManager.LayoutParams.FLAG_FULLSCREEN; 30 Window window = this.getWindow(); 31 window.setFlags(flag, flag); 32 33 setContentView(R.layout.activity_main); 34 35 mBtnCamera = (Button) findViewById(R.id.main_btn); 36 mVgCamera = (ViewGroup) findViewById(R.id.camera_layout); 37 mCameraView = new CameraView(this, mVgCamera); 38 39 mBtnCamera.setOnClickListener(new OnClickListener() { 40 41 @Override 42 public void onClick(View v) { 43 // TODO Auto-generated method stub 44 mBtnCamera.setVisibility(View.GONE); 45 mVgCamera.setVisibility(View.VISIBLE); 46 47 setCameraSize(); 48 49 } 50 }); 51 } 52 53 private void setCameraSize() { 54 55 ViewGroup.LayoutParams params = mCameraView.getViewGroup() 56 .getLayoutParams(); 57 DisplayMetrics dm = new DisplayMetrics(); 58 this.getWindowManager().getDefaultDisplay().getMetrics(dm);// 獲得屏幕尺寸 59 params.width = dm.widthPixels; 60 params.height = dm.heightPixels; 61 62 mCameraView.getViewGroup().setLayoutParams(params); 63 mCameraView.initScreenSize(params.width, params.height); 64 mCameraView.show(); 65 } 66 67 @Override 68 public boolean onCreateOptionsMenu(Menu menu) { 69 // Inflate the menu; this adds items to the action bar if it is present. 70 getMenuInflater().inflate(R.menu.main, menu); 71 return true; 72 } 73 74 @Override 75 public boolean onOptionsItemSelected(MenuItem item) { 76 // Handle action bar item clicks here. The action bar will 77 // automatically handle clicks on the Home/Up button, so long 78 // as you specify a parent activity in AndroidManifest.xml. 79 int id = item.getItemId(); 80 if (id == R.id.action_settings) { 81 return true; 82 } 83 return super.onOptionsItemSelected(item); 84 } 85 }
在Manifest加入:
<uses-permission android:name="android.permission.CAMERA" /> <uses-feature android:name="android.hardware.camera" /> <uses-feature android:name="android.hardware.camera.autofocus" />
最后一些資源文件string和color

<?xml version="1.0" encoding="utf-8"?> <resources> <string name="app_name">Demo_camera</string> <string name="hello_world">Hello world!</string> <string name="action_settings">Settings</string> <string name="camera_btn_back">Back</string> <string name="camera_btn_takephoto">TakePic</string> <string name="main_btn_open_camera">Open Camera</string> <color name="RoyalBlue">#4169E1</color> </resources>
經過以上步驟,我們的設備就可以用攝像頭進行預覽了,預覽時隨意移動設備還可以自動對焦,效果如下圖:
不過,這個是用DDMS截的圖,如果是拍照,實際畫面尺寸會與看到的稍有差別,原因在代碼里也有寫。
關於拍照takePicture(Camera.ShutterCallback, Camera.PictureCallback, Camera.PictureCallback, Camera.PictureCallback)的使用也很簡單,在回調PictureCallback中重寫public void onPictureTaken(byte[] data, Camera camera) {}方法,data就是圖片數據,用bitmapfactory來decode一下,再處理一下顯示的旋轉方向與尺寸就ok了,這部分代碼有空再補吧。
好了,又復習一遍這個過程發現還是蠻簡單的。多看看官方的文檔就好。ok,收工睡覺。
參考資料:
android sdk docs
Feb.2nd
快放假了,年底略忙,項目需要年前趕個版本出來。
把上次的拍照過程補一下:
主要是拍照的回調方法,也就對拍照得到的數據按自己的需求處理一下,簡單地decode成Bitmap就可以,然后加你想要的動畫,另外加一些濾鏡啊什么的效果,也就是一些數字圖像處理算法,這個不在拍照范疇之內。下面的方法我僅僅對照片方向做了處理,然后縮放0.8倍在主界面顯示(懶得再寫個view放照片了,就用imageview顯示,為了有點差別就縮小圖片了
private final class MyPictureCallback implements PictureCallback { @Override public void onPictureTaken(byte[] data, Camera camera) { // TODO Auto-generated method stub try { Bitmap bitmapRotate = BitmapFactory.decodeByteArray(data, 0, data.length); Bitmap mBmp = bitmapRotate; int nDegree = getPictureDegree(mActivity, mCameraInfo); if (nDegree != 0) { Matrix matrix = new Matrix(); matrix.preRotate(nDegree); mBmp = Bitmap.createBitmap(bitmapRotate, 0, 0, bitmapRotate.getWidth(), bitmapRotate.getHeight(), matrix, true); }
Bitmap bmpScale = Bitmap.createScaledBitmap(mBmp, (int) (mScreenWidth * 0.8), (int) (mScreenHeight * 0.8), true); if (mListener != null) mListener.onTakePic(bmpScale); closeCamera(); } catch (Exception e) { e.printStackTrace(); } } }
寫好回調后在takePhoto按鈕事件下補充:
if (mCamera != null) mCamera.takePicture(null, null, new MyPictureCallback());
在back按鈕下補充:
takePicCancle();
cameraView最后補充代碼:
public void closeCamera() { if (mCamera != null) { mCamera.stopPreview(); mCamera.release(); // 釋放照相機 mCamera = null; mCameraInfo = null; } } public void takePicCancle() { closeCamera(); // sigCameraCancle.emit(); if (mListener != null) mListener.onBack(); }
為了讓mainActivity能得到在camera里的按鈕事件反饋,在cameraView中加兩個接口:
public interface CameraViewListener { public void onTakePic(Bitmap bmp); public void onBack(); } private CameraViewListener mListener; public void setListener(CameraViewListener listener) { mListener = listener; }
然后在MainActivity添加以下代碼:
implements CameraViewListener
implements CameraViewListener mCameraView.setListener(this); @Override public void onTakePic(Bitmap bmp) { // TODO Auto-generated method stub mVgCamera.setVisibility(View.GONE); mPhoto.setVisibility(View.VISIBLE); mPhoto.setImageBitmap(bmp); } @Override public void onBack() { // TODO Auto-generated method stub mBtnCamera.setVisibility(View.VISIBLE); mVgCamera.setVisibility(View.GONE); }
mPhoto是一個ImageView
<ImageView android:id="@+id/main_photo" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerHorizontal="true" android:layout_centerVertical="true" android:visibility="gone" />
以上,按下拍照即可在主界面看到拍照得到的畫面:
如果還需要完整代碼請持續關注,我有空把Github整理好了會發出github的地址~~