El uso de la cámara personalizada Camera en Android

En la aplicación, a menudo nos encontramos con la necesidad de crear una función de carga de fotos, pero nuestro enfoque general es ajustar directamente la interfaz de la cámara del sistema o utilizar una interfaz proporcionada por un tercero. ¿Qué debemos hacer si los usuarios tienen requisitos personalizados o tenemos algunas de nuestras propias necesidades? Hoy seguimos los pasos para completar la producción de una cámara personalizada.

API relacionada con la cámara

Usamos principalmente dos clases para tomar fotografías, una es SurfaceView, que presentamos la última vez; la otra es Cámara. Por lo tanto, debemos aprender sobre las API relacionadas con la cámara.

  • getNumberOfCameras: Obtiene el número de cámaras de este dispositivo.
  • abierto: enciende la cámara, la cámara trasera está encendida de forma predeterminada. Si hay varias cámaras, abrir (0) significa abrir la cámara trasera y abrir (1) significa abrir la cámara frontal.
  • getParameters: obtenga los parámetros de la cámara y devuelva el objeto Camera.Parameters.
  • setParameters: establece los parámetros de la cámara para tomar fotografías. Los parámetros específicos de la cámara se establecen llamando a los siguientes métodos de Camera.Parameters.

setPreviewSize

Establecer el tamaño de la interfaz de vista previa
setPictureSize Establece el tamaño de la imagen guardada.
setPictureFormat Configura el formato de la imagen. Generalmente, ImageFormat.JPEG se utiliza para representar el formato JPG.
setFocusMode Establezca el modo de enfoque. El valor Camera.Parameters.FOCUS_MODE_AUTO solo se enfocará una vez; el valor FOCUS_MODE_CONTINUOUS_PICTURE se enfocará continuamente
  • setPreviewDisplay: establece el soporte de superficie de la interfaz de vista previa, es decir, el objeto SurfaceHolder. Este método debe llamarse en el método surfaceCreated de SurfaceHolder.Callback.
  • startPreview: inicia la vista previa. Este método debe llamarse después del método setPreviewDisplay.
  • desbloquear: la cámara debe estar desbloqueada durante la grabación, para que la cámara pueda seguir grabando. Este método debe llamarse después del método startPreview.
  • setDisplayOrientation: establece el ángulo de la vista previa. El 0 grados de Android está a las tres en punto en posición horizontal, mientras que la pantalla del teléfono móvil está en posición vertical y debe girarse 90 grados desde la posición horizontal a la posición vertical.
  • autoFocus: establece el evento de enfoque. El método onAutoFocus de la interfaz de enfoque automático de parámetros AutoFocusCallback se activa cuando se completa el enfoque y se solicita al usuario que tome una fotografía después de que se complete el enfoque.
  • takePicture: comience a tomar una foto y configure eventos relacionados para tomar fotos. El primer parámetro es la interfaz de devolución de llamada del obturador ShutterCallback, su método onShutter se activa cuando se presiona el obturador y el sonido de la foto generalmente se puede reproducir aquí, y el valor predeterminado es "clic"; el segundo parámetro PictureCallback representa la interfaz de devolución de llamada del original. imagen, normalmente No hay necesidad de lidiar con pasar nulo directamente; el tercer parámetro PictureCallback representa la interfaz de devolución de llamada de imágenes JPG, y los datos de la imagen comprimida se pueden obtener en el método onPictureTaken de esta interfaz.
  • setZoomChangeListener: establece el evento de cambio de la relación de zoom. El método onZoomChange del detector de cambios de zoom OnZoomChangeListener se activa cuando cambia la relación de zoom.
  • setPreviewCallback: establece el evento de devolución de llamada de vista previa, generalmente llamado durante el disparo continuo. El método onPreviewFrame de la interfaz de devolución de llamada de vista previa PreviewCallback se activa cuando cambia la imagen de vista previa.
  • stopPreview: detiene la vista previa.
  • bloquear: bloquea la cámara después de grabar. Este método se llama después del método stopPreview.
  • liberación: suelta la cámara. Debido a que la cámara no se puede abrir repetidamente, debe soltar la cámara cada vez que salga de la cámara.

2. El código establece la vista de superficie SurfaceView

