美文网首页
Android相机开发(一):最简单的相机

Android相机开发(一):最简单的相机

作者: 南北VS东西 | 来源:发表于2023-01-03 17:15 被阅读0次

    转载自:Penguin

    概述

    作为系列的第一篇,就当做是Android开发入门了。本文主要讲解怎么在Android Studio下,从最开始慢慢实现一个只能拿来预览(不能拍照)的相机APP。

    新建一个空的APP

    打开Android Studio,选择New->New Project,然后给你的APP起个名字,比如:

    ![pload-images.jianshu.io/upload_images/9127909-7c61c6aeaf83b66a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240 "New Project")

    然后设置支持的最低API等级,我选择的是API 19: Android 4.4

    Target Android Devices

    再之后选择默认Activity,为了让我们能够更清楚Android的架构,选择Add No Activity

    Add an Activity to Mobile

    这样我们的空APP就生成了,文件目录大概会是这个样子:

    Empty File List

    这个APP还不能够运行,如果你尝试运行,Android Studio会报错

    添加外观,让APP能够运行

    添加一个layout

    res下新建一个文件夹layout,在layout下新建一个文件activity_main.xml

    Layout File List

    activity_main.xml下会出现一个编辑布局的窗口,这时点击左下角的Text,即activity_main.xml的文本编辑模式,在其中加入如下代码:

    XML

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <TextView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:text="Hello World!" />
    </LinearLayout>
    

    这个activity_main.xml就是一个布局文件,而我们刚才设计了一个布局,布局方式是LinearLayout,其中只有一个TextView,其内容是Hello World!

    使用这个layout

    新建好一个布局文件后,我们需要让APP使用这个布局。

    java->com.polarxiong.camerademo(这个根据你自己写的APP名字来)下新建一个Java Class MainActivity

    Main Activity File List

    新建后会自动创建一个空的Java类:

    Java

    public class MainActivity {
    }
    

    接下来在这个类中加入一些代码:

    Java

    import android.app.Activity;
    import android.os.Bundle;
    
    public class MainActivity extends Activity {
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
        }
    }
    

    解释一下,Activity类可以创建一个“窗口”,就是APP运行时的那个窗口,而通过Activity这个“窗口”添加布局,就能在窗口中应用这个布局,让用户能够看到;所以MainActivity需要继承Activity,作为这个APP的主窗口。

    onCreate()重载了ActivityonCreate(),在窗口被创建时执行这个方法,setContentView()即设置这个窗口的布局,这里我们选择刚才创建的布局R.layout.activity_main。这样当MainActivity这个窗口创建时,就会应用activity_main这个布局。

    添加APP入口

    我们希望APP在打开时就会显示MainActivity这个窗口,但我们怎么告诉APP呢?答案是manifests下的AndroidManifest.xml文件,这个文件对APP来说是至关重要的,其中定义了关于APP的一些重要信息,AndroidManifest.xml会在APP的代码执行之前就会读取。

    AndroidManifest.xml初始时大概是这样:

    XML

    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.polarxiong.camerademo">
    
        <application
            android:allowBackup="true"
            android:icon="@mipmap/ic_launcher"
            android:label="@string/app_name"
            android:supportsRtl="true"
            android:theme="@style/AppTheme">
    
        </application>
    
    </manifest>
    

    现在我们给APP添加一个入口,也就是一个activity:

    XML

    <activity android:name=".MainActivity">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>
    

    其中activity android:name=".MainActivity"就是指定了刚才的窗口MainActivity,修改后的AndroidManifest.xml像这样:

    XML

    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.polarxiong.camerademo">
    
        <application
            android:allowBackup="true"
            android:icon="@mipmap/ic_launcher"
            android:label="@string/app_name"
            android:supportsRtl="true"
            android:theme="@style/AppTheme">
            <activity android:name=".MainActivity">
                <intent-filter>
                    <action android:name="android.intent.action.MAIN" />
                    <category android:name="android.intent.category.LAUNCHER" />
                </intent-filter>
            </activity>
        </application>
    
    </manifest>
    

    运行

    现在点击Android Studio上方的运行按钮就可以运行这个APP啦,你可以选择在真机或模拟器下运行,运行的效果像这样:


    TextView Screenshot

    让APP显示相机预览

    想让相机拍到的画面显示在APP的窗口中,简单来说就是要启动相机,并且将相机内容绑定到窗口。

    修改布局

    想要相机预览显示在窗口中,实际上就是要显示在布局中,我们首先在布局中给相机预览留个位置,对于这个APP来说的话,就是把全部的位置都留给相机预览了。

    修改activity_main.xml

    XML

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <FrameLayout
            android:id="@+id/camera_preview"
            android:layout_width="0px"
            android:layout_height="fill_parent"
            android:layout_weight="1"
            />
    </LinearLayout>
    

    我们会让相机预览填充在FrameLayout中,但因为这个填充会在Java代码中实现,所以目前我们只给它指定一个ID,供以后使用,ID名字就是camera_preview;而剩下的就是在设置这个FrameLayout所占布局大小,设置为占有全部。

    添加CameraPreview类

    从布局层面来说,我们想要添加相机预览实际上就是在FrameLayout中再添加一个View,这个View可以理解为一个“控件”,就像之前的TextView,也是View中的一种,只不过相机预览这个View的内容是会一直变化的预览帧。因为Android并没有提供相机预览这个View,所以需要我们自己创造一个,而这个View我们就起名叫做CameraPreview

    java->com.polarxiong.camerademo(这个根据你自己写的APP名字来)下新建一个Java Class CameraPreview,并修改其内容为:

    Java

    import android.content.Context;
    import android.hardware.Camera;
    import android.util.Log;
    import android.view.SurfaceHolder;
    import android.view.SurfaceView;
    
    import java.io.IOException;
    
    public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback {
        private static final String TAG = "CameraPreview";
        private SurfaceHolder mHolder;
        private Camera mCamera;
    
        public CameraPreview(Context context) {
            super(context);
            mHolder = getHolder();
            mHolder.addCallback(this);
        }
    
        private static Camera getCameraInstance() {
            Camera c = null;
            try {
                c = Camera.open();
            } catch (Exception e) {
                Log.d(TAG, "camera is not available");
            }
            return c;
        }
    
        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());
            }
        }
    
        public void surfaceDestroyed(SurfaceHolder holder) {
            mHolder.removeCallback(this);
            mCamera.setPreviewCallback(null);
            mCamera.stopPreview();
            mCamera.release();
            mCamera = null;
        }
    
        public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
        }
    }
    

    SurfaceView是一个包含有SurfaceView,而Surface用来处理直接呈现在屏幕上的内容,这个我们不细究,可以认为是相机预览的原始数据交给Surface就能转换成呈现在屏幕上的样子,这也是为什么CameraPreview一定要继承SurfaceViewCameraPreview还使用了SurfaceHolder.Callback接口,这个接口包含有三个方法:surfaceChanged()surfaceCreated()surfaceDestroyed(),这三个方法会对应在Surface内容变化、Surface生成和Surface销毁时触发。

    成员变量mHolder保存这个Surface的“持有者”(还是Holder顺口),而只有Holder才能对对应的Surface进行修改。成员变量mCamera保存相机Camera的实例。

    先看构造函数,构造函数实际就是指定本View(即CameraPreview)是这个SurfaceHolder

    对于SurfaceView来说,这个View创建时就会创建Surface,而当Surface创建时就会触发surfaceCreated(),所以我们就要在surfaceCreated()中打开相机、开始预览,并将预览帧交给Surface处理。getCameraInstance()是一个比较安全的获取并打开相机的方法,很简单。CamerasetPreviewDisplay()方法就是告知将预览帧数据交给谁,这里当然就是这个SurfaceHolder了;最后用startPreview()开启相机,这样我们就完成了整个过程,创建好了CameraPreview类。

    但我们还要做一些善后处理,相机是共享资源,在APP运行结束后就应当“放弃”相机。我们在APP退出,即surfaceDestroyed()中完成这些善后处理,surfaceDestroyed()中的代码很简单,不需要详细说明,就是构造函数和
    surfaceCreated()的逆过程。

    我们目前还不需要surfaceChanged(),所以代码留空。

    CameraPreview加入到FrameLayout

    上一步只是创建了一个View,而现在就是要将这个View添加到activity_main中,因为这个View是实时创建的,当然我们不能直接去修改activity_main.xml,而应当在MainActivity中用代码添加。

    修改MainActivity,在onCreate()最后添加:

    Java

    CameraPreview mPreview = new CameraPreview(this);
    FrameLayout preview = (FrameLayout) findViewById(R.id.camera_preview);
    preview.addView(mPreview);
    

    代码首先创建一个CameraPreview的实例mPreview,再在布局中通过ID找到FrameLayout,最后在FrameLayout中添加mPreview。修改之后的MainActivity就是:

    Java

    import android.app.Activity;
    import android.os.Bundle;
    import android.widget.FrameLayout;
    
    public class MainActivity extends Activity {
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            CameraPreview mPreview = new CameraPreview(this);
            FrameLayout preview = (FrameLayout) findViewById(R.id.camera_preview);
            preview.addView(mPreview);
        }
    }
    

    在AndroidManifest中申请和声明相机

    现在APP启动时就会调用MainActivity,而MainActivity创建时就会创建CameraPreviewCameraPreview创建时则会调用相机并开启相机预览。现在还存在的一个问题是APP启动后才会调用相机,而很显然我们希望APP在安装时就告知Android需要用到相机,这就是AndroidManifest要干的事情啦。

    AndroidManifest.xmlmanifest下加入:

    XML

    <uses-permission android:name="android.permission.CAMERA" />
    <uses-feature android:name="android.hardware.camera" />
    

    AndroidManifest.xmlactivity内加入:

    XML

    android:screenOrientation="landscape"
    

    最后AndroidManifest.xml像这样:

    XML

    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.polarxiong.camerademo">
        <uses-permission android:name="android.permission.CAMERA" />
        <uses-feature android:name="android.hardware.camera" />
    
        <application
            android:allowBackup="true"
            android:icon="@mipmap/ic_launcher"
            android:label="@string/app_name"
            android:supportsRtl="true"
            android:theme="@style/AppTheme">
            <activity android:name=".MainActivity"
                android:screenOrientation="landscape">
                <intent-filter>
                    <action android:name="android.intent.action.MAIN" />
                    <category android:name="android.intent.category.LAUNCHER" />
                </intent-filter>
            </activity>
        </application>
    
    </manifest>
    

    uses-permission是APP申请相机权限,这样在安装APP时就会显示APP需要相机;uses-feature则可以防止APP被安装到没有相机的Android设备上(目前仅Google Play支持)。android:screenOrientation则是设置APP的方向,水平或竖直,这里设置为水平,因为从实际来说,相机预览一直是水平的。

    运行

    上述步骤完成后整个APP就算开发完毕了,虽然基本没啥功能,但总算是能跑起来了。如下:


    Photo

    一点唠叨

    很显然这个“相机”APP还不能算真正的相机,不能拍照,而且还支持对焦,这个屏幕一篇模糊简直不能用。但如果你能成功写出这个APP,本文的目的也算达到了,至少你已经大体明白了Android开发的步骤,以及Android APP运行的基本过程。随后的系列文章会基于这个APP扩展各方面的功能,欢迎继续阅读。

    DEMO

    本文实现的相机APP源码都放在GitHub上,如果需要请点击zhantong/AndroidCamera-PreviewOnly

    参考

    相关文章

      网友评论

          本文标题:Android相机开发(一):最简单的相机

          本文链接:https://www.haomeiwen.com/subject/rdulcdtx.html