lib -> zxing-lite

This commit is contained in:
Jenly
2021-06-30 14:02:57 +08:00
parent 63f78fd8e6
commit 6a28ea25ba
42 changed files with 1963 additions and 1954 deletions

View File

@@ -0,0 +1,33 @@
package com.king.zxing;
import androidx.annotation.NonNull;
import androidx.camera.core.CameraSelector;
import androidx.camera.core.ImageAnalysis;
import androidx.camera.core.Preview;
/**
* 相机配置:主要用于提供相机预览时可自定义一些配置,便于扩展
* @author <a href="mailto:jenly1314@gmail.com">Jenly</a>
*/
public class CameraConfig {
public CameraConfig(){
}
@NonNull
public Preview options(@NonNull Preview.Builder builder){
return builder.build();
}
@NonNull
public CameraSelector options(@NonNull CameraSelector.Builder builder){
return builder.build();
}
@NonNull
public ImageAnalysis options(@NonNull ImageAnalysis.Builder builder){
return builder.build();
}
}

View File

@@ -0,0 +1,183 @@
package com.king.zxing;
import android.content.Intent;
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.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;
/**
* @author <a href="mailto:jenly1314@gmail.com">Jenly</a>
*/
public abstract class CameraScan implements ICamera,ICameraControl {
public static String SCAN_RESULT = "SCAN_RESULT";
/** A camera on the device facing the same direction as the device's screen. */
public static int LENS_FACING_FRONT = CameraSelector.LENS_FACING_FRONT;
/** A camera on the device facing the opposite direction as the device's screen. */
public static int LENS_FACING_BACK = CameraSelector.LENS_FACING_BACK;
/**
* 是否需要支持自动缩放
*/
private boolean isNeedAutoZoom = false;
/**
* 是否需要支持触摸缩放
*/
private boolean isNeedTouchZoom = true;
/**
* 是否需要支持触摸缩放
* @return
*/
protected boolean isNeedTouchZoom() {
return isNeedTouchZoom;
}
/**
* 设置是否需要支持触摸缩放
* @param needTouchZoom
* @return
*/
public CameraScan setNeedTouchZoom(boolean needTouchZoom) {
isNeedTouchZoom = needTouchZoom;
return this;
}
/**
* 是否需要支持自动缩放
* @return
*/
protected boolean isNeedAutoZoom() {
return isNeedAutoZoom;
}
/**
* 设置是否需要支持自动缩放
* @param needAutoZoom
* @return
*/
public CameraScan setNeedAutoZoom(boolean needAutoZoom) {
isNeedAutoZoom = needAutoZoom;
return this;
}
/**
* 设置相机配置,请在{@link #startCamera()}之前调用
* @param cameraConfig
*/
public abstract CameraScan setCameraConfig(CameraConfig cameraConfig);
/**
* 设置是否分析图像,通过此方法可以动态控制是否分析图像,常用于中断扫码识别。如:连扫时,扫到结果,然后停止分析图像
*
* 1. 因为分析图像默认为true如果想支持连扫在{@link OnScanResultCallback#onScanResultCallback(Result)}返回true拦截即可。
* 当连扫的处理逻辑比较复杂时请在处理逻辑前通过调用setAnalyzeImage(false)来停止分析图像,
* 等逻辑处理完后再调用getCameraScan().setAnalyzeImage(true)来继续分析图像。
*
* 2. 如果只是想拦截扫码结果回调自己处理逻辑,但并不想继续分析图像(即不想连扫),可通过
* 调用getCameraScan().setAnalyzeImage(false)来停止分析图像。
* @param analyze
*/
public abstract CameraScan setAnalyzeImage(boolean analyze);
/**
* 设置分析器,如果内置的一些分析器不满足您的需求,你也可以自定义{@link Analyzer}
* 自定义时,切记需在{@link #startCamera()}之前调用才有效。
*
* 内置了一些{@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 vibrate
*/
public abstract CameraScan setVibrate(boolean vibrate);
/**
* 设置是否播放提示音
* @param playBeep
*/
public abstract CameraScan setPlayBeep(boolean playBeep);
/**
* 设置扫码结果回调
* @param callback
*/
public abstract CameraScan setOnScanResultCallback(OnScanResultCallback callback);
/**
* 绑定手电筒,绑定后可根据光线传感器,动态显示或隐藏手电筒
* @param v
*/
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{
/**
* 扫码结果回调
* @param result
* @return 返回false表示不拦截将关闭扫码界面并将结果返回给调用界面
* 返回true表示拦截需自己处理逻辑。当isAnalyze为true时默认会继续分析图像也就是连扫
* 如果只是想拦截扫码结果回调,并不想继续分析图像(不想连扫),请在拦截扫码逻辑处通过调
* 用{@link CameraScan#setAnalyzeImage(boolean)}
* 因为{@link CameraScan#setAnalyzeImage(boolean)}方法能动态控制是否继续分析图像。
*
*/
boolean onScanResultCallback(Result result);
/**
* 扫码结果识别失败时触发此回调方法
*/
default void onScanResultFailure(){
}
}
/**
* 解析扫码结果
* @param data
* @return
*/
@Nullable
public static String parseScanResult(Intent data){
if(data != null){
return data.getStringExtra(SCAN_RESULT);
}
return null;
}
}

View File

@@ -0,0 +1,214 @@
/*
* Copyright (C) 2018 Jenly Yu
*
* 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.
*/
package com.king.zxing;
import android.Manifest;
import android.os.Bundle;
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;
/**
* @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;
protected View ivFlashlight;
private CameraScan mCameraScan;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
int layoutId = getLayoutId();
if(isContentView(layoutId)){
setContentView(layoutId);
}
initUI();
}
/**
* 初始化
*/
public void initUI(){
previewView = findViewById(getPreviewViewId());
int viewfinderViewId = getViewfinderViewId();
if(viewfinderViewId != 0){
viewfinderView = findViewById(viewfinderViewId);
}
int ivFlashlightId = getFlashlightId();
if(ivFlashlightId != 0){
ivFlashlight = findViewById(ivFlashlightId);
if(ivFlashlight != null){
ivFlashlight.setOnClickListener(v -> onClickFlashlight());
}
}
initCameraScan();
startCamera();
}
/**
* 点击手电筒
*/
protected void onClickFlashlight(){
toggleTorchState();
}
/**
* 初始化CameraScan
*/
public void initCameraScan(){
mCameraScan = new DefaultCameraScan(this,previewView);
mCameraScan.setOnScanResultCallback(this);
}
/**
* 启动相机预览
*/
public void startCamera(){
if(mCameraScan != null){
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 toggleTorchState(){
if(mCameraScan != null){
boolean isTorch = mCameraScan.isTorchEnabled();
mCameraScan.enableTorch(!isTorch);
if(ivFlashlight != null){
ivFlashlight.setSelected(!isTorch);
}
}
}
@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();
super.onDestroy();
}
/**
* 返回true时会自动初始化{@link #setContentView(int)}返回为false是需自己去初始化{@link #setContentView(int)}
* @param layoutId
* @return 默认返回true
*/
public boolean isContentView(@LayoutRes int layoutId){
return true;
}
/**
* 布局id
* @return
*/
public int getLayoutId(){
return R.layout.zxl_capture;
}
/**
* {@link #viewfinderView} 的 ID
* @return 默认返回{@code R.id.viewfinderView}, 如果不需要扫码框可以返回0
*/
public int getViewfinderViewId(){
return R.id.viewfinderView;
}
/**
* 预览界面{@link #previewView} 的ID
* @return
*/
public int getPreviewViewId(){
return R.id.previewView;
}
/**
* 获取 {@link #ivFlashlight} 的ID
* @return 默认返回{@code R.id.ivFlashlight}, 如果不需要手电筒按钮可以返回0
*/
public int getFlashlightId(){
return R.id.ivFlashlight;
}
/**
* Get {@link CameraScan}
* @return {@link #mCameraScan}
*/
public CameraScan getCameraScan(){
return mCameraScan;
}
/**
* 接收扫码结果回调
* @param result 扫码结果
* @return 返回true表示拦截将不自动执行后续逻辑为false表示不拦截默认不拦截
*/
@Override
public boolean onScanResultCallback(Result result) {
return false;
}
}

View File

