重构v2.0

This commit is contained in:
Jenly
2020-12-24 17:50:42 +08:00
parent 5eb7511ed9
commit a6a529f130
31 changed files with 639 additions and 331 deletions

View File

@@ -6,6 +6,7 @@ import androidx.camera.core.ImageAnalysis;
import androidx.camera.core.Preview;
/**
* 相机配置:主要用于提供相机预览时可自定义一些配置,便于扩展
* @author <a href="mailto:jenly1314@gmail.com">Jenly</a>
*/
public class CameraConfig {

View File

@@ -1,27 +1,23 @@
package com.king.zxing;
import android.content.Intent;
import android.view.MotionEvent;
import android.view.View;
import com.google.zxing.Result;
import com.google.zxing.qrcode.QRCodeReader;
import com.king.zxing.analyze.Analyzer;
import com.king.zxing.util.LogUtils;
import com.king.zxing.analyze.AreaRectAnalyzer;
import com.king.zxing.analyze.BarcodeFormatAnalyzer;
import com.king.zxing.analyze.ImageAnalyzer;
import com.king.zxing.analyze.MultiFormatAnalyzer;
import androidx.annotation.Nullable;
import androidx.camera.core.CameraSelector;
import static com.king.zxing.CameraScan.*;
/**
* @author <a href="mailto:jenly1314@gmail.com">Jenly</a>
*/
public abstract class CameraScan implements ICameraScan {
/**
* 默认触控误差值
*/
private static final int DEVIATION = 6;
public abstract class CameraScan implements ICamera,ICameraControl {
public static String SCAN_RESULT = "SCAN_RESULT";
@@ -34,14 +30,20 @@ public abstract class CameraScan implements ICameraScan {
/**
* 是否需要支持自动缩放
*/
private boolean isNeedAutoZoom = true;
private boolean isNeedAutoZoom = false;
/**
* 是否需要支持触摸缩放
*/
private boolean isNeedTouchZoom = true;
private float mOldDistance;
/**
* 是否需要支持触摸缩放
* @return
*/
protected boolean isNeedTouchZoom() {
return isNeedTouchZoom;
}
/**
@@ -72,46 +74,6 @@ public abstract class CameraScan implements ICameraScan {
return this;
}
protected boolean onTouchEvent(MotionEvent event) {
if(getCamera() != null && isNeedTouchZoom){
LogUtils.d("action:" + (event.getAction() & MotionEvent.ACTION_MASK));
if(event.getPointerCount() > 1) {
switch (event.getAction() & MotionEvent.ACTION_MASK) {//多点触控
case MotionEvent.ACTION_POINTER_DOWN:
mOldDistance = calcFingerSpacing(event);
break;
case MotionEvent.ACTION_MOVE:
float newDistance = calcFingerSpacing(event);
if (newDistance > mOldDistance + DEVIATION) {
zoomIn();
} else if (newDistance < mOldDistance - DEVIATION) {
zoomOut();
}
mOldDistance = newDistance;
break;
case MotionEvent.ACTION_POINTER_UP:
case MotionEvent.ACTION_UP:
return false;
}
}
return true;
}
return false;
}
/**
* 计算两指间距离
* @param event
* @return
*/
private float calcFingerSpacing(MotionEvent event) {
float x = event.getX(0) - event.getX(1);
float y = event.getY(0) - event.getY(1);
return (float) Math.sqrt(x * x + y * y);
}
/**
* 设置相机配置,请在{@link #startCamera()}之前调用
* @param cameraConfig
@@ -125,29 +87,18 @@ public abstract class CameraScan implements ICameraScan {
public abstract CameraScan setAnalyzeImage(boolean analyze);
/**
* 设置分析器
* 设置分析器,内置了一些{@link Analyzer}的实现类如下
* @see {@link MultiFormatAnalyzer}
* @see {@link AreaRectAnalyzer}
* @see {@link ImageAnalyzer}
*
* @see {@link BarcodeFormatAnalyzer}
* @see {@link QRCodeReader}
*
* @param analyzer
*/
public abstract CameraScan setAnalyzer(Analyzer analyzer);
/**
* 设置手电筒是否开启
* @param torch
*/
public abstract CameraScan enableTorch(boolean torch);
/**
* 手电筒是否开启
* @return
*/
public abstract boolean isTorchEnabled();
/**
* 是否支持闪光灯
* @return
*/
public abstract boolean hasFlashUnit();
/**
* 设置是否震动
* @param vibrate
@@ -172,6 +123,17 @@ public abstract class CameraScan implements ICameraScan {
*/
public abstract CameraScan bindFlashlightView(@Nullable View v);
/**
* 设置光线足够暗的阈值单位lux需要通过{@link #bindFlashlightView(View)}绑定手电筒才有效
* @param lightLux
*/
public abstract CameraScan setDarkLightLux(float lightLux);
/**
* 设置光线足够明亮的阈值单位lux需要通过{@link #bindFlashlightView(View)}绑定手电筒才有效
* @param lightLux
*/
public abstract CameraScan setBrightLightLux(float lightLux);
public interface OnScanResultCallback{
boolean onScanResultCallback(Result result);

View File

@@ -15,23 +15,26 @@
*/
package com.king.zxing;
import android.Manifest;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.View;
import com.google.zxing.Result;
import com.king.zxing.util.LogUtils;
import com.king.zxing.util.PermissionUtils;
import androidx.annotation.LayoutRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.camera.view.PreviewView;
import androidx.core.app.ActivityCompat;
/**
* @author <a href="mailto:jenly1314@gmail.com">Jenly</a>
*/
public class CaptureActivity extends AppCompatActivity implements CameraScan.OnScanResultCallback{
private static final int CAMERA_PERMISSION_REQUEST_CODE = 0X86;
protected PreviewView previewView;
protected ViewfinderView viewfinderView;
@@ -62,32 +65,50 @@ public class CaptureActivity extends AppCompatActivity implements CameraScan.OnS
if(ivFlashlightId != 0){
ivFlashlight = findViewById(ivFlashlightId);
if(ivFlashlight != null){
ivFlashlight.setOnClickListener(v -> toggleTorch());
ivFlashlight.setOnClickListener(v -> toggleTorchState());
}
}
initCameraScan();
startCamera();
}
/**
* 初始化CameraScan
*/
public void initCameraScan(){
mCameraScan = new DefaultCameraScan(this,previewView);
mCameraScan.setOnScanResultCallback(this);
}
/**
* 启动相机预览
*/
public void startCamera(){
if(mCameraScan != null){
mCameraScan.startCamera();
if(PermissionUtils.checkPermission(this,Manifest.permission.CAMERA)){
mCameraScan.startCamera();
}else{
LogUtils.d("checkPermissionResult != PERMISSION_GRANTED");
PermissionUtils.requestPermission(this,Manifest.permission.CAMERA,CAMERA_PERMISSION_REQUEST_CODE);
}
}
}
/**
* 释放相机
*/
private void releaseCamera(){
if(mCameraScan != null){
mCameraScan.release();
}
}
protected void toggleTorch(){
/**
* 切换闪光灯状态(开启/关闭)
*/
protected void toggleTorchState(){
if(mCameraScan != null){
boolean isTorch = mCameraScan.isTorchEnabled();
mCameraScan.enableTorch(!isTorch);
@@ -97,6 +118,27 @@ public class CaptureActivity extends AppCompatActivity implements CameraScan.OnS
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if(requestCode == CAMERA_PERMISSION_REQUEST_CODE){
requestCameraPermissionResult(permissions,grantResults);
}
}
/**
* 请求Camera权限回调结果
* @param permissions
* @param grantResults
*/
public void requestCameraPermissionResult(@NonNull String[] permissions, @NonNull int[] grantResults){
if(PermissionUtils.requestPermissionsResult(Manifest.permission.CAMERA,permissions,grantResults)){
startCamera();
}else{
finish();
}
}
@Override
protected void onDestroy() {
releaseCamera();

View File

@@ -15,12 +15,15 @@
*/
package com.king.zxing;
import android.Manifest;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.google.zxing.Result;
import com.king.zxing.util.LogUtils;
import com.king.zxing.util.PermissionUtils;
import androidx.annotation.LayoutRes;
import androidx.annotation.NonNull;
@@ -32,6 +35,8 @@ import androidx.fragment.app.Fragment;
*/
public class CaptureFragment extends Fragment implements CameraScan.OnScanResultCallback {
private static final int CAMERA_PERMISSION_REQUEST_CODE = 0X86;
private View mRootView;
protected PreviewView previewView;
@@ -73,31 +78,48 @@ public class CaptureFragment extends Fragment implements CameraScan.OnScanResult
if(ivFlashlightId != 0){
ivFlashlight = mRootView.findViewById(ivFlashlightId);
if(ivFlashlight != null){
ivFlashlight.setOnClickListener(v -> toggleTorch());
ivFlashlight.setOnClickListener(v -> toggleTorchState());
}
}
initCameraScan();
startCamera();
}
/**
* 初始化CameraScan
*/
public void initCameraScan(){
mCameraScan = new DefaultCameraScan(this,previewView);
mCameraScan.setOnScanResultCallback(this);
}
/**
* 启动相机预览
*/
public void startCamera(){
if(mCameraScan != null){
mCameraScan.startCamera();
if(PermissionUtils.checkPermission(getContext(), Manifest.permission.CAMERA)){
mCameraScan.startCamera();
}else{
LogUtils.d("checkPermissionResult != PERMISSION_GRANTED");
PermissionUtils.requestPermission(this,Manifest.permission.CAMERA,CAMERA_PERMISSION_REQUEST_CODE);
}
}
}
/**
* 释放相机
*/
private void releaseCamera(){
if(mCameraScan != null){
mCameraScan.release();
}
}
protected void toggleTorch(){
/**
* 切换闪光灯状态(开启/关闭)
*/
protected void toggleTorchState(){
if(mCameraScan != null){
boolean isTorch = mCameraScan.isTorchEnabled();
mCameraScan.enableTorch(!isTorch);
@@ -107,6 +129,27 @@ public class CaptureFragment extends Fragment implements CameraScan.OnScanResult
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if(requestCode == CAMERA_PERMISSION_REQUEST_CODE){
requestCameraPermissionResult(permissions,grantResults);
}
}
/**
* 请求Camera权限回调结果
* @param permissions
* @param grantResults
*/
public void requestCameraPermissionResult(@NonNull String[] permissions, @NonNull int[] grantResults){
if(PermissionUtils.requestPermissionsResult(Manifest.permission.CAMERA,permissions,grantResults)){
startCamera();
}else{
getActivity().finish();
}
}
@Override
public void onDestroy() {
releaseCamera();

View File

@@ -2,6 +2,7 @@ package com.king.zxing;
import android.graphics.Rect;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.DecodeHintType;
import com.google.zxing.common.GlobalHistogramBinarizer;
import com.google.zxing.common.HybridBinarizer;
@@ -12,6 +13,30 @@ import androidx.annotation.FloatRange;
/**
* 解码配置:主要用于在扫码识别时,提供一些配置,便于扩展。通过配置可决定内置分析器的能力,从而间接的控制并简化扫码识别的流程
* <p></>
* 设置解码 {@link #setHints(Map)}内置的一些解码可参见如下:
* @see {@link DecodeFormatManager#DEFAULT_HINTS}
* @see {@link DecodeFormatManager#ALL_HINTS}
* @see {@link DecodeFormatManager#CODE_128_HINTS}
* @see {@link DecodeFormatManager#QR_CODE_HINTS}
* @see {@link DecodeFormatManager#ONE_DIMENSIONAL_HINTS}
* @see {@link DecodeFormatManager#TWO_DIMENSIONAL_HINTS}
* @see {@link DecodeFormatManager#DEFAULT_HINTS}
*
* 如果不满足您也可以通过{@link DecodeFormatManager#createDecodeHints(BarcodeFormat...)}自己配置支持的格式
*
* <p></>
* 识别区域可设置的方式有如下几种:
* {@link #setFullAreaScan(boolean)} 设置是否支持全区域扫码识别,优先级比识别区域高
* {@link #setAnalyzeAreaRect(Rect)} 设置需要分析识别区域,优先级比识别区域比例高,当设置了指定的分析区域时,识别区域比例和识别区域偏移量相关参数都将无效
* {@link #setAreaRectRatio(float)} 设置识别区域比例默认0.9,设置的比例最终会在预览区域裁剪基于此比例的一个矩形进行扫码识别,优先级最低
*
* 因为{@link androidx.camera.view.PreviewView}的预览区域是经过裁剪的所以这里的区域并不是用户所能预览到的区域而是指Camera预览的真实区域
* 您还可以通过{@link CameraScan#setCameraConfig(CameraConfig)}去自定义配置{@link CameraConfig}的配置信息控制预览相关配置信息
*
* 即判定区域分析的优先级顺序为:{@link #setFullAreaScan(boolean)} -> {@link #setAnalyzeAreaRect(Rect)} -> {@link #setAreaRectRatio(float)}
* <p></>
*
* @author <a href="mailto:jenly1314@gmail.com">Jenly</a>
*/
@@ -75,6 +100,18 @@ public class DecodeConfig {
/**
* 设置解码
* @param hints {@link DecodeFormatManager}
*
* 内置的一些解码可参见如下:
* @see {@link DecodeFormatManager#DEFAULT_HINTS}
* @see {@link DecodeFormatManager#ALL_HINTS}
* @see {@link DecodeFormatManager#CODE_128_HINTS}
* @see {@link DecodeFormatManager#QR_CODE_HINTS}
* @see {@link DecodeFormatManager#ONE_DIMENSIONAL_HINTS}
* @see {@link DecodeFormatManager#TWO_DIMENSIONAL_HINTS}
* @see {@link DecodeFormatManager#DEFAULT_HINTS}
*
* 如果不满足您也可以通过{@link DecodeFormatManager#createDecodeHints(BarcodeFormat...)}自己配置支持的格式
*
* @return
*/
public DecodeConfig setHints(Map<DecodeHintType, Object> hints) {
@@ -184,8 +221,19 @@ public class DecodeConfig {
}
/**
* 设置需要分析识别区域,当设置了指定的分析区域时,识别区域比例和识别区域相关参数都将无效
* 设置需要分析识别区域,优先级比识别区域比例高,当设置了指定的分析区域时,识别区域比例和识别区域偏移量相关参数都将无效
* @param analyzeAreaRect
*
* 识别区域可设置的方式有如下几种:
* {@link #setFullAreaScan(boolean)} 设置是否支持全区域扫码识别,优先级比识别区域高
* {@link #setAnalyzeAreaRect(Rect)} 设置需要分析识别区域,优先级比识别区域比例高,当设置了指定的分析区域时,识别区域比例和识别区域偏移量相关参数都将无效
* {@link #setAreaRectRatio(float)} 设置识别区域比例默认0.9,设置的比例最终会在预览区域裁剪基于此比例的一个矩形进行扫码识别,优先级最低
*
* 因为{@link androidx.camera.view.PreviewView}的预览区域是经过裁剪的所以这里的区域并不是用户所能预览到的区域而是指Camera预览的真实区域
* 您还可以通过{@link CameraScan#setCameraConfig(CameraConfig)}去自定义配置{@link CameraConfig}的配置信息控制预览相关配置信息
*
* 即判定区域分析的优先级顺序为:{@link #setFullAreaScan(boolean)} -> {@link #setAnalyzeAreaRect(Rect)} -> {@link #setAreaRectRatio(float)}
*
* @return
*/
public DecodeConfig setAnalyzeAreaRect(Rect analyzeAreaRect) {
@@ -202,8 +250,18 @@ public class DecodeConfig {
}
/**
* 设置是否支持全区域扫码识别,优先级比识别区域比例
* 设置是否支持全区域扫码识别,优先级比识别区域高
* @param fullAreaScan 默认为{@code true}
*
* 识别区域可设置的方式有如下几种:
* {@link #setFullAreaScan(boolean)} 设置是否支持全区域扫码识别,优先级比识别区域高
* {@link #setAnalyzeAreaRect(Rect)} 设置需要分析识别区域,优先级比识别区域比例高,当设置了指定的分析区域时,识别区域比例和识别区域偏移量相关参数都将无效
* {@link #setAreaRectRatio(float)} 设置识别区域比例默认0.9,设置的比例最终会在预览区域裁剪基于此比例的一个矩形进行扫码识别,优先级最低
*
* 因为{@link androidx.camera.view.PreviewView}的预览区域是经过裁剪的所以这里的区域并不是用户所能预览到的区域而是指Camera预览的真实区域
* 您还可以通过{@link CameraScan#setCameraConfig(CameraConfig)}去自定义配置{@link CameraConfig}的配置信息控制预览相关配置信息
*
* 即判定区域分析的优先级顺序为:{@link #setFullAreaScan(boolean)} -> {@link #setAnalyzeAreaRect(Rect)} -> {@link #setAreaRectRatio(float)}
* @return
*/
public DecodeConfig setFullAreaScan(boolean fullAreaScan) {
@@ -220,8 +278,19 @@ public class DecodeConfig {
}
/**
* 设置识别区域比例默认0.9,设置的比例最终会在预览区域裁剪基于此比例的一个矩形进行扫码识别
* 设置识别区域比例默认0.9,设置的比例最终会在预览区域裁剪基于此比例的一个矩形进行扫码识别,优先级最低
* @param areaRectRatio
*
* 识别区域可设置的方式有如下几种:
* {@link #setFullAreaScan(boolean)} 设置是否支持全区域扫码识别,优先级比识别区域高
* {@link #setAnalyzeAreaRect(Rect)} 设置需要分析识别区域,优先级比识别区域比例高,当设置了指定的分析区域时,识别区域比例和识别区域偏移量相关参数都将无效
* {@link #setAreaRectRatio(float)} 设置识别区域比例默认0.9,设置的比例最终会在预览区域裁剪基于此比例的一个矩形进行扫码识别,优先级最低
*
* 因为{@link androidx.camera.view.PreviewView}的预览区域是经过裁剪的所以这里的区域并不是用户所能预览到的区域而是指Camera预览的真实区域
* 您还可以通过{@link CameraScan#setCameraConfig(CameraConfig)}去自定义配置{@link CameraConfig}的配置信息控制预览相关配置信息
*
* 即判定区域分析的优先级顺序为:{@link #setFullAreaScan(boolean)} -> {@link #setAnalyzeAreaRect(Rect)} -> {@link #setAreaRectRatio(float)}
*
* @return
*/
public DecodeConfig setAreaRectRatio(@FloatRange(from = 0.5,to = 1.0) float areaRectRatio) {

View File

@@ -4,7 +4,7 @@ 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;
@@ -24,6 +24,7 @@ import androidx.camera.core.CameraSelector;
import androidx.camera.core.ImageAnalysis;
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;
@@ -87,18 +88,38 @@ public class DefaultCameraScan extends CameraScan {
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 -> {
handleAnalyzeResult(result);
});
ScaleGestureDetector scaleGestureDetector = new ScaleGestureDetector(mContext, mOnScaleGestureListener);
mPreviewView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
LogUtils.d("click");
}
});
mPreviewView.setOnTouchListener((v, event) -> onTouchEvent(event));
mPreviewView.setOnTouchListener((v, event) -> {
if(isNeedTouchZoom()){
return scaleGestureDetector.onTouchEvent(event);
}
return false;
});
DisplayMetrics displayMetrics = mContext.getResources().getDisplayMetrics();
mScreenWidth = displayMetrics.widthPixels;
@@ -109,14 +130,16 @@ public class DefaultCameraScan extends CameraScan {
mAmbientLightManager.register();
mAmbientLightManager.setOnLightSensorEventListener((dark, lightLux) -> {
if(flashlightView != null){
flashlightView.setSelected(!dark);
if(dark){
if(flashlightView.getVisibility() != View.VISIBLE){
flashlightView.setVisibility(View.VISIBLE);
flashlightView.setSelected(isTorchEnabled());
}
}else if(flashlightView.getVisibility() == View.VISIBLE){
}else if(flashlightView.getVisibility() == View.VISIBLE && !isTorchEnabled()){
flashlightView.setVisibility(View.INVISIBLE);
flashlightView.setSelected(false);
}
}
});
}
@@ -131,7 +154,6 @@ public class DefaultCameraScan extends CameraScan {
}
}
@Override
public CameraScan setCameraConfig(CameraConfig cameraConfig) {
if(cameraConfig != null){
@@ -288,10 +310,15 @@ public class DefaultCameraScan extends CameraScan {
}
}
@Override
public void zoomTo(float ratio) {
if(mCamera != null){
mCamera.getCameraControl().setZoomRatio(ratio);
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);
}
}
@@ -323,11 +350,10 @@ public class DefaultCameraScan extends CameraScan {
}
@Override
public CameraScan enableTorch(boolean torch) {
public void enableTorch(boolean torch) {
if(mCamera != null && hasFlashUnit()){
mCamera.getCameraControl().enableTorch(torch);
}
return this;
}
@Override
@@ -401,4 +427,18 @@ public class DefaultCameraScan extends CameraScan {
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;
}
}

View File

@@ -0,0 +1,33 @@
package com.king.zxing;
import androidx.annotation.Nullable;
import androidx.camera.core.Camera;
/**
* @author <a href="mailto:jenly1314@gmail.com">Jenly</a>
*/
public interface ICamera {
/**
* 启动相机预览
*/
void startCamera();
/**
* 停止相机预览
*/
void stopCamera();
/**
* 获取{@link Camera}
* @return
*/
@Nullable Camera getCamera();
/**
* 释放
*/
void release();
}

View File

@@ -1,29 +1,11 @@
package com.king.zxing;
import android.view.MotionEvent;
import com.google.zxing.Result;
import androidx.annotation.FloatRange;
import androidx.annotation.Nullable;
import androidx.camera.core.Camera;
/**
* @author <a href="mailto:jenly1314@gmail.com">Jenly</a>
*/
public interface ICameraScan {
/**
* 启动相机预览
*/
void startCamera();
/**
* 停止相机预览
*/
void stopCamera();
public interface ICameraControl {
/**
* 放大
@@ -42,12 +24,12 @@ public interface ICameraScan {
void zoomTo(float ratio);
/**
* 放大
* 线性放大
*/
void lineZoomIn();
/**
* 缩小
* 线性缩小
*/
void lineZoomOut();
@@ -57,13 +39,21 @@ public interface ICameraScan {
*/
void lineZoomTo(@FloatRange(from = 0.0,to = 1.0) float linearZoom);
/**
* 设置闪光灯手电筒是否开启
* @param torch
*/
void enableTorch(boolean torch);
/**
* 获取{@link Camera}
* 闪光灯手电筒是否开启
* @return
*/
@Nullable Camera getCamera();
void release();
boolean isTorchEnabled();
/**
* 是否支持闪光灯
* @return
*/
boolean hasFlashUnit();
}

View File

@@ -42,6 +42,8 @@ import androidx.annotation.ColorRes;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import static com.king.zxing.ViewfinderView.FrameGravity.*;
/**
* This view is overlaid on top of the camera preview. It adds the viewfinder rectangle and partial
* transparency outside it, as well as the laser scanner animation and result points.
@@ -79,10 +81,10 @@ public class ViewfinderView extends View {
* 扫码框四角颜色
*/
private int cornerColor;
/**
* 结果点颜色
*/
private int resultPointColor;
// /**
// * 结果点颜色
// */
// private int resultPointColor;
/**
* 提示文本与扫码框的边距
@@ -118,14 +120,6 @@ public class ViewfinderView extends View {
*/
private boolean isShowResultPoint;
/**
* 屏幕宽
*/
private int screenWidth;
/**
* 屏幕高
*/
private int screenHeight;
/**
* 扫码框宽
*/
@@ -185,13 +179,17 @@ public class ViewfinderView extends View {
*/
private float frameRatio;
/**
* 扫码框内间距
*/
private float framePaddingLeft;
private float framePaddingTop;
private float framePaddingRight;
private float framePaddingBottom;
private List<ResultPoint> possibleResultPoints;
private List<ResultPoint> lastPossibleResultPoints;
/**
* 扫码框对齐方式
*/
private FrameGravity frameGravity;
public enum LaserStyle{
NONE(0),LINE(1),GRID(2);
@@ -201,13 +199,11 @@ public class ViewfinderView extends View {
}
private static LaserStyle getFromInt(int value){
for(LaserStyle style : LaserStyle.values()){
if(style.mValue == value){
return style;
}
}
return LaserStyle.LINE;
}
}
@@ -222,17 +218,33 @@ public class ViewfinderView extends View {
}
private static TextLocation getFromInt(int value){
for(TextLocation location : TextLocation.values()){
if(location.mValue == value){
return location;
}
}
return TextLocation.TOP;
}
}
public enum FrameGravity {
CENTER(0), LEFT(1), TOP(2), RIGHT(3), BOTTOM(4);
private int mValue;
FrameGravity(int value) {
mValue = value;
}
private static FrameGravity getFromInt(int value) {
for (FrameGravity gravity : values()) {
if (gravity.mValue == value) {
return gravity;
}
}
return CENTER;
}
}
public ViewfinderView(Context context) {
@@ -256,7 +268,7 @@ public class ViewfinderView extends View {
frameColor = array.getColor(R.styleable.ViewfinderView_frameColor, ContextCompat.getColor(context,R.color.viewfinder_frame));
cornerColor = array.getColor(R.styleable.ViewfinderView_cornerColor, ContextCompat.getColor(context,R.color.viewfinder_corner));
laserColor = array.getColor(R.styleable.ViewfinderView_laserColor, ContextCompat.getColor(context,R.color.viewfinder_laser));
resultPointColor = array.getColor(R.styleable.ViewfinderView_resultPointColor, ContextCompat.getColor(context,R.color.viewfinder_result_point_color));
// resultPointColor = array.getColor(R.styleable.ViewfinderView_resultPointColor, ContextCompat.getColor(context,R.color.viewfinder_result_point_color));
labelText = array.getString(R.styleable.ViewfinderView_labelText);
labelTextColor = array.getColor(R.styleable.ViewfinderView_labelTextColor, ContextCompat.getColor(context,R.color.viewfinder_text_color));
@@ -264,7 +276,7 @@ public class ViewfinderView extends View {
labelTextPadding = array.getDimension(R.styleable.ViewfinderView_labelTextPadding,TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,24,getResources().getDisplayMetrics()));
labelTextLocation = TextLocation.getFromInt(array.getInt(R.styleable.ViewfinderView_labelTextLocation,0));
isShowResultPoint = array.getBoolean(R.styleable.ViewfinderView_showResultPoint,false);
// isShowResultPoint = array.getBoolean(R.styleable.ViewfinderView_showResultPoint,false);
frameWidth = array.getDimensionPixelSize(R.styleable.ViewfinderView_frameWidth,0);
frameHeight = array.getDimensionPixelSize(R.styleable.ViewfinderView_frameHeight,0);
@@ -284,27 +296,12 @@ public class ViewfinderView extends View {
framePaddingTop = array.getDimension(R.styleable.ViewfinderView_framePaddingTop,0);
framePaddingRight = array.getDimension(R.styleable.ViewfinderView_framePaddingRight,0);
framePaddingBottom = array.getDimension(R.styleable.ViewfinderView_framePaddingBottom,0);
frameGravity = FrameGravity.getFromInt(array.getInt(R.styleable.ViewfinderView_frameGravity, CENTER.mValue));
array.recycle();
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
textPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
possibleResultPoints = new ArrayList<>(5);
lastPossibleResultPoints = null;
screenWidth = getDisplayMetrics().widthPixels;
screenHeight = getDisplayMetrics().heightPixels;
int size = (int)(Math.min(screenWidth,screenHeight) * frameRatio);
if(frameWidth<=0 || frameWidth > screenWidth){
frameWidth = size;
}
if(frameHeight<=0 || frameHeight > screenHeight){
frameHeight = size;
}
}
private DisplayMetrics getDisplayMetrics(){
@@ -327,13 +324,43 @@ public class ViewfinderView extends View {
this.labelTextSize = textSize;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//扫码框默认居中,支持利用内距偏移扫码框
int leftOffset = (int)((screenWidth - frameWidth) / 2 + framePaddingLeft - framePaddingRight);
int topOffset = (int)((screenHeight - frameHeight) / 2 + framePaddingTop - framePaddingBottom);
frame = new Rect(leftOffset, topOffset, leftOffset + frameWidth, topOffset + frameHeight);
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
initFrame(w,h);
}
private void initFrame(int width,int height){
int size = (int)(Math.min(width,height) * frameRatio);
if(frameWidth <= 0 || frameWidth > width){
frameWidth = size;
}
if(frameHeight <= 0 || frameHeight > height){
frameHeight = size;
}
float leftOffsets = (width - frameWidth) / 2 + framePaddingLeft - framePaddingRight;
float topOffsets = (height - frameHeight) / 2 + framePaddingTop - framePaddingBottom;
switch (frameGravity){
case LEFT:
leftOffsets = framePaddingLeft;
break;
case TOP:
topOffsets = framePaddingTop;
break;
case RIGHT:
leftOffsets = width - frameWidth + framePaddingRight;
break;
case BOTTOM:
topOffsets = height - frameHeight + framePaddingBottom;
break;
}
frame = new Rect((int)leftOffsets, (int)topOffsets, (int)leftOffsets + frameWidth, (int)topOffsets + frameHeight);
}
@Override
@@ -361,8 +388,6 @@ public class ViewfinderView extends View {
drawCorner(canvas, frame);
//绘制提示信息
drawTextInfo(canvas, frame);
//绘制扫码结果点
drawResultPoint(canvas,frame);
// Request another update at the animation interval, but only repaint the laser line,
// not the entire viewfinder mask.
postInvalidateDelayed(scannerAnimationDelay,
@@ -385,11 +410,10 @@ public class ViewfinderView extends View {
StaticLayout staticLayout = new StaticLayout(labelText,textPaint,canvas.getWidth(), Layout.Alignment.ALIGN_NORMAL,1.0f,0.0f,true);
if(labelTextLocation == TextLocation.BOTTOM){
canvas.translate(frame.left + frame.width() / 2,frame.bottom + labelTextPadding);
staticLayout.draw(canvas);
}else{
canvas.translate(frame.left + frame.width() / 2,frame.top - labelTextPadding - staticLayout.getHeight());
staticLayout.draw(canvas);
}
staticLayout.draw(canvas);
}
}
@@ -529,53 +553,16 @@ public class ViewfinderView extends View {
* @param height
*/
private void drawExterior(Canvas canvas, Rect frame, int width, int height) {
paint.setColor(maskColor);
canvas.drawRect(0, 0, width, frame.top, paint);
canvas.drawRect(0, frame.top, frame.left, frame.bottom, paint);
canvas.drawRect(frame.right, frame.top, width, frame.bottom, paint);
canvas.drawRect(0, frame.bottom, width, height, paint);
}
/**
* 绘制扫码结果点
* @param canvas
* @param frame
*/
private void drawResultPoint(Canvas canvas,Rect frame){
if(!isShowResultPoint){
return;
}
List<ResultPoint> currentPossible = possibleResultPoints;
List<ResultPoint> currentLast = lastPossibleResultPoints;
if (currentPossible.isEmpty()) {
lastPossibleResultPoints = null;
} else {
possibleResultPoints = new ArrayList<>(5);
lastPossibleResultPoints = currentPossible;
paint.setAlpha(CURRENT_POINT_OPACITY);
paint.setColor(resultPointColor);
synchronized (currentPossible) {
float radius = POINT_SIZE / 2.0f;
for (ResultPoint point : currentPossible) {
canvas.drawCircle( point.getX(),point.getY(), radius, paint);
}
}
}
if (currentLast != null) {
paint.setAlpha(CURRENT_POINT_OPACITY / 2);
paint.setColor(resultPointColor);
synchronized (currentLast) {
float radius = POINT_SIZE / 2.0f;
for (ResultPoint point : currentLast) {
canvas.drawCircle( point.getX(),point.getY(), radius, paint);
}
}
if(maskColor != 0){
paint.setColor(maskColor);
canvas.drawRect(0, 0, width, frame.top, paint);
canvas.drawRect(0, frame.top, frame.left, frame.bottom, paint);
canvas.drawRect(frame.right, frame.top, width, frame.bottom, paint);
canvas.drawRect(0, frame.bottom, width, height, paint);
}
}
public void drawViewfinder() {
invalidate();
}
@@ -597,21 +584,5 @@ public class ViewfinderView extends View {
}
public void addPossibleResultPoint(ResultPoint point) {
if(isShowResultPoint){
List<ResultPoint> points = possibleResultPoints;
synchronized (points) {
points.add(point);
int size = points.size();
if (size > MAX_RESULT_POINTS) {
// trim it
points.subList(0, size - MAX_RESULT_POINTS / 2).clear();
}
}
}
}
}

View File

@@ -4,6 +4,7 @@ import android.annotation.SuppressLint;
import android.graphics.ImageFormat;
import com.google.zxing.Result;
import com.king.zxing.util.LogUtils;
import java.nio.ByteBuffer;
@@ -35,6 +36,7 @@ public abstract class ImageAnalyzer implements Analyzer {
buffer.get(data);
return analyze(data,image.getWidth(),image.getHeight());
}
LogUtils.w("imageFormat: " + image.getFormat());
return null;
}

View File

@@ -0,0 +1,111 @@
package com.king.zxing.util;
import android.app.Activity;
import android.content.Context;
import android.content.pm.PackageManager;
import androidx.annotation.IntRange;
import androidx.annotation.NonNull;
import androidx.core.app.ActivityCompat;
import androidx.fragment.app.Fragment;
/**
* @author <a href="mailto:jenly1314@gmail.com">Jenly</a>
*/
public class PermissionUtils {
private PermissionUtils(){
throw new AssertionError();
}
/**
* 检测是否授权
* @param context
* @param permission
* @return 返回{@code true} 表示已授权,{@code false}表示未授权
*/
public static boolean checkPermission(@NonNull Context context, @NonNull String permission){
return ActivityCompat.checkSelfPermission(context,permission) == PackageManager.PERMISSION_GRANTED;
}
/**
* 请求权限
* @param activity
* @param permission
* @param requestCode
*/
public static void requestPermission(@NonNull Activity activity,@NonNull String permission, @IntRange(from = 0) int requestCode){
requestPermissions(activity,new String[]{permission},requestCode);
}
/**
* 请求权限
* @param fragment
* @param permission
* @param requestCode
*/
public static void requestPermission(@NonNull Fragment fragment, @NonNull String permission, @IntRange(from = 0) int requestCode){
requestPermissions(fragment,new String[]{permission},requestCode);
}
/**
* 请求权限
* @param activity
* @param permissions
* @param requestCode
*/
public static void requestPermissions(@NonNull Activity activity,@NonNull String[] permissions, @IntRange(from = 0) int requestCode){
ActivityCompat.requestPermissions(activity,permissions,requestCode);
}
/**
* 请求权限
* @param fragment
* @param permissions
* @param requestCode
*/
public static void requestPermissions(@NonNull Fragment fragment,@NonNull String[] permissions, @IntRange(from = 0) int requestCode){
fragment.requestPermissions(permissions,requestCode);
}
/**
* 请求权限结果
* @param requestPermission 请求的权限
* @param permissions
* @param grantResults
* @return 返回{@code true} 表示已授权,{@code false}表示未授权
*/
public static boolean requestPermissionsResult(@NonNull String requestPermission, @NonNull String[] permissions, @NonNull int[] grantResults){
int length = permissions.length;
for(int i = 0; i < length; i++){
if(requestPermission.equals(permissions[i])){
if(grantResults[i] == PackageManager.PERMISSION_GRANTED){
return true;
}
}
}
return false;
}
/**
* 请求权限结果
* @param requestPermissions 请求的权限
* @param permissions
* @param grantResults
* @return 返回{@code true} 表示全部已授权,{@code false}表示未全部授权
*/
public static boolean requestPermissionsResult(@NonNull String[] requestPermissions, @NonNull String[] permissions, @NonNull int[] grantResults){
int length = permissions.length;
for(int i = 0; i < length; i++){
for(int j = 0; j < requestPermissions.length; j++){
if(requestPermissions[j].equals(permissions[i])){
if(grantResults[i] != PackageManager.PERMISSION_GRANTED){
return false;
}
}
}
}
return true;
}
}

View File

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_selected="true" android:drawable="@drawable/zxl_torch_on"/>
<item android:drawable="@drawable/zxl_torch_off"/>
<item android:state_selected="true" android:drawable="@drawable/zxl_flashlight_on"/>
<item android:drawable="@drawable/zxl_flashlight_off"/>
</selector>

View File

@@ -1,16 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (C) 2008 ZXing authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
@@ -28,6 +16,6 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:src="@drawable/zxl_torch_selector"
android:layout_marginTop="@dimen/torchMarginTop" />
android:src="@drawable/zxl_flashlight_selector"
android:layout_marginTop="@dimen/zxl_flashlight_margin_top" />
</FrameLayout>

View File

@@ -4,7 +4,6 @@
<attr name="frameColor" format="color" />
<attr name="cornerColor" format="color"/>
<attr name="laserColor" format="color"/>
<attr name="resultPointColor" format="color"/>
<attr name="labelText" format="string"/>
<attr name="labelTextColor" format="color"/>
<attr name="labelTextSize" format="dimension"/>
@@ -13,7 +12,6 @@
<enum name="top" value="0"/>
<enum name="bottom" value="1"/>
</attr>
<attr name="showResultPoint" format="boolean"/>
<attr name="frameWidth" format="dimension"/>
<attr name="frameHeight" format="dimension"/>
<attr name="gridColumn" format="integer"/>
@@ -34,6 +32,13 @@
<attr name="framePaddingTop" format="dimension"/>
<attr name="framePaddingRight" format="dimension"/>
<attr name="framePaddingBottom" format="dimension"/>
<attr name="frameGravity" format="enum">
<enum name="center" value="0"/>
<enum name="left" value="1"/>
<enum name="top" value="2"/>
<enum name="right" value="3"/>
<enum name="bottom" value="4"/>
</attr>
</declare-styleable>

View File

@@ -5,10 +5,9 @@
<color name="viewfinder_frame">#7F1FB3E2</color>
<color name="viewfinder_corner">#FF1FB3E2</color>
<color name="viewfinder_laser">#FF1FB3E2</color>
<color name="viewfinder_result_point_color">#C0FFBD21</color>
<color name="viewfinder_text_color">#FFC0C0C0</color>
<color name="capture_status_bar_color">#00000000</color>
<color name="capture_navigation_bar_color">#00000000</color>
<color name="zxl_capture_status_bar_color">#00000000</color>
<color name="zxl_capture_navigation_bar_color">#00000000</color>
</resources>

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<dimen name="torchMarginTop">80dp</dimen>
<dimen name="zxl_flashlight_margin_top">80dp</dimen>
</resources>

View File

@@ -1,10 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<item type="id" name="decode"/>
<item type="id" name="decode_failed"/>
<item type="id" name="decode_succeeded"/>
<item type="id" name="launch_product_query"/>
<item type="id" name="quit"/>
<item type="id" name="restart_preview"/>
<item type="id" name="return_scan_result"/>
</resources>

View File

@@ -1,2 +0,0 @@
<resources>
</resources>

View File

@@ -8,8 +8,8 @@
<item name="colorPrimaryDark">@android:color/black</item>
<item name="android:windowTranslucentStatus">true</item>
<item name="android:windowTranslucentNavigation">true</item>
<item name="android:statusBarColor">@color/capture_status_bar_color</item>
<item name="android:navigationBarColor">@color/capture_navigation_bar_color</item>
<item name="android:statusBarColor">@color/zxl_capture_status_bar_color</item>
<item name="android:navigationBarColor">@color/zxl_capture_navigation_bar_color</item>
</style>
</resources>