Android自定义相机

版权声明:本文出自李紫洋的博客 转载请指明出处https://blog.csdn.net/qq_41912447 https://blog.csdn.net/qq_41912447/article/details/83447994

转载请指明出处@https://blog.csdn.net/qq_41912447
版权归博主所有

/**
**本文由自己搜集资料编程 如有错误请指明
**李紫洋著
*/

导引

相机成为了一个app必不可少的一个重要组成 它可以实现拍照,扫描二维码,视频聊天等 今天博主就来讲一下安卓开发相机 今天就带大家入门 更多资料请参考谷歌安卓开发官网android developers

  • 简介
    相机是属于系统资源 本身硬件就自带相机 你可以直接调用他来大大减少你的开发时间 像美颜相机这种app就需要自定义相机 现在大部分都有第三方平台 提供api供你使用 这同时大大减少你的代码量和团队创建的资金 第三方框架的使用大大减少你的工作量 同时他们的体系更加的完善 会减少bug的出现

  • 设备兼容的问题需要大家考虑 像小米 oppo vivo 华为等设备都对硬件系统做了一定的处理 用来优化他们的设备性能性 所以请参考他们这些设备的官网开发资料进行一定的处理

一般的处理方法是在功能清单注册这些设备的权限 在代码中对这些设备进行一定的处理

  • 同时6.0动态权限也需要大家考虑 让用户有一个高的体验 这的确是初学者的一个重大考验 你需要考虑所有会发生的情况
    1软件安装提示权限
    2
    <1.用户同意继续使用app
    <2.用户拒绝

    》》》1如果有必不可少的权限则退出 像相机app如果不给予相机权限只能强制退出软件了 这时就要提示用户如果拒绝则不再可以使用此软件
    》》》2如果此权限并不正常影响你的app正常启动 则可以选择在使用你的功能时再次询问是否打开权限 这时可以使用intent信使让用户跳转到系统权限打开的类手动打开权限
    》》为了避免多次对话框出现对用户体验造成影响 甚至会卸载app
    用户可能随时关闭权限 对你的app进行管理 有可能你的app是一个相机软件 用户不能理解你使用电话权限的理由 你可以在用户拒绝的时候提示用户此权限的意义 不要在权限申请的时候进行处理 过多的提示会降低用户的体验

3对手机系统版本做出判断 低版本安卓设备权限处理与6.0版本以上处理方法不同 主要是6.0动态权限加入危险权限和安全权限 安全权限则只需要在功能清单注册即可 一般一组权限只需要注册一个即可 用户同意则会给予你这组权限的所有

**需要对设备本地数据库具有一定的理解 用来存储一些重要数据永久保存 将在软件卸载的时候消失 同时也要对网络传输数据库有一定的理解 了解json xml传输数据 json有一个好的工具是谷歌的gson 后续会编写相关的博客

  • 6.0动态权限有一张步骤图 这里就不公告了 我用自己的理解把他用通俗的语言写出来了 请不要觉得文字太多 每个点都是需要考虑的

  • 注 所有资源请以谷歌开发者文档官网为标准
    好了废话不多说 现在来看我的一个简单示例代码
    这里示例调用系统相机 和自定义相机两个部分

调用系统相机

先来看布局文件来了解一下步骤

下面来示例这个java类的编码

package com.liziyang.dall;
//这里是自己的包名 必不可少的

import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.provider.MediaStore;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;

import java.io.File;
import java.util.EventListener;
//这里是导入的api库 有些是安卓的 有些是java的 也可以是框架的库 在写本类的时候需要查看导入的库 别导错库