@@ -0,0 +1,244 @@
/*
* Copyright (C) 2019 Jenly Yu
*
* 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.
*/
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;
import androidx.camera.view.PreviewView;
import androidx.fragment.app.Fragment;
/**
* @author <a href="mailto:jenly1314@gmail.com">Jenly</a>
*/
public class CaptureFragment extends Fragment implements CameraScan.OnScanResultCallback {
private static final int CAMERA_PERMISSION_REQUEST_CODE = 0X86;
private View mRootView;
protected PreviewView previewView;
protected ViewfinderView viewfinderView;
protected View ivFlashlight;
private CameraScan mCameraScan;
public static CaptureFragment newInstance() {
Bundle args = new Bundle();
CaptureFragment fragment = new CaptureFragment();
fragment.setArguments(args);
return fragment;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
int layoutId = getLayoutId();
if(isContentView(layoutId)){
mRootView = createRootView(inflater,container);
}
initUI();
return mRootView;
}
/**
* 初始化
*/
public void initUI(){
previewView = mRootView.findViewById(getPreviewViewId());
int viewfinderViewId = getViewfinderViewId();
if(viewfinderViewId != 0){
viewfinderView = mRootView.findViewById(viewfinderViewId);
}
int ivFlashlightId = getFlashlightId();
if(ivFlashlightId != 0){
ivFlashlight = mRootView.findViewById(ivFlashlightId);
if(ivFlashlight != null){
ivFlashlight.setOnClickListener(v -> onClickFlashlight());
}
}
initCameraScan();
startCamera();
}
/**
* 点击手电筒
*/
protected void onClickFlashlight(){
toggleTorchState();
}
/**
* 初始化CameraScan
*/
public void initCameraScan(){
mCameraScan = new DefaultCameraScan(this,previewView);
mCameraScan.setOnScanResultCallback(this);
}
/**
* 启动相机预览
*/
public void startCamera(){
if(mCameraScan != null){
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 toggleTorchState(){
if(mCameraScan != null){
boolean isTorch = mCameraScan.isTorchEnabled();
mCameraScan.enableTorch(!isTorch);
if(ivFlashlight != null){
ivFlashlight.setSelected(!isTorch);
}
}
}
@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();
super.onDestroy();
}
/**
* 返回true时会自动初始化{@link #createRootView(LayoutInflater, ViewGroup)}返回为false是需自己去初始化{@link #createRootView(LayoutInflater, ViewGroup)}
* @param layoutId
* @return 默认返回true
*/
public boolean isContentView(@LayoutRes int layoutId){
return true;
}
/**
* 创建{@link #mRootView}
* @param inflater
* @param container
* @return
*/
@NonNull
public View createRootView(LayoutInflater inflater, ViewGroup container){
return inflater.inflate(getLayoutId(),container,false);
}
/**
* 布局id
* @return
*/
public int getLayoutId(){
return R.layout.zxl_capture;
}
/**
* {@link #viewfinderView} 的 ID
* @return 默认返回{@code R.id.viewfinderView}, 如果不需要扫码框可以返回0
*/
public int getViewfinderViewId(){
return R.id.viewfinderView;
}
/**
* 预览界面{@link #previewView} 的ID
* @return
*/
public int getPreviewViewId(){
return R.id.previewView;
}
/**
* 获取 {@link #ivFlashlight} 的ID
* @return 默认返回{@code R.id.ivFlashlight}, 如果不需要手电筒按钮可以返回0
*/
public int getFlashlightId(){
return R.id.ivFlashlight;
}
/**
* Get {@link CameraScan}
* @return {@link #mCameraScan}
*/
public CameraScan getCameraScan(){
return mCameraScan;
}
/**
* 接收扫码结果回调
* @param result 扫码结果
* @return 返回true表示拦截将不自动执行后续逻辑为false表示不拦截默认不拦截
*/
@Override
public boolean onScanResultCallback(Result result) {
return false;
}
//--------------------------------------------
public View getRootView() {
return mRootView;
}
}

View File

@@ -0,0 +1,355 @@
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;
import java.util.Map;
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)} 设置识别区域比例,默认{@link #DEFAULT_AREA_RECT_RATIO},设置的比例最终会在预览区域裁剪基于此比例的一个矩形进行扫码识别,优先级最低
*
* 因为{@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>
*/
public class DecodeConfig {
private Map<DecodeHintType,Object> hints = DecodeFormatManager.DEFAULT_HINTS;
public static final float DEFAULT_AREA_RECT_RATIO = 0.8f;
/**
* 是否支持使用多解码
*/
private boolean isMultiDecode = true;
/**
* 是否支持识别反色码(条码黑白颜色反转的码)
*/
private boolean isSupportLuminanceInvert;
/**
* 是否支持识别反色码(条码黑白颜色反转的码)使用多解码
*/
private boolean isSupportLuminanceInvertMultiDecode;
/**
* 是否支持垂直的条码
*/
private boolean isSupportVerticalCode;
/**
* 是否支持垂直的条码,使用多解码
*/
private boolean isSupportVerticalCodeMultiDecode;
/**
* 需要分析识别区域
*/
private Rect analyzeAreaRect;
/**
* 是否支持全区域扫码识别
*/
private boolean isFullAreaScan = false;
/**
* 识别区域比例默认0.8
*/
private float areaRectRatio = DEFAULT_AREA_RECT_RATIO;
/**
* 识别区域垂直方向偏移量
*/
private int areaRectVerticalOffset;
/**
* 识别区域水平方向偏移量
*/
private int areaRectHorizontalOffset;
public DecodeConfig(){
}
public Map<DecodeHintType, Object> getHints() {
return hints;
}
/**
* 设置解码
* @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) {
this.hints = hints;
return this;
}
/**
* 是否支持识别反色码,黑白颜色反转
* @return
*/
public boolean isSupportLuminanceInvert() {
return isSupportLuminanceInvert;
}
/**
* 设置是否支持识别反色码,黑白颜色反转
* @param supportLuminanceInvert 默认为{@code false},想要增强支持扫码识别反色码时可使用,相应的也会增加性能消耗。
* @return
*/
public DecodeConfig setSupportLuminanceInvert(boolean supportLuminanceInvert) {
isSupportLuminanceInvert = supportLuminanceInvert;
return this;
}
/**
* 是否支持扫垂直的条码
* @return
*/
public boolean isSupportVerticalCode() {
return isSupportVerticalCode;
}
/**
* 设置是否支持扫垂直的条码
* @param supportVerticalCode 默认为{@code false},想要增强支持扫码识别垂直的条码时可使用,相应的也会增加性能消耗。
* @return
*/
public DecodeConfig setSupportVerticalCode(boolean supportVerticalCode) {
isSupportVerticalCode = supportVerticalCode;
return this;
}
/**
* 是否支持使用多解码
* @return
*/
public boolean isMultiDecode() {
return isMultiDecode;
}
/**
* 是否支持使用多解码
* @see {@link HybridBinarizer} , {@link GlobalHistogramBinarizer}
* @param multiDecode 默认为{@code true}
* @return
*/
public DecodeConfig setMultiDecode(boolean multiDecode) {
isMultiDecode = multiDecode;
return this;
}
/**
* 是否支持识别反色码(条码黑白颜色反转的码)使用多解码
* @return
*/
public boolean isSupportLuminanceInvertMultiDecode() {
return isSupportLuminanceInvertMultiDecode;
}
/**
* 设置是否支持识别反色码(条码黑白颜色反转的码)使用多解码
* @see {@link HybridBinarizer} , {@link GlobalHistogramBinarizer}
* @param supportLuminanceInvertMultiDecode 默认为{@code false},想要增强支持扫码识别反色码时可使用,相应的也会增加性能消耗。
* @return
*/
public DecodeConfig setSupportLuminanceInvertMultiDecode(boolean supportLuminanceInvertMultiDecode) {
isSupportLuminanceInvertMultiDecode = supportLuminanceInvertMultiDecode;
return this;
}
/**
* 是否支持垂直的条码,使用多解码
* @return
*/
public boolean isSupportVerticalCodeMultiDecode() {
return isSupportVerticalCodeMultiDecode;
}
/**
* 设置是否支持垂直的条码,使用多解码
* @see {@link HybridBinarizer} , {@link GlobalHistogramBinarizer}
* @param supportVerticalCodeMultiDecode 默认为{@code false},想要增强支持扫码识别垂直的条码时可使用,相应的也会增加性能消耗。
* @return
*/
public DecodeConfig setSupportVerticalCodeMultiDecode(boolean supportVerticalCodeMultiDecode) {
isSupportVerticalCodeMultiDecode = supportVerticalCodeMultiDecode;
return this;
}
/**
* 需要分析识别区域
* @return
*/
public Rect getAnalyzeAreaRect() {
return analyzeAreaRect;
}
/**
* 设置需要分析识别区域,优先级比识别区域比例高,当设置了指定的分析区域时,识别区域比例和识别区域偏移量相关参数都将无效
* @param analyzeAreaRect
*
* 识别区域可设置的方式有如下几种:
* {@link #setFullAreaScan(boolean)} 设置是否支持全区域扫码识别,优先级比识别区域高
* {@link #setAnalyzeAreaRect(Rect)} 设置需要分析识别区域,优先级比识别区域比例高,当设置了指定的分析区域时,识别区域比例和识别区域偏移量相关参数都将无效
* {@link #setAreaRectRatio(float)} 设置识别区域比例,默认{@link #DEFAULT_AREA_RECT_RATIO},设置的比例最终会在预览区域裁剪基于此比例的一个矩形进行扫码识别,优先级最低
*
* 因为{@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) {
this.analyzeAreaRect = analyzeAreaRect;
return this;
}
/**
* 是否支持全区域扫码识别
* @return
*/
public boolean isFullAreaScan() {
return isFullAreaScan;
}
/**
* 设置是否支持全区域扫码识别,优先级比识别区域高
* @param fullAreaScan 默认为{@code true}
*
* 识别区域可设置的方式有如下几种:
* {@link #setFullAreaScan(boolean)} 设置是否支持全区域扫码识别,优先级比识别区域高
* {@link #setAnalyzeAreaRect(Rect)} 设置需要分析识别区域,优先级比识别区域比例高,当设置了指定的分析区域时,识别区域比例和识别区域偏移量相关参数都将无效
* {@link #setAreaRectRatio(float)} 设置识别区域比例,默认{@link #DEFAULT_AREA_RECT_RATIO},设置的比例最终会在预览区域裁剪基于此比例的一个矩形进行扫码识别,优先级最低
*
* 因为{@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) {
isFullAreaScan = fullAreaScan;
return this;
}
/**
* 识别区域比例,默认{@link #DEFAULT_AREA_RECT_RATIO},设置的比例最终会在预览区域裁剪基于此比例的一个矩形进行扫码识别
* @return
*/
public float getAreaRectRatio() {
return areaRectRatio;
}
/**
* 设置识别区域比例,默认{@link #DEFAULT_AREA_RECT_RATIO},设置的比例最终会在预览区域裁剪基于此比例的一个矩形进行扫码识别,优先级最低
* @param areaRectRatio
*
* 识别区域可设置的方式有如下几种:
* {@link #setFullAreaScan(boolean)} 设置是否支持全区域扫码识别,优先级比识别区域高
* {@link #setAnalyzeAreaRect(Rect)} 设置需要分析识别区域,优先级比识别区域比例高,当设置了指定的分析区域时,识别区域比例和识别区域偏移量相关参数都将无效
* {@link #setAreaRectRatio(float)} 设置识别区域比例,默认{@link #DEFAULT_AREA_RECT_RATIO},设置的比例最终会在预览区域裁剪基于此比例的一个矩形进行扫码识别,优先级最低
*
* 因为{@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) {
this.areaRectRatio = areaRectRatio;
return this;
}
/**
* 识别区域垂直方向偏移量支持负数大于0时居中心向下偏移小于0时居中心向上偏移
* @return
*/
public int getAreaRectVerticalOffset() {
return areaRectVerticalOffset;
}
/**
* 设置识别区域垂直方向偏移量支持负数大于0时居中心向下偏移小于0时居中心向上偏移
* @param areaRectVerticalOffset
* @return
*/
public DecodeConfig setAreaRectVerticalOffset(int areaRectVerticalOffset) {
this.areaRectVerticalOffset = areaRectVerticalOffset;
return this;
}
/**
* 识别区域水平方向偏移量支持负数大于0时居中心向右偏移小于0时居中心向左偏移
* @return
*/
public int getAreaRectHorizontalOffset() {
return areaRectHorizontalOffset;
}
/**
* 设置识别区域水平方向偏移量支持负数大于0时居中心向右偏移小于0时居中心向左偏移
* @param areaRectHorizontalOffset
* @return
*/
public DecodeConfig setAreaRectHorizontalOffset(int areaRectHorizontalOffset) {
this.areaRectHorizontalOffset = areaRectHorizontalOffset;
return this;
}
@Override
public String toString() {
return "DecodeConfig{" +
"hints=" + hints +
", isMultiDecode=" + isMultiDecode +
", isSupportLuminanceInvert=" + isSupportLuminanceInvert +
", isSupportLuminanceInvertMultiDecode=" + isSupportLuminanceInvertMultiDecode +
", isSupportVerticalCode=" + isSupportVerticalCode +
", isSupportVerticalCodeMultiDecode=" + isSupportVerticalCodeMultiDecode +
", analyzeAreaRect=" + analyzeAreaRect +
", isFullAreaScan=" + isFullAreaScan +
", areaRectRatio=" + areaRectRatio +
", areaRectVerticalOffset=" + areaRectVerticalOffset +
", areaRectHorizontalOffset=" + areaRectHorizontalOffset +
'}';
}
}

View File

@@ -0,0 +1,195 @@
package com.king.zxing;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.DecodeHintType;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import androidx.annotation.NonNull;
/**
* @author <a href="mailto:jenly1314@gmail.com">Jenly</a>
*/
public final class DecodeFormatManager {
/**
* 所有的
*/
public static final Map<DecodeHintType,Object> ALL_HINTS = new EnumMap<>(DecodeHintType.class);
/**
* CODE_128 (最常用的一维码)
*/
public static final Map<DecodeHintType,Object> CODE_128_HINTS = createDecodeHint(BarcodeFormat.CODE_128);
/**
* QR_CODE (最常用的二维码)
*/
public static final Map<DecodeHintType,Object> QR_CODE_HINTS = createDecodeHint(BarcodeFormat.QR_CODE);
/**
* 一维码
*/
public static final Map<DecodeHintType,Object> ONE_DIMENSIONAL_HINTS = new EnumMap<>(DecodeHintType.class);
/**
* 二维码
*/
public static final Map<DecodeHintType,Object> TWO_DIMENSIONAL_HINTS = new EnumMap<>(DecodeHintType.class);
/**
* 默认
*/
public static final Map<DecodeHintType,Object> DEFAULT_HINTS = new EnumMap<>(DecodeHintType.class);
static {
//all hints
addDecodeHintTypes(ALL_HINTS,getAllFormats());
//one dimension
addDecodeHintTypes(ONE_DIMENSIONAL_HINTS,getOneDimensionalFormats());
//Two dimension
addDecodeHintTypes(TWO_DIMENSIONAL_HINTS,getTwoDimensionalFormats());
//default hints
addDecodeHintTypes(DEFAULT_HINTS,getDefaultFormats());
}
/**
* 所有支持的{@link BarcodeFormat}
* @return
*/
private static List<BarcodeFormat> getAllFormats(){
List<BarcodeFormat> list = new ArrayList<>();
list.add(BarcodeFormat.AZTEC);
list.add(BarcodeFormat.CODABAR);
list.add(BarcodeFormat.CODE_39);
list.add(BarcodeFormat.CODE_93);
list.add(BarcodeFormat.CODE_128);
list.add(BarcodeFormat.DATA_MATRIX);
list.add(BarcodeFormat.EAN_8);
list.add(BarcodeFormat.EAN_13);
list.add(BarcodeFormat.ITF);
list.add(BarcodeFormat.MAXICODE);
list.add(BarcodeFormat.PDF_417);
list.add(BarcodeFormat.QR_CODE);
list.add(BarcodeFormat.RSS_14);
list.add(BarcodeFormat.RSS_EXPANDED);
list.add(BarcodeFormat.UPC_A);
list.add(BarcodeFormat.UPC_E);
list.add(BarcodeFormat.UPC_EAN_EXTENSION);
return list;
}
/**
* 二维码
* 包括如下几种格式:
* {@link BarcodeFormat#CODABAR}
* {@link BarcodeFormat#CODE_39}
* {@link BarcodeFormat#CODE_93}
* {@link BarcodeFormat#CODE_128}
* {@link BarcodeFormat#EAN_8}
* {@link BarcodeFormat#EAN_13}
* {@link BarcodeFormat#ITF}
* {@link BarcodeFormat#RSS_14}
* {@link BarcodeFormat#RSS_EXPANDED}
* {@link BarcodeFormat#UPC_A}
* {@link BarcodeFormat#UPC_E}
* {@link BarcodeFormat#UPC_EAN_EXTENSION}
* @return
*/
private static List<BarcodeFormat> getOneDimensionalFormats(){
List<BarcodeFormat> list = new ArrayList<>();
list.add(BarcodeFormat.CODABAR);
list.add(BarcodeFormat.CODE_39);
list.add(BarcodeFormat.CODE_93);
list.add(BarcodeFormat.CODE_128);
list.add(BarcodeFormat.EAN_8);
list.add(BarcodeFormat.EAN_13);
list.add(BarcodeFormat.ITF);
list.add(BarcodeFormat.RSS_14);
list.add(BarcodeFormat.RSS_EXPANDED);
list.add(BarcodeFormat.UPC_A);
list.add(BarcodeFormat.UPC_E);
list.add(BarcodeFormat.UPC_EAN_EXTENSION);
return list;
}
/**
* 二维码
* 包括如下几种格式:
* {@link BarcodeFormat#AZTEC}
* {@link BarcodeFormat#DATA_MATRIX}
* {@link BarcodeFormat#MAXICODE}
* {@link BarcodeFormat#PDF_417}
* {@link BarcodeFormat#QR_CODE}
* @return
*/
private static List<BarcodeFormat> getTwoDimensionalFormats(){
List<BarcodeFormat> list = new ArrayList<>();
list.add(BarcodeFormat.AZTEC);
list.add(BarcodeFormat.DATA_MATRIX);
list.add(BarcodeFormat.MAXICODE);
list.add(BarcodeFormat.PDF_417);
list.add(BarcodeFormat.QR_CODE);
return list;
}
/**
* 默认支持的格式
* 包括如下几种格式:
* {@link BarcodeFormat#QR_CODE}
* {@link BarcodeFormat#UPC_A}
* {@link BarcodeFormat#EAN_13}
* {@link BarcodeFormat#CODE_128}
* @return
*/
private static List<BarcodeFormat> getDefaultFormats(){
List<BarcodeFormat> list = new ArrayList<>();
list.add(BarcodeFormat.QR_CODE);
list.add(BarcodeFormat.UPC_A);
list.add(BarcodeFormat.EAN_13);
list.add(BarcodeFormat.CODE_128);
return list;
}
private static <T> List<T> singletonList(T o){
return Collections.singletonList(o);
}
/**
* 支持解码的格式
* @param barcodeFormats {@link BarcodeFormat}
* @return
*/
public static Map<DecodeHintType,Object> createDecodeHints(@NonNull BarcodeFormat... barcodeFormats){
Map<DecodeHintType,Object> hints = new EnumMap<>(DecodeHintType.class);
addDecodeHintTypes(hints, Arrays.asList(barcodeFormats));
return hints;
}
/**
* 支持解码的格式
* @param barcodeFormat {@link BarcodeFormat}
* @return
*/
public static Map<DecodeHintType,Object> createDecodeHint(@NonNull BarcodeFormat barcodeFormat){
Map<DecodeHintType,Object> hints = new EnumMap<>(DecodeHintType.class);
addDecodeHintTypes(hints,singletonList(barcodeFormat));
return hints;
}
/**
*
* @param hints
* @param formats
*/
private static void addDecodeHintTypes(Map<DecodeHintType,Object> hints,List<BarcodeFormat> formats){
// Image is known to be of one of a few possible formats.
hints.put(DecodeHintType.POSSIBLE_FORMATS, formats);
// Spend more time to try to find a barcode; optimize for accuracy, not speed.
hints.put(DecodeHintType.TRY_HARDER, Boolean.TRUE);
// Specifies what character encoding to use when decoding, where applicable (type String)
hints.put(DecodeHintType.CHARACTER_SET, "UTF-8");
}
}

View File

@@ -0,0 +1,524 @@
package com.king.zxing;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.util.DisplayMetrics;
import android.util.Size;
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.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;
private Size mTargetSize;
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));
//因为为了保持流畅性和性能限制在1080p在此前提下尽可能的找到屏幕接近的分辨率
if(mScreenWidth < mScreenHeight){
float ratio = mScreenWidth / (float)mScreenHeight;
if(ratio > 0.7){//一般应用于平板
mTargetSize = new Size(mScreenWidth,mScreenWidth / 3 * 4);
}else{
mTargetSize = new Size(mScreenWidth,mScreenWidth / 9 * 16);
}
}else{
float ratio = mScreenHeight / (float)mScreenWidth;
if(ratio > 0.7){//一般应用于平板
mTargetSize = new Size(mScreenHeight / 3 * 4, mScreenHeight);
}else{
mTargetSize = new Size(mScreenHeight / 9 * 16, mScreenHeight);
}
}
LogUtils.d("targetSize:" + mTargetSize);
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()
.setTargetResolution(mTargetSize));
//相机选择器
CameraSelector cameraSelector = mCameraConfig.options(new CameraSelector.Builder());
//设置SurfaceProvider
preview.setSurfaceProvider(mPreviewView.getSurfaceProvider());
//图像分析
ImageAnalysis imageAnalysis = mCameraConfig.options(new ImageAnalysis.Builder()
.setTargetResolution(mTargetSize)
.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;
}
}

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

