文前一问,咱们最近的基金和股票表现怎么呢? 哈哈哈哈哈

利用Camerax实现一个相机应用,爽歪歪~Android

Camerax是什么?

CameraX 是一个 Jetpack 库,首要用于高效的开发相机运用,比较于Camera2与Camera1,削减了心智担负,我从前用过Camera2与Camera1分别去测验开发一个就有根底功用的相机运用,耗费了大量时刻,相关类太多,且关于不同厂商来说很可能有具有不同的作用,需求做许多兼容处理,而在CameraX上,这些问题产生的概率要更小。 简略总结就是

  • 运用简略,无需过多重视相机的装备(这里就能够处理相机预览变形和旋转的问题)
  • 生命周期绑定,这里能够省去打开封闭相机和对相机进行生命周期办理的作业
  • 兼容性问题要更少
  • 封装合理,削减心智担负

Camerax的运用

运用CameraX自定义相机也能够简略分为以下几步:

  • 权限装备
  • 初始化相机
  • 预览设置
  • 摄影设置

权限装备

<uses-feature android:name="android.hardware.camera.any" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
   android:maxSdkVersion="28" />

其间:android.hardware.camera.any的装备是声明CameraX能够运用设备上的任何一个摄像头,不论是前置还是后置,结合动态请求权限库完结这一步,具体不再论述。

初始化相机

  // 相机进程提供者
  private ProcessCameraProvider cameraProvider;
  // 相机进程提供者 Future,相机初始化完结后回来相机进程提供者实例化目标
  private ListenableFuture<ProcessCameraProvider> cameraProviderFuture;
  // 初始化办法
  private void initProcessCameraProvider(){
        // 获取相机进程提供者Future
        cameraProviderFuture = ProcessCameraProvider.getInstance(context);
        cameraProviderFuture.addListener(() -> {
            try {
                cameraProvider = cameraProviderFuture.get();
                // 绑定预览界面
                bindPreview(cameraProvider);
            } catch (ExecutionException | InterruptedException e) {
                // No errors need to be handled for this Future.
                // This should never be reached.
            }
        }, ContextCompat.getMainExecutor(context));
    }

此处首要是获取相机进程提供者,这是非常重要的,由于Camerax自己现已封装好了生命周期的办理,需求将其他Camerax的相关组件绑定到ProcessCameraProvider上。

预览设置

  // 预览组件
  private PreviewView previewView;
  // 相机模组选择器
  private CameraSelector cameraSelector;
  // 预览目标
  private Preview preview;
  // 相机目标
  private Camera camera;
  private void bindPreview(@NonNull ProcessCameraProvider cameraProvider) {
        // 初始化相机模组选择器 并选择后主摄像头
        cameraSelector = new CameraSelector.Builder()
                .requireLensFacing(CameraSelector.LENS_FACING_BACK)
                .build();
        // 初始化预览界面 
        initPreView();
        // 设定 全局份额
        ...
        setRatio(videoWidth, videoHeight)
  }
  // 初始化预览界面
   private void initPreView() {
        previewView = new PreviewView(getContext());
        addView(previewView);
        previewView.setScaleType(PreviewView.ScaleType.FILL_CENTER);
        ...
   }
   // 设定预览界面 与 输出预览图画的份额
   private void setPreviewRatio(int width,  int height, Size size) {
        LayoutParams frameLayout = new FrameLayout.LayoutParams(width, height);
        frameLayout.gravity = Gravity.CENTER;
        previewView.setLayoutParams(frameLayout);
        preview = new Preview.Builder()
                // 设定预览份额输出的预览图画份额
                .setTargetResolution(size)
                .build();
                               preview.setSurfaceProvider(previewView.getSurfaceProvider());
    }
   // 绑定预览目标 输出预览画面
    camera = cameraProvider.bindToLifecycle((LifecycleOwner) getContext(), cameraSelector, preview, imageCapture);
    imageCapture 是什么呢???????请看下文

此处所做处理首要是以下几步

  • 指定预览相机
  • 设定预览界面大小(或许份额)
  • 绑定cameraProvider到ProcessCameraProvider