public class MainActivity extends Activity {
//新建一个类的的初始格式  这个类继承了活动  用来实现UI界面的逻辑实现 
//extends是继承属性  你可以根据需要继承所需的类 
    //声明控件
    private Button button,button2;
    private ImageView imageView;
    private TextView textView;
    private String fileName="";
    //定义一个 路径的字符串 这里是一个空值 下面将会用到
       //定义一个string类型的fileName方便以后使用  用来作为拍摄的图片名字

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate( savedInstanceState );
        //四大组件活动的生命周期 oncreate
        //UI界面控件的逻辑实现 需要在这里实现  
        setContentView( R.layout.activity_main );
        //设置上下文对象
        //当java类继承活动就需要设置上下文
        button=findViewById( R.id.button );
        button2=findViewById( R.id.button2 );
        imageView=findViewById( R.id.imageView );
        textView=findViewById( R.id.textView );
        //声明对象绑定UI控件ID
        button.setOnClickListener( new View.OnClickListener() {
        //按钮实现点击功能  不要在xml布局文件中使用onclick 可能会出现bug 部分机型不匹配的可能
            public void onClick(View view) {
                //使用intent启动组件
                //系统相机的activity位置 mediaStore.ACTION_IMAGE_CAPTURE
                //这里使用意图 将界面跳转到系统相机界面 这些系统界面的代码简写在官方文档查询 最好把他们下载下来 方便使用的时候查询
           
                 Intent intent = new Intent( MediaStore.ACTION_IMAGE_CAPTURE  );
                 //意图intent 也可以写成下面两行 相信大家都懂
                 //Intent intent=new Intent(); 
                 //ntent.setClass(MainActivity.this,MediaStore.ACTION_IMAGE_CAPTURE .class);

                 //定义一个file路径  设置为内存卡路径
                File dir= Environment.getExternalStorageDirectory();//存在sd卡上
                //如果没有sd卡或sd卡内存已满则会保存到手机设备中
                //最好把网络需要的图片缓存下来 像app内置图片最好保存到分配的内置包中 防止给用户带来差的体验 
                //假如你在自己相册看到你不想要的图片视频等 你就会卸载
                //命名图片 得到系统毫秒
                fileName="hey"+System.currentTimeMillis()+".jpg";
                //初始化路径 把是存在在sd卡上和 保存图片的名字设置进去
                File file=new File( dir,fileName );
                //封装路径 把文件路径和名字两个参数封装进去
                
                Uri fileuri=Uri.fromFile( file );
                //设置图片路径 可以为网络等 最好选择一个框架 例如glide和ImageLoader框架对图片进行优化 如果是网络获取文件 例如okHttp或xUtils 这些框架建议大家去了解一下
                //设置uri 其他需要图片解码 uri途径就是存储设备和图片名
                //选择拍摄照片存放位置 第二个可以给这个图片一个uri
                //将intent添加额外数据 额外 读出 uri位置
                intent.putExtra( MediaStore.EXTRA_OUTPUT,fileuri );
                //意图添加其他传输数据
                //需要接受的方法 用类似方法将他取出来
                //有返回启动activity intent传输 返回码为0 返回码是自己写的
                startActivityForResult( intent,0);
                //带传值的启动活动  这个方法常用  
                //startActivity不太常用 他不可以传值
            }
        } );
        /**启动系统相机的方法 只需要用intent启动系统组件 指明系统组件
         * 自定义存储位置 得到外部存储  定义路径 自定义图片名字 写一个string数组为图片名字 为了防止名字相同 使用毫秒
         * 将自定义名字和路径包装到uri中
         *指明intent需要的功能 回传数据
         *添加intent功能和uri地址
         * 带回传启动activity 指明intent和回传码
         */
        button2.setOnClickListener( new View.OnClickListener() {
            @Override
            public void onClick(View view) {
            //这里就直接使用意图跳转界面 所需的放在另一个类方法实现
                Intent intent=new Intent( MainActivity.this,Main2Activity.class );
                startActivity( intent );

            }
        } );
    }

    //有返回值会回调这个方法
    @Override
    protected void onActivityResult(int requestCode,int resultCode,Intent data) {
        super.onActivityResult( requestCode,resultCode,data );
        //打印log 方便测试
        Log.i( "TEST","onActivityResult" );
        //如果回传码为0和回传码生成
        if(requestCode==0&&resultCode==RESULT_OK) {
            //指明路径
            File dir = Environment.getExternalStorageDirectory();//存在sd卡上
            //命名图片 得到系统毫秒
            File file = new File( dir,fileName );
            Uri fileuri = Uri.fromFile( file );
            //intent启动组件 返回数据的方法 onActivityResult
            //设置图片uri uri图片不用解码
            imageView.setImageURI( fileuri );
            //建议使用URI方法 不要需要解码否则可能会出现bug  要不然使用框架
        }
    }
}

