在Api21中Camera类被废弃,取而代之的是camera2包。相对来说,camera2比Camera使用起来看似复杂了好多,但是在灵活性方面增加了很多。不过出于兼容性的考虑,加上当前Android设备5.0以下的占比还是比较大的,所以在相机开发的过程中,Camera类还是不得不掌握的。在本文中,讲解的是Camera的基本使用。而关于camera2的使用,在以后的文章中会单独再讲。

拍照步骤

  1. 添加相机相关权限
  2. 通过Camera.open(int)获得一个相机实例
  3. 利用camera.getParameters()得到相机实例的默认设置Camera.Parameters
  4. 如果需要的话,修改Camera.Parameters并调用camera.setParameters(Camera.Parameters)来修改相机设置
  5. 调用camera.setDisplayOrientation(int)来设置正确的预览方向
  6. 调用camera.setPreviewDisplay(SurfaceHolder)来设置预览,如果没有这一步,相机是无法开始预览的
  7. 调用camera.startPreview()来开启预览,对于拍照,这一步是必须的
  8. 在需要的时候调用camera.takePicture(Camera.ShutterCallback, Camera.PictureCallback, Camera.PictureCallback, Camera.PictureCallback)来拍摄照片
  9. 拍摄照片后,相机会停止预览,如果需要再次拍摄多张照片,需要再次调用camera.startPreview()来重新开始预览
  10. 调用camera.stopPreview()来停止预览
  11. 一定要在onPause()的时候调用camera.release()来释放camera,在onResume中重新开始camera

相机权限

使用相机进行拍照,需要添加以下权限:

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

拍摄的照片需要存储到内存卡的话,还需要内存卡读写的权限:

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

拍照注意事项

根据拍照步骤就可以使用相机进行拍照,但是在使用过程中,有诸多需要注意的地方。

开启相机

开启相机直接调用Camera.open(),理论上就可以得到一个相机实例。但是在开启相机前,最好check一下。虽然目前基本上所有的手机都是前后摄像头,但是也不排斥有些奇葩的手机,只有后摄像头,或者干脆没有摄像头的。所有在open一个camera前,先检查是否存在该id的camera。Camera.getNumberOfCameras()可以获得当前设备的Camera的个数N,N为0表示不支持摄像头,否则对应的Camera的ID就是0—(N-1)。

相机设置

Camera的Parameters提供了诸多属性,可以对相机进行多种设置,包括预览大小及格式、照片大小及格式、预览频率、拍摄场景、颜色效果、对焦方式等等,具体设置可参考官方手册。
拍照尤其需要注意的是对预览大小、照片大小以及对焦方式的设置。
在对预览大小、照片大小及对焦方式设置时,设置的值必须是当前设备所支持的。否则,预览大小和照片大小设置会无效,对焦方式设置会导致崩溃。它们都有相应的方法,获取相应的支持的列表。对应的依次为getSupportedPictureSizes()getSupportedPictureSizes()getSupportedFocusModes()

相机预览方向

