update: 这篇blog没有处理android sdk api>=23时的动态权限问题。建议直接使用这一篇:Android SurfaceView Tutorial With Example


对于基于摄像头的Android应用,实时取景是一个基本前提,通过前置或后置摄像头持续获取捕获到的内容,可以进一步做处理(人脸检测、美颜、滤镜等)。

所谓实时取景,简单说就是调用android的摄像头,把摄像头捕获的内容显示在apk的界面上。只要应用不关闭,相机就持续捕获,apk上看到的就是实时的取景了。

采用SurfaceView和Camera来做这件事。

是SDK自带的SurfaceView类而不是实现它的子类;在布局XML文件中使用SurfaceView而不是FrameLayout。因此,代码量很少也很容易理解。

从View到SurfaceView

android应用,和用户交互的GUI界面,是搭载在Activity上的。Activity创建的时候,往往会做setContentView(R.id.main_layout),这是根据xml布局文件设定要预先确定好的各种view对象,这些组件在xml中进行设计、设定。当然也可以在Java代码中进一步动态增加view对象。相当于layout作为各种view的容器。

android sdk自带了很多view的子类供使用。

View本身:继承自java.lang.Object类,实现了Drawable.Callback, KeyEvent.Callback, AccessibilitiyEventSource接口。

直接子类有:

AnalogClock:模拟时钟,有3个指针那种。

ImageView:显示图像。其实,任何Drawable对象都可以用ImageView来显示。

KeyboardView:内置键盘。

MediaRouteButton:媒体路由按钮(不太懂。似乎和多媒体、网络路由相关)

ProgressBar:(可视化)进度条展示。

Space:空白视图。轻量级视图。作用:在不同组件之间插入缝隙、间隔。

SurfaceView:提供了一个专门用于绘制的Surface。Surface的格式、尺寸可以控制。SurfaceView控制这个surface的绘制位置。。。中文翻译

TextView:文本视图。

TextureView:纹理视图。sdk4.0之后的API中可用。常被拿来和SurfaceView比较。

ViewGroup:用来容纳其他view对象。是layout(布局)和view containers(视图容器)的基类。

ViewStub:视图存根。大小为0、不可见,用来占坑的,apk运行时把坑交给资源。

间接子类有:

AbsListView

AbsSeekBar

AbsSpinner

AbsoluteLayout

AdapterView

AdapterViewAnimator

AdapterViewFlipper

以及其他56个间接子类。

可以看到,SurfaceView和TextureView两个view子类,都能用于实时取景框的显示。但是考虑到TextureView需要开启硬件加速的支持,不考虑。以及,目前看来SurfaceView本身也能胜任实时取景的任务。

代码

layout文件:surfaceview_main.xml

看到很多教程用的都是FrameLayout,而不是SurfaceView。我很不理解:为什么不用SurfaceView呢?不好用吗?

anyway,我这里就用SurfaceView了,在我测试过的代码中是完全可用的。

<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这三者相当于MVC的存在,Surface是数据,SurfaceView负责显示,SurfaceHolder控制了Surface。通过让Activity实现SurfaceHolder.Callback接口,开发者自行实现下面三个函数,开发者就完成内容的处理:

public void surfaceCreated(SurfaceHolder holder);
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height);
public void surfaceDestroyed(SurfaceHolder holder);

而具体到实现,一些额外的细节也要考虑到:相机的初始化和释放;应用暂停时释放相机,恢复时获取相机;屏幕方向与显示方向的一致。所以有如下代码:

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.
* 网上找了一些博客、教程和代码,稍微有点头绪了,现在写自己的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
// 创建Activity时执行的动作
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暂停时执行的动作:把相机关闭,避免占用导致其他应用无法使用相机
protected void onPause() {
super.onPause(); mCamera.setPreviewCallback(null);
mCamera.stopPreview();
mCamera.release();
mCamera = null;
} @Override
// 恢复apk时执行的动作
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必须实现的方法
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必须实现的方法
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height){
refreshCamera(); // 这一步是否多余?在以后复杂的使用场景下,此步骤是必须的。
int rotation = getDisplayOrientation(); //获取当前窗口方向
mCamera.setDisplayOrientation(rotation); //设定相机显示方向
} // SurfaceHolder.Callback必须实现的方法
public void surfaceDestroyed(SurfaceHolder holder){
mHolder.removeCallback(this);
mCamera.setPreviewCallback(null);
mCamera.stopPreview();
mCamera.release();
mCamera = null;
} // === 以下是各种辅助函数 === // 获取camera实例
public static Camera getCameraInstance(){
Camera c = null;
try {
c = Camera.open();
} catch(Exception e){
Log.d("TAG", "camera is not available");
}
return c;
} // 获取当前窗口管理器显示方向
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); // 这里其实还是不太懂:为什么要获取camInfo的方向呢?相当于相机标定??
int result = (camInfo.orientation - degrees + 360) % 360; return result;
} // 刷新相机
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 记得添加相机权限

