Flutter Hybrid Composition實(shí)現(xiàn)自定義相機(jī)預(yù)覽原生插件

目錄

前言

在做Flutter開(kāi)發(fā)的時(shí)候,有時(shí)候Flutter有些功能實(shí)現(xiàn)不了,因此需要自定義原生插件來(lái)解決,下面就是通過(guò)Hybrid Composition實(shí)現(xiàn)自定義相機(jī)預(yù)覽原生控件

實(shí)現(xiàn)方法

1.創(chuàng)建Flutter插件工程

這里要選擇Plugin


2.創(chuàng)建Widget

這里要記住viewType的值('hybrid-view-camera')待會(huì)寫Android原生代碼的時(shí)候要對(duì)應(yīng)起來(lái)

class NativeAndroidView extends StatefulWidget {
  const NativeAndroidView({Key? key}) : super(key: key);

  @override
  State<NativeAndroidView> createState() => _NativeAndroidViewState();
}

class _NativeAndroidViewState extends State<NativeAndroidView> {
  @override
  Widget build(BuildContext context) {
    final String viewType = 'hybrid-view-camera';
    final Map<String, dynamic> creationParams = <String, dynamic>{};

    return PlatformViewLink(
      viewType: viewType,
      surfaceFactory:
          (BuildContext context, PlatformViewController controller) {
        return AndroidViewSurface(
          controller: controller as AndroidViewController,
          gestureRecognizers: const <Factory<OneSequenceGestureRecognizer>>{},
          hitTestBehavior: PlatformViewHitTestBehavior.opaque,
        );
      },
      onCreatePlatformView: (PlatformViewCreationParams params) {
        return PlatformViewsService.initSurfaceAndroidView(
          id: params.id,
          viewType: viewType,
          layoutDirection: TextDirection.ltr,
          creationParams: creationParams,
          creationParamsCodec: StandardMessageCodec(),
        )
          ..addOnPlatformViewCreatedListener(params.onPlatformViewCreated)
          ..create();
      },
    );
  }
}
3.編輯原生代碼

鼠標(biāo)右鍵點(diǎn)擊插件項(xiàng)目苞尝,選擇Flutter->Open Android module in Android Studio

新增NativeView用于實(shí)現(xiàn)相機(jī)預(yù)覽

internal class NativeView(context: Context, id: Int, creationParams: Any?) :
    PlatformView ,Camera.PreviewCallback, SurfaceHolder.Callback {
    private var mSurfaceView: SurfaceView? = null
    private var surfaceHolder: SurfaceHolder? = null
    private var mCamera: Camera? = null
    var autoFocusTimer: Timer? = null
    init {
        mSurfaceView = SurfaceView(context)
        surfaceHolder = mSurfaceView!!.holder
        surfaceHolder!!.addCallback(this)
        CameraUtils.init(context)
    }

    override fun getView(): View {
        return mSurfaceView!!
    }

    override fun dispose() {
        closeCamera()
    }

    /**
     * 初始化相機(jī)
     */
    private fun initCamera() {
        try {
            mCamera!!.setPreviewCallback(this)
            val parameters = mCamera!!.parameters
            parameters.previewFormat = ImageFormat.NV21
            //根據(jù)設(shè)置的寬高 和手機(jī)支持的分辨率對(duì)比計(jì)算出合適的寬高算法
            val optionSize = CameraUtils.findBestPreviewResolution(mCamera)
            // parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);//部分手機(jī)無(wú)效
            parameters.setPreviewSize(optionSize.width, optionSize.height)
            //設(shè)置照片尺寸
            parameters.setPictureSize(optionSize.width, optionSize.height)
            mCamera!!.parameters = parameters
            mCamera!!.setDisplayOrientation(90)
            //開(kāi)啟預(yù)覽
            mCamera!!.startPreview()
        } catch (e: java.lang.Exception) {
            e.printStackTrace()
        }
    }

    /**
     * 釋放相機(jī)
     */
    private fun closeCamera() {
        try {
            if (autoFocusTimer != null) {
                autoFocusTimer!!.cancel()
            }
            if (mCamera != null) {
                mCamera!!.stopPreview()
//                mCamera.release();//加上要掛啊
                mCamera = null
            }
        } catch (e: java.lang.Exception) {
        }
    }

    override fun onPreviewFrame(p0: ByteArray?, p1: Camera?) {
        
    }

    override fun surfaceCreated(p0: SurfaceHolder) {
        try {
            mCamera = Camera.open(0) //0:后置 1:前置
            initCamera()
        } catch (e: java.lang.Exception) {
            e.printStackTrace()
        }
    }

    override fun surfaceChanged(holder: SurfaceHolder, p1: Int, p2: Int, p3: Int) {
        try {
            mCamera!!.setPreviewDisplay(holder)
            initAutoFocusTimer()
        } catch (e: IOException) {
            e.printStackTrace()
        }
    }

    override fun surfaceDestroyed(p0: SurfaceHolder) {
        closeCamera()
    }

    private fun initAutoFocusTimer() {
        if (autoFocusTimer == null) {
            autoFocusTimer = Timer()
            autoFocusTimer!!.schedule(object : TimerTask() {
                override fun run() {
                    if (mCamera != null) {
                        mCamera!!.autoFocus { success, camera ->

                        }
                    }
                }
            }, 0, 600)
        }
    }
}

