前幾天的項目需要使用CameraAPI自己定義照相機,之前用過的二維碼也要自己寫底層代碼,於是總結一下使用CameraAPI的幾點事項。現在由於JDK7.0及其以上版本的官方文檔已經不再推薦使用camera包而是camera2包,但這次還是先講camera的使用,至於camera2等以後再講。 首先 ...
前幾天的項目需要使用CameraAPI自己定義照相機,之前用過的二維碼也要自己寫底層代碼,於是總結一下使用CameraAPI的幾點事項。現在由於JDK7.0及其以上版本的官方文檔已經不再推薦使用camera包而是camera2包,但這次還是先講camera的使用,至於camera2等以後再講。
首先是添加照相機許可權,在清單文件中必須添加攝像頭硬體許可權和使用功能,其中功能可以根據項目需求選擇性放入。
1 <uses-permission android:name="android.permission.CAMERA"/> 2 <!--使用攝像頭硬體功能--> 3 <uses-feature android:name="android.hardware.camera"/> 4 <!--自動對焦功能--> 5 <uses-feature android:name="android.hardware.camera.autofocus"/> 6 <!--閃光燈功能--> 7 <uses-feature android:name="android.hardware.camera.flash"/> 8 <!--前置攝像頭--> 9 <uses-feature android:name="android.hardware.camera.front"/>
關於許可權和對應的功能可以參考文章 http://www.cnblogs.com/BobGo/articles/5646751.html;
我在項目中需要兩個功能,一是在顯示攝像頭的SurfaceView上添加一層ImageView,可以在ImageView上繪製不同的遮罩層;還有一個功能是攝像頭拍攝照片之後顯示及保存圖片。
第一是攝像頭簡單的使用,在這裡可能會遇到在SurfaceView中攝像頭的預覽出現變形問題,下麵會提到解決措施:
1 SurfaceHolder surfaceHolder = surfaceView.getHolder(); 2 surfaceHolder.addCallback(this); 3 surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
在當前類中實現SurfaceHolder.Callback 介面,重寫三個方法 surfaceCreated(), surfaceChanged(), surfaceDestroy(),這三個方法就是SurfaceView對應的生命周期;
在surfaceCreated()中執行Camera.open()返回一個Camera對象,打開攝像頭硬體,在surfaceDestroy()中,Camera對象調用release()釋放攝像頭;在surfaceChanged()中,設置攝像頭參數,其中getBestSize()是確定手機攝像頭硬體可以使用的最合適大小,這個演算法是官方提供的。我在之前不採用這個演算法而直接設置大小,導致在測試機運行時SurfaceView顯示的圖片出現變形。
1 @Override 2 public void surfaceCreated(SurfaceHolder holder) { 3 camera = Camera.open(); 4 } 5 6 @Override 7 public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { 8 //已經獲得Surface的width和height,設置Camera的參數 9 Camera.Parameters parameters = camera.getParameters(); 10 Camera.Size size=getBestSize(parameters.getSupportedPreviewSizes()); 11 int w=size.width; 12 int h=size.height; 13 parameters.setPreviewSize(w, h); 14 parameters.setPictureSize(w, h); 15 // List<Camera.Size> vSizeList = parameters.getSupportedPictureSizes(); 16 // for (int num = 0; num < vSizeList.size(); num++) { 17 // Camera.Size vSize = vSizeList.get(num); 18 // } 19 // if (t?his.getResources().getConfiguration().orientation != Configuration.ORIENTATION_LANDSCAPE) { 20 //強制豎屏模式 21 parameters.set("orientation", "portrait"); 22 //在2.2以上可以使用,預覽顯示旋轉90 23 // parameters.setRotation(90); 24 camera.setDisplayOrientation(90); 25 // } else { 26 // parameters.set("orientation", "landscape"); 27 //在2.2以上可以使用 28 // camera.setDisplayOrientation(90); 29 // } 30 camera.setParameters(parameters); 31 try { 32 //設置顯示 33 camera.setPreviewDisplay(holder); 34 } catch (IOException exception) { 35 camera.release(); 36 camera = null; 37 } 38 //開始預覽 39 camera.startPreview(); 40 //設置自動對焦 41 camera.autoFocus(new Camera.AutoFocusCallback() { 42 @Override 43 public void onAutoFocus(boolean success, Camera camera) { 44 if (success) { 45 // success為true表示對焦成功,改變對焦狀態圖像 46 } 47 } 48 }); 49 } 50 51 private Camera.Size getBestSize(List<Camera.Size> supportedPreviewSizes) { 52 Camera.Size largestSize=supportedPreviewSizes.get(0); 53 int largestArea= supportedPreviewSizes.get(0).height*supportedPreviewSizes.get(0).width; 54 for (Camera.Size s:supportedPreviewSizes){ 55 int area=s.width*s.height; 56 if(area>largestArea){ 57 largestArea=area; 58 largestSize=s; 59 } 60 } 61 return largestSize; 62 63 } 64 65 @Override 66 public void surfaceDestroyed(SurfaceHolder holder) { 67 // 釋放手機攝像頭 68 camera.release(); 69 }
也可以在surfaceChanged()中設置閃光燈等功能:
1 parameters.setFlashMode(isChecked?Camera.Parameters.FLASH_MODE_ON:Camera.Parameters.FLASH_MODE_OFF);
最後在需要攝像的點擊監聽中調用takePicture(Camera.ShutterCallback, Camera.PictureCallback, Camera.PictureCallback, Camera.PictureCallback)函數來完成拍照,這個函數中可以四個回調介面,ShutterCallback是快門按下的回調,在這裡我們可以設置播放“咔嚓”聲之類的操作,後面有三個PictureCallback介面,分別對應三份圖像數據:原始圖像、縮放和壓縮圖像和JPG圖像,圖像數據可以在PictureCallback介面的void onPictureTaken(byte[] data, Camera camera)中獲得,三份數據相應的三個回調正好按照參數順序調用,通常我們只關心JPG圖像數據,此時前面兩個PictureCallback介面參數可以直接傳null;
每次調用takePicture() 獲取圖像後,攝像頭會停止預覽,假如需要繼續拍照,則我們需要在上面的PictureCallback的onPictureTaken() 函數末尾,Camera對象再次調用startPreview() 函數;在不需要拍照的時候,Camera對象需要主動調用stopPreview() 停止預覽功能;
第二個功能是攝像圖片的顯示和保存,在這個功能中可能會出現兩個問題,一個是拍攝圖片橫屏顯示,另一個是將上邊SurfaceView上方的ImageView遮罩層一同保存到用戶手機,實現類似於圖像合成的效果;
關於圖片橫屏顯示的問題,是因為Android官方認為手機橫屏才是攝像頭的正確打開方式,所以保存的圖片也是橫屏保存的,所以如果想豎屏顯示圖片,就要把圖片順時針旋轉90度,調整到豎屏顯示模式,具體代碼如下:
1 private void setImageByte(ImageView showImage, byte[] data) { 2 Bitmap bitmap= BitmapFactory.decodeByteArray(data, 0, data.length); 3 showImage.setImageBitmap(bitmap); 4 Matrix matrix = showImage.getImageMatrix(); 5 matrix.setRotate(90); 6 showImage.setImageBitmap(Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true)); 7 }
還有一個問題是解決兩個控制項內容同時保存的問題,這個其實想到了方法就很簡單了,自定義一個FrameLayout的子佈局,在該佈局中只加入一個方法
1 public Bitmap getBitmap(){ 2 //設置緩存 3 setDrawingCacheEnabled(true); 4 buildDrawingCache(); 5 //從緩存中獲取當前屏幕的圖片 6 return getDrawingCache(); 7 }
然後在上文用到的xml佈局中將該佈局作為要同時保存圖像的父控制項。
最後在java代碼中需要保存合成圖片的地方,只需要實例化剛剛自定義的FrameLayout對象調用getBitmap(),就能返回合成之後的Bitmap對象;
所以我們的功能就能完全實現了。