package com.king.zxing; import android.annotation.SuppressLint; import android.app.Activity; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.util.DisplayMetrics; import android.view.MotionEvent; import android.view.ScaleGestureDetector; import android.view.View; import com.google.common.util.concurrent.ListenableFuture; import com.google.zxing.BarcodeFormat; import com.google.zxing.Result; import com.google.zxing.ResultPoint; import com.google.zxing.common.detector.MathUtils; import com.king.zxing.analyze.Analyzer; import com.king.zxing.analyze.MultiFormatAnalyzer; import com.king.zxing.config.AspectRatioCameraConfig; import com.king.zxing.config.CameraConfig; import com.king.zxing.config.ResolutionCameraConfig; import com.king.zxing.manager.AmbientLightManager; import com.king.zxing.manager.BeepManager; import com.king.zxing.util.LogUtils; import java.util.concurrent.Executors; import androidx.annotation.FloatRange; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.camera.core.Camera; import androidx.camera.core.CameraInfo; import androidx.camera.core.CameraSelector; import androidx.camera.core.FocusMeteringAction; import androidx.camera.core.ImageAnalysis; import androidx.camera.core.MeteringPoint; import androidx.camera.core.Preview; import androidx.camera.core.TorchState; import androidx.camera.core.ZoomState; import androidx.camera.lifecycle.ProcessCameraProvider; import androidx.camera.view.PreviewView; import androidx.core.content.ContextCompat; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentActivity; import androidx.lifecycle.LifecycleOwner; import androidx.lifecycle.MutableLiveData; /** * 相机扫描基类;{@link DefaultCameraScan} 为 {@link CameraScan} 的默认实现 *

* 快速实现扫描识别主要有以下几种方式: *

* 1、通过继承 {@link CaptureActivity}或者{@link CaptureFragment}或其子类,可快速实现扫描识别。 * (适用于大多数场景,自定义布局时需覆写getLayoutId方法) *

* 2、在你项目的Activity或者Fragment中实例化一个{@link DefaultCameraScan}。(适用于想在扫码界面写交互逻辑,又因为项目 * 架构或其它原因,无法直接或间接继承{@link CaptureActivity}或{@link CaptureFragment}时使用) *

* 3、继承{@link CameraScan}自己实现一个,可参照默认实现类{@link DefaultCameraScan},其他步骤同方式2。(高级用法,谨慎使用) * * @author Jenly */ public class DefaultCameraScan extends CameraScan { /** * Defines the maximum duration in milliseconds between a touch pad * touch and release for a given touch to be considered a tap (click) as * opposed to a hover movement gesture. */ private static final int HOVER_TAP_TIMEOUT = 150; /** * Defines the maximum distance in pixels that a touch pad touch can move * before being released for it to be considered a tap (click) as opposed * to a hover movement gesture. */ private static final int HOVER_TAP_SLOP = 20; /** * 每次缩放改变的步长 */ private static final float ZOOM_STEP_SIZE = 0.1F; private FragmentActivity mFragmentActivity; private Context mContext; private LifecycleOwner mLifecycleOwner; /** * 预览视图 */ private PreviewView mPreviewView; private ListenableFuture mCameraProviderFuture; /** * 相机 */ private Camera mCamera; /** * 相机配置 */ private CameraConfig mCameraConfig; /** * 分析器 */ private Analyzer mAnalyzer; /** * 是否分析 */ private volatile boolean isAnalyze = true; /** * 是否已经分析出结果 */ private volatile boolean isAnalyzeResult; /** * 闪光灯(手电筒)视图 */ private View flashlightView; private MutableLiveData mResultLiveData; /** * 扫描结果回调 */ private OnScanResultCallback mOnScanResultCallback; /** * 蜂鸣音效管理器:主要用于播放蜂鸣提示音和振动效果 */ private BeepManager mBeepManager; /** * 环境光线管理器:主要通过传感器来监听光线的亮度变化 */ private AmbientLightManager mAmbientLightManager; private int mOrientation; private int mImageWidth; private int mImageHeight; /** * 最后自动缩放时间 */ private long mLastAutoZoomTime; /** * 最后点击时间,根据两次点击时间间隔用于区分单机和触摸缩放事件 */ private long mLastHoveTapTime; /** * 是否是点击事件 */ private boolean isClickTap; /** * 按下时X坐标 */ private float mDownX; /** * 按下时Y坐标 */ private float mDownY; public DefaultCameraScan(@NonNull FragmentActivity activity, @NonNull PreviewView previewView) { this.mFragmentActivity = activity; this.mLifecycleOwner = activity; this.mContext = activity; this.mPreviewView = previewView; initData(); } public DefaultCameraScan(@NonNull Fragment fragment, @NonNull PreviewView previewView) { this.mFragmentActivity = fragment.getActivity(); this.mLifecycleOwner = fragment; this.mContext = fragment.getContext(); this.mPreviewView = previewView; initData(); } /** * 缩放手势检测 */ private ScaleGestureDetector.OnScaleGestureListener mOnScaleGestureListener = new ScaleGestureDetector.SimpleOnScaleGestureListener() { @Override public boolean onScale(ScaleGestureDetector detector) { float scale = detector.getScaleFactor(); if (mCamera != null) { float ratio = mCamera.getCameraInfo().getZoomState().getValue().getZoomRatio(); // 根据缩放的手势和当前比例进行缩放 zoomTo(ratio * scale); return true; } return false; } }; /** * 初始化 */ @SuppressLint("ClickableViewAccessibility") private void initData() { mResultLiveData = new MutableLiveData<>(); mResultLiveData.observe(mLifecycleOwner, result -> { if (result != null) { handleAnalyzeResult(result); } else if (mOnScanResultCallback != null) { mOnScanResultCallback.onScanResultFailure(); } }); mOrientation = mContext.getResources().getConfiguration().orientation; ScaleGestureDetector scaleGestureDetector = new ScaleGestureDetector(mContext, mOnScaleGestureListener); mPreviewView.setOnTouchListener((v, event) -> { handlePreviewViewClickTap(event); if (isNeedTouchZoom()) { return scaleGestureDetector.onTouchEvent(event); } return false; }); mBeepManager = new BeepManager(mContext); mAmbientLightManager = new AmbientLightManager(mContext); mAmbientLightManager.register(); mAmbientLightManager.setOnLightSensorEventListener((dark, lightLux) -> { if (flashlightView != null) { if (dark) { if (flashlightView.getVisibility() != View.VISIBLE) { flashlightView.setVisibility(View.VISIBLE); flashlightView.setSelected(isTorchEnabled()); } } else if (flashlightView.getVisibility() == View.VISIBLE && !isTorchEnabled()) { flashlightView.setVisibility(View.INVISIBLE); flashlightView.setSelected(false); } } }); } /** * 处理预览视图点击事件;如果触发的点击事件被判定对焦操作,则开始自动对焦 * * @param event 事件 */ private void handlePreviewViewClickTap(MotionEvent event) { if (event.getPointerCount() == 1) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: isClickTap = true; mDownX = event.getX(); mDownY = event.getY(); mLastHoveTapTime = System.currentTimeMillis(); break; case MotionEvent.ACTION_MOVE: isClickTap = MathUtils.distance(mDownX, mDownY, event.getX(), event.getY()) < HOVER_TAP_SLOP; break; case MotionEvent.ACTION_UP: if (isClickTap && mLastHoveTapTime + HOVER_TAP_TIMEOUT > System.currentTimeMillis()) { // 开始对焦和测光 startFocusAndMetering(event.getX(), event.getY()); } break; } } } /** * 开始对焦和测光 * * @param x X轴坐标 * @param y Y轴坐标 */ private void startFocusAndMetering(float x, float y) { if (mCamera != null) { MeteringPoint point = mPreviewView.getMeteringPointFactory().createPoint(x, y); FocusMeteringAction focusMeteringAction = new FocusMeteringAction.Builder(point).build(); if (mCamera.getCameraInfo().isFocusMeteringSupported(focusMeteringAction)) { mCamera.getCameraControl().startFocusAndMetering(focusMeteringAction); LogUtils.d("startFocusAndMetering:" + x + "," + y); } } } @Override public CameraScan setCameraConfig(CameraConfig cameraConfig) { if (cameraConfig != null) { this.mCameraConfig = cameraConfig; } return this; } /** * 初始化相机配置 */ /** * 初始化相机配置 */ private void initCameraConfig(Context context) { if (mCameraConfig == null) { DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics(); int size = Math.min(displayMetrics.widthPixels, displayMetrics.heightPixels); // 根据分辨率初始化缺省配置CameraConfig;在此前提下尽可能的找到比屏幕分辨率小一级的配置;在适配、性能与体验之间得有所取舍,找到平衡点。 if (size > ResolutionCameraConfig.IMAGE_QUALITY_1080P) { mCameraConfig = new ResolutionCameraConfig(context); } else if (size > ResolutionCameraConfig.IMAGE_QUALITY_720P) { mCameraConfig = new ResolutionCameraConfig(context, ResolutionCameraConfig.IMAGE_QUALITY_720P); } else { mCameraConfig = new AspectRatioCameraConfig(context); } } } @Override public void startCamera() { initCameraConfig(mContext); if (mAnalyzer == null) { mAnalyzer = new MultiFormatAnalyzer(); } mCameraProviderFuture = ProcessCameraProvider.getInstance(mContext); mCameraProviderFuture.addListener(() -> { try { Preview preview = mCameraConfig.options(new Preview.Builder()); //相机选择器 CameraSelector cameraSelector = mCameraConfig.options(new CameraSelector.Builder()); //设置SurfaceProvider preview.setSurfaceProvider(mPreviewView.getSurfaceProvider()); //图像分析 ImageAnalysis imageAnalysis = mCameraConfig.options(new ImageAnalysis.Builder() .setOutputImageFormat(ImageAnalysis.OUTPUT_IMAGE_FORMAT_YUV_420_888) .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)); imageAnalysis.setAnalyzer(Executors.newSingleThreadExecutor(), image -> { mImageWidth = image.getWidth(); mImageHeight = image.getHeight(); if (isAnalyze && !isAnalyzeResult && mAnalyzer != null) { Result result = mAnalyzer.analyze(image, mOrientation); mResultLiveData.postValue(result); } image.close(); }); if (mCamera != null) { mCameraProviderFuture.get().unbindAll(); } // 绑定到生命周期 mCamera = mCameraProviderFuture.get().bindToLifecycle(mLifecycleOwner, cameraSelector, preview, imageAnalysis); } catch (Exception e) { LogUtils.e(e); } }, ContextCompat.getMainExecutor(mContext)); } /** * 处理分析结果 * * @param result */ private synchronized void handleAnalyzeResult(Result result) { if (isAnalyzeResult || !isAnalyze) { return; } isAnalyzeResult = true; if (mBeepManager != null) { mBeepManager.playBeepSoundAndVibrate(); } if (result.getBarcodeFormat() == BarcodeFormat.QR_CODE && isNeedAutoZoom() && mLastAutoZoomTime + 100 < System.currentTimeMillis()) { ResultPoint[] points = result.getResultPoints(); if (points != null && points.length >= 2) { float distance1 = ResultPoint.distance(points[0], points[1]); float maxDistance = distance1; if (points.length >= 3) { float distance2 = ResultPoint.distance(points[1], points[2]); float distance3 = ResultPoint.distance(points[0], points[2]); maxDistance = Math.max(Math.max(distance1, distance2), distance3); } if (handleAutoZoom((int) maxDistance, result)) { return; } } } scanResultCallback(result); } /** * 处理自动缩放 * * @param distance * @param result * @return */ private boolean handleAutoZoom(int distance, Result result) { int size = Math.min(mImageWidth, mImageHeight); if (distance * 4 < size) { mLastAutoZoomTime = System.currentTimeMillis(); zoomIn(); scanResultCallback(result); return true; } return false; } /** * 扫描结果回调 * * @param result 扫描结果 */ private void scanResultCallback(Result result) { if (mOnScanResultCallback != null && mOnScanResultCallback.onScanResultCallback(result)) { /* * 如果拦截了结果,则重置分析结果状态,并当isAnalyze为true时,默认会继续分析图像(也就是连扫)。 * 如果只是想拦截扫码结果回调,并不想继续分析图像(不想连扫),请在拦截扫码逻辑处通过调用 * setAnalyzeImage(false),因为setAnalyzeImage方法能动态控制是否继续分析图像。 */ isAnalyzeResult = false; return; } if (mFragmentActivity != null) { Intent intent = new Intent(); intent.putExtra(SCAN_RESULT, result.getText()); mFragmentActivity.setResult(Activity.RESULT_OK, intent); mFragmentActivity.finish(); } } @Override public void stopCamera() { if (mCameraProviderFuture != null) { try { mCameraProviderFuture.get().unbindAll(); } catch (Exception e) { LogUtils.e(e); } } } @Override public CameraScan setAnalyzeImage(boolean analyze) { isAnalyze = analyze; return this; } @Override public CameraScan setAnalyzer(Analyzer analyzer) { mAnalyzer = analyzer; return this; } @Override public void zoomIn() { if (mCamera != null) { float ratio = getCameraInfo().getZoomState().getValue().getZoomRatio() + ZOOM_STEP_SIZE; float maxRatio = getCameraInfo().getZoomState().getValue().getMaxZoomRatio(); if (ratio <= maxRatio) { mCamera.getCameraControl().setZoomRatio(ratio); } } } @Override public void zoomOut() { if (mCamera != null) { float ratio = getCameraInfo().getZoomState().getValue().getZoomRatio() - ZOOM_STEP_SIZE; float minRatio = getCameraInfo().getZoomState().getValue().getMinZoomRatio(); if (ratio >= minRatio) { mCamera.getCameraControl().setZoomRatio(ratio); } } } @Override public void zoomTo(float ratio) { if (mCamera != null) { ZoomState zoomState = getCameraInfo().getZoomState().getValue(); float maxRatio = zoomState.getMaxZoomRatio(); float minRatio = zoomState.getMinZoomRatio(); float zoom = Math.max(Math.min(ratio, maxRatio), minRatio); mCamera.getCameraControl().setZoomRatio(zoom); } } @Override public void lineZoomIn() { if (mCamera != null) { float zoom = getCameraInfo().getZoomState().getValue().getLinearZoom() + ZOOM_STEP_SIZE; if (zoom <= 1f) { mCamera.getCameraControl().setLinearZoom(zoom); } } } @Override public void lineZoomOut() { if (mCamera != null) { float zoom = getCameraInfo().getZoomState().getValue().getLinearZoom() - ZOOM_STEP_SIZE; if (zoom >= 0f) { mCamera.getCameraControl().setLinearZoom(zoom); } } } @Override public void lineZoomTo(@FloatRange(from = 0.0, to = 1.0) float linearZoom) { if (mCamera != null) { mCamera.getCameraControl().setLinearZoom(linearZoom); } } @Override public void enableTorch(boolean torch) { if (mCamera != null && hasFlashUnit()) { mCamera.getCameraControl().enableTorch(torch); } } @Override public boolean isTorchEnabled() { if (mCamera != null) { return mCamera.getCameraInfo().getTorchState().getValue() == TorchState.ON; } return false; } @Override public boolean hasFlashUnit() { if (mCamera != null) { return mCamera.getCameraInfo().hasFlashUnit(); } return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_FLASH); } @Override public CameraScan setVibrate(boolean vibrate) { if (mBeepManager != null) { mBeepManager.setVibrate(vibrate); } return this; } @Override public CameraScan setPlayBeep(boolean playBeep) { if (mBeepManager != null) { mBeepManager.setPlayBeep(playBeep); } return this; } @Override public CameraScan setOnScanResultCallback(OnScanResultCallback callback) { this.mOnScanResultCallback = callback; return this; } @Nullable @Override public Camera getCamera() { return mCamera; } /** * CameraInfo * * @return {@link CameraInfo} */ private CameraInfo getCameraInfo() { return mCamera.getCameraInfo(); } @Override public void release() { isAnalyze = false; flashlightView = null; if (mAmbientLightManager != null) { mAmbientLightManager.unregister(); } if (mBeepManager != null) { mBeepManager.close(); } stopCamera(); } @Override public CameraScan bindFlashlightView(@Nullable View v) { flashlightView = v; if (mAmbientLightManager != null) { mAmbientLightManager.setLightSensorEnabled(v != null); } return this; } @Override public CameraScan setDarkLightLux(float lightLux) { if (mAmbientLightManager != null) { mAmbientLightManager.setDarkLightLux(lightLux); } return this; } @Override public CameraScan setBrightLightLux(float lightLux) { if (mAmbientLightManager != null) { mAmbientLightManager.setBrightLightLux(lightLux); } return this; } }