摄影设置

   // 画面捕捉目标
   private ImageCapture imageCapture;
   // 出事化捕捉界面 改改参数试试
   private void initImageCapture(Size size) {
        imageCapture = new ImageCapture.Builder()
                //优化捕获速度,可能降低图片质量
                .setCaptureMode(ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY)
                .setTargetResolution(size)
                //设置初始的旋转视点
                .setTargetRotation(Surface.ROTATION_0)
                .build();
    }
    // 公开的摄影办法
    public void takePicture() {
        // 这是我自己写的东西类,不重要
        File file = new File(ToolsFile.createImagePathUri(getContext()));
        ImageCapture.OutputFileOptions outputFileOptions =
                new ImageCapture.OutputFileOptions.Builder(file)
                        .build();
        imageCapture.takePicture(outputFileOptions, (Executor) cameraExecutor,
                new ImageCapture.OnImageSavedCallback() {
                    @Override
                    public void onImageSaved(ImageCapture.OutputFileResults outputFileResults) {
                        try {
                            Uri contentUri = outputFileResults.getSavedUri();
                            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
                                ApplicationData.globalContext.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, contentUri));
                            } else {
                                MediaStore.Images.Media.insertImage(ApplicationData.globalContext.getContentResolver(),
                                        file.getAbsolutePath(), file.getName(), null);
                            }
                            onFileSaved(contentUri);
                        } catch (FileNotFoundException e) {
                            e.printStackTrace();
                        }
                    }
                    @Override
                    public void onError(ImageCaptureException error) {
                    }
                }
        );
    }

这部分首要是做了这几步来完结捕捉画面,存储至相册的才能

  • 设置捕捉目标
  • 设置摄影保存办法

由上,咱们基于Camerax就完结了一个根底相机,但是在咱们的印象中相机最基本的功用里还得有点击聚集才算是完好的根底的可用的相机。 跟着我的过程一起来完结最后的收尾作业

  • previewView.setOnTouchListener,监听previewView接触
  • 封装一个手势类,处理预览窗口的接触事情
  • 设置焦点,展现焦点窗口

