Android实时取景:用SurfaceView实现
update: 這篇blog沒有處理android sdk api>=23時(shí)的動(dòng)態(tài)權(quán)限問題。建議直接使用這一篇:Android SurfaceView Tutorial With Example
對(duì)于基于攝像頭的Android應(yīng)用,實(shí)時(shí)取景是一個(gè)基本前提,通過前置或后置攝像頭持續(xù)獲取捕獲到的內(nèi)容,可以進(jìn)一步做處理(人臉檢測(cè)、美顏、濾鏡等)。
所謂實(shí)時(shí)取景,簡(jiǎn)單說就是調(diào)用android的攝像頭,把攝像頭捕獲的內(nèi)容顯示在apk的界面上。只要應(yīng)用不關(guān)閉,相機(jī)就持續(xù)捕獲,apk上看到的就是實(shí)時(shí)的取景了。
采用SurfaceView和Camera來做這件事。
是SDK自帶的SurfaceView類而不是實(shí)現(xiàn)它的子類;在布局XML文件中使用SurfaceView而不是FrameLayout。因此,代碼量很少也很容易理解。
從View到SurfaceView
android應(yīng)用,和用戶交互的GUI界面,是搭載在Activity上的。Activity創(chuàng)建的時(shí)候,往往會(huì)做setContentView(R.id.main_layout),這是根據(jù)xml布局文件設(shè)定要預(yù)先確定好的各種view對(duì)象,這些組件在xml中進(jìn)行設(shè)計(jì)、設(shè)定。當(dāng)然也可以在Java代碼中進(jìn)一步動(dòng)態(tài)增加view對(duì)象。相當(dāng)于layout作為各種view的容器。
android sdk自帶了很多view的子類供使用。
View本身:繼承自java.lang.Object類,實(shí)現(xiàn)了Drawable.Callback, KeyEvent.Callback, AccessibilitiyEventSource接口。
直接子類有:
AnalogClock:模擬時(shí)鐘,有3個(gè)指針那種。
ImageView:顯示圖像。其實(shí),任何Drawable對(duì)象都可以用ImageView來顯示。
KeyboardView:內(nèi)置鍵盤。
MediaRouteButton:媒體路由按鈕(不太懂。似乎和多媒體、網(wǎng)絡(luò)路由相關(guān))
ProgressBar:(可視化)進(jìn)度條展示。
Space:空白視圖。輕量級(jí)視圖。作用:在不同組件之間插入縫隙、間隔。
SurfaceView:提供了一個(gè)專門用于繪制的Surface。Surface的格式、尺寸可以控制。SurfaceView控制這個(gè)surface的繪制位置。。。中文翻譯
TextView:文本視圖。
TextureView:紋理視圖。sdk4.0之后的API中可用。常被拿來和SurfaceView比較。
ViewGroup:用來容納其他view對(duì)象。是layout(布局)和view containers(視圖容器)的基類。
ViewStub:視圖存根。大小為0、不可見,用來占坑的,apk運(yùn)行時(shí)把坑交給資源。
間接子類有:
AbsListView
AbsSeekBar
AbsSpinner
AbsoluteLayout
AdapterView
AdapterViewAnimator
AdapterViewFlipper
以及其他56個(gè)間接子類。
可以看到,SurfaceView和TextureView兩個(gè)view子類,都能用于實(shí)時(shí)取景框的顯示。但是考慮到TextureView需要開啟硬件加速的支持,不考慮。以及,目前看來SurfaceView本身也能勝任實(shí)時(shí)取景的任務(wù)。
代碼
layout文件:surfaceview_main.xml
看到很多教程用的都是FrameLayout,而不是SurfaceView。我很不理解:為什么不用SurfaceView呢?不好用嗎?
anyway,我這里就用SurfaceView了,在我測(cè)試過的代碼中是完全可用的。
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/container"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical"
tools:context="com.example.chris.myapplication.MainActivity">
<SurfaceView
android:id="@+id/surfaceView"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1"/>
</LinearLayout>
java代碼 ChrisActivity.java
Surface、SurfaceView、SurfaceHolder這三者相當(dāng)于MVC的存在,Surface是數(shù)據(jù),SurfaceView負(fù)責(zé)顯示,SurfaceHolder控制了Surface。通過讓Activity實(shí)現(xiàn)SurfaceHolder.Callback接口,開發(fā)者自行實(shí)現(xiàn)下面三個(gè)函數(shù),開發(fā)者就完成內(nèi)容的處理:
public void surfaceCreated(SurfaceHolder holder);
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height);
public void surfaceDestroyed(SurfaceHolder holder);
而具體到實(shí)現(xiàn),一些額外的細(xì)節(jié)也要考慮到:相機(jī)的初始化和釋放;應(yīng)用暫停時(shí)釋放相機(jī),恢復(fù)時(shí)獲取相機(jī);屏幕方向與顯示方向的一致。所以有如下代碼:
package com.example.chris.myapplication;
import android.app.Activity;
import android.hardware.Camera;
import android.os.Bundle;
import android.util.Log;
import android.view.Display;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.WindowManager;
import java.io.IOException;
/**
* Created by chris on 2017/6/25.
* 網(wǎng)上找了一些博客、教程和代碼,稍微有點(diǎn)頭緒了,現(xiàn)在寫自己的Activity代碼
*/
@SuppressWarnings("deprecation")
// TODO:把camera換成camera2接口??
public class ChrisActivity extends Activity implements SurfaceHolder.Callback{
private static final String TAG = "ChrisAcvitity";
private Camera mCamera;
private SurfaceHolder mHolder;
private SurfaceView mView;
@Override
// 創(chuàng)建Activity時(shí)執(zhí)行的動(dòng)作
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.surfaceview_main);
mView = (SurfaceView) findViewById(R.id.surfaceView);
mHolder = mView.getHolder();
mHolder.addCallback(this);
}
@Override
// apk暫停時(shí)執(zhí)行的動(dòng)作:把相機(jī)關(guān)閉,避免占用導(dǎo)致其他應(yīng)用無法使用相機(jī)
protected void onPause() {
super.onPause();
mCamera.setPreviewCallback(null);
mCamera.stopPreview();
mCamera.release();
mCamera = null;
}
@Override
// 恢復(fù)apk時(shí)執(zhí)行的動(dòng)作
protected void onResume() {
super.onResume();
if (null!=mCamera){
mCamera = getCameraInstance();
try {
mCamera.setPreviewDisplay(mHolder);
mCamera.startPreview();
} catch(IOException e) {
Log.d(TAG, "Error setting camera preview: " + e.getMessage());
}
}
}
// SurfaceHolder.Callback必須實(shí)現(xiàn)的方法
public void surfaceCreated(SurfaceHolder holder){
mCamera = getCameraInstance();
try {
mCamera.setPreviewDisplay(holder);
mCamera.startPreview();
} catch(IOException e) {
Log.d(TAG, "Error setting camera preview: " + e.getMessage());
}
}
// SurfaceHolder.Callback必須實(shí)現(xiàn)的方法
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height){
refreshCamera(); // 這一步是否多余?在以后復(fù)雜的使用場(chǎng)景下,此步驟是必須的。
int rotation = getDisplayOrientation(); //獲取當(dāng)前窗口方向
mCamera.setDisplayOrientation(rotation); //設(shè)定相機(jī)顯示方向
}
// SurfaceHolder.Callback必須實(shí)現(xiàn)的方法
public void surfaceDestroyed(SurfaceHolder holder){
mHolder.removeCallback(this);
mCamera.setPreviewCallback(null);
mCamera.stopPreview();
mCamera.release();
mCamera = null;
}
// === 以下是各種輔助函數(shù) ===
// 獲取camera實(shí)例
public static Camera getCameraInstance(){
Camera c = null;
try {
c = Camera.open();
} catch(Exception e){
Log.d("TAG", "camera is not available");
}
return c;
}
// 獲取當(dāng)前窗口管理器顯示方向
private int getDisplayOrientation(){
WindowManager windowManager = getWindowManager();
Display display = windowManager.getDefaultDisplay();
int rotation = display.getRotation();
int degrees = 0;
switch (rotation){
case Surface.ROTATION_0:
degrees = 0;
break;
case Surface.ROTATION_90:
degrees = 90;
break;
case Surface.ROTATION_180:
degrees = 180;
break;
case Surface.ROTATION_270:
degrees = 270;
break;
}
android.hardware.Camera.CameraInfo camInfo =
new android.hardware.Camera.CameraInfo();
android.hardware.Camera.getCameraInfo(Camera.CameraInfo.CAMERA_FACING_BACK, camInfo);
// 這里其實(shí)還是不太懂:為什么要獲取camInfo的方向呢?相當(dāng)于相機(jī)標(biāo)定??
int result = (camInfo.orientation - degrees + 360) % 360;
return result;
}
// 刷新相機(jī)
private void refreshCamera(){
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) {
}
}
}
AndroidManifest.xml 記得添加相機(jī)權(quán)限
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.chris.myapplication" >
<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera.autofocus" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme" >
<activity android:name=".ChrisActivity" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
總結(jié)
以上是生活随笔為你收集整理的Android实时取景:用SurfaceView实现的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 2020母亲节(2020年的母亲节是几月
- 下一篇: 四元数基础