參考資料:
SurfaceView:http://www.cnblogs.com/xuling/archive/2011/06/06/android.html
android.hardware.Camera2:http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0428/2811.html
Camera預設捕獲畫面橫向: http://blog.sina.com.cn/s/blog_777c69930100y7nv.html
感謝以上大神們的的無私分享!
之前在公司寫了一個自定義CameraView,年代久遠,回頭看代碼時居然有點看不懂了。。。
真是好記性不如爛筆頭啊~
趁着年底不忙有時間,再次重寫下Camera,話不多說,開始撸代碼。
1.權限
首先需要在AndroidManifest檔案中配置權限:
<!-- 權限 -->
<!-- 攝像頭權限 -->
<uses-permission android:name="android.permission.CAMERA" />
<!-- 閃光燈權限 -->
<uses-permission android:name="android.permission.FLASHLIGHT" />
<!-- 在SDCard中建立與删除檔案權限 -->
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
<!-- 寫入SD卡權限 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<!-- 功能 -->
<!-- 攝像頭功能 -->
<uses-feature android:name="android.hardware.camera" />
<!-- 攝像頭自動對焦功能 -->
<uses-feature android:name="android.hardware.camera.autofocus" />
2.預覽圖像
2.1 SurfaceView和SurfaceHolder
關于SurfaceView的詳細介紹可以點這裡:http://www.cnblogs.com/xuling/archive/2011/06/06/android.html
2.1.1 SurfaceView
因為我們需要預覽照相機中的圖像,而這個圖像又是動态變化的,是以必須用到這個SurfaceView。
SurfaceView繼承自View,能夠在非UI線程中在螢幕上繪圖,是以我們可以在預覽圖像的同時進行一些别的操作。
我們把它寫在布局檔案中,使用findViewById獲得它的執行個體即可。
2.1.2 SurfaceHolder
SurfaceHolder相當于是SurfaceView的控制器,用來操縱surface。處理它的Canvas上畫的效果和動畫,控制表面,大小,像素等。
實作SurfaceView需要實作SurfaceHolder.Callback接口,可以自定義一個類繼承SurfaceView并實作這個接口,也可以讓Activity直接實作這個接口,我們這裡使用第二種。
實作SurfaceHolder.Callback接口需要實作三個方法:
public class CameraActivity extends AppCompatActivity implements SurfaceHolder.Callback {
...
mSurfaceHolder.addCallback(this);
...
@Override
public void surfaceCreated(SurfaceHolder holder) {
//建立時觸發,surfaceView生命周期的開始,在這裡打開相機
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
//surface的大小發生改變時觸發,在這裡預覽圖像
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
//銷毀時觸發,surfaceView生命周期的結束,在這裡關閉相機
}
}
有了預覽圖像的容器,下面就真正開始使用Camera了。
2.2 Camera
2.2.1 import注意事項
導入包的時候注意是android.hardware.Camera,而不是android.graphics.Camera,不要搞錯了;
hardware中的Camera是控制裝置攝像頭的,graphics中的Camera是對圖像進行處理的。
PS:在android5.0以上,android.hardware.Camera已過時,推薦使用的是android.hardware.Camera2類;但由于Camera2類不向下相容,而且目前安卓手機5.0以上的不多,是以我們還是使用過時的android.hardware.Camera。
關于Camera2類的詳情,點這裡:http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0428/2811.html
感謝 泡在網上的日子 的無私分享~
2.2.2 生成預覽圖像
在重寫的surfaceCreated方法中初始化camera,并開啟預覽
@Override
public void surfaceCreated(SurfaceHolder holder) {
mCamera = Camera.open();//使用靜态方法open初始化camera對象,預設打開的是後置攝像頭
try {
mCamera.setPreviewDisplay(mSurfaceHolder);//設定在surfaceView上顯示預覽
mCamera.startPreview();//開始預覽
} catch (IOException e) {
//在異常處理裡釋放camera并置為null
mCamera.release();
mCamera = null;
e.printStackTrace();
}
}
可以直接把預覽寫在surfaceCreated裡,也可以寫在surfaceChanged裡
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
//也可以把預覽寫在這裡
}
在surfaceView被銷毀時,停止預覽并釋放camera對象并置為null
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
//停止預覽并釋放camera對象并置為null
mCamera.stopPreview();
mCamera.release();
mCamera = null;
}
這時候運作程式,點選同意調用攝像頭,我們會發現有圖像了,
可是,為什麼是歪的?
。。。whta the hell。。。
2.2.3 設定預覽方向
之是以是歪的,是因為攝像頭預設捕獲的畫面byte[]是根據橫向來的,而我們的應用是豎向的,
解決辦法是調用setDisplayOrientation來設定PreviewDisplay的方向,效果就是将捕獲的畫面旋轉多少度顯示。
詳情點這裡:http://blog.sina.com.cn/s/blog_777c69930100y7nv.html
是以我們隻要在預覽前調用下setDisplayOrientation這個方法就好了
@Override
public void surfaceCreated(SurfaceHolder holder) {
mCamera = Camera.open();
try {
mCamera.setPreviewDisplay(mSurfaceHolder);
//設定預覽偏移90度,一般的裝置都是90,但某些裝置會偏移180
mCamera.setDisplayOrientation();
mCamera.startPreview();
} catch (IOException e) {
mCamera.release();
mCamera = null;
e.printStackTrace();
}
}
2.2.4 Camera.Parameters 相機參數類
Camera.Parameters是相機參數類,在這裡可以給camera對象設定分辨率,圖檔方向,閃光燈模式等等一些參數,用以實作更豐富的功能。
使用方式如下:
@Override
public void surfaceCreated(SurfaceHolder holder) {
mCamera = Camera.open();
try {
mCamera.setPreviewDisplay(mSurfaceHolder);
mCamera.setDisplayOrientation();
/**Camera.Parameters**/
Camera.Parameters parameters = mCamera.getParameters();//得到一個已有的(預設的)參數
parameters.setPreviewSize(, );//設定分辨率,後面有詳細說明
parameters.setRotation();//設定照相生成的圖檔的方向,後面有詳細說明
parameters.setFlashMode(Camera.Parameters.FLASH_MODE_OFF);//設定閃光燈模式為關
mCamera.setParameters(parameters);//将參數賦給camera
mCamera.startPreview();
} catch (IOException e) {
mCamera.release();
mCamera = null;
e.printStackTrace();
}
}
注意:這裡設定分辨率是不能随便設定的,因為每個裝置所支援的分辨率不一樣,如果設定了裝置不支援的分辨率程式就會崩潰,是以上面我把分辨率寫死是非常不可取的;
可以通過getSupportedPreviewSizes()獲得裝置支援的分辨率list。
List<Camera.Size> sizeList = parameters.getSupportedPreviewSizes();//獲得裝置所支援的分辨率清單
for (int i = ; i < sizeList.size(); i++) {
//這裡的TAG是一個常量字元串,用來辨別log
Log.i(TAG, "width:" + sizeList.get(i).width + ",height:" + sizeList.get(i).height);
}
運作這行代碼,我們可以看到如下log
- :: -/com.waka.workspace.wakacamera I/CameraActivity: width:,height:
- :: -/com.waka.workspace.wakacamera I/CameraActivity: width:,height:
- :: -/com.waka.workspace.wakacamera I/CameraActivity: width:,height:
- :: -/com.waka.workspace.wakacamera I/CameraActivity: width:,height:
- :: -/com.waka.workspace.wakacamera I/CameraActivity: width:,height:
- :: -/com.waka.workspace.wakacamera I/CameraActivity: width:,height:
- :: -/com.waka.workspace.wakacamera I/CameraActivity: width:,height:
- :: -/com.waka.workspace.wakacamera I/CameraActivity: width:,height:
- :: -/com.waka.workspace.wakacamera I/CameraActivity: width:,height:
- :: -/com.waka.workspace.wakacamera I/CameraActivity: width:,height:
- :: -/com.waka.workspace.wakacamera I/CameraActivity: width:,height:
- :: -/com.waka.workspace.wakacamera I/CameraActivity: width:,height:
- :: -/com.waka.workspace.wakacamera I/CameraActivity: width:,height:
- :: -/com.waka.workspace.wakacamera I/CameraActivity: width:,height:
- :: -/com.waka.workspace.wakacamera I/CameraActivity: width:,height:
- :: -/com.waka.workspace.wakacamera I/CameraActivity: width:,height:
- :: -/com.waka.workspace.wakacamera I/CameraActivity: width:,height:
在這裡面我們可以獲得所有的支援的分辨率;
但是注意,這裡面的width都比height大,這是因為系統預設的圖像捕捉是橫屏的,而非我們所熟悉的豎屏。
2.2.5 獲得最佳分辨率
我們發現,sizeList的分辨率大小雖然有一定的規律,但是并不是最後一個就是螢幕的分辨率;
比如我測試的手機螢幕分辨率為1920x1080,但是sizeList的最後一個是1680x1248,
是以我們還要對這些資料進行一下篩選,找到最适合螢幕的分辨率。
代碼如下:
/**
* 獲得最佳分辨率
* 注意:因為相機預設是橫屏的,是以傳參的時候要注意,width和height都是橫屏下的
*
* @param parameters 相機參數對象
* @param width 期望寬度
* @param height 期望高度
* @return
*/
private int[] getBestResolution(Camera.Parameters parameters, int width, int height) {
int[] bestResolution = new int[];//int數組,用來存儲最佳寬度和最佳高度
int bestResolutionWidth = -;//最佳寬度
int bestResolutionHeight = -;//最佳高度
List<Camera.Size> sizeList = parameters.getSupportedPreviewSizes();//獲得裝置所支援的分辨率清單
int difference = ;//最小內插補點,初始化市需要設定成一個很大的數
//周遊sizeList,找出與期望分辨率內插補點最小的分辨率
for (int i = ; i < sizeList.size(); i++) {
int differenceWidth = Math.abs(width - sizeList.get(i).width);//求出寬的內插補點
int differenceHeight = Math.abs(height - sizeList.get(i).height);//求出高的內插補點
//如果它們兩的和,小于最小內插補點
if ((differenceWidth + differenceHeight) < difference) {
difference = (differenceWidth + differenceHeight);//更新最小內插補點
bestResolutionWidth = sizeList.get(i).width;//指派給最佳寬度
bestResolutionHeight = sizeList.get(i).height;//指派給最佳高度
}
}
//最後将最佳寬度和最佳高度添加到數組中
bestResolution[] = bestResolutionWidth;
bestResolution[] = bestResolutionHeight;
return bestResolution;//傳回最佳分辨率數組
}
注意:這裡期望寬度和期望高度是為了擴充功能,
一般來說照相機都全屏,直接傳入手機分辨率就可以了
但是如果不全屏呢?
是以就可以在這裡傳入希望的高度和寬度,保證預覽圖像不變形。
修改surfaceCreate方法中的代碼:
@Override
public void surfaceCreated(SurfaceHolder holder) {
mCamera = Camera.open();
try {
mCamera.setPreviewDisplay(mSurfaceHolder);
mCamera.setDisplayOrientation();
Camera.Parameters parameters = mCamera.getParameters();
/**獲得螢幕分辨率**/
Display display = this.getWindowManager().getDefaultDisplay();
Point size = new Point();
display.getSize(size);
int screenWidth = size.x;
int screenHeight = size.y;
/**獲得最佳分辨率,注意此時要傳的width和height是指橫屏時的,是以要颠倒一下**/
int[] bestResolution = Utils.getBestResolution(parameters, screenHeight, screenWidth);//Utils是一個工具類,我習慣把操作的方法放在一個工具類中,作為靜态方法使用
parameters.setPreviewSize(bestResolution[], bestResolution[]);
parameters.setRotation();
parameters.setFlashMode(Camera.Parameters.FLASH_MODE_OFF);
mCamera.setParameters(parameters);
mCamera.startPreview();
} catch (IOException e) {
mCamera.release();
mCamera = null;
e.printStackTrace();
}
}
好了,現在應該就可以看到不變形的圖像了~
如果還變形,請隐藏通知欄,ActionBar和底部的虛拟導航鍵欄,等等等等等。。。
是以這個時候期望寬度和期望高度就有用了,我們可以計算出除去這些系統欄的高度,然後傳入我們算好的值,就可以自動比對出最佳分辨率~
2.2.6 設定自動對焦
現在預覽出來的圖像終于是正常大小了,可還是模模糊糊的,這咋拍?這坑定不能拍啊
是以我們需要實作相機的自動對焦功能,在這裡Android已經給我們封裝的很好了,我們隻需要簡單的調用一下方法就行。
好多手機裡自帶的那種停下來就自動進行對焦的方式我還不太會,就先寫了個觸摸自動對焦
自動對焦需要配置相關權限,和實作Camera.AutoFocusCallback接口
代碼如下:
//重寫onTouchEvent方法
@Override
public boolean onTouchEvent(MotionEvent event) {
mCamera.autoFocus(this);//讓Activity實作接口
return true;
}
//實作接口中的方法,自動對焦完成時的回調
@Override
public void onAutoFocus(boolean success, Camera camera) {
//在這裡可以判斷對焦是否成功,進行一些操作
}
自動對焦已經完成,調用Google的東西真是很簡單,我們可以在這裡加個提示框啊什麼的,後面有時間再說。
2.2.7 照相
終于要照相了,T^T
照相調用camera.takePicture方法,
代碼如下:
“`
private Camera.PictureCallback pictureCallback = new Camera.PictureCallback() {//照相動作回調用的pictureCallback
//在這裡可以獲得拍照後的圖檔資料
@Override
public void onPictureTaken(byte[] data, Camera camera) {
//byte[]數組data就是圖檔資料,可以在這裡對圖檔進行處理
mCamera.startPreview();//恢複預覽
}
};
//點選事件
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.imgbtnTakePhoto:
mCamera.takePicture(null, null, pictureCallback);//拍照會停止預覽
break;
default:
break;
}
}
就先寫到這裡吧,有精力再補充。。