這里的CameraUtils相機(jī)操作類代碼如下

public class CameraUtils {
    public static float scale;

    public static int densityDpi;

    public static float fontScale;

    public static int screenWidth;
    public static int screenHeight;

    public static void init(Context context) {
        DisplayMetrics dm = context.getResources().getDisplayMetrics();
        scale = dm.density;
        densityDpi = dm.densityDpi;
        fontScale = dm.scaledDensity;
        if (dm.widthPixels < dm.heightPixels) {
            screenWidth = dm.widthPixels;
            screenHeight = dm.heightPixels;
        } else {
            screenWidth = dm.heightPixels;
            screenHeight = dm.widthPixels;
        }
        Log.e("screen", "屏幕寬度是:" + screenWidth + " 高度是:" + screenHeight + " dp:" + scale + " fontScale:" + fontScale);
    }

    //降序
    private CameraDropSizeComparator dropSizeComparator = new CameraDropSizeComparator();
    //升序
    private CameraAscendSizeComparator ascendSizeComparator = new CameraAscendSizeComparator();
    private static CameraUtils myCamPara = null;

    private CameraUtils() {

    }

    public static CameraUtils getInstance() {
        if (myCamPara == null) {
            myCamPara = new CameraUtils();
            return myCamPara;
        } else {
            return myCamPara;
        }
    }