Luego vamos paso a paso para darnos cuenta de nuestra función. En primer lugar, tenemos que hacer la vista de superficie.

En primer lugar, nuestro diseño es así.

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <SurfaceView
        android:id="@+id/surfaceView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

    <Button
        android:id="@+id/bt_take_photo"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:text="拍照"/>
</RelativeLayout>

Hay dos elementos en él, uno es nuestra vista de superficie SurfaceView y el otro es nuestro botón de cámara.

A continuación se muestra el código de nuestra actividad.

public class MainActivity extends WaterPermissionActivity<MainModel>
        implements MainCallback, View.OnClickListener {

    private Button bt_take_photo;
    private SurfaceView surfaceView;

    @Override
    protected MainModel getModelImp() {
        return new MainModel(this,this);
    }

    @Override
    protected int getContentLayoutId() {
        return R.layout.activity_main;
    }

    @Override
    protected void initWidget() {
        bt_take_photo = findViewById(R.id.bt_take_photo);
        surfaceView = findViewById(R.id.surfaceView);
        bt_take_photo.setOnClickListener(this);
        //获取表面视图的表面持有者
        SurfaceHolder holder = surfaceView.getHolder();
        //给表面持有者添加表面变更监听器
        holder.addCallback(mSurfaceCallback);
        //去除黑色背景,TRANSLUCENT半透明,TRANSPARENT透明
        holder.setFormat(PixelFormat.TRANSPARENT);
        requestPermission(CAMERA);
    }

    @Override
    protected void doCamera() {
        requestPermission(READ_EXTERNAL_STORAGE);
    }

    @Override
    protected void doSDRead() {
        requestPermission(WRITE_EXTERNAL_STORAGE);
    }

    @Override
    protected void doSDWrite() {
        
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.bt_take_photo:
                //点击拍照

                break;
        }
    }

    /**
     * 表面变更监听器
     */
    private SurfaceHolder.Callback mSurfaceCallback = new SurfaceHolder.Callback() {
        @Override
        public void surfaceCreated(@NonNull SurfaceHolder holder) {

        }

        @Override
        public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, int height) {

        }

        @Override
        public void surfaceDestroyed(@NonNull SurfaceHolder holder) {

        }
    };
}

MainActivity, MainCallback, MainModel son mi estructura de marco MVC, no se preocupan por ellos. Si quieres saber sobre los estudiantes, puedes leer mi blog anterior o dejar un mensaje para preguntarme. El código también contiene el contenido de nuestra solicitud de permisos dinámicos, las reglas antiguas, puede usar las suyas propias.

El código central tiene dos partes, la primera parte

//获取表面视图的表面持有者
SurfaceHolder holder = surfaceView.getHolder();
//给表面持有者添加表面变更监听器
holder.addCallback(mSurfaceCallback);
//去除黑色背景,TRANSLUCENT半透明,TRANSPARENT透明
holder.setFormat(PixelFormat.TRANSPARENT);

La segunda parte es la definición de mSurfaceCallback en la primera parte

private SurfaceHolder.Callback mSurfaceCallback = new SurfaceHolder.Callback() {
        @Override
        public void surfaceCreated(@NonNull SurfaceHolder holder) {

        }

        @Override
        public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, int height) {

        }

        @Override
        public void surfaceDestroyed(@NonNull SurfaceHolder holder) {

        }
    };

No olvide los permisos configurados en el archivo de manifiesto

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

En este punto, nuestra vista de superficie está configurada.

3. Muestra la captura de pantalla de la cámara en SurfaceView.

En primer lugar, presentamos una clase de herramienta para el funcionamiento de la cámara, la usaremos más adelante.

public class CameraUtil {

    private static final Pattern COMMA_PATTERN = Pattern.compile(",");

    public static Point getSize(Context ctx) {
        WindowManager wm = (WindowManager) ctx.getSystemService(Context.WINDOW_SERVICE);
        DisplayMetrics dm = new DisplayMetrics();
        wm.getDefaultDisplay().getMetrics(dm);
        Point size = new Point();
        size.x = dm.widthPixels;
        size.y = dm.heightPixels;
        return size;
    }