在这里插入图片描述
布局不是很美观 只是一个demo 有需要可以参考网络资料 对布局优化
来看一下布局代码

<?xml version="1.0" encoding="utf-8"?>
<!--relativeLayout相对布局
这个布局可以让你更好的精确控制控件位置 最好使用谷歌新推出的布局   他避免了嵌套布局 加快了加载速度-->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".Main2Activity">
    <!---
    使用surfaceView来当作相机的画面 注意要全屏设置 用来美化布局-->
    <SurfaceView
        android:id="@+id/surfaceView2"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_alignParentBottom="true" />
        <!--这里嵌套了一个布局 
        android:gravity="center" 把布局里的控件放到布局中心 如 --*-- -->

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="80dp"
        android:gravity="center"
        android:layout_alignParentBottom="true"
        android:layout_alignParentLeft="true">
        <!--图片按钮 用来设置拍摄图片-->

        <ImageButton
            android:id="@+id/imageButton2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@android:drawable/btn_radio" />
            <!--图片控件进行预览-->

        <ImageView
            android:id="@+id/imageView3"
            android:layout_width="80dp"
            android:layout_height="80dp"
            android:src="@color/colorAccent" />

    </LinearLayout>




</RelativeLayout>

来看自定义相机的java具体实现类的代码编写

package com.liziyang.dall;


import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.hardware.Camera;
import android.os.Bundle;
import android.app.Activity;
import android.os.Environment;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.widget.ImageButton;
import android.widget.ImageView;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