@@ -0,0 +1,59 @@
package com.king.zxing;
import androidx.annotation.FloatRange;
/**
* @author <a href="mailto:jenly1314@gmail.com">Jenly</a>
*/
public interface ICameraControl {
/**
* 放大
*/
void zoomIn();
/**
* 缩小
*/
void zoomOut();
/**
* 缩放到指定比例
* @param ratio
*/
void zoomTo(float ratio);
/**
* 线性放大
*/
void lineZoomIn();
/**
* 线性缩小
*/
void lineZoomOut();
/**
* 线性缩放到指定比例
* @param linearZoom
*/
void lineZoomTo(@FloatRange(from = 0.0,to = 1.0) float linearZoom);
/**
* 设置闪光灯(手电筒)是否开启
* @param torch
*/
void enableTorch(boolean torch);
/**
* 闪光灯(手电筒)是否开启
* @return
*/
boolean isTorchEnabled();
/**
* 是否支持闪光灯
* @return
*/
boolean hasFlashUnit();
}

View File

@@ -0,0 +1,580 @@
package com.king.zxing;
/*
* 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.
*/
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Shader;
import android.text.Layout;
import android.text.StaticLayout;
import android.text.TextPaint;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.TypedValue;
import android.view.View;
import androidx.annotation.ColorInt;
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.
*
* @author dswitkin@google.com (Daniel Switkin)
*/
public class ViewfinderView extends View {
private static final int CURRENT_POINT_OPACITY = 0xA0;
private static final int MAX_RESULT_POINTS = 20;
private static final int POINT_SIZE = 30;
/**
* 画笔
*/
private Paint paint;
/**
* 文本画笔
*/
private TextPaint textPaint;
/**
* 扫码框外面遮罩颜色
*/
private int maskColor;
/**
* 扫描区域边框颜色
*/
private int frameColor;
/**
* 扫描线颜色
*/
private int laserColor;
/**
* 扫码框四角颜色
*/
private int cornerColor;
/**
* 提示文本与扫码框的边距
*/
private float labelTextPadding;
/**
* 提示文本的宽度
*/
private int labelTextWidth;
/**
* 提示文本的位置
*/
private TextLocation labelTextLocation;
/**
* 扫描区域提示文本
*/
private String labelText;
/**
* 扫描区域提示文本颜色
*/
private int labelTextColor;
/**
* 提示文本字体大小
*/
private float labelTextSize;
/**
* 扫描线开始位置
*/
public int scannerStart = 0;
/**
* 扫描线结束位置
*/
public int scannerEnd = 0;
/**
* 扫码框宽
*/
private int frameWidth;
/**
* 扫码框高
*/
private int frameHeight;
/**
* 扫描激光线风格
*/
private LaserStyle laserStyle;
/**
* 网格列数
*/
private int gridColumn;
/**
* 网格高度
*/
private int gridHeight;
/**
* 扫码框
*/
private Rect frame;
/**
* 扫描区边角的宽
*/
private int cornerRectWidth;
/**
* 扫描区边角的高
*/
private int cornerRectHeight;
/**
* 扫描线每次移动距离
*/
private int scannerLineMoveDistance;
/**
* 扫描线高度
*/
private int scannerLineHeight;
/**
* 边框线宽度
*/
private int frameLineWidth;
/**
* 扫描动画延迟间隔时间 默认20毫秒
*/
private int scannerAnimationDelay;
/**
* 扫码框占比
*/
private float frameRatio;
/**
* 扫码框内间距
*/
private float framePaddingLeft;
private float framePaddingTop;
private float framePaddingRight;
private float framePaddingBottom;
/**
* 扫码框对齐方式
*/
private FrameGravity frameGravity;
private Point point;
private int pointColor;
private int pointStrokeColor;
private float pointRadius;
private float pointStrokeRatio = 1.2f;
public enum LaserStyle{
NONE(0),LINE(1),GRID(2);
private int mValue;
LaserStyle(int value){
mValue = value;
}
private static LaserStyle getFromInt(int value){
for(LaserStyle style : LaserStyle.values()){
if(style.mValue == value){
return style;
}
}
return LaserStyle.LINE;
}
}
public enum TextLocation {
TOP(0),BOTTOM(1);
private int mValue;
TextLocation(int value){
mValue = value;
}
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) {
this(context,null);
}
public ViewfinderView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs,0);
}
public ViewfinderView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context,attrs);
}
private void init(Context context, AttributeSet attrs) {
//初始化自定义属性信息
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.ViewfinderView);
maskColor = array.getColor(R.styleable.ViewfinderView_maskColor, ContextCompat.getColor(context,R.color.viewfinder_mask));
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));
labelText = array.getString(R.styleable.ViewfinderView_labelText);
labelTextColor = array.getColor(R.styleable.ViewfinderView_labelTextColor, ContextCompat.getColor(context,R.color.viewfinder_text_color));
labelTextSize = array.getDimension(R.styleable.ViewfinderView_labelTextSize, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,14f,getResources().getDisplayMetrics()));
labelTextPadding = array.getDimension(R.styleable.ViewfinderView_labelTextPadding,TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,24,getResources().getDisplayMetrics()));
labelTextWidth = array.getDimensionPixelSize(R.styleable.ViewfinderView_labelTextWidth,0);
labelTextLocation = TextLocation.getFromInt(array.getInt(R.styleable.ViewfinderView_labelTextLocation,0));
frameWidth = array.getDimensionPixelSize(R.styleable.ViewfinderView_frameWidth,0);
frameHeight = array.getDimensionPixelSize(R.styleable.ViewfinderView_frameHeight,0);
laserStyle = LaserStyle.getFromInt(array.getInt(R.styleable.ViewfinderView_laserStyle,LaserStyle.LINE.mValue));
gridColumn = array.getInt(R.styleable.ViewfinderView_gridColumn,20);
gridHeight = (int)array.getDimension(R.styleable.ViewfinderView_gridHeight,TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,40,getResources().getDisplayMetrics()));
cornerRectWidth = (int)array.getDimension(R.styleable.ViewfinderView_cornerRectWidth,TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,4,getResources().getDisplayMetrics()));
cornerRectHeight = (int)array.getDimension(R.styleable.ViewfinderView_cornerRectHeight,TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,16,getResources().getDisplayMetrics()));
scannerLineMoveDistance = (int)array.getDimension(R.styleable.ViewfinderView_scannerLineMoveDistance,TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,2,getResources().getDisplayMetrics()));
scannerLineHeight = (int)array.getDimension(R.styleable.ViewfinderView_scannerLineHeight,TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,5,getResources().getDisplayMetrics()));
frameLineWidth = (int)array.getDimension(R.styleable.ViewfinderView_frameLineWidth,TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,1,getResources().getDisplayMetrics()));
scannerAnimationDelay = array.getInteger(R.styleable.ViewfinderView_scannerAnimationDelay,20);
frameRatio = array.getFloat(R.styleable.ViewfinderView_frameRatio,0.625f);
framePaddingLeft = array.getDimension(R.styleable.ViewfinderView_framePaddingLeft,0);
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();
pointColor = laserColor;
pointStrokeColor = Color.WHITE;
pointRadius = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,10,getResources().getDisplayMetrics());
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
textPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
}
private DisplayMetrics getDisplayMetrics(){
return getResources().getDisplayMetrics();
}
public void setLabelText(String labelText) {
this.labelText = labelText;
}
public void setLabelTextColor(@ColorInt int color) {
this.labelTextColor = color;
}
public void setLabelTextColorResource(@ColorRes int id){
this.labelTextColor = ContextCompat.getColor(getContext(),id);
}
public void setLabelTextSize(float textSize) {
this.labelTextSize = textSize;
}
@Override
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;
}
if(labelTextWidth <= 0){
labelTextWidth = width - getPaddingLeft() - getPaddingRight();
}
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
public void onDraw(Canvas canvas) {
if (frame == null) {
return;
}
if(scannerStart == 0 || scannerEnd == 0) {
scannerStart = frame.top;
scannerEnd = frame.bottom - scannerLineHeight;
}
int width = canvas.getWidth();
int height = canvas.getHeight();
// 绘制模糊区域
drawExterior(canvas,frame,width,height);
// 绘制扫描动画
drawLaserScanner(canvas,frame);
// 绘制取景区域框
drawFrame(canvas, frame);
// 绘制取景区域边角
drawCorner(canvas, frame);
//绘制提示信息
drawTextInfo(canvas, frame);
// 间隔更新取景区域
postInvalidateDelayed(scannerAnimationDelay, frame.left, frame.top, frame.right, frame.bottom);
}
/**
* 绘制文本
* @param canvas
* @param frame
*/
private void drawTextInfo(Canvas canvas, Rect frame) {
if(!TextUtils.isEmpty(labelText)){
textPaint.setColor(labelTextColor);
textPaint.setTextSize(labelTextSize);
textPaint.setTextAlign(Paint.Align.CENTER);
StaticLayout staticLayout = new StaticLayout(labelText,textPaint,labelTextWidth, Layout.Alignment.ALIGN_NORMAL,1.2f,0.0f,true);
if(labelTextLocation == TextLocation.BOTTOM){
canvas.translate(frame.left + frame.width() / 2,frame.bottom + labelTextPadding);
}else{
canvas.translate(frame.left + frame.width() / 2,frame.top - labelTextPadding - staticLayout.getHeight());
}
staticLayout.draw(canvas);
}
}
/**
* 绘制边角
* @param canvas
* @param frame
*/
private void drawCorner(Canvas canvas, Rect frame) {
paint.setColor(cornerColor);
//左上
canvas.drawRect(frame.left, frame.top, frame.left + cornerRectWidth, frame.top + cornerRectHeight, paint);
canvas.drawRect(frame.left, frame.top, frame.left + cornerRectHeight, frame.top + cornerRectWidth, paint);
//右上
canvas.drawRect(frame.right - cornerRectWidth, frame.top, frame.right, frame.top + cornerRectHeight, paint);
canvas.drawRect(frame.right - cornerRectHeight, frame.top, frame.right, frame.top + cornerRectWidth, paint);
//左下
canvas.drawRect(frame.left, frame.bottom - cornerRectWidth, frame.left + cornerRectHeight, frame.bottom, paint);
canvas.drawRect(frame.left, frame.bottom - cornerRectHeight, frame.left + cornerRectWidth, frame.bottom, paint);
//右下
canvas.drawRect(frame.right - cornerRectWidth, frame.bottom - cornerRectHeight, frame.right, frame.bottom, paint);
canvas.drawRect(frame.right - cornerRectHeight, frame.bottom - cornerRectWidth, frame.right, frame.bottom, paint);
}
/**
* 绘制激光扫描线
* @param canvas
* @param frame
*/
private void drawLaserScanner(Canvas canvas, Rect frame) {
if(laserStyle != null){
paint.setColor(laserColor);
switch (laserStyle){
case LINE://线
drawLineScanner(canvas,frame);
break;
case GRID://网格
drawGridScanner(canvas,frame);
break;
}
paint.setShader(null);
}
}
/**
* 绘制线性式扫描
* @param canvas
* @param frame
*/
private void drawLineScanner(Canvas canvas,Rect frame){
//线性渐变
LinearGradient linearGradient = new LinearGradient(
frame.left, scannerStart,
frame.left, scannerStart + scannerLineHeight,
shadeColor(laserColor),
laserColor,
Shader.TileMode.MIRROR);
paint.setShader(linearGradient);
if(scannerStart <= scannerEnd) {
//椭圆
RectF rectF = new RectF(frame.left + 2 * scannerLineHeight, scannerStart, frame.right - 2 * scannerLineHeight, scannerStart + scannerLineHeight);
canvas.drawOval(rectF, paint);
scannerStart += scannerLineMoveDistance;
} else {
scannerStart = frame.top;
}
}
/**
* 绘制网格式扫描
* @param canvas
* @param frame
*/
private void drawGridScanner(Canvas canvas,Rect frame){
int stroke = 2;
paint.setStrokeWidth(stroke);
//计算Y轴开始位置
int startY = gridHeight > 0 && scannerStart - frame.top > gridHeight ? scannerStart - gridHeight : frame.top;
LinearGradient linearGradient = new LinearGradient(frame.left + frame.width()/2, startY, frame.left + frame.width()/2, scannerStart, new int[]{shadeColor(laserColor), laserColor}, new float[]{0,1f}, LinearGradient.TileMode.CLAMP);
//给画笔设置着色器
paint.setShader(linearGradient);
float wUnit = frame.width() * 1.0f/ gridColumn;
float hUnit = wUnit;
//遍历绘制网格纵线
for (int i = 1; i < gridColumn; i++) {
canvas.drawLine(frame.left + i * wUnit, startY,frame.left + i * wUnit, scannerStart,paint);
}
int height = gridHeight > 0 && scannerStart - frame.top > gridHeight ? gridHeight : scannerStart - frame.top;
//遍历绘制网格横线
for (int i = 0; i <= height/hUnit; i++) {
canvas.drawLine(frame.left, scannerStart - i * hUnit,frame.right, scannerStart - i * hUnit,paint);
}
if(scannerStart<scannerEnd){
scannerStart += scannerLineMoveDistance;
} else {
scannerStart = frame.top;
}
}
/**
* 处理颜色模糊
* @param color
* @return
*/
public int shadeColor(int color) {
String hax = Integer.toHexString(color);
String result = "01"+hax.substring(2);
return Integer.valueOf(result, 16);
}
/**
* 绘制扫描区边框
* @param canvas
* @param frame
*/
private void drawFrame(Canvas canvas, Rect frame) {
paint.setColor(frameColor);
canvas.drawRect(frame.left, frame.top, frame.right, frame.top + frameLineWidth, paint);
canvas.drawRect(frame.left, frame.top, frame.left + frameLineWidth, frame.bottom, paint);
canvas.drawRect(frame.right - frameLineWidth, frame.top, frame.right, frame.bottom, paint);
canvas.drawRect(frame.left, frame.bottom - frameLineWidth, frame.right, frame.bottom, paint);
}
/**
* 绘制模糊区域
* @param canvas
* @param frame
* @param width
* @param height
*/
private void drawExterior(Canvas canvas, Rect frame, int width, int height) {
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();
}
public void setLaserStyle(LaserStyle laserStyle) {
this.laserStyle = laserStyle;
}
}

