相機(jī):Camera
相機(jī)現(xiàn)在已經(jīng)不僅僅是手機(jī)必備神器了,甚至相機(jī)的拍照質(zhì)量已經(jīng)是很多人買手機(jī)的首選條件了。而對于相機(jī)而言主要有兩大功能:拍照片和拍視頻。Android 為此兩種方式:
- 相機(jī) intent
- 相機(jī) API
本節(jié)我們就一起來看看相機(jī)的具體用法。
1. 打開 Camera 的兩大方式
目前市面上絕大多數(shù)的 Android 手機(jī)是有前后兩個攝像頭,當(dāng)然有部分特殊機(jī)型會存在其他的情況,本節(jié)主要針對雙攝像頭設(shè)備做解析。
在前面有提到過,通常使用相機(jī)有兩大方式:“Intent”和“API”。最大的差別就是“Intent”是跳轉(zhuǎn)到系統(tǒng)提供的相機(jī)頁面,而使用“API”是封閉在自己的 App 中使用相機(jī)。我們可以通過“Intent”直接打開系統(tǒng)提供的相機(jī) Activity,在用戶拍攝完成之后系統(tǒng) Activity 會通知我們拍攝結(jié)果,然后拿到拍攝的圖片或者視頻。而直接通過 API 就需要我們?nèi)ラ_發(fā)一整套相機(jī)的控制頁面,自行完成照片 / 視頻的拍攝進(jìn)而直接拿到用戶拍攝的結(jié)果。
2. Camera 的基本用法
我們分別看看這兩種打開方式的使用方法:
2.1 使用 Intent 打開
通過使用 MediaStore 類提供的兩個 Intnet 常量,可以直接將相機(jī)操作托管給 Android 系統(tǒng)而無需創(chuàng)建 Camera 實(shí)例:
- ACTION_IMAGE_CAPTURE:
拍攝照片 - ACTION_VIDEO_CAPTURE:
拍攝視頻
2.2 使用 API 打開
采用 API 打開會讓整個相機(jī)程序都封閉在自己的 APP 中完成,這里需要明確幾個概念:
-
Camera 類
使用 API 之前需要創(chuàng)建 Camera 實(shí)例,然后通過 API 來初始化 Camera 進(jìn)而拿到實(shí)時拍攝的畫面 -
SurfaceView
用來渲染視頻實(shí)時畫面的 View
采用 Intent 打開相機(jī)的方法非常簡單,接下來我們?nèi)淌褂?Camera API 來實(shí)現(xiàn)一下相機(jī)功能。
3. Camera 使用示例
使用 API 來拍照會相對比較麻煩一點(diǎn),首先需要獲取權(quán)限,那么對于 Android 6.0 版本以上的系統(tǒng)除了要在 Manifest 里面加入 Camera 權(quán)限之外,還需要動態(tài)獲取權(quán)限,這個可能大家在用一些比較老的教程示例時會踩坑。
獲取到權(quán)限就可以打開 Camera 了,然后拿到 Camera 實(shí)例設(shè)置一個 SurfaceView 來渲染預(yù)覽頁面,在用戶需要拍照的時候調(diào)用 Camera 的takePicture()
方法獲取當(dāng)前幀,最后保存輸出到文件中或者在新的 Activity 中展示,這樣就算完成了一次拍照閉環(huán)。
3.1 Camera 拍照邏輯
首先看看整個拍照的代碼如下:
package com.emercy.myapplication;
import android.Manifest;
import android.app.Activity;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.hardware.Camera;
import android.os.Build;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
public class MainActivity extends Activity {
public static final String CAMERA_PATH = "path";
public static final String CAMERA_IMG = "img";
private SurfaceView mSurfaceView;
private Button mTakePhoto;
private Camera mCamera = null;
private SurfaceHolder.Callback mCallback = new SurfaceHolder.Callback() {
@Override
public void surfaceCreated(SurfaceHolder holder) {
startPreview();
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
stopPreview();
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
getPermission();
bindViews();
}
/**
* 獲取權(quán)限
*/
private void getPermission() {
if (Build.VERSION.SDK_INT > 22) {
if (checkSelfPermission(android.Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
//先判斷有沒有權(quán)限 ,沒有就在這里進(jìn)行權(quán)限的申請
requestPermissions(new String[]{Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE}, 100);
} else {
//說明已經(jīng)獲取到攝像頭權(quán)限了
Log.i("Imooc Camera", "已經(jīng)獲取了權(quán)限");
}
} else {
//這個說明系統(tǒng)版本在6.0之下,不需要動態(tài)獲取權(quán)限。
Log.i("Imooc Camera", "這個說明系統(tǒng)版本在6.0之下,不需要動態(tài)獲取權(quán)限。");
}
}
private void bindViews() {
mSurfaceView = (SurfaceView) findViewById(R.id.sfv_preview);
mTakePhoto = (Button) findViewById(R.id.btn_take);
mSurfaceView.getHolder().addCallback(mCallback);
mTakePhoto.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mCamera.takePicture(null, null, new Camera.PictureCallback() {
@Override
public void onPictureTaken(byte[] data, Camera camera) {
String path;
if (TextUtils.isEmpty(path = savePhoto(data))) {
Intent it = new Intent(MainActivity.this, PreviewActivity.class);
it.putExtra(CAMERA_PATH, path);
startActivity(it);
} else {
Toast.makeText(MainActivity.this, "拍照失敗", Toast.LENGTH_SHORT).show();
}
}
});
}
});
}
private String savePhoto(byte[] bytes) {
try {
File file = File.createTempFile(CAMERA_IMG, "");
FileOutputStream fos = new FileOutputStream(file);
fos.write(bytes);
fos.flush();
fos.close();
return file.getAbsolutePath();
} catch (IOException e) {
e.printStackTrace();
}
return "";
}
private void startPreview() {
mCamera = Camera.open();
try {
mCamera.setPreviewDisplay(mSurfaceView.getHolder());
mCamera.setDisplayOrientation(90); // 讓相機(jī)旋轉(zhuǎn)90度
mCamera.startPreview();
} catch (IOException e) {
e.printStackTrace();
}
}
private void stopPreview() {
mCamera.stopPreview();
mCamera.release();
mCamera = null;
}
}
里面涉及到的幾個新概念大家需要注意,然后記得在SurfaceHolder.Callback()
的surfaceCreated()
中啟動預(yù)覽,在surfaceDestroyed()
方法中要停止預(yù)覽,其他的基本上按照步驟來不會有什么問題。
3.2 相機(jī)布局
目前市面上有很多相機(jī) App,各種布局千變?nèi)f化,大家完全可以按照自己的喜好來進(jìn)行設(shè)計(jì)。這里只做一個拍照預(yù)覽和拍照按鈕。
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<SurfaceView
android:id="@+id/sfv_preview"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<Button
android:id="@+id/btn_take"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|center"
android:text="拍照" />
</FrameLayout>
3.3 照片回看頁面
點(diǎn)擊“拍照” Button 獲取到當(dāng)前畫面之后,我們可以就可以按照自己的邏輯對圖片進(jìn)行操作了。比如可以通過 Http 上傳、通過 Socket 發(fā)送給其他設(shè)備、保存到本地、或者傳遞給其他 App 等。本例子中將圖片傳遞給另一個 Activity 專門用于查看圖片,下面是 PhotoActivity 的代碼:
package com.emercy.myapplication;
import android.app.Activity;
import android.net.Uri;
import android.os.Bundle;
import android.widget.ImageView;
import java.io.File;
import static com.emercy.myapplication.MainActivity.CAMERA_PATH;
public class PhotoActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ImageView img = new ImageView(this);
String path = getIntent().getStringExtra(CAMERA_PATH);
if (path != null) {
img.setImageURI(Uri.fromFile(new File(path)));
}
setContentView(img);
}
}
通過 Intent 接收圖片路徑,然后展示在 ImageView 上,需要注意的是PhotoActivity 中是直接將 ImageView 作為參數(shù)直接設(shè)置給了 SetContentView(),這樣相當(dāng)于布局文件中只有一個 ImageView 的寫法。
最后別忘了在 Manifest 當(dāng)中添加權(quán)限:
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
編譯運(yùn)行效果如下:
點(diǎn)擊拍照進(jìn)入 PhotoActivity,展示的就是我們拍照的畫面。
4. 小結(jié)
本節(jié)學(xué)習(xí)了一個手機(jī)上必不可少的設(shè)備,通過相機(jī)我們可以拍攝照片或者視頻,Android 系統(tǒng)提供了兩大打開方式:Intent 調(diào)用系統(tǒng)相機(jī) Activitiy或者用 Camera API 自行實(shí)現(xiàn)拍照功能。第一種非常簡單,將所有的相機(jī)操作都托管給系統(tǒng),我們只關(guān)心最終用戶拍攝的結(jié)果;而第二種就需要我們自己初始化 Camera 對象從而實(shí)現(xiàn)拍攝,在實(shí)際開發(fā)中如果你的功能是和拍攝強(qiáng)相關(guān),需要一些定制化的拍攝體驗(yàn),那么一定要使用第二種方式來自己實(shí)現(xiàn) Camera,但是如果你只是一個簡單的拍照獲取圖片,那么第一種會讓你事半功倍。