<?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>

Android实时取景:用SurfaceView实现的更多相关文章

  1. android中使用surfaceview+MediaPlayer播放视频

    Android中播放视频主要有两种方式: 使用其自带的播放器.指定Action为ACTION_VIEW,Data为Uri,Type为其MIME类型 使用android自带的VideoView,这种方法 ...

  2. Android中使用SurfaceView+MediaPlayer+自定义的MediaController实现自定义的视屏播放器

    效果图如下: (PS本来是要给大家穿gif动态图的,无奈太大了,没法上传) 功能实现:暂停,播放,快进,快退,全屏,退出全屏,等基本功能 实现的思路: 在主布局中放置一个SurfaceView,在Su ...

  3. android 98 MediaPlayer+SurfaceView播放视频

    package com.itheima.videoplayer; import java.io.IOException; import android.media.MediaPlayer; impor ...

  4. Android中使用SurfaceView和Canvas来绘制动画

    事实上每一个View中都有Canvas能够用来绘制动画.仅仅须要在这个View中重载onDraw()方法就能够,可是SurfaceView类是一个专门用来制动动画的类. Canvas(中文叫做&quo ...

  5. Android之View / SurfaceView / GLSurfaceView

    Android游戏当中主要的除了控制类外就是显示类View.SurfaceView是从View基类中派生出来的显示类.android游戏开发中常用的三种视图是:view.SurfaceView和GLS ...

  6. Android中surface,surfaceview,sufaceholder以及surface客户端的关系

    这里以照相机camera功能的实现来解释surface,surfaceview,sufaceholder以及surface客户端(本例子中指的是camera)的关系,surface及其client(客 ...

  7. android下面使用SurfaceView+ mediaPlayer播放视频

    final SurfaceView surfaceView = new SurfaceView(StartupActivity.this); StartupActivity.this.mediaPla ...

  8. Android MediaPlayer和SurfaceView播放视频

    昨天介绍了VideoView播放视频,今天再介绍一种播放视频的方法MediaPlayer和SurfaceView,MediaPlayer播放音频,SurfaceView来显示图像,具体步骤如下: 1. ...

  9. Android开发之SurfaceView

    SurfaceView介绍 通常情况程序的View和用户响应都是在同一个线程中处理的,这也是为什么处理长时间事件(例如访问网络)需要放到另外的线程中去(防止阻塞当前UI线程的操作和绘制).但是在其他线 ...

随机推荐

  1. python3+requests库框架设计04-配置文件

    python3配置文件的增删改查等操作可以使用内置的ConfigParser模块,可以自行百度学习,也可以看Python3学习笔记27-ConfigParser模块 配置文件一般存放着环境信息,比如u ...

  2. 《The Practice and Theory of Bolshevism》的笔记-第114页

    章节名:International Policy 页码:第114页 2017-09-30 15:11:24 Among religions, Bolshevism is to be reckoned ...

  3. 001_获取nginx证书

    一. 以下命令可以获取nginx域名的证书 openssl s_client -showcerts -connect www.jyall.com:443 < /dev/null 2>&am ...

  4. Jmeter怎样打印日志

    1.在日志中显示 log.info(xxx) 2.在控制台处

  5. web@css普通布局 , 高级布局 , 布局坑

    1.高级布局<文档流概念>:页面从上至下,块式标签一行一行排列,内联式一行中从左至右排列<BFC规则>:左右位置(左右margin)垂直位置(上下margin)容器内外(互不影 ...

  6. Python2018-列表的相关操作

    列表中存放的数据是可以进行修改的,比如"增"."删"."改" .“查” "增"-----append, extend, ...

  7. python old six day

    今天主要内容: . is 和== 的区别 . 编程的问题 一.       is和==的区别! is  比较的是内存地址 ==  比较的是值 记住结果就好 ⑴id 通过id() 我们查看到一个变量表示 ...

  8. 性能工具之JMeter+InfluxDB+Grafana打造压测可视化实时监控【转】

    概述 本文我们将介绍如何使用JMeter+InfluxDB+Grafana打造压测可视化实时监控. 引言 我们很多时候在使用JMeter做性能测试,我们很难及时察看压测过程中应用的性能状况,总是需要等 ...

  9. VUE 多页面配置(一)

    1. 概述 1.1 说明 项目开发过程中会遇到需要多个主页展示情况,故在vue单页面的基础上进行配置多页面开发以满足此需求. 2. 实例 2.1 页面配置 2.1.1 默认首页 使用vue脚手架搭建后 ...

  10. PYTHON- 操作系统和python程序

    操作系统基础 应用程序的启动:(重点!!!) python解释器安装,多版本共存 执行python程序的两种方式 运行一个python程序经历的三个阶段(重要) python 的内存管理 ====== ...