调用系统相机拍照和自定义相机拍照。

本文介绍了一种自定义相机功能的实现方法,重点解决了不同设备上拍摄照片角度旋转的问题。文章详细介绍了如何根据屏幕旋转角度调整相机预览,并提供了一个用于获取所需旋转角度的实用方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

    前一段时间,做项目的时候遇到了一个需要上传身份证的小功能。可以调用系统的相机。估计大部分人想到调用系统相机这种方式。开始我使用的也是这样一种方式。先写争取的代码。后面再写相关的坑。
                Intent intent = new Intent();
                intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE);
                //判断版本
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {   //如果在Android7.0以上,使用FileProvider获取Uri
                    intent.setFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
                    Uri contentUri = FileProvider.getUriForFile(Main2Activity.this, "demo.scroll.com.cameraproject", tempFile);
                    intent.putExtra(MediaStore.EXTRA_OUTPUT, contentUri);
                } else {    //否则使用Uri.fromFile(file)方法获取Uri
                    tempFile = new File(Environment.getExternalStorageDirectory().getPath(), System.currentTimeMillis() + ".jpg");
                    intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(tempFile));
                }
                if(intent.resolveActivity(getPackageManager())!=null){
                    startActivityForResult(intent,SYSTEM_CAMERA_CODE);
                }

为啥要用两种方式判断SDK 的版本问题呢,这是由于7.0之后调用系统相机会出现问题,崩溃。FileUriExposedException,原因是file:// 不被允许作为一个附加的 Uri 的意图 这样的Uri不在被调用系统相机的intent允许。具体的使用FileProvider的逻辑可以自百度(笔者没有7.0的手机测试)。还有一个坑是

if(data !=null){ //可能尚未指定intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);  
                //返回有缩略图  
                if(data.hasExtra("data")){  
                    Bitmap thumbnail = data.getParcelableExtra("data");  
                    //得到bitmap后的操作,如压缩处理...  
                }  
            }

