SurfaceView實現簡單的相機
SurfaceView繼承自View,主要用來展示視頻流的繪製,典型的應用場景是相機,視頻播放器,遊戲界面繪製等。它獨立於UI線程進行繪製,所以不會阻塞UI線程。本文將結合一個簡單的相機demo介紹SurfaceView的使用。
我們實現的相機功能很簡單,可以進行相機預覽,點擊拍照按鈕拍照,並展示拍攝的照片,點擊確定返回相機預覽界面。
佈局如下:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
...
>
<SurfaceView
android:id="@+id/preview_surface_view"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<ImageView
android:id="@+id/image_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone"/>
<Button
android:id="@+id/take_photo_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:text="拍照"/>
<Button
android:id="@+id/confirm_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:text="確定"/>
</RelativeLayout>
SurfaceView的初始化如下,首先根據SurfaceView獲取SurfaceHolder,SurfaceHolder是一個接口,提供了控制SurfaceView界面的函數,比如控制界面的尺寸、格式,編輯界面像素,監控界面的變化等。然後給SurfaceHolder添加CallBack回調函數,三個回調函數對應了界面的三個狀態,創建,修改和銷燬。在這裏我們在界面創界後開始進行相機預覽。
public class MainActivity extends AppCompatActivity {
...
private Camera camera;
private SurfaceHolder surfaceHolder;
private byte[] pictureDataBytes;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
surfaceHolder = surfaceView.getHolder();
surfaceHolder.addCallback(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) {
}
});
}
相機的初始化在onResume裏進行,銷燬在onPause裏進行:
@Override
protected void onResume() {
super.onResume();
initCamera();
}
@Override
protected void onPause() {
super.onPause();
releaseCamera();
}
private void initCamera() {
if (camera == null) {
camera = Camera.open();
}
}
private void releaseCamera() {
if (camera != null) {
camera.release();
camera = null;
}
}
啓動相機預覽的函數實現如下,通過setPreviewDisplay指定顯示預覽的view:
private void startPreview() {
if (camera != null) {
try {
camera.setPreviewDisplay(surfaceHolder);
if (isCapturing) {
camera.startPreview();
}
} catch (IOException e) {
e.printStackTrace();
Toast.makeText(this, "Unable to open camera.", Toast.LENGTH_SHORT) .show();
}
}
}
點擊拍照按鈕,進行拍照並展示。這裏爲了防止相機拍攝的照片太大導致OOM錯誤,要對圖片進行壓縮,壓縮包括兩個方式,尺寸壓縮和像素格式壓縮,可以參考博客從Oppo手機拍照無法展示談圖片壓縮:
@OnClick(R.id.take_photo_btn)
protected void onTakePhotoClicked(View v) {
camera.takePicture(null, null, new Camera.PictureCallback() {
@Override
public void onPictureTaken(byte[] data, Camera camera) {
pictureDataBytes = data;
showPicture();
}
});
}
private void showPicture() {
Bitmap bitmap = BitmapFactory.decodeByteArray(pictureDataBytes, 0, pictureDataBytes.length);
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = calculateSampleSize(bitmap.getHeight(), bitmap.getWidth());
options.inPreferredConfig = Bitmap.Config.RGB_565;
InputStream inputStream = bitmapToStream(bitmap);
imageView.setImageBitmap(BitmapFactory.decodeStream(inputStream, null, options));
...
}
計算採樣比例採用下面的函數:
// 獲取採樣比例
public int calculateSampleSize(int outWidth, int outHeight) {
int scale = 1;
if (outHeight > MAX_HEIGHT || outWidth > MAX_WIDTH) {
int maxSize = MAX_WIDTH > MAX_HEIGHT ? MAX_WIDTH : MAX_HEIGHT;
scale = (int) Math.pow(2, (int) Math.round(Math.log(maxSize /(double) Math.max(outHeight, outWidth)) / Math.log(0.5)));
}
return scale;
}
最後記得在Manifest裏添加相機權限:
<uses-permission android:name="android.permission.CAMERA"/>
參考:
https://developer.android.com/reference/android/view/SurfaceView.html
http://archive.oreilly.com/oreillyschool/courses/android2/CameraAdvanced.html