监听previewView接触

  // 预览画面事情监听
  private void initListener() {
        previewView.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                LiveData<ZoomState> zoomState = camera.getCameraInfo().getZoomState();
                float maxZoomRatio = zoomState.getValue().getMaxZoomRatio();
                float minZoomRatio = zoomState.getValue().getMinZoomRatio();
                // 自定义预览界面touch类
                return new CameraXGestureDetector(getContext()) {
                    @Override
                    void zoom() {
                        float zoomRatio = zoomState.getValue().getZoomRatio();
                        if (zoomRatio < maxZoomRatio) {
                            camera.getCameraControl().setZoomRatio((float) (zoomRatio + 0.1));
                        }
                    }
                    @Override
                    void zoomOut() {
                        float zoomRatio = zoomState.getValue().getZoomRatio();
                        if (zoomRatio > minZoomRatio) {
                            camera.getCameraControl().setZoomRatio((float) (zoomRatio - 0.1));
                        }
                    }
                    @Override
                    void click(float x, float y) {
                        Log.e("click", "click");
                    }
                    @Override
                    void doubleClick(float x, float y) {
                        Log.e("doubleClick", "doubleClick");
                        float zoomRatio = zoomState.getValue().getZoomRatio();
                        if (zoomRatio > minZoomRatio) {
                            camera.getCameraControl().setLinearZoom(0f);
                        } else {
                            camera.getCameraControl().setLinearZoom(0.5f);
                        }
                    }
                    @Override
                    void longClick(float x, float y) {
                        showTapView(x, y);
                    }
                }.onTouchEvent(event);
            }
        });
    }  
    // 相机手势事情
    public abstract class CameraXGestureDetector {
    private GestureDetector mGestureDetector;
    /**
     * 缩放相关
     */
    private float currentDistance = 0;
    private float lastDistance = 0;
    public  CameraXGestureDetector(Context context) {
        mGestureDetector = new GestureDetector(context, onGestureListener);
        mGestureDetector.setOnDoubleTapListener(onDoubleTapListener);
    }
    public boolean onTouchEvent(MotionEvent event){
        return  mGestureDetector.onTouchEvent(event);
    }

手势事情封装

  GestureDetector.OnGestureListener onGestureListener = new GestureDetector.OnGestureListener() {
        @Override
        public boolean onDown(MotionEvent e) {
            Log.i("","onDown: 按下");
            return true;
        }
        @Override
        public void onShowPress(MotionEvent e) {
            Log.i("","onShowPress: 刚碰上还没松开");
        }
        @Override
        public boolean onSingleTapUp(MotionEvent e) {
            Log.i("","onSingleTapUp: 轻轻一碰后立刻松开");
            return true;
        }
        @Override
        public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
            Log.i("","onScroll: 按下后拖动");
            // 大于两个接触点
            if (e2.getPointerCount() >= 2) {
                //event中封存了所有屏幕被接触的点的信息,第一个接触的位置能够通过event.getX(0)/getY(0)得到
                float offSetX = e2.getX(0) - e2.getX(1);
                float offSetY = e2.getY(0) - e2.getY(1);
                //运用三角函数的公式,通过计算X,Y坐标的差值,计算两点间的间隔
                currentDistance = (float) Math.sqrt(offSetX * offSetX + offSetY * offSetY);
                if (lastDistance == 0) {//如果是第一次进行判别
                    lastDistance = currentDistance;
                } else {
                    if (currentDistance - lastDistance > 10) {
                        // 扩大
                         zoom();
                    } else if (lastDistance - currentDistance > 10) {
                        // 缩小
                         zoomOut();
                    }
                }
                //在一次缩放操作完结后,将本次的间隔赋值给lastDistance,以便下一次判别
                //但这种办法写在move动作中,意味着手指一向没有抬起,监控两手指之间的改变间隔超过10
                //就履行缩放操作,不是在两次点击之间的间隔改变来判别缩放操作
                //故这种将本次间隔留待下一次判别的办法,不能在两次点击之间运用
                lastDistance = currentDistance;
            }
            return true;
        }
        @Override
        public void onLongPress(MotionEvent e) {
            Log.i("","onLongPress: 长按屏幕");
            longClick(e.getX(), e.getY());
        }
        @Override
        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
            Log.i("","onFling: 滑动后松开");
            currentDistance = 0;
            lastDistance = 0;
            return true;
        }
    };
    GestureDetector.OnDoubleTapListener onDoubleTapListener = new GestureDetector.OnDoubleTapListener() {
        @Override
        public boolean onSingleTapConfirmed(MotionEvent e) {
            Log.i("click","onSingleTapConfirmed: 严厉的单击");
            click(e.getX(), e.getY());
            return true;
        }
        @Override
        public boolean onDoubleTap(MotionEvent e) {
            Log.i("","onDoubleTap: 双击");
            doubleClick(e.getX(), e.getY());
            return true;
        }
        @Override
        public boolean onDoubleTapEvent(MotionEvent e) {
            Log.i("","onDoubleTapEvent: 表明产生双击行为");
            return true;
        }
    };
    /**
     * 扩大
     */
    abstract void zoom();
    /**
     * 缩小
     */
    abstract void zoomOut();
    /**
     * 点击
     */
    abstract void click(float x, float y);
    /**
     * 双击
     */
    abstract  void doubleClick(float x, float y);
    /**
     * 长按
     */
    abstract void longClick(float x, float y);
}

实际作用

如此咱们就得到了一个完好的根底的相机运用,让咱们来看看作用吧

利用Camerax实现一个相机应用,爽歪歪~Android
利用Camerax实现一个相机应用,爽歪歪~Android

完好代码现已上传,请点击下面的项目地址查收

项目地址:github.com/runner-up-j…

*文章预告:前端和android之间传递流数据,你让我传个图哇,我怎么能传个图?

首要是整理一下android下webview和native直接文件传递的计划,并输出一个多文件分片传流的demo*

文章初版已完结,希望咱们能够提供定见协助小弟完善一下,文章地址/post/726003…

同时推荐我同事写的一篇好文:/post/726014…