Android自定义拍照实现

本文介绍了如何实现一套自定义的Android相机功能,包括预览布局、点触聚焦和自定义相机布局。通过抽离预览图层、设置相机参数、实现聚焦回调以及创建自定义相机布局,确保相机功能的易用性和可扩展性。文中还提到针对不同设备的屏幕比例和相机尺寸的适配策略。

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

前言

由于网上大部分自定义相机的实现,都是耦合性比较强的,不方便今后的复用,所以我自己实现了一套自定义相机,方便以后的扩展。自定义相机分为以下3个部分。文末有免费福利哦

  • 相机的预览布局SurfaceView ,方便用户实时预览。写成自定义控件,方便今后的复用。
  • 相机的自动聚焦以及点触聚焦,拍照需要聚焦,要不然拍出的图片很可能是模糊的。写成自定义控件,方便今后的复用。
  • 相机的自定义布局,这部分随着需求的迭代变换,前面的2大块不需要改动。Venn Diagram 11.png

一、预览布局的实现

(1) 抽离预览图层为一个单独的自定义控件CameraPreview ,传递Camre对象,设置必要的相机预览参数。

package com.focustech.xyz.baselibrary.camera;

import android.app.Activity;
import android.content.Context;
import android.graphics.ImageFormat;
import android.hardware.Camera;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

import com.focustech.xyz.baselibrary.common.XyzLogger;

import java.io.IOException;
import java.util.SortedSet;

/**
 *
 */
public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback {
    private SurfaceHolder mHolder;
    private Camera mCamera;
    private boolean isPreview;
    private Context context;
    /**
     * 预览尺寸集合
     */
    private final SizeMap mPreviewSizes = new SizeMap();
    /**
     * 图片尺寸集合
     */
    private final SizeMap mPictureSizes = new SizeMap();
    /**
     * 屏幕旋转显示角度
     */
    private int mDisplayOrientation;
    /**
     * 设备屏宽比
     */
    private AspectRatio mAspectRatio;

    /**
     *
     * @param context
     * @param mCamera
     */
    public CameraPreview(Context context, Camera mCamera) {
        super(context);
        this.context = context;
        this.mCamera = mCamera;
        this.mHolder = getHolder();
        this.mHolder.addCallback(this);
        mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
        mDisplayOrientation = ((Activity) context).getWindowManager().getDefaultDisplay().getRotation();
        mAspectRatio = AspectRatio.of(16, 9);
    }


    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        try {
            //设置设备高宽比
            mAspectRatio = getDeviceAspectRatio((Activity) context);
            //设置预览方向
            mCamera.setDisplayOrientation(90);
            Camera.Parameters parameters = mCamera.getParameters();
            //获取所有支持的预览尺寸
            mPreviewSizes.clear();
            for (Camera.Size size : parameters.getSupportedPreviewSizes()) {
                mPreviewSizes.add(new Size(size.width, size.height));
            }
            //获取所有支持的图片尺寸
            mPictureSizes.clear();
            for (Camera.Size size : parameters.getSupportedPictureSizes()) {
                mPictureSizes.add(new Size(size.width, size.height));
            }
            Size previewSize = chooseOptimalSize(mPreviewSizes.sizes(mAspectRatio));
            Size pictureSize = mPictureSizes.sizes(mAspectRatio).last();
            //设置相机参数
            parameters.setPreviewSize(previewSize.getWidth(), previewSize.getHeight());
            parameters.setPictureSize(pictureSize.getWidth(), pictureSize.getHeight());
            parameters.setPictureFormat(ImageFormat.JPEG);
            parameters.setRotation(90);
            mCamera.setParameters(parameters);
            //把这个预览效果展示在SurfaceView上面
            mCamera.setPreviewDisplay(holder);
            //开启预览效果
            mCamera.startPreview();
            isPreview = true;
        } catch (IOException e) {
            XyzLogger.e("CameraPreview", "相机预览错误: " + e.getMessage());
        }
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
        if (holder.getSurface() == null) {
            return;
        }
        //停止预览效果
        mCamera.stopPreview();
        //重新设置预览效果
        try {
            mCamera.setPreviewDisplay(mHolder);
        } catch (IOException e) {
            e.printStackTrace();
        }
        mCamera.startPreview();
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        if (mCamera != null) {
            if (isPreview) {
                //正在预览
                mCamera.stopPreview();
                mCamera.release();
            }
        }
    }


    /**
     * 注释:获取设备屏宽比
     * 时间:2019/3/4 0004 12:55
     * 作者:郭翰林
     */
    private AspectRatio getDeviceAspectRatio(Activity activity) {
        int width = activity.getWindow().getDecorView().getWidth();
        int height = activity.getWindow().getDecorView().getHeight();
        return AspectRatio.of(height, width);
    }

    /**
     * 注释:选择合适的预览尺寸
     * 时间:2019/3/4 0004 11:25
     * 作者:郭翰林
     *
     * @param sizes
     * @return
     */
    @SuppressWarnings("SuspiciousNameCombination")
    private Size chooseOptimalSize(SortedSet<Size> sizes) {
        int desiredWidth;
        int desiredHeight;
        final int surfaceWidth = getWidth();
        final int surfaceHeight = getHeight();
        if (isLandscape(mDisplayOrientation)) {
            desiredWidth = surfaceHeight;
            desiredHeight = surfaceWidth;
        } else {
            desiredWidth = surfaceWidth;
            desiredHeight = surfaceHeight;
        }
        Size result = null;
        for (Size size : sizes) {
            if (desiredWidth <= size.getWidth() && desiredHeight <= size.getHeight()) {
                return size;
            }
            result = size;
        }
        return result;
    }

    /**
     * Test if the supplied orientation is in landscape.
     *
     * @param orientationDegrees Orientation in degrees (0,90,180,270)
     * @return True if in landscape, false if portrait
     */
    private boolean isLandscape(int orientationDegrees) {
        return (orientationDegrees == 90 ||
                orientationDegrees == 270);
    }
}

