Android 自定义相机保存图片

Android实现自定义相机

前言

开发安卓过程中,经常会用到相机,有时候系统相机不能满足开发者的需要,这时候就需要自定义相机来实现。(注意:本文只是提供思路,有关键代码,但是代码不全)

相机说明

自定义相机Activity需要用到Camera这个安卓原生的相机类,需要一个surfaceView来预览,如果要拍照声音还需要一个ToneGenerator类,如果需要监听屏幕旋转角度,需要实现SensorEventListener这个监听,还需要感应重力的Sensor以及SensorManager。

SurfaceHolder以及SurfaceHolder.Callback
surfaceHolder它是一个用于控制surface的接口,它提供了控制surface 的大小,格式,上面的像素,即监视其改变的。SurfaceHolder.Callback在底层的Surface状态发生变化的时候通知View。

相机启动

启动相机是在surfaceHolder的回调函数surfaceCreated里面做,需要注意启动相机前要做一些配置:

配置文件

surfaceview.getHolder()
.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);//设置surfaceview不维护自己的缓冲区,而是等待屏幕的渲染引擎将内容推送到用户面前
surfaceview.getHolder().setFixedSize(width, height);
surfaceview.getHolder().setKeepScreenOn(true);// 屏幕常亮
surfaceview.getHolder().addCallback(new SurfaceCallback());// 为SurfaceView的句柄添加一个回数调函
sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
orientation_SensorListener = new OrientationSensorListener(
sensor_handler);
sensorManager.registerListener(orientation_SensorListener, sensor,
SensorManager.SENSOR_DELAY_UI);

启动相机的方法

/**
 * 启动相机
 */
 private void startCamera() {
    try {
        if (null == mCamera) {
        // 获得Camera对象
        mCamera = Camera.open();
        // 设置用于显示拍照摄像的SurfaceHolder对象
        if (mCamera != null) {
            SurfaceHolder mSurfaceHolder =surfaceview.getHolder();
            mCamera.setPreviewDisplay(mSurfaceHolder);
            mCamera.setErrorCallback(new CameraonError());//记录相机错误的回调函数
            mCamera.startPreview();
            }
        }
        } catch (Exception e) {
            e.printStackTrace();
            // 释放手机摄像头
            Toast.makeText(PersonPhotoActivity.this, "相机启动失败请重新启动",Toast.LENGTH_SHORT).show();
            if (mCamera != null) {
                mCamera.release();
                mCamera = null;
            }
            finish();
        } finally {
            is_takingPhoto = false;//防止连拍的标志
        }
    }

surfaceHolder的回调函数

private final class SurfaceCallback implements Callback {

    // 预览变化时调用该方法
    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width,int height) {
    try {
        Camera.Parameters parameters = mCamera.getParameters();
        parameters.setPictureFormat(PixelFormat.JPEG);
        parameters.setPictureSize(1920, 1080);//设置照片分辨率16:9
        mCamera.setErrorCallback(new CameraonError());
        mCamera.setParameters(parameters);
        mCamera.startPreview();
        } catch (Exception e) {
        }
    }

    // 开始预览时调用该方法
    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        startCamera();
    }

    // 停止预览时调用该方法
    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        if (mCamera != null) {
            mCamera.release(); // 释放照相机
            mCamera = null;
        }
    }
}

相机拍照

点击拍照按钮,拍照时调用自动对焦(无法自动对焦时怎么做?)

public void takePicture() {
    if (!is_takingPhoto) {
        is_takingPhoto = true;//防止多次调用
        Toast.makeText(getApplication(), "点击拍照按钮后请不要晃动,以免影响图片拍摄效果!",Toast.LENGTH_SHORT).show();
        if (mCamera == null) {
            Reset();
            startCamera();
        } else {
            // 自动对焦
            mCamera.autoFocus(new AutoFocusCallback() {
            @Override
            public void onAutoFocus(boolean success, Camera camera) {
            // 自动对焦成功后才拍摄
            if (success) {
                img_focus.setImageResource(R.drawable.focus2);
                sensorManag.unregisterListener(
                                orientation_SensorListener);
                mCamera.takePicture(shutterCallback, null,new MyPictureCallback());
                mCamera.cancelAutoFocus();//有人说加这行可以使无法对焦的机器去自动对焦,在小米手机上试了好用,但是不知道其他机器
            } else {
                img_focus.setImageResource(R.drawable.focus1);
                Toast.makeText(getApplication(), "对焦失败,请重拍!",
                                    Toast.LENGTH_LONG).show();
                is_takingPhoto = false;
                }
                }
            });
        }
    }
}