大部分人通过这种方式去判断调用系统相机回调之后的图片,其实这样是可以判断是否拍照成功的,但是我们只能通过这个方式获取到我们需要的图片的缩略图,去过data去获取Uri是为空的。所以这样是行不通的。

        还有一个比较蛋疼的问题,有可能我们通过调用系统相机拍照所得到的照片,在点击拍照后预览是正常的(角度正常没有旋转)但是,Android就是一个开源系统,现在这么多厂商而且那么多手机需要去适配,难免在有些手机上预览正常,但是设置到你的ImageView上之后角度旋转了,是不是觉得很蛋疼呢。网上有很多说法是,可以通过ExifInterface 这个类来获取所拍照片的各种属性值。例如照片的方向。

 /**
     * 获取图片的旋转角度
     * @param imagePath 图片的绝对路径
     */
    private int getBitmapDegree(String imagePath) {
        int degree = 0;
        try {
            // 从指定路径下读取图片,并获取其EXIF信息
            ExifInterface exifInterface = new ExifInterface(imagePath);
            // 获取图片的旋转信息
            int orientation = exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION,
                    ExifInterface.ORIENTATION_NORMAL);
            switch (orientation) {
                case ExifInterface.ORIENTATION_ROTATE_90:
                    degree = 90;
                    break;
                case ExifInterface.ORIENTATION_ROTATE_180:
                    degree = 180;
                    break;
                case ExifInterface.ORIENTATION_ROTATE_270:
                    degree = 270;
                    break;
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return degree;
    }

用这个方法获得照片要旋转的角度。在用下面的方法去旋转Bimap的角度设置到我们的ImageView上面。

/**
     * 旋转图片
     * @param angle 被旋转角度
     * @param bitmap 图片对象
     * @return 旋转后的图片
     */
    public static Bitmap rotaingImageView(int angle, Bitmap bitmap) {
        Bitmap returnBm = null;
        // 根据旋转角度,生成旋转矩阵
        Matrix matrix = new Matrix();
        matrix.postRotate(angle%360);
        try {
            // 将原始图片按照旋转矩阵进行旋转,并得到新的图片
            returnBm = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
        } catch (OutOfMemoryError e) {
        }
        if (returnBm == null) {
            returnBm = bitmap;
        }
        if (bitmap != returnBm) {
            bitmap.recycle();
        }
        return returnBm;
    }

是不是感觉很完美呢,但是事实可能并不能如我们所愿,在我用这种方法尝试的时候上面获取到要旋转的角度一直为0,并且我的测试手机就是照片旋转的。是不是很无语,因此我只能选择自定义相机的方式去实现了。

先直接上代码吧。定义一个属于自己的CameraView 

public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback {
    private final String TAG = CameraPreview.class.getSimpleName();
    private SurfaceHolder mHolder;
    private Camera mCamera;
    public CameraPreview(Context context, Camera camera) {
        super(context);
        mCamera = camera;
        mHolder = getHolder();
        mHolder.addCallback(this);
        mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        try {
            if(mCamera!=null){
                mCamera.setPreviewDisplay(holder);
                mCamera.startPreview();
            }
        } catch (IOException e) {
            Log.d(TAG, "Error setting camera preview: " + e.getMessage());
        }
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
        if (mHolder.getSurface() == null){
            return;
        }
        try {
            mCamera.stopPreview();
            mCamera.setPreviewDisplay(mHolder);
            mCamera.startPreview();
            mCamera.autoFocus(null);
        } catch (Exception e){
            Log.d(TAG, "Error starting camera preview: " + e.getMessage());
        }
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
    }
}
基本的相机的定义需要的就是最原始的这一些代码。
mCamera.setPreviewDisplay(mHolder);

其实就是将相机所预览的对象放在SurfaceHolder中进行预览,这些其实都可以在Android的开发文档里面看到,里面有具体的实例https://developer.android.google.cn/guide/topics/media/camera,最主要的我觉得是相机的一些参数的处理或者设置。

一般我们将我们CameraView所在的Activity设置位

android:screenOrientation="portrait"

一个具体的朝向,横向或者纵向,这样防止我们在使用自定义相机的时候,在预览的时候,横屏或者竖屏拍照的切换,导致activity重建,给人一种不好的体验。例如上面我们设置的activity一直位竖屏,这个牵扯到我们后面手机所拍出的照片的角度调整。在代码中我们可以使用

getWindowManager().getDefaultDisplay().getRotation()

来获取我们相机的出事旋转角度。贴出代码然后结合代码讲解吧。

public class MainActivity extends Activity {
    private static final String TAG = MainActivity.class.getSimpleName();
    private static final int MY_PERMISSIONS_REQUEST_CAMERA =3 ;
    private Camera mCamera;
    private CameraPreview mPreview;
    private View confirm_cancle_layout;
    private Button captureButton,cancel_btn,confirm_btn;
    private String mFilePath = "";
    private OrientationEventListener mOrientationEventListener;
    private Camera.CameraInfo mCameraInfo = new Camera.CameraInfo();
    private static final int LANDSCAPE_90 = 90;
    private static final int LANDSCAPE_270 = 270;
    private static final int FACING_BACK = 0;
    private static final int FACING_FRONT = 1;
    private static final int INVALID_CAMERA_ID = -1;
    private Camera.Parameters mParameters;
    private int mCameraId;
    private Display display;
    private int mLastKnownRotation = -1;
    static final SparseIntArray DISPLAY_ORIENTATIONS = new SparseIntArray();
    private int screenOretation = 0;
    private int onClickOrtation =0;
    private boolean isHasInited = false;

    static {
        DISPLAY_ORIENTATIONS.put(Surface.ROTATION_0, 0);
        DISPLAY_ORIENTATIONS.put(Surface.ROTATION_90, 90);
        DISPLAY_ORIENTATIONS.put(Surface.ROTATION_180, 180);
        DISPLAY_ORIENTATIONS.put(Surface.ROTATION_270, 270);
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        getWindowManager().getDefaultDisplay();
        display = getWindowManager().getDefaultDisplay();
    }


    private Camera.PictureCallback mPicture = new Camera.PictureCallback() {

        @Override
        public void onPictureTaken(byte[] data, Camera camera) {

            File pictureFile = SDcardUtil.getOutputMediaFile(SDcardUtil.MEDIA_TYPE_IMAGE);
            if (pictureFile == null){
                Log.d(TAG, "Error creating media file, check storage permissions: ");
                return;
            }
            mFilePath = pictureFile.getAbsolutePath();
            try {
                FileOutputStream fos = new FileOutputStream(pictureFile);
                fos.write(data);
                fos.close();
            } catch (FileNotFoundException e) {
                Log.d(TAG, "File not found: " + e.getMessage());
            } catch (IOException e) {
                Log.d(TAG, "Error accessing file: " + e.getMessage());
            }
        }
    };

    //检测是否有相机的硬件
    private boolean checkCameraFeature(){
        return getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA);
    }


    @Override
    protected void onResume() {
        super.onResume();
        if(checkCameraFeature()){
            if(PERMISSION_GRANTED != ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA)){
                ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA},
                        MY_PERMISSIONS_REQUEST_CAMERA);
                return;
            }
            if(!isHasInited){
                initCamera();
                initView();
                isHasInited = true;
            }else{
                mCamera.stopPreview();
            }

        }
    }

    public  void initCamera(){
        try {
            chooseCamera();
          /*  if (mCamera != null) {
                mCamera.stopPreview();//停止预览
                mCamera.release();//释放相机资源
                mCamera = null;
            }*/
            mCamera = Camera.open(mCameraId);
            mParameters = mCamera.getParameters();
            int screenWidth = display.getWidth();
            int screenHeight = display.getHeight();
            mParameters.setPictureSize(screenWidth, screenHeight);
            mParameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
        }
        catch (Exception e){
            e.printStackTrace();
        }
        if(mOrientationEventListener==null){
            mOrientationEventListener = new OrientationEventListener(this, SensorManager.SENSOR_DELAY_NORMAL) {
                @Override
                public void onOrientationChanged(int orientation) {
                    //只检测是否有四个角度的改变
                    if (orientation > 350 || orientation < 10) { //0度
                        screenOretation = 0;
                    } else if (orientation > 80 && orientation < 100) { //90度
                        screenOretation = 90;
                    } else if (orientation > 170 && orientation < 190) { //180度
                        screenOretation = 180;
                    } else if (orientation > 260 && orientation < 280) { //270度
                        screenOretation = 270;
                    }
                    if(mCamera!=null && mParameters!=null){
                        setCameraDisplayOrientation(orientation);
                    }
                }
            };
            if (mOrientationEventListener.canDetectOrientation()) {
                Log.v(TAG, "Can detect orientation");
                mOrientationEventListener.enable();
            } else {
                Log.v(TAG, "Cannot detect orientation");
                mOrientationEventListener.disable();
            }
        }
    }

   /* @Override
    protected void onDestroy() {
        super.onDestroy();

    }*/

    @Override
    public void finish() {
        mCamera.stopPreview();//停止预览
        mCamera.release();//释放相机资源
        mCamera = null;
        super.finish();
    }

    private void initView(){
        mPreview = new CameraPreview(this, mCamera);
        FrameLayout preview = (FrameLayout) findViewById(R.id.camera_preview);
        preview.addView(mPreview);

        captureButton = (Button) findViewById(R.id.button_capture);
        captureButton.setOnClickListener(
                new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        onClickOrtation = screenOretation+mCameraInfo.orientation;
                        Log.i("*********","onClickOrtation==="+onClickOrtation+"    result ======="+mCameraInfo.orientation);
                        if(mCamera!=null){
                            mCamera.takePicture(null, null, mPicture);
                            captureButton.setVisibility(View.GONE);
                            confirm_cancle_layout.setVisibility(View.VISIBLE);
                        }
                    }
                }
        );
        confirm_cancle_layout = findViewById(R.id.confirm_cancle_layout);
        cancel_btn = (Button) findViewById(R.id.cancel_btn);
        confirm_btn = (Button) findViewById(R.id.confirm_btn);
        cancel_btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mCamera.stopPreview();
                mCamera.startPreview();
                confirm_cancle_layout.setVisibility(View.GONE);
                captureButton.setVisibility(View.VISIBLE);
            }
        });
        confirm_btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(MainActivity.this, Main2Activity.class);
                intent.putExtra("file_path",mFilePath);
                intent.putExtra("rotation",onClickOrtation);
                setResult(RESULT_OK,intent);
                MainActivity.this.finish();
            }
        });
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
                                           @NonNull int[] grantResults) {
        switch (requestCode) {
            case MY_PERMISSIONS_REQUEST_CAMERA:
                initCamera();

                break;
        }
    }
    public  void setCameraDisplayOrientation(int orientation) {
        if (orientation == OrientationEventListener.ORIENTATION_UNKNOWN ||
                display == null) {
            return;
        }
        int rotation = display.getRotation();
        if(mLastKnownRotation != rotation){
            mLastKnownRotation = rotation;
        }
        Log.i(TAG,"rotation==="+rotation+"    mCameraInfo.orientation ======="+mCameraInfo.orientation);
        int result = calcDisplayOrientation(DISPLAY_ORIENTATIONS.get(rotation));
        Log.i(TAG,"rotation==="+rotation+"    result ======="+result);
        if(mCamera!=null){
            mCamera.setDisplayOrientation(result);
        }
    }


    private int calcDisplayOrientation(int screenOrientationDegrees) {
        if (mCameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
            return (360 - (mCameraInfo.orientation + screenOrientationDegrees) % 360) % 360;
        } else {  // back-facing
            return (mCameraInfo.orientation - screenOrientationDegrees + 360) % 360;
        }
    }

    private void chooseCamera() {
        for (int i = 0, count = Camera.getNumberOfCameras(); i < count; i++) {
            Camera.getCameraInfo(i, mCameraInfo);
            if (mCameraInfo.facing == FACING_BACK) {
                mCameraId = i;
                return;
            }
        }
        mCameraId = INVALID_CAMERA_ID;
    }