View File

@@ -0,0 +1,24 @@
package com.king.zxing.analyze;
import com.google.zxing.Result;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.camera.core.ImageProxy;
import android.content.res.Configuration;
/**
* 分析器
* @author <a href="mailto:jenly1314@gmail.com">Jenly</a>
*/
public interface Analyzer {
/**
* Analyzes an image to produce a result.
* @param image The image to analyze
* @param orientation {@link Configuration#ORIENTATION_LANDSCAPE}, {@link Configuration#ORIENTATION_PORTRAIT}.
* @return
*/
@Nullable
Result analyze(@NonNull ImageProxy image,int orientation);
}

View File

@@ -0,0 +1,68 @@
package com.king.zxing.analyze;
import android.graphics.Rect;
import com.google.zxing.DecodeHintType;
import com.google.zxing.Result;
import com.king.zxing.DecodeFormatManager;
import com.king.zxing.DecodeConfig;
import com.king.zxing.util.LogUtils;
import java.util.Map;
import androidx.annotation.Nullable;
/**
* @author <a href="mailto:jenly1314@gmail.com">Jenly</a>
*/
public abstract class AreaRectAnalyzer extends ImageAnalyzer {
DecodeConfig mDecodeConfig;
Map<DecodeHintType,?> mHints;
boolean isMultiDecode = true;
private float mAreaRectRatio = DecodeConfig.DEFAULT_AREA_RECT_RATIO;
private int mAreaRectHorizontalOffset = 0;
private int mAreaRectVerticalOffset = 0;
public AreaRectAnalyzer(@Nullable DecodeConfig config){
this.mDecodeConfig = config;
if(config != null){
mHints = config.getHints();
isMultiDecode = config.isMultiDecode();
mAreaRectRatio = config.getAreaRectRatio();
mAreaRectHorizontalOffset = config.getAreaRectHorizontalOffset();
mAreaRectVerticalOffset = config.getAreaRectVerticalOffset();
}else{
mHints = DecodeFormatManager.DEFAULT_HINTS;
}
}
@Nullable
@Override
public Result analyze(byte[] data, int width, int height) {
if(mDecodeConfig != null){
if(mDecodeConfig.isFullAreaScan()){
//mDecodeConfig为空或者支持全区域扫码识别时直接使用全区域进行扫码识别
return analyze(data,width,height,0,0,width,height);
}
Rect rect = mDecodeConfig.getAnalyzeAreaRect();
if(rect != null){//如果分析区域不为空,则使用指定的区域进行扫码识别
return analyze(data,width,height,rect.left,rect.top,rect.width(),rect.height());
}
}
//如果分析区域为空,则通过识别区域比例和相关的偏移量计算出最终的区域进行扫码识别
int size = (int)(Math.min(width,height) * mAreaRectRatio);
int left = (width-size)/2 + mAreaRectHorizontalOffset;
int top = (height-size)/2 + mAreaRectVerticalOffset;
return analyze(data,width,height,left,top,size,size);
}
@Nullable
public abstract Result analyze(byte[] data, int dataWidth, int dataHeight,int left,int top,int width,int height);
}