    /**
     * 保證預(yù)覽方向正確
     *
     * @param activity
     * @param cameraId
     * @param camera
     */
    public void setCameraDisplayOrientation(Activity activity,
                                            int cameraId, Camera camera) {
        Camera.CameraInfo info =
                new Camera.CameraInfo();
        Camera.getCameraInfo(cameraId, info);
        int rotation = activity.getWindowManager().getDefaultDisplay()
                .getRotation();
        int degrees = 0;
        switch (rotation) {
            case Surface.ROTATION_0:
                degrees = 0;
                break;
            case Surface.ROTATION_90:
                degrees = 90;
                break;
            case Surface.ROTATION_180:
                degrees = 180;
                break;
            case Surface.ROTATION_270:
                degrees = 270;
                break;
        }

        int result;
        if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
            result = (info.orientation + degrees) % 360;
            result = (360 - result) % 360;  // compensate the mirror
        } else {  // back-facing
            result = (info.orientation - degrees + 360) % 360;
        }
        camera.setDisplayOrientation(result);
    }


    public Bitmap setTakePicktrueOrientation(int id, Bitmap bitmap) {
        Camera.CameraInfo info = new Camera.CameraInfo();
        Camera.getCameraInfo(id, info);
        bitmap = rotaingImageView(id, info.orientation, bitmap);
        return bitmap;
    }

    /**
     * 把相機(jī)拍照返回照片轉(zhuǎn)正
     *
     * @param angle 旋轉(zhuǎn)角度
     * @return bitmap 圖片
     */
    public Bitmap rotaingImageView(int id, int angle, Bitmap bitmap) {
        //旋轉(zhuǎn)圖片 動(dòng)作
        Matrix matrix = new Matrix();
        matrix.postRotate(angle);
        //加入翻轉(zhuǎn) 把相機(jī)拍照返回照片轉(zhuǎn)正
        if (id == 1) {
            matrix.postScale(-1, 1);
        }
        // 創(chuàng)建新的圖片
        Bitmap resizedBitmap = Bitmap.createBitmap(bitmap, 0, 0,
                bitmap.getWidth(), bitmap.getHeight(), matrix, true);
        return resizedBitmap;
    }

    /**
     * 獲取所有支持的預(yù)覽尺寸
     */
    public Size getPropPreviewSize(List<Size> list, int minWidth) {
        Collections.sort(list, ascendSizeComparator);

        int i = 0;
        for (Size s : list) {
            if ((s.width >= minWidth)) {
                break;
            }
            i++;
        }
        if (i == list.size()) {
            i = 0;//如果沒(méi)找到,就選最小的size
        }
        return list.get(i);
    }

    /**
     * 獲取所有支持的返回圖片尺寸
     */
    public Size getPropPictureSize(List<Size> list, int minWidth) {
        Collections.sort(list, ascendSizeComparator);

        int i = 0;
        for (Size s : list) {
            if ((s.width >= minWidth)) {
                break;
            }
            i++;
        }
        if (i == list.size()) {
            i = 0;//如果沒(méi)找到佑惠,就選最小的size
        }
        return list.get(i);
    }

    public boolean equalRate(Size s, float rate) {
        float r = (float) (s.width) / (float) (s.height);
        return Math.abs(r - rate) <= 0.03;
    }

    //降序
    public class CameraDropSizeComparator implements Comparator<Size> {
        public int compare(Size lhs, Size rhs) {
            if (lhs.width == rhs.width) {
                return 0;
            } else if (lhs.width < rhs.width) {
                return 1;
            } else {
                return -1;
            }
        }

    }

    //升序
    public class CameraAscendSizeComparator implements Comparator<Size> {
        public int compare(Size lhs, Size rhs) {
            if (lhs.width == rhs.width) {
                return 0;
            } else if (lhs.width > rhs.width) {
                return 1;
            } else {
                return -1;
            }
        }

    }

    /**
     * 打印支持的previewSizes
     */
    public void printSupportPreviewSize(Camera.Parameters params) {
        List<Size> previewSizes = params.getSupportedPreviewSizes();
        for (int i = 0; i < previewSizes.size(); i++) {
            Size size = previewSizes.get(i);
        }

    }

    /**
     * 打印支持的pictureSizes
     */
    public void printSupportPictureSize(Camera.Parameters params) {
        List<Size> pictureSizes = params.getSupportedPictureSizes();
        for (int i = 0; i < pictureSizes.size(); i++) {
            Size size = pictureSizes.get(i);
        }
    }

    /**
     * 打印支持的聚焦模式
     */
    public void printSupportFocusMode(Camera.Parameters params) {
        List<String> focusModes = params.getSupportedFocusModes();
        for (String mode : focusModes) {
        }
    }

    /**
     * 打開(kāi)閃關(guān)燈
     */
    public void turnLightOn(Camera mCamera) {
        if (mCamera == null) {
            return;
        }
        Camera.Parameters parameters = mCamera.getParameters();
        if (parameters == null) {
            return;
        }
        List<String> flashModes = parameters.getSupportedFlashModes();
        // Check if camera flash exists
        if (flashModes == null) {
            // Use the screen as a flashlight (next best thing)
            return;
        }
        String flashMode = parameters.getFlashMode();
        if (!Camera.Parameters.FLASH_MODE_ON.equals(flashMode)) {
            // Turn on the flash
            if (flashModes.contains(Camera.Parameters.FLASH_MODE_TORCH)) {
                parameters.setFlashMode(Camera.Parameters.FLASH_MODE_TORCH);
                mCamera.setParameters(parameters);
            } else {
            }
        }
    }


    /**
     * 自動(dòng)模式閃光燈
     */
    public void turnLightAuto(Camera mCamera) {
        if (mCamera == null) {
            return;
        }
        Camera.Parameters parameters = mCamera.getParameters();
        if (parameters == null) {
            return;
        }
        List<String> flashModes = parameters.getSupportedFlashModes();
        // Check if camera flash exists
        if (flashModes == null) {
            // Use the screen as a flashlight (next best thing)
            return;
        }
        String flashMode = parameters.getFlashMode();
        if (!Camera.Parameters.FLASH_MODE_AUTO.equals(flashMode)) {
            // Turn on the flash
            if (flashModes.contains(Camera.Parameters.FLASH_MODE_TORCH)) {
                parameters.setFlashMode(Camera.Parameters.FLASH_MODE_TORCH);
                mCamera.setParameters(parameters);
            } else {
            }
        }
    }


    public static Camera.CameraInfo getCameraInfo(int facing) {
        int numberOfCameras = Camera.getNumberOfCameras();
        Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
        for (int i = 0; i < numberOfCameras; i++) {
            Camera.getCameraInfo(i, cameraInfo);
            if (cameraInfo.facing == facing) {
                return cameraInfo;
            }
        }
        return null;
    }


    /**
     * 最小預(yù)覽界面的分辨率
     */
    private static final int MIN_PREVIEW_PIXELS = 480 * 320;
    /**
     * 最大寬高比差
     */
    private static final double MAX_ASPECT_DISTORTION = 0.5;


    /**
     * 關(guān)閉閃光燈
     *
     * @param mCamera
     */
    public void turnLightOff(Camera mCamera) {
        if (mCamera == null) {
            return;
        }
        Camera.Parameters parameters = mCamera.getParameters();
        if (parameters == null) {
            return;
        }
        List<String> flashModes = parameters.getSupportedFlashModes();
        String flashMode = parameters.getFlashMode();
        // Check if camera flash exists
        if (flashModes == null) {
            return;
        }
        if (!Camera.Parameters.FLASH_MODE_OFF.equals(flashMode)) {
            // Turn off the flash
            if (flashModes.contains(Camera.Parameters.FLASH_MODE_TORCH)) {
                parameters.setFlashMode(Camera.Parameters.FLASH_MODE_TORCH);
                mCamera.setParameters(parameters);
            } else {
            }
        }
    }

    public static Size findBestPreviewResolution(Camera mCamera) {
        Camera.Parameters cameraParameters = mCamera.getParameters();
        Size defaultPreviewResolution = cameraParameters.getPreviewSize();

        List<Size> rawSupportedSizes = cameraParameters.getSupportedPreviewSizes();
        if (rawSupportedSizes == null) {
            return defaultPreviewResolution;
        }

        // 按照分辨率從大到小排序
        List<Size> supportedPreviewResolutions = new ArrayList<Size>(rawSupportedSizes);
        Collections.sort(supportedPreviewResolutions, new Comparator<Size>() {
            @Override
            public int compare(Size a, Size b) {
                int aPixels = a.height * a.width;
                int bPixels = b.height * b.width;
                if (bPixels < aPixels) {
                    return -1;
                }
                if (bPixels > aPixels) {
                    return 1;
                }
                return 0;
            }
        });

        StringBuilder previewResolutionSb = new StringBuilder();
        for (Size supportedPreviewResolution : supportedPreviewResolutions) {
            previewResolutionSb.append(supportedPreviewResolution.width).append('x').append(supportedPreviewResolution.height)
                    .append(' ');
        }

        // 移除不符合條件的分辨率
        double screenAspectRatio = (double) (screenWidth / screenHeight);
        Iterator<Size> it = supportedPreviewResolutions.iterator();
        while (it.hasNext()) {
            Size supportedPreviewResolution = it.next();
            int width = supportedPreviewResolution.width;
            int height = supportedPreviewResolution.height;

            // 移除低于下限的分辨率,盡可能取高分辨率
            if (width * height < MIN_PREVIEW_PIXELS) {
                it.remove();
                continue;
            }

            // 在camera分辨率與屏幕分辨率寬高比不相等的情況下齐疙,找出差距最小的一組分辨率
            // 由于camera的分辨率是width>height膜楷,我們?cè)O(shè)置的portrait模式中,width<height
            // 因此這里要先交換然preview寬高比后在比較
            boolean isCandidatePortrait = width > height;
            int maybeFlippedWidth = isCandidatePortrait ? height : width;
            int maybeFlippedHeight = isCandidatePortrait ? width : height;
            double aspectRatio = (double) maybeFlippedWidth / (double) maybeFlippedHeight;
            double distortion = Math.abs(aspectRatio - screenAspectRatio);

            // 找到與屏幕分辨率完全匹配的預(yù)覽界面分辨率直接返回
            if (maybeFlippedWidth == screenWidth
                    && maybeFlippedHeight == screenHeight) {
                return supportedPreviewResolution;
            }

            if (distortion > MAX_ASPECT_DISTORTION) {
                it.remove();
                continue;
            }

        }

        // 如果沒(méi)有找到合適的贞奋,并且還有候選的像素赌厅,則設(shè)置其中最大比例的,對(duì)于配置比較低的機(jī)器不太合適
        if (!supportedPreviewResolutions.isEmpty()) {
            Size largestPreview = supportedPreviewResolutions.get(0);
            return largestPreview;
        }

        // 沒(méi)有找到合適的轿塔,就返回默認(rèn)的

        return defaultPreviewResolution;
    }
}
public static int densityDpi;