挑几个重要的变量讲解一下

private OrientationEventListener mOrientationEventListener;  //一个监听屏旋转变化角度的变量

if(mOrientationEventListener==null){
            mOrientationEventListener = new OrientationEventListener(this, SensorManager.SENSOR_DELAY_NORMAL) {
                @Override
                public void onOrientationChanged(int orientation) {
                    //只检测是否有四个角度的改变
                    if (orientation > 350 || orientation < 10) { //0度
                        screenOretation = 0;
                    } else if (orientation > 80 && orientation < 100) { //90度
                        screenOretation = 90;
                    } else if (orientation > 170 && orientation < 190) { //180度
                        screenOretation = 180;
                    } else if (orientation > 260 && orientation < 280) { //270度
                        screenOretation = 270;
                    }
                    if(mCamera!=null && mParameters!=null){
                        setCameraDisplayOrientation(orientation);
                    }
                }
            };
            if (mOrientationEventListener.canDetectOrientation()) {
                Log.v(TAG, "Can detect orientation");
                mOrientationEventListener.enable();
            } else {
                Log.v(TAG, "Cannot detect orientation");
                mOrientationEventListener.disable();
            }
        }

代码段中根据范围,将屏幕角度分为0,90,180,270,其实还有360也就是与0度重合而已,因为拍照的时候,无论我们拍照的角度是怎么样,他只可能在正确的角度的基础上旋转这几个角度中的一个,不可能只出现旋转60度(除非自己特殊处理)这也就是,我们其实在最开始定义的