    public static Point getCameraSize(Camera.Parameters params, Point screenSize) {
        String previewSizeValueString = params.get("preview-size-values");
        if (previewSizeValueString == null) {
            previewSizeValueString = params.get("preview-size-value");
        }
        Point cameraSize = null;
        if (previewSizeValueString != null) {
            cameraSize = findBestPreviewSizeValue(previewSizeValueString, screenSize);
        }
        if (cameraSize == null) {
            cameraSize = new Point((screenSize.x >> 3) << 3, (screenSize.y >> 3) << 3);
        }
        return cameraSize;
    }

    private static Point findBestPreviewSizeValue(CharSequence previewSizeValueString, Point screenSize) {
        int bestX = 0;
        int bestY = 0;
        int diff = Integer.MAX_VALUE;
        for (String previewSize : COMMA_PATTERN.split(previewSizeValueString)) {
            previewSize = previewSize.trim();
            int dimPosition = previewSize.indexOf('x');
            if (dimPosition < 0) {
                continue;
            }

            int newX;
            int newY;
            try {
                newX = Integer.parseInt(previewSize.substring(0, dimPosition));
                newY = Integer.parseInt(previewSize.substring(dimPosition + 1));
            } catch (NumberFormatException nfe) {
                continue;
            }

            int newDiff = Math.abs((newX - screenSize.x) + (newY - screenSize.y));
            if (newDiff == 0) {
                bestX = newX;
                bestY = newY;
                break;
            } else if (newDiff < diff) {
                bestX = newX;
                bestY = newY;
                diff = newDiff;
            }
        }

        if (bestX > 0 && bestY > 0) {
            return new Point(bestX, bestY);
        }
        return null;
    }
}

Luego agregamos las siguientes variables miembro en MainActivity justo ahora

private Camera mCamera;//声明一个相机对象
private boolean isPreviewing;//是否正在预览
private Point mCameraSize;//相机画面的尺寸
private int mCameraType = 0;//设置前置还是后置 0:后置  1:前置
public static int CAMERA_BEHIND = 0; // 后置摄像头
public static int CAMERA_FRONT = 1; // 前置摄像头

En SurfaceHolder.Callback agregamos el siguiente código

    /**
     * 表面变更监听器
     */
    private SurfaceHolder.Callback mSurfaceCallback = new SurfaceHolder.Callback() {
        @Override
        public void surfaceCreated(@NonNull SurfaceHolder holder) {
            //打开摄像头
            mCamera = Camera.open(mCameraType);
            try {
                //设置相机的预览页面
                mCamera.setPreviewDisplay(holder);
                //获得相机画面的尺寸
                mCameraSize = CameraUtil.getCameraSize(mCamera.getParameters()
                        , CameraUtil.getSize(MainActivity.this));
                //获取相机的参数信息
                Camera.Parameters parameters = mCamera.getParameters();
                //设置预览界面的尺寸
                parameters.setPreviewSize(1920, 1080);
                //设置图片的分辨率
                parameters.setPictureSize(1920, 1080);
                parameters.setJpegQuality(100); // 设置照片质量
                //设置图片的格式
                parameters.setPictureFormat(ImageFormat.JPEG);
                //设置对焦模式为自动对焦。前置摄像头似乎无法自动对焦
                if (mCameraType == CAMERA_BEHIND) {
                    parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
                }
                //设置相机的参数信息
                mCamera.setParameters(parameters);
            } catch (IOException e) {
                e.printStackTrace();
                mCamera.release();//遇到异常要释放相机资源
                mCamera = null;
            }
        }

        @Override
        public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, int height) {
            //设置相机的展示角度
            mCamera.setDisplayOrientation(90);
            //开始预览画面
            mCamera.startPreview();
            isPreviewing = true;
            //开始自动对焦
            mCamera.autoFocus(null);
            //设置相机的预览监听器。注意这里的setPreviewCallback给连拍功能用
//            mCamera.setPreviewCallback(mPr);
        }

        @Override
        public void surfaceDestroyed(@NonNull SurfaceHolder holder) {
            //将预览监听器置空
            mCamera.setPreviewCallback(null);
            //停止预览画面
            mCamera.stopPreview();
            //释放相机资源
            mCamera.release();
            mCamera = null;
        }
    };

De esta forma, encontraremos que SurfaceView muestra nuestra imagen cuando la ejecutamos.