public class Main2Activity extends Activity {
    //指明需要使用到相机
    //声明相机 因为需要用到自定义相机
    private Camera camera;
    //使用屏幕surface
    //只要是自定义的相机视频动画等需要用到屏幕view
    private SurfaceView surfaceView;
    //屏幕管理者
    private SurfaceHolder surfaceHolder;
    //屏幕管理的方法名
    private ImageButton imageButton2;
    private ImageView imageView3;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate( savedInstanceState );
        setContentView( R.layout.activity_main2 );
        surfaceView=findViewById( R.id.surfaceView2 );
        surfaceHolder=surfaceView.getHolder();
        imageButton2=findViewById( R.id.imageButton2 );
        imageView3=findViewById( R.id.imageView3 );
        imageButton2.setOnClickListener( new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                //照片拍摄图片 照片回调
                //括号中需要传入几个参数
                camera.takePicture( null,null,new Camera.PictureCallback() {
                    @Override
                    public void onPictureTaken(byte[] data,Camera camera) {
                        //预览
                        //bitmap位图  array数组 数据  数据的长度
                        //得到数据bitmap  数组 数据为0  数据的长度
                        Bitmap bitmap= BitmapFactory.decodeByteArray( data,0,data.length );
                        //预览视图设置图片位图
                        imageView3.setImageBitmap( bitmap );
                        //这里把拍摄的图片设置到UI界面的图片上 
                        //并没有进行处理 你也可以把他储存到内存卡上 把bitmap进行封装
                        //buffer缓冲 outPut输出 steam流 缓冲输入流初始化
                        BufferedOutputStream bufferedOutputStream=null;
                        //对缓存进行初始化 
                        //可能会出现bug  对可能出现的异常进行抛出
                        try  {
                            String fileName="hey"+System.currentTimeMillis()+".jpg";
                            //设置文件名
                             bufferedOutputStream = new BufferedOutputStream( new FileOutputStream( new File( Environment.getExternalStorageDirectory(),fileName ) )) ;
                            //保存  100压缩率 按原图保存
                            //compress压缩 format格式 100 缓冲输出流
                            bitmap.compress( Bitmap.CompressFormat.JPEG,100,bufferedOutputStream );
                            //对图片进行压缩  设置输出流为内容

                        }catch (FileNotFoundException e){
                            e.printStackTrace();
                        }finally {
                        //最后对所用到的东西进行关闭 以免内存泄漏
                            //使用完毕假如缓冲输出流不是空值 关闭缓存输入流
                            //防止使用后用户打开其他软件会出现bug
                            if (bufferedOutputStream!=null){
                            //判断输出流是否为空  
                                try {
                                //对输出流进行关闭
                                    bufferedOutputStream.close();
                                } catch (IOException e) {
                                //抛出异常
                                    e.printStackTrace();
                                }
                            }

                        }
                    }
                } );
            }
        } );
        //缓存持有者 添加回传
        surfaceHolder.addCallback( new SurfaceHolder.Callback(){
        //重写下面的方法
            @Override
            public void surfaceCreated(SurfaceHolder surfaceHolder) {
                //缓存创建的时候
                //得到相机 初始化
                //得到相机对象 
                camera=getCamera();
                //如果相机不为空
                if (camera!=null){
                    try {
                        //preview预览 display显示 缓存持有者
                        //设置缓存路径
                        camera.setPreviewDisplay( surfaceHolder );
                        //缓存开始预览
                        //开始缓存
                        camera.startPreview();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }

            }

            @Override
            public void surfaceChanged(SurfaceHolder surfaceHolder,int i,int i1,int i2) {

            }

            @Override
            public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
                //缓存销毁时 release释放
                //释放相机 以免再次使用相机出现被占用的现象
                releaseCamera();

            }
        } );
        //内容持有者设置种类
        surfaceHolder.setType( SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
        //得到相机
        camera=getCamera();
        //如果相机不为空
        if (camera!=null){
            try {
                //相机 设置preview预览 display显示
                camera.setPreviewDisplay( surfaceHolder );
                //相机开始预览  这是相机里的方法  调用
                //preview预览
                camera.startPreview();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }

    @Override
    protected void onDestroy() {
        //release释放
        releaseCamera();
        super.onDestroy();
    }

    //得到相机
    private Camera getCamera() {
        //如果相机 为空
        if (camera == null) {
            try {
                //打开相机
                camera = Camera.open();
                //返回相机
                return camera;
            } catch (Exception ex) {

                return null;
            }
        }
        //不管怎么样  返回相机
        return camera;
    }
    //release释放
    private void releaseCamera(){
        //如果相机不为空 释放相机   初始化
        if (camera!=null){
            camera.release();
            camera=null;
        }
    }

}

/***

  • 自定义相机的实现
  • 首先使用surface布局视图来显示自定义的画面
  • 得到画面缓存器 用来实现预览
  • 路径图片名字 压缩的处理
  • 使用surface回传
  • 软件进程关闭的处理 释放相机和画面持有者
    */

最后别忘了在功能清单注册权限

  • 注:6.0动态权限危险权限 需要特殊的处理
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.liziyang.dall">


    <!-- 如果不必须使用自定义相机改为false  required必须 -->
    <uses-permission android:name="android.permission.CAMERA" />
    <uses-permission android:name="android.permission.CAMERA2" />
    <uses-permission android:name="android.permission.RECORD_AUDIO" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

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

    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        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>
        <activity
            android:name=".Main2Activity"
            android:screenOrientation="landscape"
            android:label="@string/title_activity_main2">
        </activity>
    </application>

</manifest>

注:自定义相机权限处理方法不同 读取内存卡 使用相机 为危险权限 请注意加以处理 这里不写 用户必须去打开相应的权限 才可以继续使用
假如进行拍摄录制 需要加入一个音频录制的权限 后续会写6.0动态权限的处理

 <!-- 如果不必须使用自定义相机改为false  required必须 -->
    <uses-permission android:name="android.permission.CAMERA" />
    <uses-permission android:name="android.permission.CAMERA2" />
    <uses-permission android:name="android.permission.RECORD_AUDIO" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

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

    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

欢迎大家来参考我的博客 如有错误 请评论指出

同时我简书和博客有些不同步 关注我的技术主题,每天都有优质技术文章推送 简书扫一扫下方二维码即可关注:
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_41912447/article/details/83447994
今日推荐