static {
        DISPLAY_ORIENTATIONS.put(Surface.ROTATION_0, 0);
        DISPLAY_ORIENTATIONS.put(Surface.ROTATION_90, 90);
        DISPLAY_ORIENTATIONS.put(Surface.ROTATION_180, 180);
        DISPLAY_ORIENTATIONS.put(Surface.ROTATION_270, 270);
    }

屏幕方向只有这四个值其中的一个一样类似的意思。

public  void setCameraDisplayOrientation(int orientation){ }  //这个是根据屏幕旋转角度和本身自己的角度设置相机照片的预览角度

在最后点击拍照按钮的时候我们记录下的角度是

onClickOrtation = screenOretation+mCameraInfo.orientation;

这个OnClickOrtation其实是最后我们自定义相机拍出来的图片需要我们自己处理之后,才能显示正常朝向的图片所需要旋转的角度额。

图片旋转的处理方法

/**
     * 旋转图片
     * @param angle 被旋转角度
     * @param bitmap 图片对象
     * @return 旋转后的图片
     */
    public static Bitmap rotaingImageView(int angle, Bitmap bitmap) {
        Bitmap returnBm = null;
        // 根据旋转角度,生成旋转矩阵
        Matrix matrix = new Matrix();
        matrix.postRotate(angle%360);
        try {
            // 将原始图片按照旋转矩阵进行旋转,并得到新的图片
            returnBm = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
        } catch (OutOfMemoryError e) {
        }
        if (returnBm == null) {
            returnBm = bitmap;
        }
        if (bitmap != returnBm) {
            bitmap.recycle();
        }
        return returnBm;
    }
在这里其实我最想记录的就是得到正确朝向图片我们所需要处理的问题,关键是怎么样正确得到图片所需要旋转的角度,当然如果能够直接通过上面的
getBitmapDegree(String path)

方法获取所需要旋转的角度更好。

当然如果有什么不懂或者觉得不好实现的,完全可以去借鉴google/CameraView来去设置相机的参数和属性,比如预览的大小,自动聚焦,相机的质量问题,也可以借鉴其中的代码自己扩展,还实现了Camera,Camera2比如说点击某点聚焦等等,相信这里面都有很好的答案。代码传送门

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值