public static float fontScale;

public static int screenWidth;
public static int screenHeight;

public static void init(Context context) {
    DisplayMetrics dm = context.getResources().getDisplayMetrics();
    scale = dm.density;
    densityDpi = dm.densityDpi;
    fontScale = dm.scaledDensity;
    if (dm.widthPixels < dm.heightPixels) {
        screenWidth = dm.widthPixels;
        screenHeight = dm.heightPixels;
    } else {
        screenWidth = dm.heightPixels;
        screenHeight = dm.widthPixels;
    }
    Log.e("screen", "屏幕寬度是:" + screenWidth + " 高度是:" + screenHeight + " dp:" + scale + " fontScale:" + fontScale);
}

//降序
private CameraDropSizeComparator dropSizeComparator = new CameraDropSizeComparator();
//升序
private CameraAscendSizeComparator ascendSizeComparator = new CameraAscendSizeComparator();
private static CameraUtils myCamPara = null;

private CameraUtils() {

}

public static CameraUtils getInstance() {
    if (myCamPara == null) {
        myCamPara = new CameraUtils();
        return myCamPara;
    } else {
        return myCamPara;
    }
}

/**
 * 保證預(yù)覽方向正確
 *
 * @param activity
 * @param cameraId
 * @param camera
 */
public void setCameraDisplayOrientation(Activity activity,
                                        int cameraId, Camera camera) {
    Camera.CameraInfo info =
            new Camera.CameraInfo();
    Camera.getCameraInfo(cameraId, info);
    int rotation = activity.getWindowManager().getDefaultDisplay()
            .getRotation();
    int degrees = 0;
    switch (rotation) {
        case Surface.ROTATION_0:
            degrees = 0;
            break;
        case Surface.ROTATION_90:
            degrees = 90;
            break;
        case Surface.ROTATION_180:
            degrees = 180;
            break;
        case Surface.ROTATION_270:
            degrees = 270;
            break;
    }

    int result;
    if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
        result = (info.orientation + degrees) % 360;
        result = (360 - result) % 360;  // compensate the mirror
    } else {  // back-facing
        result = (info.orientation - degrees + 360) % 360;
    }
    camera.setDisplayOrientation(result);
}