View File

@@ -0,0 +1,97 @@
package com.king.zxing.analyze;
import com.google.zxing.BinaryBitmap;
import com.google.zxing.DecodeHintType;
import com.google.zxing.LuminanceSource;
import com.google.zxing.PlanarYUVLuminanceSource;
import com.google.zxing.Reader;
import com.google.zxing.Result;
import com.google.zxing.common.GlobalHistogramBinarizer;
import com.google.zxing.common.HybridBinarizer;
import com.king.zxing.DecodeConfig;
import com.king.zxing.util.LogUtils;
import java.util.Map;
import androidx.annotation.Nullable;
/**
* @author <a href="mailto:jenly1314@gmail.com">Jenly</a>
*/
public abstract class BarcodeFormatAnalyzer extends AreaRectAnalyzer {
private Reader mReader;
public BarcodeFormatAnalyzer(@Nullable Map<DecodeHintType,Object> hints){
this(new DecodeConfig().setHints(hints));
}
public BarcodeFormatAnalyzer(@Nullable DecodeConfig config) {
super(config);
initReader();
}
private void initReader(){
mReader = createReader();
}
@Nullable
@Override
public Result analyze(byte[] data, int dataWidth, int dataHeight,int left,int top,int width,int height) {
Result rawResult = null;
if(mReader != null){
try {
long start = System.currentTimeMillis();
PlanarYUVLuminanceSource source = new PlanarYUVLuminanceSource(data,dataWidth,dataHeight,left,top,width,height,false);
rawResult = decodeInternal(source,isMultiDecode);
if(rawResult == null && mDecodeConfig != null){
if(rawResult == null && mDecodeConfig.isSupportVerticalCode()){
byte[] rotatedData = new byte[data.length];
for (int y = 0; y < dataHeight; y++) {
for (int x = 0; x < dataWidth; x++){
rotatedData[x * dataHeight + dataHeight - y - 1] = data[x + y * dataWidth];
}
}
rawResult = decodeInternal(new PlanarYUVLuminanceSource(rotatedData,dataHeight,dataWidth,top,left,height,width,false),mDecodeConfig.isSupportVerticalCodeMultiDecode());
}
if(mDecodeConfig.isSupportLuminanceInvert()){
rawResult = decodeInternal(source.invert(),mDecodeConfig.isSupportLuminanceInvertMultiDecode());
}
}
if(rawResult != null){
long end = System.currentTimeMillis();
LogUtils.d("Found barcode in " + (end - start) + " ms");
}
} catch (Exception e) {
}finally {
mReader.reset();
}
}
return rawResult;
}
private Result decodeInternal(LuminanceSource source,boolean isMultiDecode){
Result result = null;
try{
try{
//采用HybridBinarizer解析
result = mReader.decode(new BinaryBitmap(new HybridBinarizer(source)),mHints);
}catch (Exception e){
}
if(isMultiDecode && result == null){
//如果没有解析成功再采用GlobalHistogramBinarizer解析一次
result = mReader.decode(new BinaryBitmap(new GlobalHistogramBinarizer(source)),mHints);
}
}catch (Exception e){
}
return result;
}
public abstract Reader createReader();
}

View File

@@ -0,0 +1,55 @@
package com.king.zxing.analyze;
import android.annotation.SuppressLint;
import android.content.res.Configuration;
import android.graphics.ImageFormat;
import com.google.zxing.Result;
import com.king.zxing.util.LogUtils;
import java.nio.ByteBuffer;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.camera.core.ImageProxy;
/**
* 图像分析器
* @author <a href="mailto:jenly1314@gmail.com">Jenly</a>
*/
public abstract class ImageAnalyzer implements Analyzer {
/**
* 分析图像数据
* @param data
* @param width
* @param height
*/
@Nullable
public abstract Result analyze(byte[] data, int width, int height);
@Override
public Result analyze(@NonNull ImageProxy image,int orientation) {
if(image.getFormat() == ImageFormat.YUV_420_888){
@SuppressLint("UnsafeExperimentalUsageError")
ByteBuffer buffer = image.getPlanes()[0].getBuffer();
byte[] data = new byte[buffer.remaining()];
buffer.get(data);
int width = image.getWidth();
int height = image.getHeight();
if(orientation == Configuration.ORIENTATION_PORTRAIT){
byte[] rotatedData = new byte[data.length];
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++){
rotatedData[x * height + height - y - 1] = data[x + y * width];
}
}
return analyze(rotatedData,height,width);
}
return analyze(data,width,height);
}
LogUtils.w("imageFormat: " + image.getFormat());
return null;
}
}

View File

@@ -0,0 +1,99 @@
package com.king.zxing.analyze;
import com.google.zxing.BinaryBitmap;
import com.google.zxing.DecodeHintType;
import com.google.zxing.LuminanceSource;
import com.google.zxing.MultiFormatReader;
import com.google.zxing.PlanarYUVLuminanceSource;
import com.google.zxing.Result;
import com.google.zxing.common.GlobalHistogramBinarizer;
import com.google.zxing.common.HybridBinarizer;
import com.king.zxing.DecodeConfig;
import com.king.zxing.util.LogUtils;
import java.util.Map;
import androidx.annotation.Nullable;
/**
* @author <a href="mailto:jenly1314@gmail.com">Jenly</a>
*/
public class MultiFormatAnalyzer extends AreaRectAnalyzer {
MultiFormatReader mReader;
public MultiFormatAnalyzer(){
this((DecodeConfig)null);
}
public MultiFormatAnalyzer(@Nullable Map<DecodeHintType,Object> hints){
this(new DecodeConfig().setHints(hints));
}
public MultiFormatAnalyzer(@Nullable DecodeConfig config) {
super(config);
initReader();
}
private void initReader(){
mReader = new MultiFormatReader();
}
@Nullable
@Override
public Result analyze(byte[] data, int dataWidth, int dataHeight,int left,int top,int width,int height) {
Result rawResult = null;
try {
long start = System.currentTimeMillis();
mReader.setHints(mHints);
PlanarYUVLuminanceSource source = new PlanarYUVLuminanceSource(data,dataWidth,dataHeight,left,top,width,height,false);
rawResult = decodeInternal(source,isMultiDecode);
if(rawResult == null && mDecodeConfig != null){
if(rawResult == null && mDecodeConfig.isSupportVerticalCode()){
byte[] rotatedData = new byte[data.length];
for (int y = 0; y < dataHeight; y++) {
for (int x = 0; x < dataWidth; x++){
rotatedData[x * dataHeight + dataHeight - y - 1] = data[x + y * dataWidth];
}
}
rawResult = decodeInternal(new PlanarYUVLuminanceSource(rotatedData,dataHeight,dataWidth,top,left,height,width,false),mDecodeConfig.isSupportVerticalCodeMultiDecode());
}
if(rawResult == null && mDecodeConfig.isSupportLuminanceInvert()){
rawResult = decodeInternal(source.invert(),mDecodeConfig.isSupportLuminanceInvertMultiDecode());
}
}
if(rawResult != null){
long end = System.currentTimeMillis();
LogUtils.d("Found barcode in " + (end - start) + " ms");
}
} catch (Exception e) {
}finally {
mReader.reset();
}
return rawResult;
}
private Result decodeInternal(LuminanceSource source,boolean isMultiDecode){
Result result = null;
try{
try{
//采用HybridBinarizer解析
result = mReader.decodeWithState(new BinaryBitmap(new HybridBinarizer(source)));
}catch (Exception e){
}
if(isMultiDecode && result == null){
//如果没有解析成功再采用GlobalHistogramBinarizer解析一次
result = mReader.decodeWithState(new BinaryBitmap(new GlobalHistogramBinarizer(source)));
}
}catch (Exception e){
}
return result;
}
}

View File

