Android自定义Camera

Google-Camera.png

转载请表明出处https://www.jianshu.com/p/e7f4973fbe76,谢谢~

写在前面

Android Framework层为各种不同的Camera和Camera的特色功能提供了支持,使得你可以很方便的在应用使用拍照和录像功能。如果希望快速实现拍照与录制视频的方法是使用Intent方式调用系统提供的相机功能;当然,如果系统提供的方式不足以满足项目的需求,你就需要自定义Camera相机。本篇博客会通过Intent方式和自定义Camera两部分介绍如何使用相机功能。

传送门:https://github.com/zhijunhong/custom_view/tree/master/camera

Intent实现方式

  1. 权限申请

  • Camera permisson - 为了使用相机硬件,需要请求使用Camera的权限

    <uses-permission android:name="android.permission.CAMERA" />
    
  • Camera Features - 你的应用还必须声明使用相机功能

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

    增加相机功能到你的mainfest文件,这样Google Play可以阻止那些没有相机硬件或者没有相机特定功能的设备安装你的应用。

  • Storage Permission - 应用需要保存图片或者视频到设备的外置存储空间(SD card)上,则需要在manifest中指定存取权限

    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    
  1. 代码实现

    • 申请Camera和存储权限
        private void openCapturePicOrVideo() {
            PermissionX.init(this)
                    .permissions(Manifest.permission.WRITE_EXTERNAL_STORAGE)
                    .request(new RequestCallback() {
                        @Override
                        public void onResult(boolean allGranted, List<String> grantedList, List<String> deniedList) {
                            if (allGranted) {
                              Toast.makeText(SystemCameraActivity.this, "All permissions are granted", Toast.LENGTH_LONG).show();
    
                                //拍照
                                Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
                                fileUri = getOutputMediaFileUri(MEDIA_TYPE_IMAGE);     // create a file to save the image
                                intent.putExtra(MediaStore.EXTRA_OUTPUT, fileUri);
                                startActivityForResult(intent, CAPTURE_IMAGE_ACTIVITY_REQUEST_CODE);
    
                                //拍摄视频
    //                            Intent intent1 = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
    //                            fileUri = getOutputMediaFileUri(MEDIA_TYPE_VIDEO);       // create a file to save the video
    //                            intent1.putExtra(MediaStore.EXTRA_OUTPUT, fileUri);      // set the image file name
    //                            intent1.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 1);     // set the video image quality to high
    //                            // start the Video Capture Intent
    //                            startActivityForResult(intent1, CAPTURE_VIDEO_ACTIVITY_REQUEST_CODE);
    
                            } else {
                                Toast.makeText(SystemCameraActivity.this, "These permissions are denied: $deniedList", Toast.LENGTH_LONG).show();
                            }
                        }
                    });
        }
    

    Android6.0以后的系统,需要在代码中申请敏感权限。这里为了方便起见,直接使用郭神的开源框架PermissionX申请Camera和存储权限

  • 获取camera intent结果回调
@Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == CAPTURE_IMAGE_ACTIVITY_REQUEST_CODE) {
            if (resultCode == RESULT_OK) {
                String tip;
                if (data != null) {
                    tip = "Image saved to:" + data.getParcelableExtra("data");               //系统目录
                } else {
                    tip = "Image saved to:" + fileUri;                                       //指定目录
                }
                Toast.makeText(this, tip, Toast.LENGTH_LONG).show();
            } else if (resultCode == RESULT_CANCELED) {
            } else {
            }
        }

//        if (requestCode == CAPTURE_VIDEO_ACTIVITY_REQUEST_CODE) {
//            if (resultCode == RESULT_OK) {
//                Toast.makeText(this, "Video saved to:\n" +
//                        data.getData(), Toast.LENGTH_LONG).show();
//            } else if (resultCode == RESULT_CANCELED) {
//            } else {
//            }
//        }
    }

Notice:这里重点说明一下MediaStore.EXTRA_OUTPUT这个参数,定义了一个Uri对象来指定存放图片的路径与文件名。这个设置信息是可选的,但是强烈建议添加。如果你不指定这个值,相机程序会使用默认的文件名保存图片到默认的位置,这个值可以从Intent.getData()的字段中获取到。

自定义Camera方式

以下内容,重点分析一下如何通过系统提供的Camera底层API,诸如Camera的打开或者称之为获取、Camera预览和Camera停止预览等,来定制相机功能,实现我们项目中特定的需求

  1. 权限申请