照片捕获成功时候的处理

private final class MyPictureCallback implements PictureCallback {
        @Override
        public void onPictureTaken(byte[] data, Camera camera) {
            try {
                BitmapFactory.Options options = new BitmapFactory.Options();
                // options.inSampleSize = 2;
                if (data != null) {
                    if (data.length / 1024 > 350
                            && data.length / 1024 < 450) {
                        options.inSampleSize = 3;
                    } else if (data.length / 1024 > 450) {
                        options.inSampleSize = 4;
                    }
                    datas = data;//字节流(保存图片时候使用)
                }
                options.inPurgeable = true;//内存不足时可被回收
                options.inInputShareable = true;
                mBitmap = BitmapFactory.decodeByteArray(data, 0, data.length,options);//此bitmap作为预览时候使用,用完一定注意回收
                BitmapDrawable bitmapDrawable = new BitmapDrawable(getResources(), mBitmap);
                img_background.setBackgroundDrawable(bitmapDrawable);
                img_background.setVisibility(View.VISIBLE);
                //以下全是控件状态的变化
                //surfaceview.setVisibility(View.GONE);
                //bt_remake.setVisibility(View.VISIBLE);
                //bt_photograph.setVisibility(View.VISIBLE);
                //photograph_btn.setVisibility(View.GONE);
                //lin_takephoto.setVisibility(View.GONE);
                //btn_cancle.setVisibility(View.GONE);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                is_takingPhoto = false;
            }
        }
    }

保存图片返回本地地址url

public String savephoto(String path, String name, byte[] imagedata) {
        String msgPath = path  + name;
        try {
            //判断目录是否可用
            if (!Environment.getExternalStorageState().equals(
                    Environment.MEDIA_MOUNTED)) {
                return null;
            }
            File file = new File(path);//创建文件
            if (!file.exists()) {
                file.mkdirs();
            }
//          file.createNewFile();
            BitmapFactory.Options options = new BitmapFactory.Options();
            options.inSampleSize = 4;
            options.inPurgeable = true;
            options.inInputShareable = true;
            Bitmap bitmap = BitmapFactory.decodeByteArray(imagedata, 0,imagedata.length,options);
            FileOutputStream fi = new FileOutputStream(path +  name);
            bitmap.compress(Bitmap.CompressFormat.JPEG, 90, fi);//压缩质量
            fi.close();
            fi.flush();
            bitmap.recycle();//bitmap释放
            bitmap = null;
        } catch (Exception e) {
            e.printStackTrace();
            msgPath = "error";//保存出错
        }
        return msgPath;
    }

保存图片调用

bt_photograph.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                String path = ImageUtils.getInstance().savephoto(filePath,fileName, datas);
                is_takingPhoto = false;
                if (path.equals("error") || path == null) {
                    Toast.makeText(PersonPhotoActivity.this, "图片保存失败,请重新拍照!", 0).show();
                    Reset();//保存失败重置相机
                } else {
                    img_focus.setImageResource(R.drawable.focus1);//对焦图片设置
                    img_focus.setVisibility(View.VISIBLE);
                    Intent intent = new Intent();
                    if (degree == -1) {
                        Log.d("toast", "未捕获旋转角度");
                    } else {
                        intent.putExtra("degree", degree);
                    }
                    intent.putExtra("url", path);
                    setResult(10, intent);//成功
                    PersonPhotoActivity.this.finish();
                }
            }
        });

相机重置

重置状态,重置bitmap ,重置图标

private void reset() {
        img_focus.setImageResource(R.drawable.focus1);
        img_focus.setVisibility(View.VISIBLE);
        img_background.setBackgroundDrawable(null);
        img_background.setVisibility(View.GONE);
        surfaceview.setVisibility(View.VISIBLE);//预览关闭
        bt_remake.setVisibility(View.GONE);
        bt_photograph.setVisibility(View.GONE);
        photograph_btn.setVisibility(View.VISIBLE);
        lin_takephoto.setVisibility(View.VISIBLE);
        btn_cancle.setVisibility(View.VISIBLE);
            if(mBitmap != null){
            mBitmap.recycle();
            mBitmap = null;
        }
        if(datas != null ){
            datas = null;
        }
        if (null != mCamera) {
            mCamera.startPreview();//重置相机
        } else {
            startCamera();
        }
    }