public Bitmap setTakePicktrueOrientation(int id, Bitmap bitmap) {
    Camera.CameraInfo info = new Camera.CameraInfo();
    Camera.getCameraInfo(id, info);
    bitmap = rotaingImageView(id, info.orientation, bitmap);
    return bitmap;
}

/**
 * 把相機(jī)拍照返回照片轉(zhuǎn)正
 *
 * @param angle 旋轉(zhuǎn)角度
 * @return bitmap 圖片
 */
public Bitmap rotaingImageView(int id, int angle, Bitmap bitmap) {
    //旋轉(zhuǎn)圖片 動(dòng)作
    Matrix matrix = new Matrix();
    matrix.postRotate(angle);
    //加入翻轉(zhuǎn) 把相機(jī)拍照返回照片轉(zhuǎn)正
    if (id == 1) {
        matrix.postScale(-1, 1);
    }
    // 創(chuàng)建新的圖片
    Bitmap resizedBitmap = Bitmap.createBitmap(bitmap, 0, 0,
            bitmap.getWidth(), bitmap.getHeight(), matrix, true);
    return resizedBitmap;
}

/**
 * 獲取所有支持的預(yù)覽尺寸
 */
public Size getPropPreviewSize(List<Size> list, int minWidth) {
    Collections.sort(list, ascendSizeComparator);

    int i = 0;
    for (Size s : list) {
        if ((s.width >= minWidth)) {
            break;
        }
        i++;
    }
    if (i == list.size()) {
        i = 0;//如果沒(méi)找到特愿,就選最小的size
    }
    return list.get(i);
}

/**
 * 獲取所有支持的返回圖片尺寸
 */
public Size getPropPictureSize(List<Size> list, int minWidth) {
    Collections.sort(list, ascendSizeComparator);

    int i = 0;
    for (Size s : list) {
        if ((s.width >= minWidth)) {
            break;
        }
        i++;
    }
    if (i == list.size()) {
        i = 0;//如果沒(méi)找到,就選最小的size
    }
    return list.get(i);
}

public boolean equalRate(Size s, float rate) {
    float r = (float) (s.width) / (float) (s.height);
    return Math.abs(r - rate) <= 0.03;
}

//降序
public class CameraDropSizeComparator implements Comparator<Size> {
    public int compare(Size lhs, Size rhs) {
        if (lhs.width == rhs.width) {
            return 0;
        } else if (lhs.width < rhs.width) {
            return 1;
        } else {
            return -1;
        }
    }

}

//升序
public class CameraAscendSizeComparator implements Comparator<Size> {
    public int compare(Size lhs, Size rhs) {
        if (lhs.width == rhs.width) {
            return 0;
        } else if (lhs.width > rhs.width) {
            return 1;
        } else {
            return -1;
        }
    }

}

/**
 * 打印支持的previewSizes
 */
public void printSupportPreviewSize(Camera.Parameters params) {
    List<Size> previewSizes = params.getSupportedPreviewSizes();
    for (int i = 0; i < previewSizes.size(); i++) {
        Size size = previewSizes.get(i);
    }

}

/**
 * 打印支持的pictureSizes
 */