不对相机预览方向和应用方向设置,通常情况下得到的预览结果是无法接受的。一般应用设置的方向为固定竖向,预览设置旋转90度即可。严谨点来说,预览方向的设置是根据当前window的rotation来设置的,即((WindowManager)displayView.getContext().getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay().getRotation()的值。在Surface.ROTATION_0Surface.ROTATION_180时,Camera设置displayOrientation为90,否则设置为0。

预览View的设置

相机预览前,必须调用camera.setPreviewDisplay(SurfaceHolder)来设置预览的承载。SurfaceHolder一般取自SurfaceView、SurfaceTexture、TextureView等。一般情况下,如果不对显示的View大小做合理的设置,预览中的场景都会被变形。
如何保证预览不变形呢?预览效果变形是因为设置的previewSize和预览View的长宽比例不同造成的,将预览View的长宽比设置的与Camera的previewSize相同(需要注意的是,竖屏下是预览View的长宽比,要设置的与Camera的previewSize的宽长比相同)即可解决变形的问题。
以全屏预览为例,假如手机屏幕分辨率为1280*720,相机支持的预览大小没有1280*720的,也没有这个比例的,但是有1280*960的,相机预览大小选的也是这个。这时候,将预览View的大小设置为1280*960即可,超出屏幕的部分不予理会。值得注意的是,如果预览View的父布局是RelativeLayout,设置宽度大于父布局是无效的。可以重写RelativeLayout的onMeasure来实现,或者将父布局改为FrameLayout。

拍照监听及图片处理

相机拍照时在预览时,调用takePicture(Camera.ShutterCallback, Camera.PictureCallback, Camera.PictureCallback, Camera.PictureCallback)(或其重载方法)来实现拍照的,其中第一个参数表示图像捕获时刻的回调。可以看到此方法的后三个参数类型是一样的,都是图像回调。分别表示原始图数据回调、展示图像数据的回调、JPEG图像数据的回调。图像回调中得到byte数组decode为image后,图片的方向都是摄像头原始的图像方向。
可以通过parameters.setRotation(int)来改变最后一个回调中图像数据的方向。个人推荐不设置,直接在回调中利用矩阵变换统一处理。因为利用parameters.setRotation(int)来旋转图像,在不同手机上会有差异。
与预览View设置类似,pictureSize设置的值,影响了最后的拍照结果,处理时需要对拍照的结果进行裁剪,使图片结果和在可视区域预览的结果相同。前摄像头拍摄的结果还需要做对称变换,以保证“所见即所得”。

拍照示例

首先需要一个相机的控制类CameraKitKat,继承自ACamera,ACamera是一个抽象类,具有open(int),close()方法,控制相机的开启和关闭。这样做主要是为了后面扩展使用Camera2。CameraKitKat源码如下:

public class CameraKitKat extends ACamera{    private Camera camera;    private SurfaceHolder holder;    private float displayScale;    public CameraKitKat(SurfaceView surfaceView) {        super(surfaceView);        init();    }    private void init(){        holder=displayView.getHolder();    }    @Override    public void open(int type){        int rotation=((WindowManager)displayView.getContext().getSystemService(Context.WINDOW_SERVICE))                .getDefaultDisplay().getRotation();        if(!openCamera(type))return;        setParameters(camera,rotation);        setDisplayOrientation(camera,rotation);        setPreviewDisplay(camera,holder);        camera.startPreview();    }    @Override    public void close(){        camera.stopPreview();        camera.release();    }    //调整SurfaceView的大小    private void resizeDisplayView(){        Camera.Parameters parameters=camera.getParameters();        Camera.Size size=parameters.getPreviewSize();        FrameLayout.LayoutParams p= (FrameLayout.LayoutParams) displayView.getLayoutParams();        float scale=size.width/(float)size.height;        displayScale=displayView.getHeight()/(float)displayView.getWidth();        if(scale>displayScale){            p.height= (int) (scale*displayView.getWidth());            p.width=displayView.getWidth();        }else{            p.width= (int) (displayView.getHeight()/scale);            p.height=displayView.getHeight();        }        Log.e("wuwang","-->"+size.width+"/"+size.height);        Log.e("wuwang","--<"+p.height+"/"+p.width);        displayView.setLayoutParams(p);        displayView.invalidate();    }    private boolean checkCameraId(int cameraId){        return cameraId>=0&&cameraId//相机使用第一步,打开相机,获得相机实例    private boolean openCamera(int cameraId){        if(!checkCameraId(cameraId))return false;        camera=Camera.open(cameraId);        return true;    }    //相机使用第二步,设置相机实例参数    //TODO :里面还存在问题,需要修改    private void setParameters(Camera camera,int rotation){        Camera.Parameters parameters=camera.getParameters();        //PreviewSize设置为设备支持的最高分辨率        final Camera.Size size=Collections.max(camera.getParameters().getSupportedPreviewSizes(),new Comparator() {            @Override            public int compare(Camera.Size lhs, Camera.Size rhs) {                return lhs.width*lhs.height-rhs.width*rhs.height;            }        });        parameters.setPreviewSize(size.width,size.height);        //PictureSize设置为和预览大小最近的        Camera.Size picSize=Collections.max(parameters.getSupportedPictureSizes(), new Comparator() {            @Override            public int compare(Camera.Size lhs, Camera.Size rhs) {                return (int) (Math.sqrt(Math.pow(size.width-rhs.width,2)+Math.pow(size.height-rhs.height,2))-                        Math.sqrt(Math.pow(size.width-lhs.width,2)+Math.pow(size.height-lhs.height,2)));            }        });        parameters.setPictureSize(picSize.width,picSize.height);        //如果相机支持自动聚焦,则设置相机自动聚焦,否则不设置        if(parameters.getSupportedFocusModes().contains(Camera.Parameters.FOCUS_MODE_AUTO)){            parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);        }        //设置颜色效果//        parameters.setColorEffect(Camera.Parameters.EFFECT_MONO);        camera.setParameters(parameters);        resizeDisplayView();    }    //相机使用第三步,设置相机预览方向    private void setDisplayOrientation(Camera camera,int rotation){        if(rotation== Surface.ROTATION_0||rotation==Surface.ROTATION_180){            camera.setDisplayOrientation(90);        }else{            camera.setDisplayOrientation(0);        }    }    //相机使用第四步,设置相机预览载体SurfaceHolder    private void setPreviewDisplay(Camera camera,SurfaceHolder holder){        try {            camera.setPreviewDisplay(holder);        } catch (IOException e) {            e.printStackTrace();        }    }    @Override    public void measureSize(int width, int height) {        super.measureSize(width, height);    }    @Override    public void takePicture() {        super.takePicture();        camera.takePicture(new Camera.ShutterCallback() {            @Override            public void onShutter() {            }        }, new Camera.PictureCallback() {            @Override            public void onPictureTaken(byte[] data, Camera camera) {            }        }, new Camera.PictureCallback() {            @Override            public void onPictureTaken(byte[] data, Camera camera) {                if(pictureCallback!=null){                    pictureCallback.onPictureTaken(data,displayScale);                }            }        });    }}

本例中,使用SurfaceView作为预览View,提供SurfaceHolder给Camera。考虑到预览变形问题,使用FrameView作为其父布局。CameraPreview源码如下:

public class CameraPreview extends FrameLayout implements SurfaceHolder.Callback{    private SurfaceView surfaceView;    private ACamera camera;    private boolean isCameraBack=false;    public CameraPreview(Context context) {        this(context,null);    }    public CameraPreview(Context context, AttributeSet attrs) {        this(context, attrs,0);    }    public CameraPreview(Context context, AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);        init();    }    private void init(){        addPreview();        //后续增加CameraLollipop,根据系统版本使用Camera或者Camera2        camera=new CameraKitKat(surfaceView);        setKeepScreenOn(true);    }    @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        camera.measureSize(MeasureSpec.getSize(widthMeasureSpec),MeasureSpec.getSize(heightMeasureSpec));        super.onMeasure(widthMeasureSpec, heightMeasureSpec);    }    private void addPreview(){        surfaceView=new SurfaceView(getContext());        surfaceView.getHolder().addCallback(this);        this.addView(surfaceView);    }    @Override    public void surfaceCreated(SurfaceHolder holder) {        camera.open(isCameraBack?0:1);    }    @Override    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {    }    @Override    public void surfaceDestroyed(SurfaceHolder holder) {        setKeepScreenOn(false);        camera.close();    }    public boolean isCameraBack(){        return isCameraBack;    }    public void setOnPictureCallback(PictureCallback pictureCallback){        camera.setOnPictureCallback(pictureCallback);    }    public void takePicture(){        camera.takePicture();    }}

在拍照的Activity中,置入CameraPreview,长宽都设置为match_parent,然后增加一个拍照的按钮。增加拍照监听,并在监听中对图像数据进行处理即可。Activity的源码如下:

public class MainActivity extends Activity implements View.OnClickListener{    private View btnTake;    private ImageView ivShower;    private CameraPreview cameraPreview;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        findViewById(R.id.btnTake).setOnClickListener(this);        ivShower= (ImageView) findViewById(R.id.ivShower);        cameraPreview= (CameraPreview) findViewById(R.id.cameraView);        cameraPreview.setOnPictureCallback(new PictureCallback() {            @Override            public void onPictureTaken(byte[] data,float scale) {                ivShower.setVisibility(View.VISIBLE);                Bitmap bitmap=BitmapFactory.decodeByteArray(data,0,data.length);                Bitmap bitmap2=rotateAndCropBitmap(bitmap,cameraPreview.isCameraBack()?90:-90,scale);                saveBitmapToPath(bitmap2,Environment.getExternalStorageDirectory()+"/temp.jpeg");                ivShower.setImageBitmap(bitmap2);                bitmap.recycle();            }        });    }    @Override    protected void onDestroy() {        super.onDestroy();        File file=new File(Environment.getExternalStorageDirectory()+"/temp.jpeg");        if(file.exists()){            file.delete();        }    }    @Override    public void onClick(View v) {        switch (v.getId()){            case R.id.btnTake:                cameraPreview.takePicture();                break;            default:                break;        }    }    private Bitmap rotateAndCropBitmap(Bitmap bm,int orientationDegree,float rate){        //TODO : 貌似有些问题        int width,height;        float bmScale=bm.getHeight()/(float)bm.getWidth();        if(rate==bmScale)return bm;        else if(rate>bmScale){            width=bm.getWidth();            height= (int) (width/rate);        }else{            height= bm.getHeight();            width= (int) (height*rate);        }        Matrix m = new Matrix();        if(orientationDegree==-90){ //前摄像头,则左右镜像            m.postScale(1,-1);        }        m.postRotate(orientationDegree);        return Bitmap.createBitmap(bm,0,bm.getHeight()-height,width,height,m,true);    }    private void saveBitmapToPath(Bitmap bitmap,String path){        File file=new File(path);        try {            FileOutputStream fos=new FileOutputStream(file);            bitmap.compress(Bitmap.CompressFormat.JPEG,90,fos);            fos.flush();            fos.close();            Log.e("wuwang","filePath-->"+file.getAbsolutePath());        } catch (IOException e) {            e.printStackTrace();        }    }}

更多相关文章

  1. DEPHI XE5 XE6 ANDROID IOS开发的几点体会
  2. 配置android的测试环境
  3. Android(安卓)中的字体大小适配
  4. android使用磁场传感器和加速度传感器确定当前朝向(即:方位角),以及
  5. android 获取本地缓存文件大小,删除功能
  6. Android图标尺寸的约定
  7. Android所有系统资源图标android.R.drawable.xxx查看(纯java)
  8. Android开发实践:屏幕旋转的处理
  9. Android相机开发实战

随机推荐

  1. Android PinnedHeaderListView 详解
  2. Android 状态栏通知Notification(转载)
  3. padding和margin的区别及其属性
  4. [Android][设置最小(大)宽高]
  5. Android(安卓)Activity 和 Task 设计指导
  6. Android中的人脸检测(静态和动态)
  7. Android: 如何创建AVD以及选择合适target
  8. Android(安卓)AIDL实现调用第三方登录
  9. Android 的属性系统
  10. 专家专栏:Android层次化安全架构及核心组