参考Intent方式中的权限申请部分,不再赘述。

  1. 代码实现

  • 检查相机是否存在并可以访问

    /**
     * 检查是否有相机硬件
     *
     * @param context
     * @return
     */
    public boolean checkCameraHardware(Context context) {
        if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA)) {
            //has a camera
            return true;
        } else {
            //no camera
            return false;
        }
    }
    
  • 获取相机资源

    /**
     * 获取相机实例
     *
     * @return
     */
    public Camera getCameraInstance() {
        Camera c = null;
        try {
            int cameraCount = Camera.getNumberOfCameras();
            c = Camera.open(cameraCount - 1);
    
            //设置自动对焦参数
            Camera.Parameters parameters = c.getParameters();
            List<String> focusModes = parameters.getSupportedFocusModes();
            if (focusModes.contains(Camera.Parameters.FOCUS_MODE_AUTO)) {
                parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
            }
            c.setParameters(parameters);
    
            // TODO: 2020-06-18 可以在用户点击的区域画矩形,增加用户体验
    
        } catch (Exception e) {
            Log.i(TAG, "Camera open exception: " + e.getMessage());
        }
        return c;
    }
    
  • 创建一个继承自SurfaceView的preview类,并implement SurfaceHolder的接口的interface,这个类用来预览相机的动态图片。

    public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback {
        private static final String TAG = "CameraPreview";
        private SurfaceHolder mHolder;
        private Camera mCamera;
    
        public CameraPreview(Context context, Camera camera) {
            super(context);
            mCamera = camera;
            mHolder = getHolder();
            mHolder.addCallback(this);
    
            // deprecated setting, but required on Android versions prior to 3.0
            mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
        }
    
        @Override
        public void surfaceCreated(SurfaceHolder holder) {
            // The Surface has been created, now tell the camera where to draw the preview.
            try {
                mCamera.setPreviewDisplay(holder);
                mCamera.startPreview();
            } catch (IOException e) {
                Log.i(TAG, "Error setting camera preview: " + e.getMessage());
            }
        }
    
        @Override
        public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
            if (holder.getSurface() == null) {
                return;
            }
    
            try {
                mCamera.stopPreview();
            } catch (Exception e) {
                e.printStackTrace();
            }
    
            try {
                mCamera.setPreviewDisplay(holder);
                mCamera.startPreview();
            } catch (IOException e) {
                Log.i(TAG, "Error starting camera preview: " + e.getMessage());
            }
        }
    
        @Override
        public void surfaceDestroyed(SurfaceHolder holder) {
            //release source
        }
    }
    
  • 相机布局文件

    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
    
        <FrameLayout
            android:id="@+id/camera_preview"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_weight="1" />
    
        <Button
            android:layout_centerHorizontal="true"
            android:layout_alignParentBottom="true"
            android:id="@+id/button_capture"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Capture" />
    </RelativeLayout>
    
  • Capturing pictures监听

//点击拍照按钮
mBtnCapture.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        // get an image from the camera
        mCamera.takePicture(null, null, new Camera.PictureCallback() {
            @Override
            public void onPictureTaken(byte[] data, Camera camera) {
                FileUtils.saveTakePicture(data);
                //关闭界面
                finish();
            }
        });
    }
});
  • 保存拍摄的图片

    /**
     *
     * @param data
     */
    public static void saveTakePicture(byte[] data) {
        //save the picture
        File pictureFile = FileUtils.getOutputMediaFile(MEDIA_TYPE_IMAGE);
        if (pictureFile == null) {
            Log.i(TAG, "Error creating media file, check storage permissions");
            return;
        }
        try {
            FileOutputStream os = new FileOutputStream(pictureFile);
            os.write(data);
            os.close();
    
            Log.i(TAG, "Save Success!!");
        } catch (FileNotFoundException e) {
            Log.i(TAG, "File not found: " + e.getMessage());
        } catch (IOException e) {
            Log.i(TAG, "Error accessing file: " + e.getMessage());
        }
    }
    
  • 释放相机资源

相机硬件是一个共享资源,它必须被小心谨慎的管理使用,因此你的程序不应该和其他可能使用相机硬件的程序有冲突。

当你的程序执行完任务后,需要使用camers.realease()方法来释放相机对象。如果你的相机没有合理的释放相机,后续包括你自己的应用在内的所有的相机应用,都将无法正常打开相机并且可能导致程序崩溃。

@Override
protected void onPause() {
    super.onPause();
    CameraManger.getInstance().releaseCamera(mCamera);
}
/**
 * 释放相机资源
 */
public void releaseCamera(Camera camera) {
    if (camera != null) {
        camera.release();      //release the camera for other applications
        camera = null;
    }
}
  • 相机功能扩展

    Android提供了控制相机特性的方法,如图片格式化,闪光灯模式,设置聚焦等等。这里只是用设置聚焦举例,更多特性,还需要读者自行阅读官方文档实现。

    /**
     * 获取相机实例
     *
     * @return
     */
    public Camera getCameraInstance() {
        Camera c = null;
        try {
            int cameraCount = Camera.getNumberOfCameras();
            c = Camera.open(cameraCount - 1);
    
            //设置自动对焦参数
            Camera.Parameters parameters = c.getParameters();
            List<String> focusModes = parameters.getSupportedFocusModes();
            if (focusModes.contains(Camera.Parameters.FOCUS_MODE_AUTO)) {
                parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
            }
            c.setParameters(parameters);
    
            // TODO: 2020-06-18 可以在用户点击的区域画矩形,增加用户体验
    
        } catch (Exception e) {
            Log.i(TAG, "Camera open exception: " + e.getMessage());
        }
        return c;
    }
    

此致,希望可以对正在研究如何自定义Camera的小伙伴提供一些思路和帮助!

最后,如果此篇博文对你有所帮助,别忘了点个赞哟~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容