public void printSupportPictureSize(Camera.Parameters params) {
    List<Size> pictureSizes = params.getSupportedPictureSizes();
    for (int i = 0; i < pictureSizes.size(); i++) {
        Size size = pictureSizes.get(i);
    }
}

/**
 * 打印支持的聚焦模式
 */
public void printSupportFocusMode(Camera.Parameters params) {
    List<String> focusModes = params.getSupportedFocusModes();
    for (String mode : focusModes) {
    }
}

/**
 * 打開(kāi)閃關(guān)燈
 */
public void turnLightOn(Camera mCamera) {
    if (mCamera == null) {
        return;
    }
    Camera.Parameters parameters = mCamera.getParameters();
    if (parameters == null) {
        return;
    }
    List<String> flashModes = parameters.getSupportedFlashModes();
    // Check if camera flash exists
    if (flashModes == null) {
        // Use the screen as a flashlight (next best thing)
        return;
    }
    String flashMode = parameters.getFlashMode();
    if (!Camera.Parameters.FLASH_MODE_ON.equals(flashMode)) {
        // Turn on the flash
        if (flashModes.contains(Camera.Parameters.FLASH_MODE_TORCH)) {
            parameters.setFlashMode(Camera.Parameters.FLASH_MODE_TORCH);
            mCamera.setParameters(parameters);
        } else {
        }
    }
}


/**
 * 自動(dòng)模式閃光燈
 */
public void turnLightAuto(Camera mCamera) {
    if (mCamera == null) {
        return;
    }
    Camera.Parameters parameters = mCamera.getParameters();
    if (parameters == null) {
        return;
    }
    List<String> flashModes = parameters.getSupportedFlashModes();
    // Check if camera flash exists
    if (flashModes == null) {
        // Use the screen as a flashlight (next best thing)
        return;
    }
    String flashMode = parameters.getFlashMode();
    if (!Camera.Parameters.FLASH_MODE_AUTO.equals(flashMode)) {
        // Turn on the flash
        if (flashModes.contains(Camera.Parameters.FLASH_MODE_TORCH)) {
            parameters.setFlashMode(Camera.Parameters.FLASH_MODE_TORCH);
            mCamera.setParameters(parameters);
        } else {
        }
    }
}


public static Camera.CameraInfo getCameraInfo(int facing) {
    int numberOfCameras = Camera.getNumberOfCameras();
    Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
    for (int i = 0; i < numberOfCameras; i++) {
        Camera.getCameraInfo(i, cameraInfo);
        if (cameraInfo.facing == facing) {
            return cameraInfo;
        }
    }
    return null;
}


/**
 * 最小預(yù)覽界面的分辨率
 */
private static final int MIN_PREVIEW_PIXELS = 480 * 320;
/**
 * 最大寬高比差
 */
private static final double MAX_ASPECT_DISTORTION = 0.5;


/**
 * 關(guān)閉閃光燈
 *
 * @param mCamera
 */
public void turnLightOff(Camera mCamera) {
    if (mCamera == null) {
        return;
    }
    Camera.Parameters parameters = mCamera.getParameters();
    if (parameters == null) {
        return;
    }
    List<String> flashModes = parameters.getSupportedFlashModes();
    String flashMode = parameters.getFlashMode();
    // Check if camera flash exists
    if (flashModes == null) {
        return;
    }
    if (!Camera.Parameters.FLASH_MODE_OFF.equals(flashMode)) {
        // Turn off the flash
        if (flashModes.contains(Camera.Parameters.FLASH_MODE_TORCH)) {
            parameters.setFlashMode(Camera.Parameters.FLASH_MODE_TORCH);
            mCamera.setParameters(parameters);
        } else {
        }
    }
}