@@ -0,0 +1,35 @@
package com.king.zxing.analyze;
import com.google.zxing.DecodeHintType;
import com.google.zxing.Reader;
import com.google.zxing.qrcode.QRCodeReader;
import com.king.zxing.DecodeConfig;
import java.util.Map;
import androidx.annotation.Nullable;
/**
* @author <a href="mailto:jenly1314@gmail.com">Jenly</a>
*/
public class QRCodeAnalyzer extends BarcodeFormatAnalyzer {
public QRCodeAnalyzer() {
this((DecodeConfig)null);
}
public QRCodeAnalyzer(@Nullable Map<DecodeHintType,Object> hints){
this(new DecodeConfig().setHints(hints));
}
public QRCodeAnalyzer(@Nullable DecodeConfig config) {
super(config);
}
@Override
public Reader createReader() {
return new QRCodeReader();
}
}

View File

@@ -0,0 +1,151 @@
package com.king.zxing.manager;
/*
* Copyright (C) 2012 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.
*/
import android.content.Context;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
/**
* @author <a href="mailto:jenly1314@gmail.com">Jenly</a>
*/
public class AmbientLightManager implements SensorEventListener {
private static final int INTERVAL_TIME = 200;
protected static final float DARK_LUX = 45.0f;
protected static final float BRIGHT_LUX = 100.0f;
/**
* 光线太暗时默认照度45 lux
*/
private float darkLightLux = DARK_LUX;
/**
* 光线足够亮时默认照度100 lux
*/
private float brightLightLux = BRIGHT_LUX;
private SensorManager sensorManager;
private Sensor lightSensor;
private long lastTime;
private boolean isLightSensorEnabled;
private OnLightSensorEventListener mOnLightSensorEventListener;
public AmbientLightManager(Context context) {
sensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
lightSensor = sensorManager.getDefaultSensor(Sensor.TYPE_LIGHT);
isLightSensorEnabled = true;
}
public void register() {
if (sensorManager != null && lightSensor != null) {
sensorManager.registerListener(this, lightSensor, SensorManager.SENSOR_DELAY_NORMAL);
}
}
public void unregister() {
if (sensorManager != null && lightSensor != null) {
sensorManager.unregisterListener(this);
}
}
@Override
public void onSensorChanged(SensorEvent sensorEvent) {
if(isLightSensorEnabled){
long currentTime = System.currentTimeMillis();
if(currentTime - lastTime < INTERVAL_TIME){//降低频率
return;
}
lastTime = currentTime;
if (mOnLightSensorEventListener != null) {
float lightLux = sensorEvent.values[0];
mOnLightSensorEventListener.onSensorChanged(lightLux);
if (lightLux <= darkLightLux) {
mOnLightSensorEventListener.onSensorChanged(true,lightLux);
} else if (lightLux >= brightLightLux) {
mOnLightSensorEventListener.onSensorChanged(false,lightLux);
}
}
}
}
/**
* 设置光线足够暗的阈值单位lux
* @param lightLux
*/
public void setDarkLightLux(float lightLux){
this.darkLightLux = lightLux;
}
/**
* 设置光线足够明亮的阈值单位lux
* @param lightLux
*/
public void setBrightLightLux(float lightLux){
this.brightLightLux = lightLux;
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
// do nothing
}
public boolean isLightSensorEnabled() {
return isLightSensorEnabled;
}
/**
* 设置是否启用光线亮度传感器
* @param lightSensorEnabled
*/
public void setLightSensorEnabled(boolean lightSensorEnabled) {
isLightSensorEnabled = lightSensorEnabled;
}
/**
* 设置光线亮度传感器监听器,只有在 {@link #isLightSensorEnabled} 为{@code true} 才有效
* @param listener
*/
public void setOnLightSensorEventListener(OnLightSensorEventListener listener){
mOnLightSensorEventListener = listener;
}
public interface OnLightSensorEventListener{
/**
*
* @param lightLux 当前检测到的光线照度值
*/
default void onSensorChanged(float lightLux){
}
/**
* 传感器改变事件
* @param dark 是否太暗了,当检测到的光线照度值小于{@link #darkLightLux}时,为{@code true}
* @param lightLux 当前检测到的光线照度值
*/
void onSensorChanged(boolean dark,float lightLux);
}
}

View File

@@ -0,0 +1,111 @@
package com.king.zxing.manager;
/*
* Copyright (C) 2010 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.
*/
import android.content.Context;
import android.content.res.AssetFileDescriptor;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.os.Vibrator;
import com.king.zxing.R;
import com.king.zxing.util.LogUtils;
import java.io.Closeable;
/**
* @author <a href="mailto:jenly1314@gmail.com">Jenly</a>
*/
public final class BeepManager implements MediaPlayer.OnErrorListener, Closeable {
private static final long VIBRATE_DURATION = 200L;
private final Context context;
private MediaPlayer mediaPlayer;
private Vibrator vibrator;
private boolean playBeep;
private boolean vibrate;
public BeepManager(Context context) {
this.context = context;
this.mediaPlayer = null;
updatePrefs();
}
public void setVibrate(boolean vibrate){
this.vibrate = vibrate;
}
public void setPlayBeep(boolean playBeep){
this.playBeep = playBeep;
}
private synchronized void updatePrefs() {
if (mediaPlayer == null) {
mediaPlayer = buildMediaPlayer(context);
}
if(vibrator == null){
vibrator = (Vibrator)context.getSystemService(Context.VIBRATOR_SERVICE);
}
}
public synchronized void playBeepSoundAndVibrate() {
if (playBeep && mediaPlayer != null) {
mediaPlayer.start();
}
if (vibrate) {
vibrator.vibrate(VIBRATE_DURATION);
}
}
private MediaPlayer buildMediaPlayer(Context context) {
MediaPlayer mediaPlayer = new MediaPlayer();
try {
AssetFileDescriptor file = context.getResources().openRawResourceFd(R.raw.zxl_beep);
mediaPlayer.setDataSource(file.getFileDescriptor(), file.getStartOffset(), file.getLength());
mediaPlayer.setOnErrorListener(this);
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mediaPlayer.setLooping(false);
mediaPlayer.prepare();
return mediaPlayer;
} catch (Exception e) {
LogUtils.w(e);
mediaPlayer.release();
return null;
}
}
@Override
public synchronized boolean onError(MediaPlayer mp, int what, int extra) {
close();
updatePrefs();
return true;
}
@Override
public synchronized void close() {
try{
if (mediaPlayer != null) {
mediaPlayer.release();
mediaPlayer = null;
}
}catch (Exception e){
LogUtils.e(e);
}
}
}

View File

