预览+图片拍摄 实时预览 1.编辑activity_main
layout 文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 <?xml version="1.0" encoding="utf-8" ?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android ="http://schemas.android.com/apk/res/android" xmlns:app ="http://schemas.android.com/apk/res-auto" xmlns:tools ="http://schemas.android.com/tools" android:layout_width ="match_parent" android:layout_height ="match_parent" tools:context =".MainActivity" > <Button android:id ="@+id/camera_capture_button" android:layout_width ="100dp" android:layout_height ="100dp" android:layout_marginBottom ="50dp" android:scaleType ="fitCenter" android:text ="Take Photo" app:layout_constraintLeft_toLeftOf ="parent" app:layout_constraintRight_toRightOf ="parent" app:layout_constraintBottom_toBottomOf ="parent" android:elevation ="2dp" /> <androidx.camera.view.PreviewView android:id ="@+id/viewFinder" android:layout_width ="match_parent" android:layout_height ="match_parent" /> </androidx.constraintlayout.widget.ConstraintLayout >
1.请求 CameraProvider 1 ListenableFuture<ProcessCameraProvider> cameraProviderFuture = ProcessCameraProvider.getInstance(this );
2.检查 CameraProvider 可用性 1 2 3 4 5 6 7 8 9 10 11 cameraProviderFuture.addListener(() -> { try { ProcessCameraProvider cameraProvider = cameraProviderFuture.get(); bindPreview(cameraProvider); } catch (ExecutionException | InterruptedException e) { } }, ContextCompat.getMainExecutor(this ));
3.选择相机并绑定生命周期和用例
创建 Preview
。
指定所需的相机 LensFacing
选项。
将所选相机和任意用例绑定到生命周期。
将 Preview
连接到 PreviewView
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 void bindPreview (@NonNull ProcessCameraProvider cameraProvider) { Preview preview = new Preview .Builder().build(); PreviewView previewView = (PreviewView)findViewById(R.id.viewFinder); preview.setSurfaceProvider(previewView.getSurfaceProvider()); CameraSelector cameraSelector = new CameraSelector .Builder() .requireLensFacing(CameraSelector.LENS_FACING_BACK) .build(); processCameraProvider.unbindAll(); Camera camera = cameraProvider.bindToLifecycle((LifecycleOwner)this , cameraSelector, preview); }
到此为止:如何实现实时预览已经说明完毕。
图片拍摄: 1 2 3 4 imageCapture = new ImageCapture .Builder().setTargetRotation(mViewFinder.getDisplay().getRotation()).build();
首先,检查 imageCapture
对象是否已经实例化,以避免空指针异常。
创建一个带时间戳的输出文件,用于保存照片,确保文件名的唯一性。
创建 OutputFileOptions
对象,用于指定照片的输出方式。
调用 takePicture()
方法进行拍照操作,传入输出文件选项和保存照片的回调函数。
在保存照片的回调函数中,可以获取保存的照片的 Uri
,显示成功的提示消息,并将消息打印到日志中。
如果在保存照片时发生错误,会在回调函数的 onError()
方法中进行处理,打印错误消息到日志中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 . private void takePhoto () { if (imageCapture != null ) { File photoFile = new File (outputDirectory, new SimpleDateFormat (Configuration.FILENAME_FORMAT, Locale.SIMPLIFIED_CHINESE).format(System.currentTimeMillis()) + ".jpg" ); ImageCapture.OutputFileOptions outputFileOptions = new ImageCapture .OutputFileOptions .Builder(photoFile) .build(); imageCapture.takePicture(outputFileOptions, ContextCompat.getMainExecutor(this ), new ImageCapture .OnImageSavedCallback() { @Override public void onImageSaved (@NonNull ImageCapture.OutputFileResults outputFileResults) { Uri savedUri = Uri.fromFile(photoFile); String msg = "照片捕获成功! " + savedUri; Toast.makeText(getBaseContext(), msg, Toast.LENGTH_SHORT).show(); Log.d(Configuration.TAG, msg); } @Override public void onError (@NonNull ImageCaptureException exception) { Log.e(Configuration.TAG, "Photo capture failed: " + exception.getMessage()); } }); } }
特殊功能实现: 前后摄像头的切换 我们之前使用cameraProvider.bindToLifecycle()
的时候,有一个参数是CameraSelector
。CameraX
默认给我们提供了前置摄像头和后置摄像头的CameraSelector
。
1 2 3 4 CameraSelector cameraSelector = new CameraSelector .Builder() .requireLensFacing(CameraSelector.LENS_FACING_BACK) .build();
切换摄像头的时候,就是重新调用一下bindPreview
方法,传入新的cameraSelector
值就好了
1 2 3 4 processCameraProvider.unbindAll(); Camera camera = cameraProvider.bindToLifecycle((LifecycleOwner)this , cameraSelector, preview);
手势放大缩小 这里使用ScaleGestureDetector
类来实现手势缩放功能。
ScaleGestureDetector
用于处理缩放的工具类,用法与GestureDetector类似,都是通过onTouchEvent()关联相应的MotionEvent的。使用该类时,用户需要传入一个完整的连续不断地motion事件(包含ACTION_DOWN,ACTION_MOVE和ACTION_UP事件)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 scaleGestureDetector = new ScaleGestureDetector (this , new ScaleGestureDetector . OnScaleGestureListener() { @Override public boolean onScale (ScaleGestureDetector detector) { return true ; } @Override public boolean onScaleBegin (ScaleGestureDetector detector) { return true ; } @Override public void onScaleEnd (ScaleGestureDetector detector) { } });
在该方法中,我们首先获取缩放因子 scaleFactor
,它表示缩放手势的影响因子。
我们获取当前的缩放状态,然后获取当前的缩放比例
我们获取新的缩放级别。Math.max()
和 Math.min()
方法来限制缩放级别在最小值和最大值之间
将新的缩放级别应用到相机控制器中,并打印出当前的缩放比例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @Override public boolean onScale (ScaleGestureDetector detector) { float scaleFactor = detector.getScaleFactor(); LiveData<ZoomState> zoomState = mCameraInfo.getZoomState(); float zoomRatio = zoomState.getValue().getZoomRatio(); float minZoomRatio = zoomState.getValue().getMinZoomRatio(); float maxZoomRatio = zoomState.getValue().getMaxZoomRatio(); float newZoomLevel = zoomRatio * scaleFactor; newZoomLevel = Math.max(1.0f , Math.min(newZoomLevel, 10 )); mCameraControl.setZoomRatio(newZoomLevel); Log.d(TAG, "当前缩放比例: " + newZoomLevel); return true ; }
点击手动对焦 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 mGestureDetector = new GestureDetector (this , new GestureDetector . SimpleOnGestureListener() { @SuppressLint("RestrictedApi") @Override public boolean onSingleTapUp (MotionEvent e) { float x = e.getX(); float y = e.getY(); MeteringPointFactory factory = mViewFinder.getMeteringPointFactory(); MeteringPoint point = factory.createPoint(x, y); FocusMeteringAction action = new FocusMeteringAction .Builder(point, FocusMeteringAction.FLAG_AF).build(); ListenableFuture<FocusMeteringResult> focusMeteringResultListenableFuture = mCameraControl.startFocusAndMetering(action); focusMeteringResultListenableFuture.addListener(() -> { try { FocusMeteringResult result = focusMeteringResultListenableFuture.get(); if (result.isFocusSuccessful()) { Log.d(TAG, "对焦成功" ); runOnUiThread(new Runnable () { @Override public void run () { customBoxView.setBoxPosition(x, y); showFocusSuccessToast(); } }); } else { Log.d(TAG, "对焦失败" ); showFocusFailedToast(); } } catch (ExecutionException | InterruptedException ex) { Log.e("YourCameraActivity" , "对焦操作失败: " + ex.getMessage()); } }, CameraXExecutors.directExecutor()); return true ; } });
为 mViewFinder
设置了一个触摸监听器,并在触摸事件中调用了 scaleGestureDetector
和 mGestureDetector
的对应方法。scaleGestureDetector.onTouchEvent(event)
是用于处理缩放手势的触摸事件,它将触摸事件传递给 scaleGestureDetector
对象来处理缩放操作。mGestureDetector.onTouchEvent(event)
是用于处理其他手势的触摸事件,它将触摸事件传递给 mGestureDetector
对象来处理其他手势操作,比如单击操作或者双击等。
1 2 3 4 5 6 7 8 9 mViewFinder = (PreviewView) findViewById(R.id.viewFinder); mViewFinder.setOnTouchListener(new View .OnTouchListener() { @Override public boolean onTouch (View v, MotionEvent event) { scaleGestureDetector.onTouchEvent(event); mGestureDetector.onTouchEvent(event); return true ; } });
手机紧急晃动拍照 使用加速度传感器(Accelerometer Sensor)来检测手机的晃动,并在达到一定阈值时触发拍照操作。
注册加速度传感器监听
1 2 3 4 SensorManager sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);Sensor accelerometerSensor = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); sensorManager.registerListener(sensorEventListener, accelerometerSensor, SensorManager.SENSOR_DELAY_NORMAL);
创建一个 SensorEventListener 对象,并实现其 onSensorChanged() 方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 private SensorEventListener sensorEventListener = new SensorEventListener () { private static final float SHAKE_THRESHOLD = 2.5f ; private float lastX; private float lastY; private float lastZ; private long lastShakeTime; @Override public void onSensorChanged (SensorEvent event) { float x = event.values[0 ]; float y = event.values[1 ]; float z = event.values[2 ]; float deltaX = x - lastX; float deltaY = y - lastY; float deltaZ = z - lastZ; lastX = x; lastY = y; lastZ = z; float acceleration = (float ) Math.sqrt(deltaX * deltaX + deltaY * deltaY + deltaZ * deltaZ); long currentTime = System.currentTimeMillis(); if (acceleration > SHAKE_THRESHOLD && currentTime - lastShakeTime > 1000 ) { takePhoto(); lastShakeTime = currentTime; } } @Override public void onAccuracyChanged (Sensor sensor, int accuracy) { } };
销毁监听
1 2 3 4 5 6 @Override protected void onDestroy () { super .onDestroy(); sensorManager.unregisterListener(sensorEventListener); }
给拍照图片加水印 在 public void onImageSaved(@NonNull ImageCapture.OutputFileResults outputFileResults) {}
里调用下面的方法。WaterMarkingUtility是一个添加水印的类。
1 2 3 4 5 6 7 private void addWatermarkToPhoto (File photoFile) throws FileNotFoundException { Bitmap originalBitmap = BitmapFactory.decodeFile(photoFile.getAbsolutePath()); FileOutputStream outputStream = new FileOutputStream (photoFile); Bitmap bitmap = WaterMarkingUtility.drawTextToCenter(originalBitmap, "水印" , 500 ); bitmap.compress(Bitmap.CompressFormat.JPEG, 100 , outputStream); }