public static Size findBestPreviewResolution(Camera mCamera) {
    Camera.Parameters cameraParameters = mCamera.getParameters();
    Size defaultPreviewResolution = cameraParameters.getPreviewSize();

    List<Size> rawSupportedSizes = cameraParameters.getSupportedPreviewSizes();
    if (rawSupportedSizes == null) {
        return defaultPreviewResolution;
    }

    // 按照分辨率從大到小排序
    List<Size> supportedPreviewResolutions = new ArrayList<Size>(rawSupportedSizes);
    Collections.sort(supportedPreviewResolutions, new Comparator<Size>() {
        @Override
        public int compare(Size a, Size b) {
            int aPixels = a.height * a.width;
            int bPixels = b.height * b.width;
            if (bPixels < aPixels) {
                return -1;
            }
            if (bPixels > aPixels) {
                return 1;
            }
            return 0;
        }
    });

    StringBuilder previewResolutionSb = new StringBuilder();
    for (Size supportedPreviewResolution : supportedPreviewResolutions) {
        previewResolutionSb.append(supportedPreviewResolution.width).append('x').append(supportedPreviewResolution.height)
                .append(' ');
    }

    // 移除不符合條件的分辨率
    double screenAspectRatio = (double) (screenWidth / screenHeight);
    Iterator<Size> it = supportedPreviewResolutions.iterator();
    while (it.hasNext()) {
        Size supportedPreviewResolution = it.next();
        int width = supportedPreviewResolution.width;
        int height = supportedPreviewResolution.height;

        // 移除低于下限的分辨率勾缭,盡可能取高分辨率
        if (width * height < MIN_PREVIEW_PIXELS) {
            it.remove();
            continue;
        }

        // 在camera分辨率與屏幕分辨率寬高比不相等的情況下揍障,找出差距最小的一組分辨率
        // 由于camera的分辨率是width>height,我們?cè)O(shè)置的portrait模式中俩由,width<height
        // 因此這里要先交換然preview寬高比后在比較
        boolean isCandidatePortrait = width > height;
        int maybeFlippedWidth = isCandidatePortrait ? height : width;
        int maybeFlippedHeight = isCandidatePortrait ? width : height;
        double aspectRatio = (double) maybeFlippedWidth / (double) maybeFlippedHeight;
        double distortion = Math.abs(aspectRatio - screenAspectRatio);

        // 找到與屏幕分辨率完全匹配的預(yù)覽界面分辨率直接返回
        if (maybeFlippedWidth == screenWidth
                && maybeFlippedHeight == screenHeight) {
            return supportedPreviewResolution;
        }

        if (distortion > MAX_ASPECT_DISTORTION) {
            it.remove();
            continue;
        }

    }

    // 如果沒(méi)有找到合適的毒嫡,并且還有候選的像素,則設(shè)置其中最大比例的幻梯,對(duì)于配置比較低的機(jī)器不太合適
    if (!supportedPreviewResolutions.isEmpty()) {
        Size largestPreview = supportedPreviewResolutions.get(0);
        return largestPreview;
    }

    // 沒(méi)有找到合適的审胚,就返回默認(rèn)的

    return defaultPreviewResolution;
}

然后創(chuàng)建NativeViewFactory用于創(chuàng)建NativeView

internal class NativeViewFactory : PlatformViewFactory(StandardMessageCodec.INSTANCE) {
    override fun create(context: Context, id: Int, args: Any?): PlatformView {
        return NativeView(context, id, args)
    }
}

打開(kāi)MainActivity,進(jìn)行注冊(cè)礼旅,注意這里的"hybrid-view-camera"要與之前提到的Flutter中的viewType對(duì)應(yīng)起來(lái)

class MainActivity: FlutterActivity() {
    override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
        super.configureFlutterEngine(flutterEngine)
        flutterEngine
            .platformViewsController
            .registry
            .registerViewFactory("hybrid-view-camera",  NativeViewFactory())
    }
}

增加回調(diào)

有時(shí)候我們會(huì)根據(jù)相機(jī)的預(yù)覽數(shù)據(jù)進(jìn)行一些處理,然后我們需要把處理的結(jié)果發(fā)給Flutter洽洁,所以這里我們需要使用EventChannel進(jìn)行通信痘系。
修改_NativeAndroidViewState如下,增加EventChannel

class _NativeAndroidViewState extends State<NativeAndroidView> {
  final EventChannel? _eventChannel = EventChannel("flutter_camera/event");
  StreamSubscription<dynamic>? _streamSubscription;
  @override
  void initState() {
    super.initState();
    _streamSubscription =  _eventChannel?.receiveBroadcastStream().listen((event) {
      print(event);
      widget._dataCallBack(event);
    }) as StreamSubscription;
  }
  @override
  Widget build(BuildContext context) {
    final String viewType = 'hybrid-view-camera';
    final Map<String, dynamic> creationParams = <String, dynamic>{};

    return PlatformViewLink(
      viewType: viewType,
      surfaceFactory:
          (BuildContext context, PlatformViewController controller) {
        return AndroidViewSurface(
          controller: controller as AndroidViewController,
          gestureRecognizers: const <Factory<OneSequenceGestureRecognizer>>{},
          hitTestBehavior: PlatformViewHitTestBehavior.opaque,
        );
      },
      onCreatePlatformView: (PlatformViewCreationParams params) {
        return PlatformViewsService.initSurfaceAndroidView(
          id: params.id,
          viewType: viewType,
          layoutDirection: TextDirection.ltr,
          creationParams: creationParams,
          creationParamsCodec: StandardMessageCodec(),
        )
          ..addOnPlatformViewCreatedListener(params.onPlatformViewCreated)
          ..create();
      },
    );
  }
}