其他功能

拍照声音的实现

private ShutterCallback shutterCallback = new ShutterCallback() {
    public void onShutter() {
        if (tone == null)
            // 发出提示用户的声音
            tone = new ToneGenerator(
                        AudioManager.AUDIOFOCUS_REQUEST_GRANTED,
                        ToneGenerator.MIN_VOLUME);
            tone.startTone(ToneGenerator.TONE_PROP_BEEP);
        }
    };

开关闪光灯
相机闪光灯是通过parameter来配置。

private void turnLightOn() {
    if (mCamera == null) {
        return;
    }
    Parameters parameters = mCamera.getParameters();
    if (parameters == null) {
        return;
    }
    List<String> flashModes = parameters.getSupportedFlashModes();
    // 是否支持闪光灯
    if (flashModes == null) {
        return;
    }
    String flashMode = parameters.getFlashMode();
    if (!Parameters.FLASH_MODE_TORCH.equals(flashMode)) {
    // 开启闪光灯
    if (flashModes.contains(Parameters.FLASH_MODE_TORCH)) {
        parameters.setFlashMode(Parameters.FLASH_MODE_TORCH);
        mCamera.setParameters(parameters);//为相机开启闪光灯
        } 
    }
}

关闭闪光灯,跟开启闪光灯判断一样,只是配置不同,关键代码

//支持关闭闪光灯模式
if (!Parameters.FLASH_MODE_OFF.equals(flashMode)) {
    if (flashModes.contains(Parameters.FLASH_MODE_OFF)) {
        parameters.setFlashMode(Parameters.FLASH_MODE_OFF);
        mCamera.setParameters(parameters);
    } 
}

捕获屏幕旋转角度

private Handler sensor_handler= new Handler() {
        @Override
        public void handleMessage(android.os.Message msg) {
            if (msg.what == 888) {
                int orientation = msg.arg1;
                if (orientation > 45 && orientation < 135) {
                    degree = 180;
                    Log.e("180", "右横");
                } else if (orientation > 135 && orientation < 225) {
                    degree = 270;
                    Log.e("270", "倒屏");
                } else if (orientation > 225 && orientation < 315) {
                    degree = 0;
                    Log.e("0", "左横");
                } else if ((orientation > 315 && orientation < 360)
                        || (orientation > 0 && orientation < 45)) {
                    degree = 90;
                    Log.e("90", "竖屏");
                }
            }
            super.handleMessage(msg);
        }

重力感应改变的监听(谷歌提供)

public synchronized void onSensorChanged(SensorEvent event) {
    float[] values = event.values;
    int orientation = ORIENTATION_UNKNOWN;
    float X = -values[_DATA_X];
    float Y = -values[_DATA_Y];
    float Z = -values[_DATA_Z];        
    float magnitude = X*X + Y*Y;
    // Don't trust the angle if the magnitude is small compared to the y value
    if (magnitude * 4 >= Z*Z) {
        float OneEightyOverPi = 57.29577957855f;
        float angle = (float)Math.atan2(-Y, X) * OneEightyOverPi;
        orientation = 90 - (int)Math.round(angle);
        // normalize to 0 - 359 range
        while (orientation >= 360) {
            orientation -= 360;
        } 
        while (orientation < 0) {
            orientation += 360;
        }
    }
    if (rotateHandler!=null) {
        rotateHandler.obtainMessage(888, orientation, 0).sendToTarget();//发送消息通知改变
    }
}

类的销毁

@Override
protected void onDestroy() {
    if (mBitmap != null) {
        mBitmap.recycle();
        mBitmap = null;
    }
    if(datas != null){
        datas = null;
    }
    super.onDestroy();
}

小结

1.为什么保存要压缩图片?
因为每个型号手机相机配置不一样,相机非常好的手机拍摄一张图片有可能是6-12m,如果不做处理直接放到bitmap中,展示的时候就会内存溢出,可以根据自己需要,改变压缩保存图片的方法。
2.为什么要释放bitmap?
相机启动本身就是一件耗费资源的事情,如果bitmap一直存在activity中不释放,当activity销毁的时候内存回收机制不会立即执行,可能导致内存溢出。
3.为何要加入重力感应?
相机是有角度的,不监听角度改变,拍摄的照片就会按照相机拍摄角度显示,有感应之后放置照片按角度放置就可以。

欢迎指正,共同探讨

  • 1
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值