复制代码

这里有2处地方需要注意,相机要设置正确的预览尺寸和正确的图片的尺寸。如果预览尺寸设置错误,则预览布局会被拉伸或者收缩。如果图片尺寸设置错误,部分机型会导致闪退或者拍出的照片很不清晰。

这里适配预览尺寸和图片尺寸,是根据设备的屏宽比和Carme拿到的说支持的预览尺寸和图片尺寸计算得出应有的预览尺寸和图片尺寸,代码如下。文末有免费福利哦

//设置设备高宽比
mAspectRatio = getDeviceAspectRatio((Activity) context);.

Camera.Parameters parameters = mCamera.getParameters();
//获取所有支持的预览尺寸
mPreviewSizes.clear();
for (Camera.Size size : parameters.getSupportedPreviewSizes()) {
    mPreviewSizes.add(new Size(size.width, size.height));
}
//获取所有支持的图片尺寸
mPictureSizes.clear();
for (Camera.Size size : parameters.getSupportedPictureSizes()) {
    mPictureSizes.add(new Size(size.width, size.height));
}
Size previewSize = chooseOptimalSize(mPreviewSizes.sizes(mAspectRatio));
Size pictureSize = mPictureSizes.sizes(mAspectRatio).last();

//设置相机参数
parameters.setPreviewSize(previewSize.getWidth(), previewSize.getHeight());
parameters.setPictureSize(pictureSize.getWidth(), pictureSize.getHeight());
复制代码
/**
 * 
 */
private AspectRatio getDeviceAspectRatio(Activity activity) {
    int width = activity.getWindow().getDecorView().getWidth();
    int height = activity.getWindow().getDecorView().getHeight();
    return AspectRatio.of(height, width);
}
复制代码
/**
 * 
 *
 * @param sizes
 * @return
 */
@SuppressWarnings("SuspiciousNameCombination")
private Size chooseOptimalSize(SortedSet<Size> sizes) {
    int desiredWidth;
    int desiredHeight;
    final int surfaceWidth = getWidth();
    final int surfaceHeight = getHeight();
    if (isLandscape(mDisplayOrientation)) {
        desiredWidth = surfaceHeight;
        desiredHeight = surfaceWidth;
    } else {
        desiredWidth = surfaceWidth;
        desiredHeight = surfaceHeight;
    }
    Size result = null;
    for (Size size : sizes) {
        if (desiredWidth <= size.getWidth() && desiredHeight <= size.getHeight()) {
            return size;
        }
        result = size;
    }
    return result;
}
复制代码

(2)屏宽比AspectRatio的实现

package com.focustech.xyz.baselibrary.camera;

import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.NonNull;
import android.support.v4.util.SparseArrayCompat;

/**
 * 
 */
public class AspectRatio implements Comparable<AspectRatio>, Parcelable {
    private final static SparseArrayCompat<SparseArrayCompat<AspectRatio>> sCache
            = new SparseArrayCompat<>(16);

    private final int mX;
    private final int mY;

    /**
     *
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值