zoukankan      html  css  js  c++  java
  • Android实时取景:用SurfaceView实现

    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>
    
  • 相关阅读:
    Java数据类型转换(自动转换和强制转换)
    Java数据类型以及变量的定义
    Java8新特性_日期时间新类 LocalDate、LocalTime、LocalDateTime
    Java8新特性_接口中的默认方法
    java8 新特性 Optional容器类
    Java8新特性 并行流与串行流 Fork Join
    Java8新特性_stream API 练习
    IDEA导入JUnit4
    Reduce:规约;Collector:收集、判断性终止函数、组函数、分组、分区
    【Linux】排序命令sort
  • 原文地址:https://www.cnblogs.com/zjutzz/p/7077093.html
Copyright © 2011-2022 走看看