4. Toma fotografías

A continuación, estamos a punto de ingresar a nuestra función clave para tomar fotografías.

El primero es el evento de clic de nuestro botón de cámara. Aquí dejamos que llame a un método

@Override
public void onClick(View v) {
    switch (v.getId()) {
        case R.id.bt_take_photo:
            //点击拍照
            doTakePicture();
            break;
    }
}

La implementación del método de la cámara es la siguiente

private void doTakePicture(){
    if (isPreviewing && mCamera!=null){
        //命令相机拍摄一张照片
        mCamera.takePicture(mShutterCallback,null,mPictureCallback);
    }
}

Aquí se utilizan dos objetos, mShutterCallback es el objeto que implementamos para monitorear presionando el obturador, y mPictureCallback es la devolución de llamada del resultado de nuestra foto.

Implementación de mShutterCallback

private Camera.ShutterCallback mShutterCallback = new Camera.ShutterCallback() {
    @Override
    public void onShutter() {
            
    }
};

Este es un monitoreo después de presionar el obturador, podemos dejar que el programa reproduzca un sonido de clic aquí, o realizar algunas otras operaciones.

Implementación de mPictureCallback

private Camera.PictureCallback mPictureCallback = new Camera.PictureCallback() {
        @Override
        public void onPictureTaken(byte[] data, Camera camera) {
            Bitmap raw = null;
            if (null != data) {
                //原始图像数据data是字节数组,需要将其解析成位图
                raw = BitmapFactory.decodeByteArray(data, 0, data.length);
                //停止预览画面
                mCamera.stopPreview();
                isPreviewing = false;
            }
            //旋转位图
            Bitmap bitmap = getRotateBitmap(raw
                    , (mCameraType == CAMERA_BEHIND) ? 90 : -90);
            //获取本次拍摄的照片保存路径
            List<String> listPath = new ArrayList<>();
            listPath.add("myCamera");
            listPath.add("photos");
            mPhotoPath = PathGetUtil.getLongwayPath(MainActivity.this, listPath);
            File fileDir = new File(mPhotoPath);
            if (!fileDir.exists()) {
                fileDir.mkdirs();
            }
            File filePic = new File(mPhotoPath, "ww" + System.currentTimeMillis() + ".jpg");
            if (!filePic.exists()) {
                try {
                    filePic.createNewFile();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            saveImage(filePic.getPath(), bitmap);
            //保存文件需要时间
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //再次进入预览画面
            mCamera.startPreview();
            isPreviewing = true;
        }
    };

Entre ellos, usamos un método de herramienta para rotar mapas de bits, el código es el siguiente

// 获得旋转角度之后的位图对象
public static Bitmap getRotateBitmap(Bitmap b, float rotateDegree) {
    // 创建操作图片用的矩阵对象
    Matrix matrix = new Matrix();
    // 执行图片的旋转动作
    matrix.postRotate(rotateDegree);
    // 创建并返回旋转后的位图对象
    return Bitmap.createBitmap(b, 0, 0, b.getWidth(),b.getHeight(), matrix, false);
}

En la variable miembro, agregamos una ruta de almacenamiento de fotos

private String mPhotoPath;//照片的保存路径

Métodos relacionados con el mapa de bits de funcionamiento

    public static void saveImage(String path, Bitmap bitmap){
        try {
            BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(path));
            bitmap.compress(Bitmap.CompressFormat.JPEG,80,bos);
            bos.flush();
            bos.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

De esta forma, se completa una función de cámara personalizada.

PD: Para los teléfonos móviles actuales, se puede decir que la cámara está desactualizada. Utilizo la prueba del teléfono móvil Android 9.0, el efecto de disparo no es satisfactorio. El tamaño de la vista previa y el tamaño de la foto que configuramos ya es el máximo admitido de 1920 * 1080, por lo que obviamente no es muy adecuado para los teléfonos móviles actuales. Sin embargo, como desarrollador, necesitamos comprender esta tecnología y comprender una implementación básica. Esto puede ser de gran ayuda para nosotros en el futuro estudio de CameraX proporcionado en Camera2 y Jetpack.

Supongo que te gusta

Origin blog.csdn.net/weixin_38322371/article/details/115083565
Recomendado
Clasificación