另外我們還要NativeAndroidView中增加一個(gè)接收的回調(diào)

typedef CameraDataCallBack = void Function(String);
class NativeAndroidView extends StatefulWidget {
  final CameraDataCallBack _dataCallBack;
  const NativeAndroidView( this._dataCallBack,{Key? key}) : super(key: key);

  @override
  State<NativeAndroidView> createState() => _NativeAndroidViewState();
}

然后在使用的時(shí)候傳入回調(diào)函數(shù)

Positioned(
                  child: SizedBox(
                    width: double.maxFinite,
                    height: double.maxFinite,
                    child: NativeAndroidView((event) {
                      setState(() {
                        _timeString = event;
                      });
                    }),
                  )
              ),

然后我們?cè)谠a的MainActivity中加入EventChannel用于與Flutter通信

class MainActivity: FlutterActivity() {
    private lateinit var channel: EventChannel
    override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
        super.configureFlutterEngine(flutterEngine)
        channel = EventChannel(flutterEngine.dartExecutor.binaryMessenger, "flutter_camera/event")
        channel.setStreamHandler(
            object : EventChannel.StreamHandler {
                override fun onListen(arguments: Any?, events: EventSink) {
                    NativeView.eventSink = events
                }
                override fun onCancel(arguments: Any?) {
                    Log.w("Android", "EventChannel onCancel called")
                }
            })
        flutterEngine
            .platformViewsController
            .registry
            .registerViewFactory("hybrid-view-camera",  NativeViewFactory())
    }
}

NativeView中增加EventChannel.EventSink用于發(fā)送數(shù)據(jù)

internal class NativeView(context: Context, id: Int, creationParams: Any?) :
    PlatformView ,Camera.PreviewCallback, SurfaceHolder.Callback {
    companion object {
        var eventSink: EventChannel.EventSink? = null
    }
    ...省略部分代碼
    override fun onPreviewFrame(p0: ByteArray?, p1: Camera?) {
        eventSink?.success("${System.currentTimeMillis()}")
    }

}

最終效果如下(注意這里我們運(yùn)行的是Flutter項(xiàng)目不是Android插件項(xiàng)目)饿自,左上角的時(shí)間戳是Android原生傳過(guò)來(lái)的


案例源碼

https://gitee.com/itfitness/flutter-camera-plugin

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末汰翠,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子昭雌,更是在濱河造成了極大的恐慌复唤,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,324評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件烛卧,死亡現(xiàn)場(chǎng)離奇詭異佛纫,居然都是意外死亡妓局,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,356評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門呈宇,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)好爬,“玉大人,你說(shuō)我怎么就攤上這事甥啄〈媾冢” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 162,328評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵蜈漓,是天一觀的道長(zhǎng)穆桂。 經(jīng)常有香客問(wèn)我,道長(zhǎng)融虽,這世上最難降的妖魔是什么享完? 我笑而不...
    開(kāi)封第一講書人閱讀 58,147評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮衣形,結(jié)果婚禮上驼侠,老公的妹妹穿的比我還像新娘。我一直安慰自己谆吴,他們只是感情好倒源,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,160評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著句狼,像睡著了一般笋熬。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上腻菇,一...
    開(kāi)封第一講書人閱讀 51,115評(píng)論 1 296
  • 那天胳螟,我揣著相機(jī)與錄音,去河邊找鬼筹吐。 笑死糖耸,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的丘薛。 我是一名探鬼主播嘉竟,決...
    沈念sama閱讀 40,025評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼洋侨!你這毒婦竟也來(lái)了舍扰?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 38,867評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤希坚,失蹤者是張志新(化名)和其女友劉穎边苹,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體裁僧,經(jīng)...
    沈念sama閱讀 45,307評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡个束,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,528評(píng)論 2 332
  • 正文 我和宋清朗相戀三年慕购,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片播急。...
    茶點(diǎn)故事閱讀 39,688評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡脓钾,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出桩警,到底是詐尸還是另有隱情可训,我是刑警寧澤,帶...
    沈念sama閱讀 35,409評(píng)論 5 343
  • 正文 年R本政府宣布捶枢,位于F島的核電站握截,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏烂叔。R本人自食惡果不足惜谨胞,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,001評(píng)論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望蒜鸡。 院中可真熱鬧胯努,春花似錦、人聲如沸逢防。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,657評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)忘朝。三九已至灰署,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間局嘁,已是汗流浹背溉箕。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 32,811評(píng)論 1 268
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留悦昵,地道東北人肴茄。 一個(gè)月前我還...
    沈念sama閱讀 47,685評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像但指,于是被迫代替她去往敵國(guó)和親寡痰。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,573評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容