android 開發中經常遇到拍照的需求,android 系統幫我們把相機封裝成了Camera類,除了Camera還有個SurfaceView 需要用到,核心的就這2個。
# 先說下簡單實現,在說里面的坑
一般實現是寫個自定義view 例如(CameraView)繼承SurfaceView在View 的構造方法中完成相機的初始化 重要的函數 就是 Camera.open() 和 Camera.open(i); 前一個打開時直接打開后置攝像頭,后面的打開攝像頭方法可以選擇 打開具體的攝像頭 (前置或后置) 代碼實現如下:
/**
* 根據當前照相機狀態(前置或后置),打開對應相機
*/
private boolean openCamera () {
if ( mCamera != null ) {
mCamera .stopPreview() ;
mCamera .release() ;
mCamera = null;
}
if ( mIsFrontCamera ) {
Camera.CameraInfo cameraInfo = new Camera.CameraInfo() ;
for ( int i = 0 ; i < Camera.getNumberOfCameras () ; i++) {
Camera.getCameraInfo(i , cameraInfo) ;
if (cameraInfo. facing == Camera.CameraInfo. CAMERA_FACING_FRONT ) {
try {
mCamera = Camera. open(i) ;
} catch (Exception e) {
mCamera = null;
return false;
}
}
}
} else {
try {
mCamera = Camera.open() ;
} catch (Exception e) {
mCamera = null;
return false;
}
}
return true;
}
這里多說一句,因為系統相機這個資源全局是只有一個的,如果正在使用,就會進入鎖定狀態,這個時候想重新打開就必須先釋放。其他代碼都很簡單
第二步 就是把SurfaceView 和Camera 關聯起來了
使用SurfaceView 的時候需要設置一個SurfaceHolder.Callback(不清楚可以去看下SurfaceView的用法) 然后再SurfaceView 創建時候
getHolder().addCallback(callback);
這個callback 就是surfaceView的一些生命周期方法了,為什么要像這樣這么麻煩的關聯起來,也是和SurfaceView 特性有關了,這里也不多說了。
Callback的代碼 實現如下:
private
SurfaceHolder.Callback
callback
=
new
SurfaceHolder.Callback() {
@Override
public void surfaceCreated (SurfaceHolder holder) {
try {
if ( mCamera == null ) {
openCamera() ;
}
setCameraParameters() ;
mCamera .setPreviewDisplay(getHolder()) ;
} catch (Exception e) {
}
if ( mCamera != null ) {
mCamera .startPreview() ;
}
}
@Override
public void surfaceChanged (SurfaceHolder holder , int format , int width ,
int height) {
updateCameraOrientation() ;
}
@Override
public void surfaceDestroyed (SurfaceHolder holder) {
//停止錄像
if ( mCamera != null ) {
mCamera .stopPreview() ;
mCamera .release() ;
mCamera = null;
}
}
@Override
public void surfaceCreated (SurfaceHolder holder) {
try {
if ( mCamera == null ) {
openCamera() ;
}
setCameraParameters() ;
mCamera .setPreviewDisplay(getHolder()) ;
} catch (Exception e) {
}
if ( mCamera != null ) {
mCamera .startPreview() ;
}
}
@Override
public void surfaceChanged (SurfaceHolder holder , int format , int width ,
int height) {
updateCameraOrientation() ;
}
@Override
public void surfaceDestroyed (SurfaceHolder holder) {
//停止錄像
if ( mCamera != null ) {
mCamera .stopPreview() ;
mCamera .release() ;
mCamera = null;
}
}
}
;
surfaceCreated 主要是設置參數 預覽等,surfaceChanged回調里面處理旋轉拍照的情況 (這個后面再說),surfaceDestroyed 就是釋放資源了。
寫到這一步相機差不多就可以預覽了,就還差個拍照方法
public void
takePicture
(
final
Camera.PictureCallback callback
,
TakePictureListener listener) {
if ( mCamera == null ) return;
mCamera .autoFocus( new Camera.AutoFocusCallback() {
@Override
public void onAutoFocus ( boolean success , Camera camera) {
if ( mCamera != null ) {
mCamera .takePicture( null, null, callback ) ;
}
}
}) ;
if ( mCamera == null ) return;
mCamera .autoFocus( new Camera.AutoFocusCallback() {
@Override
public void onAutoFocus ( boolean success , Camera camera) {
if ( mCamera != null ) {
mCamera .takePicture( null, null, callback ) ;
}
}
}) ;
}
這里加了段拍照之前先對焦一次的邏輯。 ok,到這里核心代碼差不多寫完了。 下面說下其中的坑:
- 相機的權限 android.permission.CAMERA android 4.0 之后相機權限屬於運行時權限,所以即使聲明了權限也可能用戶主動拒絕,導致Camera.open(); 失敗 (所以這里還有個坑,打開相機失敗是用戶主動拒絕了權限,還是相機被占用或者其他原因失敗 區分不出來)
- 相機預覽時候圖片變形 這里就是 Camera.Parameters parameters = mCamera .getParameters(); 相機參數設置原因了 只有當SufaceView的(當前控件)寬高比和相機預覽設置的寬高比還有生成照片的寬高比一直時預覽才不會變形 代碼如下:
/**
* 設置照相機參數
*/
private void setCameraParameters () {
if ( mCamera == null ) return;
Camera.Parameters parameters = mCamera .getParameters() ;
// 選擇合適的預覽尺寸
List<Camera.Size> sizeList = parameters.getSupportedPreviewSizes() ;
Collections. sort(sizeList , new Comparator<Camera.Size>() {
@Override
public int compare (Camera.Size lhs , Camera.Size rhs) {
return rhs. height - lhs. height ;
}
}) ;
Camera.Size tempSize = null;
for (Camera.Size size : sizeList) {
if (size. width * ScreenUtil. getScreenWidth(getContext()) == ScreenUtil.getScreenHeight(getContext()) * size. height ) {
tempSize = size ; // parameters.setPreviewSize(size.width, size.height);
break;
}
}
if (tempSize == null ) {
for (Camera.Size size : sizeList) {
//小於100W像素
if (size. width * 16 == 9 * size. height ) {
tempSize = size ; // parameters.setPictureSize(size.width, size.height);
break;
}
}
}
if (tempSize == null ) {
for (Camera.Size size : sizeList) {
//小於100W像素
if (size. width * 9 == 16 * size. height ) {
tempSize = size ; // parameters.setPictureSize(size.width, size.height);
break;
}
}
}
if (tempSize == null ) {
tempSize = sizeList.get( 0 ) ;
}
parameters.setPreviewSize(tempSize. width , tempSize. height ) ;
tempSize = null;
//設置生成的圖片大小
sizeList = parameters.getSupportedPictureSizes() ;
Collections. sort(sizeList , new Comparator<Camera.Size>() {
@Override
public int compare (Camera.Size lhs , Camera.Size rhs) {
return rhs. height - lhs. height ;
}
}) ;
if (sizeList.size() > 0 ) {
for (Camera.Size size : sizeList) {
//小於100W像素
if (size. width * ScreenUtil.getScreenWidth (getContext()) == ScreenUtil.getScreenHeight(getContext()) * size. height ) {
tempSize = size ; // parameters.setPictureSize(size.width, size.height);
break;
}
}
}
if (tempSize == null ) {
for (Camera.Size size : sizeList) {
//小於100W像素
if (size. width * 16 == 9 * size. height ) {
tempSize = size ; // parameters.setPictureSize(size.width, size.height);
break;
}
}
}
if (tempSize == null ) {
for (Camera.Size size : sizeList) {
//小於100W像素
if (size. width * 9 == 16 * size. height ) {
tempSize = size ; // parameters.setPictureSize(size.width, size.height);
break;
}
}
}
if (tempSize == null ) {
tempSize = sizeList.get( 0 ) ;
}
parameters.setPictureSize(tempSize. width , tempSize. height ) ;
if (parameters.getSupportedFocusModes().contains(Camera.Parameters. FOCUS_MODE_CONTINUOUS_PICTURE )) {
parameters.setFocusMode(Camera.Parameters. FOCUS_MODE_CONTINUOUS_PICTURE ) ;
}
// mCamera .cancelAutoFocus() ; //只有加上了這一句,才會自動對焦。 然並卵!
// //設置圖片格式
parameters.setPictureFormat(ImageFormat. JPEG ) ;
parameters.setJpegQuality( 100 ) ;
parameters.setJpegThumbnailQuality( 100 ) ;
//自動聚焦模式
parameters.setFocusMode(Camera.Parameters. FOCUS_MODE_AUTO ) ;
mCamera .setParameters(parameters) ;
//開啟屏幕朝向監聽
startOrientationChangeListener() ;
* 設置照相機參數
*/
private void setCameraParameters () {
if ( mCamera == null ) return;
Camera.Parameters parameters = mCamera .getParameters() ;
// 選擇合適的預覽尺寸
List<Camera.Size> sizeList = parameters.getSupportedPreviewSizes() ;
Collections. sort(sizeList , new Comparator<Camera.Size>() {
@Override
public int compare (Camera.Size lhs , Camera.Size rhs) {
return rhs. height - lhs. height ;
}
}) ;
Camera.Size tempSize = null;
for (Camera.Size size : sizeList) {
if (size. width * ScreenUtil. getScreenWidth(getContext()) == ScreenUtil.getScreenHeight(getContext()) * size. height ) {
tempSize = size ; // parameters.setPreviewSize(size.width, size.height);
break;
}
}
if (tempSize == null ) {
for (Camera.Size size : sizeList) {
//小於100W像素
if (size. width * 16 == 9 * size. height ) {
tempSize = size ; // parameters.setPictureSize(size.width, size.height);
break;
}
}
}
if (tempSize == null ) {
for (Camera.Size size : sizeList) {
//小於100W像素
if (size. width * 9 == 16 * size. height ) {
tempSize = size ; // parameters.setPictureSize(size.width, size.height);
break;
}
}
}
if (tempSize == null ) {
tempSize = sizeList.get( 0 ) ;
}
parameters.setPreviewSize(tempSize. width , tempSize. height ) ;
tempSize = null;
//設置生成的圖片大小
sizeList = parameters.getSupportedPictureSizes() ;
Collections. sort(sizeList , new Comparator<Camera.Size>() {
@Override
public int compare (Camera.Size lhs , Camera.Size rhs) {
return rhs. height - lhs. height ;
}
}) ;
if (sizeList.size() > 0 ) {
for (Camera.Size size : sizeList) {
//小於100W像素
if (size. width * ScreenUtil.getScreenWidth (getContext()) == ScreenUtil.getScreenHeight(getContext()) * size. height ) {
tempSize = size ; // parameters.setPictureSize(size.width, size.height);
break;
}
}
}
if (tempSize == null ) {
for (Camera.Size size : sizeList) {
//小於100W像素
if (size. width * 16 == 9 * size. height ) {
tempSize = size ; // parameters.setPictureSize(size.width, size.height);
break;
}
}
}
if (tempSize == null ) {
for (Camera.Size size : sizeList) {
//小於100W像素
if (size. width * 9 == 16 * size. height ) {
tempSize = size ; // parameters.setPictureSize(size.width, size.height);
break;
}
}
}
if (tempSize == null ) {
tempSize = sizeList.get( 0 ) ;
}
parameters.setPictureSize(tempSize. width , tempSize. height ) ;
if (parameters.getSupportedFocusModes().contains(Camera.Parameters. FOCUS_MODE_CONTINUOUS_PICTURE )) {
parameters.setFocusMode(Camera.Parameters. FOCUS_MODE_CONTINUOUS_PICTURE ) ;
}
// mCamera .cancelAutoFocus() ; //只有加上了這一句,才會自動對焦。 然並卵!
// //設置圖片格式
parameters.setPictureFormat(ImageFormat. JPEG ) ;
parameters.setJpegQuality( 100 ) ;
parameters.setJpegThumbnailQuality( 100 ) ;
//自動聚焦模式
parameters.setFocusMode(Camera.Parameters. FOCUS_MODE_AUTO ) ;
mCamera .setParameters(parameters) ;
//開啟屏幕朝向監聽
startOrientationChangeListener() ;
}
這里建議尺寸就和屏幕大小一樣的 如果產品需求的預覽范圍比較小,就用其他view 去遮擋其他區域(這么做會埋下另外一個坑,但是基本所有android都能適配了)。
- 第三坑 預覽時候手機橫着或者旋轉 發現預覽的圖片不對 。這里需要動態設置相機的預覽參數,也就是前面SurfaceHolder.Callback里面surfaceChanged改變時候處理的updateCameraOrientation();代碼實現如下:
/**
* 根據當前朝向修改保存圖片的旋轉角度
*/
private void updateCameraOrientation () {
if ( mCamera != null ) {
Camera.Parameters parameters = mCamera .getParameters() ;
//rotation參數為 0、90、180、270。水平方向為0。
int rotation = 90 + mOrientation == 360 ? 0 : 90 + mOrientation ;
//前置攝像頭需要對垂直方向做變換,否則照片是顛倒的
if ( mIsFrontCamera ) {
if (rotation == 90 ) rotation = 270 ;
else if (rotation == 270 ) rotation = 90 ;
}
Log.e( "TAG" , "rotation=" + rotation + "mOrientation=" + mOrientation ) ;
parameters.setRotation(rotation) ; //生成的圖片轉90°
//預覽圖片旋轉90°
mCamera .setDisplayOrientation( 90 ) ; //預覽轉90°
mCamera .setParameters(parameters) ;
}
* 根據當前朝向修改保存圖片的旋轉角度
*/
private void updateCameraOrientation () {
if ( mCamera != null ) {
Camera.Parameters parameters = mCamera .getParameters() ;
//rotation參數為 0、90、180、270。水平方向為0。
int rotation = 90 + mOrientation == 360 ? 0 : 90 + mOrientation ;
//前置攝像頭需要對垂直方向做變換,否則照片是顛倒的
if ( mIsFrontCamera ) {
if (rotation == 90 ) rotation = 270 ;
else if (rotation == 270 ) rotation = 90 ;
}
Log.e( "TAG" , "rotation=" + rotation + "mOrientation=" + mOrientation ) ;
parameters.setRotation(rotation) ; //生成的圖片轉90°
//預覽圖片旋轉90°
mCamera .setDisplayOrientation( 90 ) ; //預覽轉90°
mCamera .setParameters(parameters) ;
}
}
需要根據前置和后置攝像頭來區分 ,還要監聽屏幕朝向來設置方向 (主要用於橫豎屏切換 如果沒這個需求 可以不加)
/**
* 啟動屏幕朝向改變監聽函數 用於在屏幕橫豎屏切換時改變保存的圖片的方向
*/
private void startOrientationChangeListener () {
OrientationEventListener mOrEventListener = new OrientationEventListener(getContext()) {
@Override
public void onOrientationChanged ( int rotation) {
if (((rotation >= 0 ) && (rotation <= 45 )) || (rotation > 315 )) {
rotation = 0 ;
} else if ((rotation > 45 ) && (rotation <= 135 )) {
rotation = 90 ;
} else if ((rotation > 135 ) && (rotation <= 225 )) {
rotation = 180 ;
} else if ((rotation > 225 ) && (rotation <= 315 )) {
rotation = 270 ;
} else {
rotation = 0 ;
}
if (rotation == mOrientation )
return;
mOrientation = rotation ;
updateCameraOrientation() ;
}
} ;
mOrEventListener.enable() ;
* 啟動屏幕朝向改變監聽函數 用於在屏幕橫豎屏切換時改變保存的圖片的方向
*/
private void startOrientationChangeListener () {
OrientationEventListener mOrEventListener = new OrientationEventListener(getContext()) {
@Override
public void onOrientationChanged ( int rotation) {
if (((rotation >= 0 ) && (rotation <= 45 )) || (rotation > 315 )) {
rotation = 0 ;
} else if ((rotation > 45 ) && (rotation <= 135 )) {
rotation = 90 ;
} else if ((rotation > 135 ) && (rotation <= 225 )) {
rotation = 180 ;
} else if ((rotation > 225 ) && (rotation <= 315 )) {
rotation = 270 ;
} else {
rotation = 0 ;
}
if (rotation == mOrientation )
return;
mOrientation = rotation ;
updateCameraOrientation() ;
}
} ;
mOrEventListener.enable() ;
}
- 三星手機拍照 旋轉了90度問題 。 三星手機部分機型攝像頭默認是橫着的,解決方法是記錄下拍照時候的角度,然后生成照片后比較下照片的長和寬,如果和預計的不一樣就像左或右旋轉90度(根據拍照時候的角度選擇左右)。
到這里相機基本功能差不多就完成了,還有很多其他的坑沒說到,但都是比較好解決了。還有就是對焦很縮放 閃光燈等功能 ,下篇文章再說吧。
