寫在前面
本文并不是基于Camera2的暇屋,所以想要了解Camera2的同學(xué)可以先散了柒昏。文題加了詳記二字苦蒿,因?yàn)橄鄼C(jī)整個(gè)打開的流程的確是比較復(fù)雜的儿捧,稍有疏忽可能就會(huì)引發(fā)一系列問題。我也是看了一下Android的文檔才整理了這篇文章墨闲,想看原文的戳這今妄。不得不說,文檔還是詳細(xì)啊~
本文主要會(huì)涉及以下內(nèi)容:
- 相機(jī)的使用流程
- 拍照及拍照期間的聚焦
- 保存圖片
先放一下最終效果圖吧鸳碧,做的比較簡(jiǎn)單蛙奖,各位不用擔(dān)心:
主要功能就是拍照保存,多的也沒啥了杆兵,項(xiàng)目地址在文末有。
使用流程
在詳細(xì)的研究相機(jī)之前仔夺,首先熟悉一下使用相機(jī)的整個(gè)流程:
- 檢測(cè)和訪問相機(jī):創(chuàng)建代碼檢測(cè)相機(jī)的存在和請(qǐng)求訪問
- 創(chuàng)建預(yù)覽類:創(chuàng)建繼承自SurfaceView和實(shí)現(xiàn)SurfaceHolder接口的預(yù)覽類琐脏。這個(gè)類展示來自相機(jī)的實(shí)時(shí)圖片
- 創(chuàng)建一個(gè)預(yù)覽布局:如果你有相機(jī)預(yù)覽類,你就需要?jiǎng)?chuàng)建一個(gè)和用戶交互的界面布局
- 為拍照或者錄像設(shè)置監(jiān)聽:對(duì)用戶的行為作出響應(yīng)缸兔,你要為你的控件設(shè)置監(jiān)聽去開始拍照或者錄像日裙,比如你設(shè)置了一個(gè)拍照的按鈕,用戶點(diǎn)擊之后就要開始拍照惰蜜。監(jiān)聽用戶行為只是其一昂拂,還有就是拍照的監(jiān)聽,這個(gè)放到后文討論
- 捕獲和保存文件:無論是拍照還是錄像抛猖,都需要有保存的功能
- 釋放相機(jī):在不使用相機(jī)時(shí)候格侯,你的應(yīng)用一定要釋放相機(jī)。
那么為什么一定要釋放相機(jī)資源呢财著?因?yàn)橄鄼C(jī)硬件是一個(gè)共享臨界資源联四,不僅你的應(yīng)用會(huì)使用,其他的應(yīng)用也會(huì)使用相機(jī)撑教。所以在不用相機(jī)的時(shí)候朝墩,一定要釋放相機(jī),不然你自己的應(yīng)用和后續(xù)其他要使用相機(jī)的應(yīng)用都無法使用相機(jī)伟姐。
流程大致就是這樣收苏,接下來一步一步的跟進(jìn)亿卤,看看這個(gè)相機(jī)到底特么的是怎么用的。
申請(qǐng)權(quán)限
Android 6.0之前 & targetSdkVersion < 23 只需要在清單文件中聲明一下權(quán)限就行
<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera" />
上面那個(gè)uses-feature鹿霸,文檔中說如果聲明了這個(gè)排吴,在Google Play中會(huì)阻止沒有相機(jī)的設(shè)備下載你的應(yīng)用。國(guó)內(nèi)的應(yīng)用商店就不知道了= =杜跷。在Android 6.0之后且targetSdkVersion >= 23就需要申請(qǐng)相機(jī)權(quán)限了傍念。我們可以在Activity的onResume中檢測(cè)是否擁有相機(jī)權(quán)限,如果擁有權(quán)限就進(jìn)行下一步的操作葛闷,如果沒有就申請(qǐng)權(quán)限憋槐,并在回調(diào)中檢測(cè)是否申請(qǐng)成功,成功的話就進(jìn)行下一步操作淑趾。代碼如下:
@Override
protected void onResume() {
super.onResume();
// 檢查權(quán)限
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) !=
PackageManager.PERMISSION_GRANTED) {
// 申請(qǐng)權(quán)限
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA}, 1);
} else {
// 已有權(quán)限
startCameraPre();
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// 如果權(quán)限申請(qǐng)成功
startCameraPre();
} else {
Toast.makeText(this, "您已拒絕打開相機(jī)阳仔,想要使用此功能請(qǐng)手動(dòng)打開相機(jī)權(quán)限", Toast.LENGTH_SHORT).show();
}
}
檢測(cè)相機(jī)是否存在
在使用之前需要先檢測(cè)一下設(shè)備是否有相機(jī):
/**
* 檢查是否擁有相機(jī)
*
* @return 如果有返回true,沒有返回false
*/
public static boolean checkCameraHardware(Context context) {
if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA)) {
// 有相機(jī)
Log.i(TAG, "有相機(jī)");
return true;
} else {
// 沒有相機(jī)
Log.i(TAG, "沒有相機(jī)");
return false;
}
}
代碼比較簡(jiǎn)單扣泊,比較坑爹的是什么呢近范?有一些嵌入式設(shè)備的Android系統(tǒng)通過這個(gè)方法是無法獲取到到底特么的是有沒有相機(jī)的。獲取的可能是錯(cuò)誤的信息延蟹,我在我們的設(shè)備上用這個(gè)代碼檢測(cè)评矩,明明沒有相機(jī)也判斷成有相機(jī)了。如果一定要判斷……還是有辦法的阱飘,直接Camera.open試一試斥杜,成功就說明有,失敗就……就失敗了唄沥匈。
訪問相機(jī)
訪問相機(jī)的代碼比較簡(jiǎn)單蔗喂,就是通過Camera.open方法拿到一個(gè)Camera的實(shí)例,需要注意的是這個(gè)open方法可能會(huì)引發(fā)異常高帖,最好還是要try catch一下的缰儿。
/**
* 獲取前置相機(jī)實(shí)例,注意6.0以上的系統(tǒng)需要?jiǎng)討B(tài)申請(qǐng)權(quán)限(如果
* target >= 23)則必須動(dòng)態(tài)申請(qǐng)散址,否則無法打開相機(jī)
*
* @return 打開成功則返回相機(jī)實(shí)例乖阵,失敗則返回null
*/
public static Camera getCameraInstance() {
Camera c;
try {
c = Camera.open(Camera.CameraInfo.CAMERA_FACING_BACK);
} catch (Exception e) {
e.printStackTrace();
// 相機(jī)正在使用或者不存在
Log.e(TAG, "相機(jī)打開失敗,正在使用或者不存在预麸,或者义起,沒有權(quán)限?");
return null;
}
return c;
}
Android 2.3版本以后可以通過Camera.open(int)來打開指定的相機(jī)师崎,我這里打開了后置攝像頭默终。
創(chuàng)建預(yù)覽類
預(yù)覽類就是用來播放相機(jī)畫面的類,預(yù)覽類是繼承自SurfaceView的。普通的View以及其子類都是共享同一個(gè)surface的齐蔽,所有的繪制都必須在UI線程進(jìn)行两疚。而SurfaceView是一種比較特殊的view,他并不與其他view共享surface含滴,而是在內(nèi)部持有了一個(gè)獨(dú)立的surface诱渤,SurfaceView負(fù)責(zé)管理這個(gè)surface的格式、尺寸以及顯示位置谈况。由于UI線程還要同事處理其他交互邏輯勺美,因此對(duì)View的更新速度和幀率無法保證,而surfaceview由于持有一個(gè)獨(dú)立的surface碑韵,因而可以在獨(dú)立的線程中進(jìn)行繪制赡茸,因此可以提供更高的幀率。自定義相機(jī)的預(yù)覽圖像由于對(duì)更新速度和幀率要求比較高祝闻,所以比較適合用surfaceview來顯示占卧。(關(guān)于surfaceview的介紹摘自Android自定義相機(jī)詳細(xì)講解)
介紹了這么多,對(duì)SurfaceView大概有個(gè)了解就可以了联喘,這個(gè)類和相機(jī)的聲明周期息息相關(guān)华蜒,需要實(shí)現(xiàn)SurfaceHolder.Callback接口來接收View創(chuàng)建和銷毀的事件。代碼如下:
package com.xiasuhuei321.cameradieorme.camera;
/**
* Created by xiasuhuei321 on 2017/8/22.
* author:luo
* e-mail:xiasuhuei321@163.com
*/
import android.content.Context;
import android.hardware.Camera;
import android.os.Environment;
import android.os.Handler;
import android.os.Looper;
import android.text.format.DateFormat;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback, Camera.AutoFocusCallback, Camera.PictureCallback {
public static final String TAG = "CameraPreview";
public static final String DIRNAME = "MyCamera";
private SurfaceHolder mHolder;
private Camera mCamera;
private boolean canTake = false;
private Context context;
public CameraPreview(Context context) {
super(context);
this.context = context;
// Install a SurfaceHolder.Callback so we get notified when the
// underlying surface is created and destroyed.
mHolder = getHolder();
// deprecated setting, but required on Android versions prior to 3.0
mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
Log.i(TAG, "CameraPreview被創(chuàng)建 " + this.hashCode());
}
/**
* surface在很多情況下都會(huì)被銷毀豁遭,這個(gè)時(shí)候相機(jī)也會(huì)被釋放叭喜。
* 而這個(gè)類的camera就無法再使用了,所以需要外部再傳入一個(gè)
* 正確的Camera實(shí)例
*
* @param mCamera Camera實(shí)例
*/
public void setCamera(Camera mCamera) {
this.mCamera = mCamera;
mHolder.addCallback(this);
surfaceCreated(getHolder());
Log.i(TAG, "serCamera" + " release = " + CameraUtil.getInstance().isRelease());
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
// surface創(chuàng)建完畢蓖谢,camera設(shè)置預(yù)覽
Log.i(TAG, "surface view被創(chuàng)建");
if (CameraUtil.getInstance().isRelease()) return;
try {
mCamera.setPreviewDisplay(holder);
mCamera.startPreview();
} catch (IOException e) {
Log.d(TAG, "Error setting camera preview: " + e.getMessage());
}
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
// 在這里可以釋放相機(jī)資源
// 也可以在Activity中釋放
Log.i(TAG, "surface 被銷毀 ");
holder.removeCallback(this);
// 停止回調(diào)域滥,以防釋放的相機(jī)再被使用導(dǎo)致異常
mCamera.setPreviewCallback(null);
// 停止預(yù)覽
mCamera.stopPreview();
mCamera.lock();
// 釋放相機(jī)資源
CameraUtil.getInstance().releaseCamera();
mCamera = null;
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
// If your preview can change or rotate, take care of those events here.
// Make sure to stop the preview before resizing or reformatting it.
if (mHolder.getSurface() == null) {
// preview surface does not exist
return;
}
// stop preview before making changes
try {
mCamera.stopPreview();
} catch (Exception e) {
// ignore: tried to stop a non-existent preview
}
// set preview size and make any resize, rotate or
// reformatting changes here
// start preview with new settings
try {
mCamera.setPreviewDisplay(mHolder);
mCamera.startPreview();
} catch (Exception e) {
Log.d(TAG, "Error starting camera preview: " + e.getMessage());
}
}
/**
* 給外部調(diào)用,用來拍照的方法
*/
public void takePhoto() {
// 因?yàn)樵O(shè)置了聚焦蜈抓,這里又設(shè)置了回調(diào)對(duì)象,所以重新開始預(yù)覽之后
// 需要一個(gè)標(biāo)志判斷是否是拍照的聚焦回調(diào)
canTake = true;
// 首先聚焦
mCamera.autoFocus(this);
// mCamera.takePicture(null, null, this);
}
@Override
public void onAutoFocus(boolean success, final Camera camera) {
Log.i(TAG, "聚焦: " + canTake);
// 不管聚焦成功與否昂儒,都開始拍照
if (canTake) {
camera.takePicture(null, null, CameraPreview.this);
}
canTake = false;
// 延時(shí)一秒沟使,重新開始預(yù)覽
new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
@Override
public void run() {
mCamera.startPreview();
}
}, 1000);
}
@Override
public void onPictureTaken(final byte[] data, Camera camera) {
Log.i(TAG, "onPictureTaken");
// 在子線程中進(jìn)行io操作
new Thread(new Runnable() {
@Override
public void run() {
saveToSd(data);
}
}).start();
}
/**
* 將照片保存至sd卡
*/
private void saveToSd(byte[] data) {
// 創(chuàng)建位圖,這一步在圖片比較大的時(shí)候可能會(huì)拋oom異常渊跋,所以跳過這一步腊嗡,直接將byte[]
// 數(shù)據(jù)寫入文件,而且如果有進(jìn)行圖片處理的需求拾酝,盡量不要另外再申請(qǐng)內(nèi)存燕少,不然很容易
// oom。所以盡量避免在這里處理圖片
// Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
// 系統(tǒng)時(shí)間
long dateTaken = System.currentTimeMillis();
// 圖像名稱
String fileName = DateFormat.format("yyyy-MM-dd kk.mm.ss", dateTaken).toString() + ".jpg";
FileOutputStream fos = null;
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
String filePath = Environment.getExternalStorageDirectory() + File.separator +
DIRNAME + File.separator + fileName;
Log.i(TAG, "文件路徑:" + filePath);
File imgFile = new File(filePath);
if (!imgFile.getParentFile().exists()) {
imgFile.getParentFile().mkdirs();
}
try {
if (!imgFile.exists()) {
imgFile.createNewFile();
}
fos = new FileOutputStream(imgFile);
// bitmap.compress(Bitmap.CompressFormat.JPEG, 100, bos);
fos.write(data);
fos.flush();
insertIntoMediaPic();
} catch (Exception e) {
} finally {
try {
if (fos != null) {
fos.close();//關(guān)閉
}
} catch (Exception e) {
e.printStackTrace();
}
}
} else {
// sd卡狀態(tài)異常蒿囤,直接插入系統(tǒng)相冊(cè)
// 暫時(shí)是空實(shí)現(xiàn)
insertIntoMediaPic();
}
}
private void insertIntoMediaPic() {
}
}
這個(gè)類可以說是字字血淚客们,各位看的時(shí)候可以結(jié)合注釋看……每一個(gè)我被坑過的地方我都詳細(xì)的注釋了出來。真的是都在代碼里了。關(guān)于圖片處理那一塊我是沒什么比較好的辦法底挫,內(nèi)存所限恒傻,在拿到byte[] data 這個(gè)圖片數(shù)據(jù)數(shù)組,我直接在轉(zhuǎn)成Bitmap那一步就OOM了建邓,后來看了一下我這里選取的是4160 * 2340的分辨率盈厘,直接寫入文件一張圖也有4~5M,這個(gè)時(shí)候的問題就是生成一個(gè)Bitmap需要申請(qǐng)很大的內(nèi)存官边,而原來的data數(shù)組因?yàn)檫@個(gè)方法還沒結(jié)束也無法釋放(Java參數(shù)傳遞是引用拷貝傳遞沸手,所以這時(shí)候依然有引用指向內(nèi)存中的data對(duì)象,GC無法回收這塊內(nèi)存)注簿,所以就算后續(xù)你不額外申請(qǐng)內(nèi)存契吉,有方法在原有的Bitmap對(duì)象上進(jìn)行操作,也是不行的滩援,因?yàn)樵谏傻臅r(shí)候就OOM了栅隐。
創(chuàng)建預(yù)覽布局
這里Android文檔中的建議是在布局中放置一個(gè)FrameLayout作為相機(jī)預(yù)覽類的父容器,我采用了這種做法玩徊,布局如下:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="horizontal">
<FrameLayout
android:id="@+id/camera_preview"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<ImageView
android:id="@+id/iv_take"
android:layout_width="80dp"
android:layout_height="80dp"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:layout_marginBottom="16dp"
android:src="@drawable/takephoto" />
</RelativeLayout>
布局比較簡(jiǎn)單租悄,就一個(gè)FrameLayout和一個(gè)ImageView,點(diǎn)擊ImageView開始拍照恩袱。
接下來看一下Activity的代碼:
package com.xiasuhuei321.cameradieorme.camera;
import android.Manifest;
import android.animation.ObjectAnimator;
import android.content.pm.PackageManager;
import android.hardware.Camera;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.view.MotionEvent;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.widget.FrameLayout;
import android.widget.Toast;
import com.xiasuhuei321.cameradieorme.R;
/**
* Created by xiasuhuei321 on 2017/8/22.
* author:luo
* e-mail:xiasuhuei321@163.com
*/
public class CameraActivity extends AppCompatActivity {
private Camera camera;
private FrameLayout preview;
private CameraPreview mPreview;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
supportRequestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_camera);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
View iv_take = findViewById(R.id.iv_take);
final ObjectAnimator scaleX = ObjectAnimator.ofFloat(iv_take, "scaleX", 1f, 0.8f);
final ObjectAnimator scaleY = ObjectAnimator.ofFloat(iv_take, "scaleY", 1f, 0.8f);
iv_take.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
v.setScaleX(0.9f);
v.setScaleY(0.9f);
scaleX.start();
scaleY.start();
break;
case MotionEvent.ACTION_UP:
v.setScaleX(1f);
v.setScaleY(1f);
scaleX.reverse();
scaleY.reverse();
break;
}
return false;
}
});
iv_take.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mPreview.takePhoto();
}
});
mPreview = new CameraPreview(this);
CameraUtil.getInstance().init(this);
}
@Override
protected void onResume() {
super.onResume();
// 檢查權(quán)限
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) !=
PackageManager.PERMISSION_GRANTED) {
// 申請(qǐng)權(quán)限
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA}, 1);
} else {
// 已有權(quán)限
startCameraPre();
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// 如果權(quán)限申請(qǐng)成功
startCameraPre();
} else {
Toast.makeText(this, "您已拒絕打開相機(jī)泣棋,想要使用此功能請(qǐng)手動(dòng)打開相機(jī)權(quán)限", Toast.LENGTH_SHORT).show();
}
}
private void startCameraPre() {
if (CameraUtil.checkCameraHardware(this)) {
camera = CameraUtil.getInstance().getCameraInstance();
}
mPreview.setCamera(camera);
preview = (FrameLayout) findViewById(R.id.camera_preview);
if (preview.getChildCount() == 0)
preview.addView(mPreview);
}
}
在開始的時(shí)候?qū)懥藘蓚€(gè)屬性動(dòng)畫,用戶在點(diǎn)擊的時(shí)候有點(diǎn)交互的感覺(貌似并沒有什么luan用)畔塔。在onResume中檢查是否擁有權(quán)限打開相機(jī)潭辈,因?yàn)?.0以上需要?jiǎng)討B(tài)申請(qǐng)啊,蛋疼澈吨。擁有權(quán)限或者用戶給了權(quán)限就執(zhí)行startCameraPre方法把敢,這個(gè)方法通過我自己寫的CameraUtil獲取并初始化了一個(gè)Camera實(shí)例。并且最后判斷FrameLayout中是否有子View谅辣,如果沒有就將我們自己的相機(jī)預(yù)覽類添加進(jìn)去修赞。這樣打開相機(jī)和拍照的整個(gè)流程就完成了,當(dāng)然了桑阶,最后還得貼一下CameraUtil代碼:
package com.xiasuhuei321.cameradieorme.camera;
import android.content.Context;
import android.content.pm.PackageManager;
import android.graphics.PixelFormat;
import android.graphics.Point;
import android.hardware.Camera;
import android.util.Log;
import android.view.WindowManager;
import java.util.List;
/**
* Created by xiasuhuei321 on 2017/8/21.
* author:luo
* e-mail:xiasuhuei321@163.com
*/
public class CameraUtil {
public static final String TAG = "CameraUtil";
private Camera camera;
private int cameraId;
private int mScreenWidth;
private int mScreenHeight;
// private Callback callback;
private boolean release = false;
private Camera.Parameters params;
private CameraUtil() {
}
private static class CameraUtilHolder {
private static CameraUtil instance = new CameraUtil();
}
public static CameraUtil getInstance() {
return CameraUtilHolder.instance;
}
public void init(Context context) {
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
Point p = new Point();
wm.getDefaultDisplay().getSize(p);
mScreenWidth = p.x;
mScreenHeight = p.y;
}
/**
* 檢查是否擁有相機(jī)
*
* @return 如果有返回true柏副,沒有返回false
*/
public static boolean checkCameraHardware(Context context) {
if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA)) {
// 有相機(jī)
return true;
} else {
// 沒有相機(jī)
return false;
}
}
/**
* 獲取前置相機(jī)實(shí)例,注意6.0以上的系統(tǒng)需要?jiǎng)討B(tài)申請(qǐng)權(quán)限(如果
* target >= 23)則必須動(dòng)態(tài)申請(qǐng)蚣录,否則無法打開相機(jī)
*
* @return 打開成功則返回相機(jī)實(shí)例割择,失敗則返回null
*/
public Camera getCameraInstance() {
if (camera != null) {
Log.i(TAG, "camera已經(jīng)打開過,返回前一個(gè)值");
return camera;
}
try {
camera = Camera.open(Camera.CameraInfo.CAMERA_FACING_BACK);
cameraId = Camera.CameraInfo.CAMERA_FACING_BACK;
} catch (Exception e) {
e.printStackTrace();
// 相機(jī)正在使用或者不存在
Log.i(TAG, "相機(jī)打開失敗萎河,正在使用或者不存在荔泳,或者蕉饼,沒有權(quán)限?");
return null;
}
initParam();
release = false;
return camera;
}
public void initParam() {
if (camera == null) {
return;
}
if (params != null) {
camera.setParameters(params);
} else {
camera.setParameters(generateDefaultParams(camera));
}
}
/**
* 允許從外部設(shè)置相機(jī)參數(shù)
*
* @param params 相機(jī)參數(shù)
*/
public void setParams(Camera.Parameters params) {
this.params = params;
}
/**
* 生成默認(rèn)的相機(jī)參數(shù)
*
* @param camera 使用該參數(shù)的相機(jī)
* @return 生成的參數(shù)
*/
public Camera.Parameters generateDefaultParams(Camera camera) {
Camera.Parameters parameters = camera.getParameters();
// 設(shè)置聚焦
if (parameters.getSupportedFocusModes().contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) {
parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);// 連續(xù)對(duì)焦模式
}
camera.cancelAutoFocus();//自動(dòng)對(duì)焦换可。
// 設(shè)置圖片格式
parameters.setPictureFormat(PixelFormat.JPEG);
// 設(shè)置照片質(zhì)量
parameters.setJpegQuality(100);
if (cameraId == Camera.CameraInfo.CAMERA_FACING_BACK) {
// 默認(rèn)打開前置攝像頭椎椰,旋轉(zhuǎn)90度即可
camera.setDisplayOrientation(90);
parameters.setRotation(90);
} else if (cameraId == Camera.CameraInfo.CAMERA_FACING_FRONT) {
// 打開后置攝像頭,旋轉(zhuǎn)270沾鳄,這個(gè)待驗(yàn)證
camera.setDisplayOrientation(270);
parameters.setRotation(180);
}
// 獲取攝像頭支持的PictureSize列表
List<Camera.Size> picSizeList = parameters.getSupportedPictureSizes();
for (Camera.Size size : picSizeList) {
Log.i(TAG, "pictureSizeList size.width=" + size.width + " size.height=" + size.height);
}
Camera.Size picSize = getProperSize(picSizeList, ((float) mScreenHeight / mScreenWidth));
parameters.setPictureSize(picSize.width, picSize.height);
// 獲取攝像頭支持的PreviewSize列表
List<Camera.Size> previewSizeList = parameters.getSupportedPreviewSizes();
for (Camera.Size size : previewSizeList) {
Log.i(TAG, "previewSizeList size.width=" + size.width + " size.height=" + size.height);
}
Camera.Size preSize = getProperSize(previewSizeList, ((float) mScreenHeight) / mScreenWidth);
Log.i(TAG, "final size is: " + picSize.width + " " + picSize.height);
if (null != preSize) {
Log.i(TAG, "preSize.width=" + preSize.width + " preSize.height=" + preSize.height);
parameters.setPreviewSize(preSize.width, preSize.height);
}
return parameters;
}
private Camera.Size getProperSize(List<Camera.Size> pictureSizeList, float screenRatio) {
Log.i(TAG, "screenRatio=" + screenRatio);
Camera.Size result = null;
for (Camera.Size size : pictureSizeList) {
float currentRatio = ((float) size.width) / size.height;
if (currentRatio - screenRatio == 0) {
result = size;
break;
}
}
if (null == result) {
for (Camera.Size size : pictureSizeList) {
float curRatio = ((float) size.width) / size.height;
if (curRatio == 4f / 3) {// 默認(rèn)w:h = 4:3
result = size;
break;
}
}
}
return result;
}
/**
* 釋放相機(jī)資源
*/
public void releaseCamera() {
if (camera != null) {
camera.release();
}
camera = null;
release = true;
}
/**
* 現(xiàn)在是否處于釋放狀態(tài)
*
* @return true釋放慨飘,false沒釋放
*/
public boolean isRelease() {
return release;
}
}
這里需要注意的就是生成相機(jī)參數(shù)那一塊了,Android中的相機(jī)默認(rèn)是橫向的译荞,我們平時(shí)用的時(shí)候肯定不是那么用的瓤的,所以通過 camera.setDisplayOrientation(90)
旋轉(zhuǎn)90度調(diào)整一下。不過設(shè)置了這個(gè)之后吞歼,如果不設(shè)置parameters.setRotation(90)
那么保存的圖片方向也不對(duì)圈膏,設(shè)置了這個(gè)之后就可以了。不過我看網(wǎng)上很多都是采用自己生成Bitmap然后自己旋轉(zhuǎn)……如果parameters.setRotation(90)
這種方式可以完成的話篙骡,最好不要采用自己處理的方式了稽坤,內(nèi)存開銷太大了。關(guān)于資源的釋放啊什么的糯俗,都在預(yù)覽類里面注釋寫好了= = 尿褪,這里就不再贅述了。
小結(jié)
最開始研究相機(jī)是因?yàn)轫?xiàng)目里一個(gè)用到相機(jī)三方總是報(bào)錯(cuò)得湘,在有空研究了一下相機(jī)之后杖玲,添了一行代碼,測(cè)試到現(xiàn)在還算比較穩(wěn)定淘正,沒有出現(xiàn)崩潰了摆马,有的時(shí)候真的是,一行代碼能改變的東西卻是非常多的鸿吆。跑題了跑題了囤采,現(xiàn)在突然感覺相機(jī)可以玩的東西很多……以后這個(gè)demo可能會(huì)繼續(xù)完善
代碼地址:https://github.com/ForgetAll/CameraDieOrMe