Android Camera 从0到1

本文详细介绍了如何自定义预览相机的SurfaceView,实现与多种手机屏幕尺寸的适配,避免图像拉伸变形。主要内容包括:SurfaceView与Camera的关联,预览尺寸与图片尺寸的选择策略,以及如何根据屏幕方向和尺寸选择最合适的预览尺寸。

1. 自定义预览相机SurfaceView
public class PreviewView extends SurfaceView implements SurfaceHolder.Callback {

    private static final String TAG = "PreviewView";

    // 获得surfaceHolder引用
    private SurfaceHolder holder;

    public PreviewView(Context context) {
        this(context, null);
    }

    public PreviewView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public PreviewView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        getScreenMetric(context);
        initView();
    }

    private void initView() {
        holder = getHolder();
        holder.addCallback(this);
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        Log.i(TAG, "surfaceCreated this: " + Thread.currentThread().getName());
        try {
            // holder绑定相机预览
            initCamera(holder);
        } catch (Exception e) {
            Log.i(TAG, e.getMessage());
        }
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
        Log.i(TAG, "surfaceChanged");
        if (mCamera != null) {
            mCamera.startPreview();
        }
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        Log.i(TAG, "surfaceDestroyed");
        // 停止预览
        mCamera.stopPreview();
        // 释放相机资源
        mCamera.release();
        mCamera = null;
        holder = null;
    }
}
2. Camera关联SurfaceView的SurfaceHolder
public void initCamera(SurfaceHolder holder) {
        // 遍历手机摄像头,默认设置为前置摄像头
        for (int i = 0; i < Camera.getNumberOfCameras(); i++) {
            // 得到摄像头的个数
            Camera.CameraInfo info = new Camera.CameraInfo();
            Camera.getCameraInfo(i, info);
            if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
                mCamera = Camera.open(i);
            }
        }
        Log.e(TAG, cameraId + "");

        if (mCamera == null) {
            mCamera = Camera.open();
        }

        try {
            // 摄像头画面显示在Surface上
            mCamera.setPreviewDisplay(holder);
        } catch (IOException e) {
            e.printStackTrace();
        }

        //设置参数,并拍照
        setCameraParams(mCamera, mScreenWidth, mScreenHeight);
    }
3. 预览尺寸(PreviewSize)

相机预览尺寸不能随意设置,贴出下面代码测试打印SurfaceView

 private void setCameraParams(Camera camera, int width, int height) {
	Camera.Parameters parameters = mCamera.getParameters();
    // 获取摄像头支持的PreviewSize列表
	List<Camera.Size> previewSizeList = parameters.getSupportedPreviewSizes();
	// 打印所有支持的预览分辨率
	for (Camera.Size size : previewSizeList) {
		Log.i(TAG, "previewSizeList size.width=" + size.width + "  size.height=" + size.height);
 	}
 	...
 	// 根据使用相机支持的预览列表,选取一个最合适的预览尺寸,这样相机预览就不会压缩变形
 	camera.setParameters(parameters);
}	  

只能通过Camera的getSupportedPreviewSizes 方法,获取支持的预览尺寸列表,并从列表中选择一个设置在parameters中,并且与展示的SurfaceView等比例,图像才不会显示变形


// 设置SurfaceView大小
PreviewView mPreviewView;
......
RelativeLayout.LayoutParams svParams = (RelativeLayout.LayoutParams) mPreviewView.getLayoutParams();
mPreviewView.height = camera.getParameters().getPreviewSize().width;
mPreviewView.width = camera.getParameters().getPreviewSize().height;
// 注意:surfaceview在重新设置宽高后,会调用到surfaceChanged方法 
mPreviewView.setLayoutParams(svParams);

4. 图片尺寸(PictureSize)
	 Camera.Parameters parameters = mCamera.getParameters();
	 // 获取摄像头支持的PictureSize列表
	 List<Camera.Size> pictureSizeList = parameters.getSupportedPictureSizes();
	 for (Camera.Size size : pictureSizeList) {
		 Log.i(TAG, "preview width: " + size.width + "  height: " + size.height);
	 }

图片尺寸同样只能从支持的列表中选取一个设置。 设置后,调用Camera的takePicture方法获得拍照的图像数据大小就是设置大小

注意PictureSize和PreviewSize的宽高比也要保证一致,否则获取的图片会将Preview时的图像裁剪成PictureSize的比例

PreviewSize只会影响预览时的分辨率,不会影响获取图片的分辨率,所以Preview只是确定了图像的取景最大范围。最终图片的分辨率是由PreviewSize来决定

5. 设置SurfaceView预览尺寸

怎么样才能够达到适配多台手机,界面不产生拉伸变形

  1. 先将获取手机支持预览的尺寸列表通过方法
// 获取相机支持预览List<Size>
parmeters.getSupportedPreviewSizes()
  1. 先进行屏幕方向的一个判断,因为预览列表里面的尺寸都是w>h(即横屏),如果屏幕是竖屏则需要先将宽高进行调换,这样方便接下来的比较
/**
     * 通过对比得到与宽高比最接近的尺寸(如果有相同尺寸,优先选择)
     * 
     * @param surfaceWidth
     *            需要被进行对比的原宽
     * @param surfaceHeight
     *            需要被进行对比的原高
     * @param preSizeList
     *            需要对比的预览尺寸列表
     * @return 得到与原宽高比例最接近的尺寸
     */
    protected Camera.Size getCloselyPreSize(int surfaceWidth, int surfaceHeight,
            List<Size> preSizeList) {
        
        int ReqTmpWidth;
        int ReqTmpHeight;
        // 当屏幕为垂直的时候需要把宽高值进行调换,保证宽大于高
        if (mIsPortrait) {
            ReqTmpWidth = surfaceHeight;
            ReqTmpHeight = surfaceWidth;
        } else {
            ReqTmpWidth = surfaceWidth;
            ReqTmpHeight = surfaceHeight;
        }
        //先查找preview中是否存在与surfaceview相同宽高的尺寸
        for(Camera.Size size : preSizeList){
            if((size.width == ReqTmpWidth) && (size.height == ReqTmpHeight)){
                return size;
            }
        }
        
        // 得到与传入的宽高比最接近的size
        float reqRatio = ((float) ReqTmpWidth) / ReqTmpHeight;
        float curRatio, deltaRatio;
        float deltaRatioMin = Float.MAX_VALUE;
        Camera.Size retSize = null;
        for (Camera.Size size : preSizeList) {
            curRatio = ((float) size.width) / size.height;
            deltaRatio = Math.abs(reqRatio - curRatio);
            if (deltaRatio < deltaRatioMin) {
                deltaRatioMin = deltaRatio;
                retSize = size;
            }
        }

        return retSize;
    }
  1. 先用for循环将预览尺寸列表每个元素宽高与surfaceview的宽高进行比较,如果存在宽高尺寸都与surfaceview宽高尺寸相同的size则将该宽高设置为预览尺寸
  2. 如果步骤3找不到相同尺寸就得进行该步骤,将尺寸列表的宽高比例和surfaceview的比例作比较,找到一个相同或相近的。(一般来说,只要surfaceview的尺寸和屏幕尺寸相同,就可以找到相同的比例)然后将该尺寸的size设置为预览尺寸
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

初心一点

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值