@@ -0,0 +1,666 @@
/*
* Copyright (C) 2018 Jenly Yu
*
* 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.
*/
package com.king.zxing.util;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.text.TextPaint;
import android.text.TextUtils;
import androidx.annotation.ColorInt;
import androidx.annotation.FloatRange;
import androidx.annotation.NonNull;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.BinaryBitmap;
import com.google.zxing.DecodeHintType;
import com.google.zxing.EncodeHintType;
import com.google.zxing.LuminanceSource;
import com.google.zxing.MultiFormatReader;
import com.google.zxing.MultiFormatWriter;
import com.google.zxing.RGBLuminanceSource;
import com.google.zxing.Result;
import com.google.zxing.WriterException;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.common.GlobalHistogramBinarizer;
import com.google.zxing.common.HybridBinarizer;
import com.google.zxing.qrcode.QRCodeWriter;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
import com.king.zxing.DecodeFormatManager;
import java.util.HashMap;
import java.util.Map;
/**
* @author Jenly <a href="mailto:jenly1314@gmail.com">Jenly</a>
*/
public final class CodeUtils {
public static final int DEFAULT_REQ_WIDTH = 480;
public static final int DEFAULT_REQ_HEIGHT = 640;
private CodeUtils(){
throw new AssertionError();
}
/**
* 生成二维码
* @param content 二维码的内容
* @param heightPix 二维码的高
* @return
*/
public static Bitmap createQRCode(String content, int heightPix) {
return createQRCode(content,heightPix,null);
}
/**
* 生成二维码
* @param content 二维码的内容
* @param heightPix 二维码的高
* @param codeColor 二维码的颜色
* @return
*/
public static Bitmap createQRCode(String content, int heightPix,int codeColor) {
return createQRCode(content,heightPix,null,codeColor);
}
/**
* 生成我二维码
* @param content 二维码的内容
* @param heightPix 二维码的高
* @param logo logo大小默认占二维码的20%
* @return
*/
public static Bitmap createQRCode(String content, int heightPix, Bitmap logo) {
return createQRCode(content,heightPix,logo,Color.BLACK);
}
/**
* 生成我二维码
* @param content 二维码的内容
* @param heightPix 二维码的高
* @param logo logo大小默认占二维码的20%
* @param codeColor 二维码的颜色
* @return
*/
public static Bitmap createQRCode(String content, int heightPix, Bitmap logo,int codeColor) {
return createQRCode(content,heightPix,logo,0.2f,codeColor);
}
/**
* 生成二维码
* @param content 二维码的内容
* @param heightPix 二维码的高
* @param logo 二维码中间的logo
* @param ratio logo所占比例 因为二维码的最大容错率为30%所以建议ratio的范围小于0.3
* @return
*/
public static Bitmap createQRCode(String content, int heightPix, Bitmap logo,@FloatRange(from = 0.0f,to = 1.0f)float ratio) {
//配置参数
Map<EncodeHintType, Object> hints = new HashMap<>();
hints.put( EncodeHintType.CHARACTER_SET, "utf-8");
//容错级别
hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);
//设置空白边距的宽度
hints.put(EncodeHintType.MARGIN, 1); //default is 4
return createQRCode(content,heightPix,logo,ratio,hints);
}
/**
* 生成二维码
* @param content 二维码的内容
* @param heightPix 二维码的高
* @param logo 二维码中间的logo
* @param ratio logo所占比例 因为二维码的最大容错率为30%所以建议ratio的范围小于0.3
* @param codeColor 二维码的颜色
* @return
*/
public static Bitmap createQRCode(String content, int heightPix, Bitmap logo,@FloatRange(from = 0.0f,to = 1.0f)float ratio,int codeColor) {
//配置参数
Map<EncodeHintType, Object> hints = new HashMap<>();
hints.put( EncodeHintType.CHARACTER_SET, "utf-8");
//容错级别
hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);
//设置空白边距的宽度
hints.put(EncodeHintType.MARGIN, 1); //default is 1
return createQRCode(content,heightPix,logo,ratio,hints,codeColor);
}
public static Bitmap createQRCode(String content, int heightPix, Bitmap logo,@FloatRange(from = 0.0f,to = 1.0f)float ratio,Map<EncodeHintType,?> hints) {
return createQRCode(content,heightPix,logo,ratio,hints,Color.BLACK);
}
/**
* 生成二维码
* @param content 二维码的内容
* @param heightPix 二维码的高
* @param logo 二维码中间的logo
* @param ratio logo所占比例 因为二维码的最大容错率为30%所以建议ratio的范围小于0.3
* @param hints
* @param codeColor 二维码的颜色
* @return
*/
public static Bitmap createQRCode(String content, int heightPix, Bitmap logo,@FloatRange(from = 0.0f,to = 1.0f)float ratio,Map<EncodeHintType,?> hints,int codeColor) {
try {
// 图像数据转换,使用了矩阵转换
BitMatrix bitMatrix = new QRCodeWriter().encode(content, BarcodeFormat.QR_CODE, heightPix, heightPix, hints);
int[] pixels = new int[heightPix * heightPix];
// 下面这里按照二维码的算法,逐个生成二维码的图片,
// 两个for循环是图片横列扫描的结果
for (int y = 0; y < heightPix; y++) {
for (int x = 0; x < heightPix; x++) {
if (bitMatrix.get(x, y)) {
pixels[y * heightPix + x] = codeColor;
} else {
pixels[y * heightPix + x] = Color.WHITE;
}
}
}
// 生成二维码图片的格式
Bitmap bitmap = Bitmap.createBitmap(heightPix, heightPix, Bitmap.Config.ARGB_8888);
bitmap.setPixels(pixels, 0, heightPix, 0, 0, heightPix, heightPix);
if (logo != null) {
bitmap = addLogo(bitmap, logo,ratio);
}
return bitmap;
} catch (Exception e) {
LogUtils.w(e.getMessage());
}
return null;
}
/**
* 在二维码中间添加Logo图案
* @param src
* @param logo
* @param ratio logo所占比例 因为二维码的最大容错率为30%所以建议ratio的范围小于0.3
* @return
*/
private static Bitmap addLogo(Bitmap src, Bitmap logo,@FloatRange(from = 0.0f,to = 1.0f) float ratio) {
if (src == null) {
return null;
}
if (logo == null) {
return src;
}
//获取图片的宽高
int srcWidth = src.getWidth();
int srcHeight = src.getHeight();
int logoWidth = logo.getWidth();
int logoHeight = logo.getHeight();
if (srcWidth == 0 || srcHeight == 0) {
return null;
}
if (logoWidth == 0 || logoHeight == 0) {
return src;
}
//logo大小为二维码整体大小
float scaleFactor = srcWidth * ratio / logoWidth;
Bitmap bitmap;
try {
bitmap = Bitmap.createBitmap(srcWidth, srcHeight, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
canvas.drawBitmap(src, 0, 0, null);
canvas.scale(scaleFactor, scaleFactor, srcWidth / 2, srcHeight / 2);
canvas.drawBitmap(logo, (srcWidth - logoWidth) / 2, (srcHeight - logoHeight) / 2, null);
canvas.save();
canvas.restore();
} catch (Exception e) {
bitmap = null;
LogUtils.w(e.getMessage());
}
return bitmap;
}
/**
* 解析二维码图片
* @param bitmapPath 需要解析的图片路径
* @return
*/
public static String parseQRCode(String bitmapPath){
Result result = parseQRCodeResult(bitmapPath);
if(result != null){
return result.getText();
}
return null;
}
/**
* 解析二维码图片
* @param bitmapPath 需要解析的图片路径
* @return
*/
public static Result parseQRCodeResult(String bitmapPath){
return parseQRCodeResult(bitmapPath,DEFAULT_REQ_WIDTH,DEFAULT_REQ_HEIGHT);
}
/**
* 解析二维码图片
* @param bitmapPath 需要解析的图片路径
* @param reqWidth 请求目标宽度,如果实际图片宽度大于此值,会自动进行压缩处理,当 reqWidth 和 reqHeight都小于或等于0时则不进行压缩处理
* @param reqHeight 请求目标高度,如果实际图片高度大于此值,会自动进行压缩处理,当 reqWidth 和 reqHeight都小于或等于0时则不进行压缩处理
* @return
*/
public static Result parseQRCodeResult(String bitmapPath,int reqWidth,int reqHeight){
return parseCodeResult(bitmapPath,reqWidth,reqHeight, DecodeFormatManager.QR_CODE_HINTS);
}
/**
* 解析一维码/二维码图片
* @param bitmapPath 需要解析的图片路径
* @return
*/
public static String parseCode(String bitmapPath){
return parseCode(bitmapPath, DecodeFormatManager.ALL_HINTS);
}
/**
* 解析一维码/二维码图片
* @param bitmapPath 需要解析的图片路径
* @param hints 解析编码类型
* @return
*/
public static String parseCode(String bitmapPath, Map<DecodeHintType,Object> hints){
Result result = parseCodeResult(bitmapPath,hints);
if(result != null){
return result.getText();
}
return null;
}
/**
* 解析二维码图片
* @param bitmap 解析的图片
* @return
*/
public static String parseQRCode(Bitmap bitmap){
return parseCode(bitmap,DecodeFormatManager.QR_CODE_HINTS);
}
/**
* 解析一维码/二维码图片
* @param bitmap 解析的图片
* @return
*/
public static String parseCode(Bitmap bitmap){
return parseCode(bitmap,DecodeFormatManager.ALL_HINTS);
}
/**
* 解析一维码/二维码图片
* @param bitmap 解析的图片
* @param hints 解析编码类型
* @return
*/
public static String parseCode(Bitmap bitmap,Map<DecodeHintType,Object> hints){
Result result = parseCodeResult(bitmap,hints);
if(result != null){
return result.getText();
}
return null;
}
/**
* 解析一维码/二维码图片
* @param bitmapPath
* @param hints 解析编码类型
* @return
*/
public static Result parseCodeResult(String bitmapPath, Map<DecodeHintType,Object> hints){
return parseCodeResult(bitmapPath,DEFAULT_REQ_WIDTH,DEFAULT_REQ_HEIGHT,hints);
}
/**
* 解析一维码/二维码图片
* @param bitmapPath 需要解析的图片路径
* @param reqWidth 请求目标宽度,如果实际图片宽度大于此值,会自动进行压缩处理,当 reqWidth 和 reqHeight都小于或等于0时则不进行压缩处理
* @param reqHeight 请求目标高度,如果实际图片高度大于此值,会自动进行压缩处理,当 reqWidth 和 reqHeight都小于或等于0时则不进行压缩处理
* @param hints 解析编码类型
* @return
*/
public static Result parseCodeResult(String bitmapPath,int reqWidth,int reqHeight, Map<DecodeHintType,Object> hints){
return parseCodeResult(compressBitmap(bitmapPath,reqWidth,reqHeight),hints);
}
/**
* 解析一维码/二维码图片
* @param bitmap 解析的图片
* @return
*/
public static Result parseCodeResult(Bitmap bitmap){
return parseCodeResult(getRGBLuminanceSource(bitmap),DecodeFormatManager.ALL_HINTS);
}
/**
* 解析一维码/二维码图片
* @param bitmap 解析的图片
* @param hints 解析编码类型
* @return
*/
public static Result parseCodeResult(Bitmap bitmap,Map<DecodeHintType,Object> hints){
return parseCodeResult(getRGBLuminanceSource(bitmap),hints);
}
/**
* 解析一维码/二维码图片
* @param source
* @param hints
* @return
*/
public static Result parseCodeResult(LuminanceSource source, Map<DecodeHintType,Object> hints){
Result result = null;
MultiFormatReader reader = new MultiFormatReader();
try{
reader.setHints(hints);
if (source != null) {
result = decodeInternal(reader,source);
if(result == null){
result = decodeInternal(reader,source.invert());
}
if(result == null && source.isRotateSupported()){
result = decodeInternal(reader,source.rotateCounterClockwise());
}
}
}catch (Exception e){
LogUtils.w(e.getMessage());
}finally {
reader.reset();
}
return result;
}
private static Result decodeInternal(MultiFormatReader reader,LuminanceSource source){
Result result = null;
try{
try{
//采用HybridBinarizer解析
result = reader.decodeWithState(new BinaryBitmap(new HybridBinarizer(source)));
}catch (Exception e){
}
if(result == null){
//如果没有解析成功再采用GlobalHistogramBinarizer解析一次
result = reader.decodeWithState(new BinaryBitmap(new GlobalHistogramBinarizer(source)));
}
}catch (Exception e){
}
return result;
}
/**
* 压缩图片
* @param path
* @return
*/
private static Bitmap compressBitmap(String path,int reqWidth,int reqHeight){
if(reqWidth > 0 && reqHeight > 0){//都大于进行判断是否压缩
BitmapFactory.Options newOpts = new BitmapFactory.Options();
// 开始读入图片此时把options.inJustDecodeBounds 设回true了
newOpts.inJustDecodeBounds = true;//获取原始图片大小
BitmapFactory.decodeFile(path, newOpts);// 此时返回bm为空
float width = newOpts.outWidth;
float height = newOpts.outHeight;
// 缩放比,由于是固定比例缩放,只用高或者宽其中一个数据进行计算即可
int wSize = 1;// wSize=1表示不缩放
if (width > reqWidth) {// 如果宽度大的话根据宽度固定大小缩放
wSize = (int) (width / reqWidth);
}
int hSize = 1;// wSize=1表示不缩放
if (height > reqHeight) {// 如果高度高的话根据宽度固定大小缩放
hSize = (int) (height / reqHeight);
}
int size = Math.max(wSize,hSize);
if (size <= 0)
size = 1;
newOpts.inSampleSize = size;// 设置缩放比例
// 重新读入图片注意此时已经把options.inJustDecodeBounds 设回false了
newOpts.inJustDecodeBounds = false;
return BitmapFactory.decodeFile(path, newOpts);
}
return BitmapFactory.decodeFile(path);
}
/**
* 获取RGBLuminanceSource
* @param bitmap
* @return
*/
private static RGBLuminanceSource getRGBLuminanceSource(@NonNull Bitmap bitmap){
int width = bitmap.getWidth();
int height = bitmap.getHeight();
int[] pixels = new int[width * height];
bitmap.getPixels(pixels, 0, bitmap.getWidth(), 0, 0, bitmap.getWidth(), bitmap.getHeight());
return new RGBLuminanceSource(width, height, pixels);
}
/**
* 生成条形码
* @param content
* @param desiredWidth
* @param desiredHeight
* @return
*/
public static Bitmap createBarCode(String content, int desiredWidth, int desiredHeight) {
return createBarCode(content,BarcodeFormat.CODE_128,desiredWidth,desiredHeight,null);
}
/**
* 生成条形码
* @param content
* @param format
* @param desiredWidth
* @param desiredHeight
* @return
*/
public static Bitmap createBarCode(String content,BarcodeFormat format, int desiredWidth, int desiredHeight) {
return createBarCode(content,format,desiredWidth,desiredHeight,null);
}
public static Bitmap createBarCode(String content, int desiredWidth, int desiredHeight, boolean isShowText) {
return createBarCode(content,BarcodeFormat.CODE_128,desiredWidth,desiredHeight,null,isShowText,40,Color.BLACK);
}
/**
* 生成条形码
* @param content
* @param desiredWidth
* @param desiredHeight
* @param isShowText
* @param codeColor
* @return
*/
public static Bitmap createBarCode(String content, int desiredWidth, int desiredHeight, boolean isShowText,@ColorInt int codeColor) {
return createBarCode(content,BarcodeFormat.CODE_128,desiredWidth,desiredHeight,null,isShowText,40,codeColor);
}
/**
* 生成条形码
* @param content
* @param format
* @param desiredWidth
* @param desiredHeight
* @param hints
* @return
*/
public static Bitmap createBarCode(String content, BarcodeFormat format, int desiredWidth, int desiredHeight, Map<EncodeHintType,?> hints) {
return createBarCode(content,format,desiredWidth,desiredHeight,hints,false,40,Color.BLACK);
}
/**
* 生成条形码
* @param content
* @param format
* @param desiredWidth
* @param desiredHeight
* @param hints
* @param isShowText
* @return
*/
public static Bitmap createBarCode(String content, BarcodeFormat format, int desiredWidth, int desiredHeight, Map<EncodeHintType,?> hints, boolean isShowText) {
return createBarCode(content,format,desiredWidth,desiredHeight,hints,isShowText,40,Color.BLACK);
}
/**
* 生成条形码
* @param content
* @param format
* @param desiredWidth
* @param desiredHeight
* @param isShowText
* @param codeColor
* @return
*/
public static Bitmap createBarCode(String content, BarcodeFormat format, int desiredWidth, int desiredHeight, boolean isShowText,@ColorInt int codeColor) {
return createBarCode(content,format,desiredWidth,desiredHeight,null,isShowText,40,codeColor);
}
/**
* 生成条形码
* @param content
* @param format
* @param desiredWidth
* @param desiredHeight
* @param hints
* @param isShowText
* @return
*/
public static Bitmap createBarCode(String content, BarcodeFormat format, int desiredWidth, int desiredHeight, Map<EncodeHintType,?> hints, boolean isShowText,@ColorInt int codeColor) {
return createBarCode(content,format,desiredWidth,desiredHeight,hints,isShowText,40,codeColor);
}
/**
* 生成条形码
* @param content
* @param format
* @param desiredWidth
* @param desiredHeight
* @param hints
* @param isShowText
* @param textSize
* @param codeColor
* @return
*/
public static Bitmap createBarCode(String content,BarcodeFormat format, int desiredWidth, int desiredHeight,Map<EncodeHintType,?> hints,boolean isShowText,int textSize,@ColorInt int codeColor) {
if(TextUtils.isEmpty(content)){
return null;
}
final int WHITE = Color.WHITE;
final int BLACK = codeColor;
MultiFormatWriter writer = new MultiFormatWriter();
try {
BitMatrix result = writer.encode(content, format, desiredWidth,
desiredHeight, hints);
int width = result.getWidth();
int height = result.getHeight();
int[] pixels = new int[width * height];
// All are 0, or black, by default
for (int y = 0; y < height; y++) {
int offset = y * width;
for (int x = 0; x < width; x++) {
pixels[offset + x] = result.get(x, y) ? BLACK : WHITE;
}
}
Bitmap bitmap = Bitmap.createBitmap(width, height,
Bitmap.Config.ARGB_8888);
bitmap.setPixels(pixels, 0, width, 0, 0, width, height);
if(isShowText){
return addCode(bitmap,content,textSize,codeColor,textSize/2);
}
return bitmap;
} catch (WriterException e) {
LogUtils.w(e.getMessage());
}
return null;
}
/**
* 条形码下面添加文本信息
* @param src
* @param code
* @param textSize
* @param textColor
* @return
*/
private static Bitmap addCode(Bitmap src, String code, int textSize, @ColorInt int textColor, int offset) {
if (src == null) {
return null;
}
if (TextUtils.isEmpty(code)) {
return src;
}
//获取图片的宽高
int srcWidth = src.getWidth();
int srcHeight = src.getHeight();
if (srcWidth <= 0 || srcHeight <= 0) {
return null;
}
Bitmap bitmap;
try {
bitmap = Bitmap.createBitmap(srcWidth, srcHeight + textSize + offset * 2, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
canvas.drawBitmap(src, 0, 0, null);
TextPaint paint = new TextPaint();
paint.setTextSize(textSize);
paint.setColor(textColor);
paint.setTextAlign(Paint.Align.CENTER);
canvas.drawText(code,srcWidth/2,srcHeight + textSize /2 + offset,paint);
canvas.save();
canvas.restore();
} catch (Exception e) {
bitmap = null;
LogUtils.w(e.getMessage());
}
return bitmap;
}
}

View File

@@ -0,0 +1,314 @@
/*
Copyright © 2015, 2016 Jenly Yu <a href="mailto:jenly1314@gmail.com">Jenly</a>
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.
*/
package com.king.zxing.util;
import android.util.Log;
/**
* @author Jenly <a href="mailto:jenly1314@gmail.com">Jenly</a>
*/
public class LogUtils {
public static final String TAG = "ZXingLite";
public static final String VERTICAL = "|";
/** 是否显示Log日志 */
private static boolean isShowLog = true;
/** Log日志优先权 */
private static int priority = 1;
/**
* Priority constant for the println method;use System.out.println
*/
public static final int PRINTLN = 1;
/**
* Priority constant for the println method; use Log.v.
*/
public static final int VERBOSE = 2;
/**
* Priority constant for the println method; use Log.d.
*/
public static final int DEBUG = 3;
/**
* Priority constant for the println method; use Log.i.
*/
public static final int INFO = 4;
/**
* Priority constant for the println method; use Log.w.
*/
public static final int WARN = 5;
/**
* Priority constant for the println method; use Log.e.
*/
public static final int ERROR = 6;
/**
* Priority constant for the println method.use Log.wtf.
*/
public static final int ASSERT = 7;
public static final String TAG_FORMAT = "%s.%s(%s:%d)";
private LogUtils(){
throw new AssertionError();
}
public static void setShowLog(boolean isShowLog) {
LogUtils.isShowLog = isShowLog;
}
public static boolean isShowLog() {
return isShowLog;
}
public static int getPriority() {
return priority;
}
public static void setPriority(int priority) {
LogUtils.priority = priority;
}
/**
* 根据堆栈生成TAG
* @return TAG|className.methodName(fileName:lineNumber)
*/
private static String generateTag(StackTraceElement caller) {
String tag = TAG_FORMAT;
String callerClazzName = caller.getClassName();
callerClazzName = callerClazzName.substring(callerClazzName.lastIndexOf(".") + 1);
tag = String.format(tag,callerClazzName, caller.getMethodName(),caller.getFileName(),caller.getLineNumber());
return new StringBuilder().append(TAG).append(VERTICAL).append(tag).toString();
}
/**
* 获取堆栈
* @param n
* n=0 VMStack
* n=1 Thread
* n=3 CurrentStack
* n=4 CallerStack
* ...
* @return
*/
public static StackTraceElement getStackTraceElement(int n) {
return Thread.currentThread().getStackTrace()[n];
}
/**
* 获取调用方的堆栈TAG
* @return
*/
private static String getCallerStackLogTag(){
return generateTag(getStackTraceElement(5));
}
/**
*
* @param t
* @return
*/
private static String getStackTraceString(Throwable t){
return Log.getStackTraceString(t);
}
// -----------------------------------Log.v
/**
* Log.v
* @param msg
*/
public static void v(String msg) {
if (isShowLog && priority <= VERBOSE)
Log.v(getCallerStackLogTag(), String.valueOf(msg));
}
public static void v(Throwable t) {
if (isShowLog && priority <= VERBOSE)
Log.v(getCallerStackLogTag(), getStackTraceString(t));
}
public static void v(String msg,Throwable t) {
if (isShowLog && priority <= VERBOSE)
Log.v(getCallerStackLogTag(), String.valueOf(msg), t);
}
// -----------------------------------Log.d
/**
* Log.d
* @param msg
*/
public static void d(String msg) {
if (isShowLog && priority <= DEBUG)
Log.d(getCallerStackLogTag(), String.valueOf(msg));
}
public static void d(Throwable t) {
if (isShowLog && priority <= DEBUG)
Log.d(getCallerStackLogTag(), getStackTraceString(t));
}
public static void d(String msg,Throwable t) {
if (isShowLog && priority <= DEBUG)
Log.d(getCallerStackLogTag(), String.valueOf(msg), t);
}
// -----------------------------------Log.i
/**
* Log.i
* @param msg
*/
public static void i(String msg) {
if (isShowLog && priority <= INFO)
Log.i(getCallerStackLogTag(), String.valueOf(msg));
}
public static void i(Throwable t) {
if (isShowLog && priority <= INFO)
Log.i(getCallerStackLogTag(), getStackTraceString(t));
}
public static void i(String msg,Throwable t) {
if (isShowLog && priority <= INFO)
Log.i(getCallerStackLogTag(), String.valueOf(msg), t);
}
// -----------------------------------Log.w
/**
* Log.w
* @param msg
*/
public static void w(String msg) {
if (isShowLog && priority <= WARN)
Log.w(getCallerStackLogTag(), String.valueOf(msg));
}
public static void w(Throwable t) {
if (isShowLog && priority <= WARN)
Log.w(getCallerStackLogTag(), getStackTraceString(t));
}
public static void w(String msg,Throwable t) {
if (isShowLog && priority <= WARN)
Log.w(getCallerStackLogTag(), String.valueOf(msg), t);
}
// -----------------------------------Log.e
/**
* Log.e
* @param msg
*/
public static void e(String msg) {
if (isShowLog && priority <= ERROR)
Log.e(getCallerStackLogTag(), String.valueOf(msg));
}
public static void e(Throwable t) {
if (isShowLog && priority <= ERROR)
Log.e(getCallerStackLogTag(), getStackTraceString(t));
}
public static void e(String msg,Throwable t) {
if (isShowLog && priority <= ERROR)
Log.e(getCallerStackLogTag(), String.valueOf(msg), t);
}
// -----------------------------------Log.wtf
/**
* Log.wtf
* @param msg
*/
public static void wtf(String msg) {
if (isShowLog && priority <= ASSERT)
Log.wtf(getCallerStackLogTag(), String.valueOf(msg));
}
public static void wtf(Throwable t) {
if (isShowLog && priority <= ASSERT)
Log.wtf(getCallerStackLogTag(), getStackTraceString(t));
}
public static void wtf(String msg,Throwable t) {
if (isShowLog && priority <= ASSERT)
Log.wtf(getCallerStackLogTag(), String.valueOf(msg), t);
}
// -----------------------------------System.out.print
/**
* System.out.print
*
* @param msg
*/
public static void print(String msg) {
if (isShowLog && priority <= PRINTLN)
System.out.print(msg);
}
public static void print(Object obj) {
if (isShowLog && priority <= PRINTLN)
System.out.print(obj);
}
// -----------------------------------System.out.printf
/**
* System.out.printf
*
* @param msg
*/
public static void printf(String msg) {
if (isShowLog && priority <= PRINTLN)
System.out.printf(msg);
}
// -----------------------------------System.out.println
/**
* System.out.println
*
* @param msg
*/
public static void println(String msg) {
if (isShowLog && priority <= PRINTLN)
System.out.println(msg);
}
public static void println(Object obj) {
if (isShowLog && priority <= PRINTLN)
System.out.println(obj);
}
}

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;
}
}