505 lines
16 KiB
Java
505 lines
16 KiB
Java
package com.king.zxing;
|
||
|
||
import android.app.Activity;
|
||
import android.content.Context;
|
||
import android.content.Intent;
|
||
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.CameraConfig;
|
||
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.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;
|
||
|
||
/**
|
||
* @author <a href="mailto:jenly1314@gmail.com">Jenly</a>
|
||
*/
|
||
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 FragmentActivity mFragmentActivity;
|
||
private Context mContext;
|
||
private LifecycleOwner mLifecycleOwner;
|
||
private PreviewView mPreviewView;
|
||
|
||
private ListenableFuture<ProcessCameraProvider> mCameraProviderFuture;
|
||
private Camera mCamera;
|
||
|
||
private CameraConfig mCameraConfig;
|
||
private Analyzer mAnalyzer;
|
||
|
||
/**
|
||
* 是否分析
|
||
*/
|
||
private volatile boolean isAnalyze = true;
|
||
|
||
/**
|
||
* 是否已经分析出结果
|
||
*/
|
||
private volatile boolean isAnalyzeResult;
|
||
|
||
private View flashlightView;
|
||
|
||
private MutableLiveData<Result> mResultLiveData;
|
||
|
||
private OnScanResultCallback mOnScanResultCallback;
|
||
|
||
private BeepManager mBeepManager;
|
||
private AmbientLightManager mAmbientLightManager;
|
||
|
||
private int mOrientation;
|
||
private int mScreenWidth;
|
||
private int mScreenHeight;
|
||
private long mLastAutoZoomTime;
|
||
private long mLastHoveTapTime;
|
||
private boolean isClickTap;
|
||
private float mDownX;
|
||
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;
|
||
}
|
||
|
||
};
|
||
|
||
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;
|
||
});
|
||
|
||
DisplayMetrics displayMetrics = mContext.getResources().getDisplayMetrics();
|
||
mScreenWidth = displayMetrics.widthPixels;
|
||
mScreenHeight = displayMetrics.heightPixels;
|
||
|
||
LogUtils.d(String.format("displayMetrics:%dx%d",mScreenWidth,mScreenHeight));
|
||
|
||
mBeepManager = new BeepManager(mContext);
|
||
mAmbientLightManager = new AmbientLightManager(mContext);
|
||
if(mAmbientLightManager != null){
|
||
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);
|
||
}
|
||
|
||
}
|
||
});
|
||
}
|
||
}
|
||
|
||
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;
|
||
}
|
||
}
|
||
}
|
||
|
||
private void startFocusAndMetering(float x, float y){
|
||
if(mCamera != null){
|
||
LogUtils.d("startFocusAndMetering:" + x + "," + y);
|
||
MeteringPoint point = mPreviewView.getMeteringPointFactory().createPoint(x,y);
|
||
mCamera.getCameraControl().startFocusAndMetering(new FocusMeteringAction.Builder(point).build());
|
||
}
|
||
}
|
||
|
||
|
||
|
||
private void initConfig(){
|
||
if(mCameraConfig == null){
|
||
mCameraConfig = new CameraConfig();
|
||
}
|
||
if(mAnalyzer == null){
|
||
mAnalyzer = new MultiFormatAnalyzer();
|
||
}
|
||
}
|
||
|
||
|
||
@Override
|
||
public CameraScan setCameraConfig(CameraConfig cameraConfig) {
|
||
if(cameraConfig != null){
|
||
this.mCameraConfig = cameraConfig;
|
||
}
|
||
return this;
|
||
}
|
||
|
||
@Override
|
||
public void startCamera(){
|
||
initConfig();
|
||
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()
|
||
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST));
|
||
imageAnalysis.setAnalyzer(Executors.newSingleThreadExecutor(), image -> {
|
||
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);
|
||
}
|
||
|
||
private boolean handleAutoZoom(int distance,Result result){
|
||
int size = Math.min(mScreenWidth,mScreenHeight);
|
||
if(distance * 4 < size){
|
||
mLastAutoZoomTime = System.currentTimeMillis();
|
||
zoomIn();
|
||
scanResultCallback(result);
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
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 = mCamera.getCameraInfo().getZoomState().getValue().getZoomRatio() + 0.1f;
|
||
float maxRatio = mCamera.getCameraInfo().getZoomState().getValue().getMaxZoomRatio();
|
||
if(ratio <= maxRatio){
|
||
mCamera.getCameraControl().setZoomRatio(ratio);
|
||
}
|
||
}
|
||
}
|
||
|
||
@Override
|
||
public void zoomOut(){
|
||
if(mCamera != null){
|
||
float ratio = mCamera.getCameraInfo().getZoomState().getValue().getZoomRatio() - 0.1f;
|
||
float minRatio = mCamera.getCameraInfo().getZoomState().getValue().getMinZoomRatio();
|
||
if(ratio >= minRatio){
|
||
mCamera.getCameraControl().setZoomRatio(ratio);
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
@Override
|
||
public void zoomTo(float ratio) {
|
||
if(mCamera != null){
|
||
ZoomState zoomState = mCamera.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 = mCamera.getCameraInfo().getZoomState().getValue().getLinearZoom() + 0.1f;
|
||
if(zoom <= 1f){
|
||
mCamera.getCameraControl().setLinearZoom(zoom);
|
||
}
|
||
}
|
||
}
|
||
|
||
@Override
|
||
public void lineZoomOut() {
|
||
if(mCamera != null){
|
||
float zoom = mCamera.getCameraInfo().getZoomState().getValue().getLinearZoom() - 0.1f;
|
||
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;
|
||
}
|
||
|
||
/**
|
||
* 是否支持闪光灯
|
||
* @return
|
||
*/
|
||
@Override
|
||
public boolean hasFlashUnit(){
|
||
if(mCamera != null){
|
||
return mCamera.getCameraInfo().hasFlashUnit();
|
||
}
|
||
return false;
|
||
}
|
||
|
||
@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;
|
||
}
|
||
|
||
|
||
@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;
|
||
}
|
||
|
||
public CameraScan setDarkLightLux(float lightLux){
|
||
if(mAmbientLightManager != null){
|
||
mAmbientLightManager.setDarkLightLux(lightLux);
|
||
}
|
||
return this;
|
||
}
|
||
|
||
public CameraScan setBrightLightLux(float lightLux){
|
||
if(mAmbientLightManager != null){
|
||
mAmbientLightManager.setBrightLightLux(lightLux);
|
||
}
|
||
return this;
|
||
}
|
||
|
||
}
|