发布v3.0.0

This commit is contained in:
Jenly
2023-08-23 23:17:08 +08:00
parent cf712b6f4b
commit f633d8e713
69 changed files with 731 additions and 5120 deletions

376
README.md
View File

@ -13,7 +13,7 @@
[![Blog](https://img.shields.io/badge/blog-Jenly-9933CC.svg)](https://jenly1314.github.io/)
[![QQGroup](https://img.shields.io/badge/QQGroup-20867961-blue.svg)](http://shang.qq.com/wpa/qunwpa?idkey=8fcc6a2f88552ea44b1.1.982c94fd124f7bb3ec227e2a400dbbfaad3dc2f5ad)
ZXingLite for Android 是ZXing的精简极速版基于ZXing库优化扫码和生成二维码/条形码功能,扫码界面完全支持自定义,也可一行代码使用默认实现扫码功能。总之你想要的都在这里
ZXingLite for Android 是ZXing的精简极速版基于ZXing库优化扫码和生成二维码/条形码功能,扫码界面完全支持自定义使用ZXingLite可快速实现扫码识别相关功能。
> 简单如斯,你不试试?
## Gif 展示
@ -21,61 +21,17 @@ ZXingLite for Android 是ZXing的精简极速版基于ZXing库优化扫码和
> 你也可以直接下载 [演示App](https://raw.githubusercontent.com/jenly1314/ZXingLite/master/app/release/app-release.apk) 体验效果
## ViewfinderView属性说明
| 属性 | 属性类型 | 默认值 | 属性说明 |
|:------------------------|:----------|:-------------------------------------|:--------------------------------------------------|
| maskColor | color | <font color=#000000>#60000000</font> | 扫描区外遮罩的颜色 |
| frameColor | color | <font color=#1FB3E2>#7F1FB3E2</font> | 扫描区边框的颜色 |
| cornerColor | color | <font color=#1FB3E2>#FF1FB3E2</font> | 扫描区边角的颜色 |
| laserColor | color | <font color=#1FB3E2>#FF1FB3E2</font> | 扫描区激光线的颜色 |
| labelText | string | | 扫描提示文本信息 |
| labelTextColor | color | <font color=#C0C0C0>#FFC0C0C0</font> | 提示文本字体颜色 |
| labelTextSize | dimension | 14sp | 提示文本字体大小 |
| labelTextPadding | dimension | 24dp | 提示文本距离扫描区的间距 |
| labelTextWidth | dimension | | 提示文本的宽度默认为View的宽度 |
| labelTextLocation | enum | bottom | 提示文本显示位置 |
| frameWidth | dimension | | 扫码框宽度 |
| frameHeight | dimension | | 扫码框高度 |
| laserStyle | enum | line | 扫描激光的样式 |
| gridColumn | integer | 20 | 网格扫描激光列数 |
| gridHeight | integer | 40dp | 网格扫描激光高度为0dp时表示动态铺满 |
| cornerRectWidth | dimension | 4dp | 扫描区边角的宽 |
| cornerRectHeight | dimension | 16dp | 扫描区边角的高 |
| scannerLineMoveDistance | dimension | 2dp | 扫描线每次移动距离 |
| scannerLineHeight | dimension | 5dp | 扫描线高度 |
| frameLineWidth | dimension | 1dp | 边框线宽度 |
| scannerAnimationDelay | integer | 20 | 扫描动画延迟间隔时间,单位:毫秒 |
| frameRatio | float | 0.625f | 扫码框与屏幕占比 |
| framePaddingLeft | dimension | 0 | 扫码框左边的内间距 |
| framePaddingTop | dimension | 0 | 扫码框上边的内间距 |
| framePaddingRight | dimension | 0 | 扫码框右边的内间距 |
| framePaddingBottom | dimension | 0 | 扫码框下边的内间距 |
| frameGravity | enum | center | 扫码框对齐方式 |
| pointColor | color | <font color=#1FB3E2>#FF1FB3E2</font> | 结果点的颜色 |
| pointStrokeColor | color | <font color=#FFFFFF>#FFFFFFFF</font> | 结果点描边的颜色 |
| pointRadius | dimension | 15dp | 结果点的半径 |
| pointStrokeRatio | float | 1.2 | 结果点描边半径与结果点半径的比例 |
| pointDrawable | reference | | 结果点自定义图片 |
| showPointAnim | boolean | true | 是否显示结果点的动画 |
| laserDrawable | reference | | 扫描激光自定义图片 |
| laserDrawableRatio | float | 0.625f | 激光扫描图片与屏幕占比 |
| viewfinderStyle | enum | classic | 取景框样式支持classic经典样式带扫码框那种、popular流行样式不带扫码框 |
## 引入
### Gradle:
1. 在Project的 **build.gradle** 里面添加远程仓库
1. 在Project的 **build.gradle** **setting.gradle**添加远程仓库
```gradle
allprojects {
repositories {
//...
mavenCentral()
}
repositories {
//...
mavenCentral()
maven { url 'https://jitpack.io' }
}
```
@ -83,7 +39,7 @@ allprojects {
```gradle
// AndroidX 版本
implementation 'com.github.jenly1314:zxing-lite:2.4.0'
implementation 'com.github.jenly1314:zxing-lite:3.0.0'
```
@ -91,183 +47,59 @@ implementation 'com.github.jenly1314:zxing-lite:2.4.0'
#### 关于ZXingLite版本与编译的SDK版本要求
> 使用 **v2.3.x** 以上版本时,要求 **compileSdkVersion >= 33**
> 使用 **v3.x.x** 以上版本时,要求 **compileSdkVersion >= 33**
> 使用 **v2.2.x** 以上版本时,要求 **compileSdkVersion >= 31**
> 如果 **compileSdkVersion < 31** 请使用 **v2.2.x** 以前的版本
#### 对于需兼容 Android 5.0 (N) 以下版本的老项目minSdk<21可使用1.x旧版本
**v1.x** 旧版本 [v1.1.9](https://github.com/jenly1314/ZXingLite/tree/androidx)
**Gradle**
1. 在Project的 **build.gradle** 里面添加远程仓库
```gradle
allprojects {
repositories {
//...
jcenter()
}
}
```
2. 在Module的 **build.gradle** 里面添加引入依赖项
```gradle
// AndroidX 版本
implementation 'com.king.zxing:zxing-lite:1.1.9-androidx'
// Android Support 版本
implementation 'com.king.zxing:zxing-lite:1.1.9'
```
> 对于 **v1.x** 版本,当你看到这里,此时的 **JCenter** 仓库如果已关闭, 可使用 **JitPack** 仓库
> 如果 **compileSdkVersion < 33** 请使用 [**v2.x版本**](https://github.com/jenly1314/ZXingLite/tree/2.x/)
## 使用说明
### 快速实现扫码识别有以下几种方式:
### 3.x版本的变化
> 1、直接使用CaptureActivity或者CaptureFragment。(默认的扫码实现)
**2.x****3.x** 主要变化如下:
* 2.x版本中的 **CameraScan** 相关核心类被移除了;
> 从3.0.0版本开始改为依赖[CameraScan](https://github.com/jenly1314/CameraScan)[CameraScan](https://github.com/jenly1314/CameraScan)是一个独立的库,单独进行维护)
> 2、通过继承CaptureActivity或者CaptureFragment并自定义布局。适用于大多场景并无需关心扫码相关逻辑自定义布局时需覆写getLayoutId方法实现示例[CustomCaptureActivity](app/src/main/java/com/king/zxing/app/CustomCaptureActivity.java) 和 [QRCodeActivity](app/src/main/java/com/king/zxing/app/QRCodeActivity.java)
* 2.x版本中的 **ViewfinderView** 被移除了;
> 从3.0.0版本开始改为依赖[ViewfinderView](https://github.com/jenly1314/ViewfinderView)[ViewfinderView](https://github.com/jenly1314/ViewfinderView)是一个独立的库,单独进行维护)
> 3、在你项目的Activity或者Fragment中实例化一个CameraScan即可。适用于想在扫码界面写交互逻辑又因为项目架构或其它原因无法直接或间接继承CaptureActivityCaptureFragment时使用)实现示例:[CustomFullScanActivity](app/src/main/java/com/king/zxing/app/CustomFullScanActivity.java)
* 2.x版本中的 **CaptureActivity****CaptureFragment** 相关基类被移除了;
> 从3.0.0版本开始改为 **BarcodeCameraActivity** 和 **BarcodeCameraFragment**
> 4、继承CameraScan自己实现一个可参照默认实现类DefaultCameraScan其它步骤同方式3。扩展高级用法谨慎使用
除了以上几点主要差异变化3.x版本的整体使用方式和2.x基本类似3.x版本在2.x版本的基础上再次进行重构**CameraScan** 相关的公共基础类从 **ZXingLite** 中移除后,维护起来更方便了。
### 关于 CameraScan
> 如果你是从 **2.x** 版本升级至 **3.x** 版本,那么你需要知道上面所说的主要差异;特别是独立出去单独维护的库,其包名都有所变化,这一点需要特别注意;请谨慎升级。
**CameraScan** 作为相机扫描的(核心)基类;所有与相机扫描相关的都是基于此类来直接或间接进行控制的。
> 如果你使用的是2.x版本的话请直接[查看v2.x分支版本](https://github.com/jenly1314/ZXingLite/tree/2.x/)
### 关于 CameraConfig
### 3.x版本的使用
主要是相机相关的配置;如:摄像头的前置后置、相机预览相关、图像分析相关等配置
3.x的实现主要是以[CameraScan](https://github.com/jenly1314/CameraScan)作为基础库去实现具体的分析检测功能,所以你可以先去看下[CameraScan](https://github.com/jenly1314/CameraScan)的使用说明;在了解了[CameraScan](https://github.com/jenly1314/CameraScan)的基本使用方式后,然后再结合当前的使用说明就可以轻松的集成并使用 **ZXingLite**了
> 你可以直接库中内置实现的相机配置: **CameraConfig** 、**AspectRatioCameraConfig** 和 **ResolutionCameraConfig**。
### 主要类说明
#### 这里简单说下各自的特点:
#### Analyzer的实现类
* **CameraConfig**:默认的相机配置
* **AspectRatioCameraConfig**:根据纵横比配置相机,使输出分析的图像尽可能的接近屏幕的比例
* **ResolutionCameraConfig**:根据尺寸配置相机的目标图像大小,使输出分析的图像的分辨率尽可能的接近屏幕尺寸
内部提供了Analyzer对应的实现都是为快速实现扫码识别而提供的分析器
> 你也可以自定义或覆写 **CameraConfig** 中的 **options** 方法,根据需要定制配置。
内部提供的分析器有多个;一般情况下,你只需要知道最终实现的 [**MultiFormatAnalyzer**](zxing-lite/src/main/java/com/king/zxing/analyze/MultiFormatAnalyzer.java) 和 [**QRCodeAnalyzer**](zxing-lite/src/main/java/com/king/zxing/analyze/QRCodeAnalyzer.java) 即可:
这里特别温馨提示默认配置在未配置相机的目标分析图像大小时会优先使用横屏640 * 480 竖屏480 * 640
**MultiFormatAnalyzer****QRCodeAnalyzer** 的主要区别,从名字大概就能看的出来;一个是可识别多种格式,一个是只识别二维码(具体需要支持识别哪些格式的条码,其实还要看提供的**DecodeConfig**是怎么配置的)。
根据这个图像质量顺便说下默认配置的优缺点:
> 本可以不需要 ****QRCodeAnalyzer****,之所以提供一个 **QRCodeAnalyzer** 是因为有很多需求是只需要识别二维码就行;如果你有连续扫码的需求或不知道怎么选时,推荐直接选择 **MultiFormatAnalyzer** 。
* 优点因为图像质量不高所以在低配置的设备上使用也能hold住这样就能尽可能的适应各种设备
* 缺点:正是由于图像质量不高,从而可能会对检测识别率略有影响,比如在某些机型上体验欠佳。
* 结论:在适配、性能与体验之间得有所取舍,找到平衡点。
#### DecodeConfig
> 当使用默认的 **CameraConfig** 在某些机型上体验欠佳时,你可以尝试使用 **AspectRatioCameraConfig** 或
**ResolutionCameraConfig** 会有意想不到奇效。
DecodeConfig解码配置主要用于在扫码识别时提供一些配置便于扩展。通过配置可决定内置分析器的能力从而间接的控制并简化扫码识别的流程。一般在使用 **Analyzer** 的实现类时,你可能会用到。
### 关于 **Analyzer**
#### DecodeFormatManager
**Analyzer** 为定义的分析器接口;主要用于分析相机预览的帧数据;通过实现 **Analyzer** 可以自定义分析过程
DecodeConfig解码格式管理器主要将多种条码格式进行划分与归类便于提供快捷配置
### 关于 **CaptureActivity** 和 **CaptureFragment**
#### CodeUtils
**CaptureActivity****CaptureFragment** 作为扫描预览界面的基类,主要目的是便于快速实现扫码识别
工具类 **CodeUtils** 中主要提供;解析条形码/二维码、生成条形码/二维码相关的能力
> 扫描预览界面内部持有 **CameraScan**,并处理了 **CameraScan** 的初始化(如:相机权限、相机预览、生命周期等细节)
## 使用示例
### CameraScan配置示例
**CameraScan** 里面包含部分支持链式调用的方法,即调用返回是 **CameraScan** 本身的一些配置建议在调用 **startCamera()** 方法之前调用。
> 如果是通过继承 **CaptureActivity** 或者 **CaptureFragment** 或其子类实现的相机扫描,可以在
**initCameraScan()** 方法中获取 **CameraScan** ,然后根据需要修改相关配置。
示例1
```java
// 获取CameraScan扫码相关的配置设置。CameraScan里面包含部分支持链式调用的方法即调用返回是CameraScan本身的一些配置建议在startCamera之前调用。
getCameraScan().setPlayBeep(true)//设置是否播放音效默认为false
.setVibrate(true)//设置是否震动默认为false
.setCameraConfig(new ResolutionCameraConfig(this))//设置相机配置信息CameraConfig可覆写options方法自定义配置
.setNeedAutoZoom(false)//二维码太小时可自动缩放默认为false
.setNeedTouchZoom(true)//支持多指触摸捏合缩放默认为true
.setDarkLightLux(45f)//设置光线足够暗的阈值单位lux需要通过{@link #bindFlashlightView(View)}绑定手电筒才有效
.setBrightLightLux(100f)//设置光线足够明亮的阈值单位lux需要通过{@link #bindFlashlightView(View)}绑定手电筒才有效
.bindFlashlightView(ivFlashlight)//绑定手电筒,绑定后可根据光线传感器,动态显示或隐藏手电筒按钮
.setOnScanResultCallback(this)//设置扫码结果回调,需要自己处理或者需要连扫时,可设置回调,自己去处理相关逻辑
.setAnalyzer(new MultiFormatAnalyzer(new DecodeConfig()))//设置分析器,DecodeConfig可以配置一些解码时的配置信息如果内置的不满足您的需求你也可以自定义实现
.setAnalyzeImage(true);//设置是否分析图片默认为true。如果设置为false相当于关闭了扫码识别功能
// 启动预览如果是通过继承CaptureActivity或CaptureFragment实现的则无需调用startCamera
getCameraScan().startCamera();
// 设置闪光灯(手电筒)是否开启,需在startCamera之后调用才有效
getCameraScan().enableTorch(torch);
```
示例2只需识别二维码的配置示例
```java
// 初始化解码配置
DecodeConfig decodeConfig = new DecodeConfig();
decodeConfig.setHints(DecodeFormatManager.QR_CODE_HINTS)//如果只有识别二维码的需求这样设置效率会更高不设置默认为DecodeFormatManager.DEFAULT_HINTS
.setFullAreaScan(false)//设置是否全区域识别默认false
.setAreaRectRatio(0.8f)//设置识别区域比例默认0.8,设置的比例最终会在预览区域裁剪基于此比例的一个矩形进行扫码识别
.setAreaRectVerticalOffset(0)//设置识别区域垂直方向偏移量默认为0为0表示居中可以为负数
.setAreaRectHorizontalOffset(0);//设置识别区域水平方向偏移量默认为0为0表示居中可以为负数
// 在启动预览之前,设置分析器,只识别二维码
getCameraScan()
.setCameraConfig(new AspectRatioCameraConfig(this))//设置相机配置,使用 AspectRatioCameraConfig
.setVibrate(true)//设置是否震动默认为false
.setAnalyzer(new MultiFormatAnalyzer(decodeConfig));//设置分析器,如果内置实现的一些分析器不满足您的需求,你也可以自定义去实现
```
### 布局示例
**PreviewView** 用来预览,布局内至少要保证有 **PreviewView**,如果是继承 **CaptureActivity****CaptureFragment**控件id可覆写`getPreviewViewId`方法自定义
**ViewfinderView** 用来渲染扫码视图,给用户起到一个视觉效果,本身扫码识别本身没有关系,如果是继承 **CaptureActivity****CaptureFragment**控件ID可复写`getViewfinderViewId`方法自定义,默认为 **previewView**返回0表示无需 **ViewfinderView**
**ivFlashlight** 是布局内置的手电筒,如果是继承 **CaptureActivity****CaptureFragment**控件id可复写`getFlashlightId`方法自定义,默认为 **ivFlashlight**。返回0表示无需内置手电筒。您也可以自己去定义
> 可自定义布局(覆写`getLayoutId`方法),布局内至少要保证有 **PreviewView**。
```Xml
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.camera.view.PreviewView
android:id="@+id/previewView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<com.king.zxing.ViewfinderView
android:id="@+id/viewfinderView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<ImageView
android:id="@+id/ivFlashlight"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="@dimen/zxl_flashlight_margin_top"
android:contentDescription="@null"
android:src="@drawable/zxl_flashlight_selector" />
</FrameLayout>
```
或在你的布局中添加
```Xml
<include layout="@layout/zxl_capture"/>
```
### 代码示例
**工具类CodeUtils的使用示例二维码/条形码)**
**CodeUtils的使用示例二维码/条形码)**
```Java
// 生成二维码
@ -280,91 +112,65 @@ getCameraScan()
CodeUtils.parseQRCode(bitmap);
```
**通过继承CaptureActivity实现扫二维码完整示例**
### BarcodeCameraScanActivity
**通过继承BarcodeCameraScanActivity实现扫二维码完整示例**
```java
public class QRCodeActivity extends CaptureActivity {
public class QRCodeScanActivity extends BarcodeCameraScanActivity {
@Override
public int getLayoutId() {
return R.layout.qr_code_activity;
public void initCameraScan(@NonNull CameraScan<Result> cameraScan) {
super.initCameraScan(cameraScan);
// 根据需要设置CameraScan相关配置
cameraScan.setPlayBeep(true);
}
@Nullable
@Override
public void initCameraScan() {
super.initCameraScan();
//初始化解码配置
public Analyzer<Result> createAnalyzer() {
// 初始化解码配置
DecodeConfig decodeConfig = new DecodeConfig();
decodeConfig.setHints(DecodeFormatManager.QR_CODE_HINTS)//如果只有识别二维码的需求这样设置效率会更高不设置默认为DecodeFormatManager.DEFAULT_HINTS
.setFullAreaScan(false)//设置是否全区域识别默认false
.setAreaRectRatio(0.8f)//设置识别区域比例默认0.8,设置的比例最终会在预览区域裁剪基于此比例的一个矩形进行扫码识别
.setAreaRectVerticalOffset(0)//设置识别区域垂直方向偏移量默认为0为0表示居中可以为负数
.setAreaRectHorizontalOffset(0);//设置识别区域水平方向偏移量默认为0为0表示居中可以为负数
//在启动预览之前,设置分析器,只识别二维码
getCameraScan()
.setVibrate(true)//设置是否震动默认为false
.setNeedAutoZoom(true)//二维码太小时可自动缩放默认为false
.setAnalyzer(new MultiFormatAnalyzer(decodeConfig));//设置分析器,如果内置实现的一些分析器不满足您的需求,你也可以自定义去实现
.setFullAreaScan(false)//设置是否全区域识别默认false
.setAreaRectRatio(0.8f)//设置识别区域比例默认0.8,设置的比例最终会在预览区域裁剪基于此比例的一个矩形进行扫码识别
.setAreaRectVerticalOffset(0)//设置识别区域垂直方向偏移量默认为0为0表示居中可以为负数
.setAreaRectHorizontalOffset(0);//设置识别区域水平方向偏移量默认为0为0表示居中可以为负数
// BarcodeCameraScanActivity默认使用的MultiFormatAnalyzer这里可以改为使用QRCodeAnalyzer
return new QRCodeAnalyzer(decodeConfig);
}
/**
* 扫码结果回调
* @param result
* @return 返回false表示不拦截将关闭扫码界面并将结果返回给调用界面
* 返回true表示拦截需自己处理逻辑。当isAnalyze为true时默认会继续分析图像也就是连扫
* 如果只是想拦截扫码结果回调,并不想继续分析图像(不想连扫),请在拦截扫码逻辑处通过调
* 用{@link CameraScan#setAnalyzeImage(boolean)}
* 因为{@link CameraScan#setAnalyzeImage(boolean)}方法能动态控制是否继续分析图像。
* 布局ID通过覆写此方法可以自定义布局
*
* @return 布局ID
*/
@Override
public boolean onScanResultCallback(Result result) {
/*
* 因为setAnalyzeImage方法能动态控制是否继续分析图像。
*
* 1. 因为分析图像默认为true如果想支持连扫返回true即可。
* 当连扫的处理逻辑比较复杂时请在处理逻辑前调用getCameraScan().setAnalyzeImage(false)
* 来停止分析图像等逻辑处理完后再调用getCameraScan().setAnalyzeImage(true)来继续分析图像。
*
* 2. 如果只是想拦截扫码结果回调自己处理逻辑,但并不想继续分析图像(即不想连扫),可通过
* 调用getCameraScan().setAnalyzeImage(false)来停止分析图像。
*/
return super.onScanResultCallback(result);
public int getLayoutId() {
return R.layout.activity_qrcode_scan;
}
@Override
public void onScanResultCallback(@NonNull AnalyzeResult<Result> result) {
// 停止分析
getCameraScan().setAnalyzeImage(false);
// 返回结果
Intent intent = new Intent();
intent.putExtra(CameraScan.SCAN_RESULT, result.getResult().getText());
setResult(Activity.RESULT_OK, intent);
finish();
}
}
```
> **BarcodeCameraScanFragment** 的使用方式与之类似。
更多使用详情,请查看[app](app)中的源码使用示例或直接查看[API帮助文档](https://jenly1314.github.io/projects/ZXingLite/doc/)
### 其他
#### AndroidManifest
如果你直接使用了默认 **CaptureActivity** 则需在你项目的AndroidManifest中注册 **CaptureActivity**,配置如下
```Xml
<activity
android:name="com.king.zxing.CaptureActivity"
android:screenOrientation="portrait"
android:theme="@style/CaptureTheme"/>
```
#### JDK版本
需使用JDK8+编译在你项目中的build.gradle的android{}中添加配置:
```gradle
compileOptions {
targetCompatibility JavaVersion.VERSION_1_8
sourceCompatibility JavaVersion.VERSION_1_8
}
```
#### API脱糖
#### JDK版本与API脱糖
当使用ZXingLite为 **v2.3.x** 以上版本时更新zxing至v3.5.1后如果要兼容Android 7.0 (N) 以下版本minSdk<24可通过脱糖获得 Java 8 及更高版本 API
@ -372,9 +178,9 @@ compileOptions {
compileOptions {
// Flag to enable support for the new language APIs
coreLibraryDesugaringEnabled true
// Sets Java compatibility to Java 8
targetCompatibility JavaVersion.VERSION_1_8
sourceCompatibility JavaVersion.VERSION_1_8
// Sets Java compatibility to Java 11
targetCompatibility JavaVersion.VERSION_11
sourceCompatibility JavaVersion.VERSION_11
}
```
@ -389,34 +195,18 @@ dependencies {
#### [MLKit](https://github.com/jenly1314/MLKit) 一个强大易用的工具包。通过ML Kit您可以很轻松的实现文字识别、条码识别、图像标记、人脸检测、对象检测等功能。
#### [WeChatQRCode](https://github.com/jenly1314/WeChatQRCode) 基于OpenCV开源的微信二维码引擎移植的扫码识别库。
## 版本说明
### v2.x 基于CameraX进行了重构
#### v2.x 相对于 v1.x 的优势
* v2.x基于CameraX抽象整体流程可扩展性更高
* v2.x基于CameraX通过预览裁剪的方式确保预览界面不变形无需铺满屏幕就能适配v1.x通过遍历Camera支持预览的尺寸找到与屏幕最接近的比例减少变形的可能性需铺满屏幕才能适配)
#### v2.x 特别说明
* v2.x如果您是通过继承CaptureActivity或CaptureFragment实现扫码功能那么动态权限申请相关都已经在CaptureActivity或CaptureFragment处理好了
* v2.x如果您是通过继承CaptureActivity或CaptureFragment实现扫码功能如果有想要修改默认配置可重写**initCameraScan**方法修改CameraScan的配置即可如果无需修改配置直接在跳转原界面的**onActivityResult** 接收扫码结果即可更多具体详情可参见[app](app)中的使用示例)。
#### v1.x 说明
[【v1.1.9】](https://github.com/jenly1314/ZXingLite/tree/androidx) 如果您正在使用 **1.x** 版本请点击下面的链接查看分支版本当前 **2.x** 版本已经基于 **CameraX** 进行重构API变化较大谨慎升级
查看AndroidX版 **1.x** 分支 [请戳此处](https://github.com/jenly1314/ZXingLite/tree/androidx)
查看Android Support版 **1.x** 分支 [请戳此处](https://github.com/jenly1314/ZXingLite/tree/android)
查看 [ **1.x** API帮助文档](https://jenly1314.github.io/projects/ZXingLite/doc/)
#### [CameraScan](https://github.com/jenly1314/CameraScan) 一个简化扫描识别流程的通用基础库。
#### [ViewfinderView](https://github.com/jenly1314/ViewfinderView) ViewfinderView一个取景视图主要用于渲染扫描相关的动画效果。
## 版本记录
#### v3.0.02023-8-23
* 将通用基础类拆分移除并进行重构后续维护更便捷
* 移除 **CameraScan** 相关核心类改为依赖[CameraScan](https://github.com/jenly1314/CameraScan)
* 移除扫码取景视图 **ViewfinderView**改为依赖[ViewfinderView](https://github.com/jenly1314/ViewfinderView)
* 移除**CaptureActivity******CaptureFragment**新增**BarcodeCameraScanActivity******BarcodeCameraScanFragment**来替代
* 优化扫描分析过程的性能体验优化帧数据分析过程
#### v2.4.02023-4-15
* 优化CameraScan的缺省配置CameraConfig相关配置
* 优化ViewfinderView自定义属性新增laserDrawableRatio

View File

@ -46,10 +46,5 @@ dependencies {
coreLibraryDesugaring deps.desugar_jdk
// implementation deps.kotlin
// implementation deps.corektx
implementation deps.easypermissions
implementation project(':zxing-lite')
}

Binary file not shown.

View File

@ -11,8 +11,8 @@
"type": "SINGLE",
"filters": [],
"attributes": [],
"versionCode": 37,
"versionName": "2.4.0",
"versionCode": 38,
"versionName": "3.0.0",
"outputFile": "app-release.apk"
}
],

View File

@ -24,34 +24,21 @@
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activity
android:name="com.king.zxing.CaptureActivity"
android:screenOrientation="portrait"
android:theme="@style/CaptureTheme"/>
<activity
android:name=".EasyCaptureActivity"
android:name=".FullScreenQRCodeScanActivity"
android:screenOrientation="portrait"
android:theme="@style/CaptureTheme"/>
<activity
android:name=".CustomCaptureActivity"
android:screenOrientation="portrait"
android:theme="@style/CaptureTheme"/>
android:theme="@style/CameraScanTheme"/>
<activity
android:name=".CaptureFragmentActivity"
android:name=".MultiFormatScanActivity"
android:screenOrientation="portrait"
android:theme="@style/CaptureTheme"/>
android:theme="@style/CameraScanTheme"/>
<activity
android:name=".CustomFullScanActivity"
android:name=".QRCodeScanActivity"
android:screenOrientation="portrait"
android:theme="@style/CaptureTheme"/>
<activity
android:name=".QRCodeActivity"
android:screenOrientation="portrait"
android:theme="@style/CaptureTheme"/>
android:theme="@style/CameraScanTheme"/>
<activity
android:name=".CodeActivity"

View File

@ -1,49 +0,0 @@
package com.king.zxing.app;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;
import com.king.zxing.CaptureFragment;
import com.king.zxing.app.util.StatusBarUtils;
import androidx.annotation.IdRes;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.fragment.app.Fragment;
/**
* Fragment扫码
* @author <a href="mailto:jenly1314@gmail.com">Jenly</a>
*/
public class CaptureFragmentActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.fragment_activity);
Toolbar toolbar = findViewById(R.id.toolbar);
StatusBarUtils.immersiveStatusBar(this,toolbar,0.2f);
TextView tvTitle = findViewById(R.id.tvTitle);
tvTitle.setText(getIntent().getStringExtra(MainActivity.KEY_TITLE));
replaceFragment(CaptureFragment.newInstance());
}
public void replaceFragment(Fragment fragment){
replaceFragment( R.id.fragmentContent,fragment);
}
public void replaceFragment(@IdRes int id, Fragment fragment) {
getSupportFragmentManager().beginTransaction().replace(id, fragment).commit();
}
public void onClick(View v){
switch (v.getId()){
case R.id.ivLeft:
finish();
break;
}
}
}

View File

@ -25,29 +25,39 @@ import android.widget.TextView;
import com.google.zxing.BarcodeFormat;
import com.king.zxing.util.CodeUtils;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
/**
* 生成条形码/二维码示例
* @author Jenly <a href="mailto:jenly1314@gmail.com">Jenly</a>
*/
public class CodeActivity extends AppCompatActivity {
private TextView tvTitle;
private TextView tvBarcodeFormat;
private ImageView ivCode;
private ExecutorService executor = Executors.newSingleThreadExecutor();
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.code_activity);
ivCode = findViewById(R.id.ivCode);
tvTitle = findViewById(R.id.tvTitle);
tvBarcodeFormat = findViewById(R.id.tvBarcodeFormat);
tvTitle.setText(getIntent().getStringExtra(MainActivity.KEY_TITLE));
boolean isQRCode = getIntent().getBooleanExtra(MainActivity.KEY_IS_QR_CODE,false);
if(isQRCode){
tvBarcodeFormat.setText("BarcodeFormat: QR_CODE");
createQRCode(getString(R.string.app_name));
}else{
tvBarcodeFormat.setText("BarcodeFormat: CODE_128");
createBarCode("1234567890");
}
}
@ -57,7 +67,7 @@ public class CodeActivity extends AppCompatActivity {
* @param content
*/
private void createQRCode(String content){
new Thread(() -> {
executor.execute(() -> {
//生成二维码相关放在子线程里面
Bitmap logo = BitmapFactory.decodeResource(getResources(),R.drawable.logo);
Bitmap bitmap = CodeUtils.createQRCode(content,600,logo);
@ -65,7 +75,7 @@ public class CodeActivity extends AppCompatActivity {
//显示二维码
ivCode.setImageBitmap(bitmap);
});
}).start();
});
}
@ -74,18 +84,17 @@ public class CodeActivity extends AppCompatActivity {
* @param content
*/
private void createBarCode(String content){
new Thread(() -> {
executor.execute(() -> {
//生成条形码相关放在子线程里面
Bitmap bitmap = CodeUtils.createBarCode(content, BarcodeFormat.CODE_128,800,200,null,true);
runOnUiThread(()->{
//显示条形码
ivCode.setImageBitmap(bitmap);
});
}).start();
});
}
public void onClick(View v){
switch (v.getId()){
case R.id.ivLeft:

View File

@ -1,134 +0,0 @@
/*
* 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.app;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;
import com.google.zxing.Result;
import com.king.zxing.CameraScan;
import com.king.zxing.CaptureActivity;
import com.king.zxing.DecodeConfig;
import com.king.zxing.DecodeFormatManager;
import com.king.zxing.analyze.MultiFormatAnalyzer;
import com.king.zxing.app.util.StatusBarUtils;
import com.king.zxing.config.ResolutionCameraConfig;
import androidx.appcompat.widget.Toolbar;
/**
* 自定义继承CaptureActivity
* @author Jenly <a href="mailto:jenly1314@gmail.com">Jenly</a>
*/
public class CustomCaptureActivity extends CaptureActivity {
private boolean isContinuousScan;
private Toast toast;
@Override
public int getLayoutId() {
return R.layout.custom_capture_activity;
}
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
Toolbar toolbar = findViewById(R.id.toolbar);
StatusBarUtils.immersiveStatusBar(this,toolbar,0.2f);
TextView tvTitle = findViewById(R.id.tvTitle);
tvTitle.setText(getIntent().getStringExtra(MainActivity.KEY_TITLE));
isContinuousScan = getIntent().getBooleanExtra(MainActivity.KEY_IS_CONTINUOUS,false);
}
@Override
public void initCameraScan() {
super.initCameraScan();
//初始化解码配置
DecodeConfig decodeConfig = new DecodeConfig();
decodeConfig.setHints(DecodeFormatManager.ALL_HINTS)////设置解码
.setSupportVerticalCode(true)//设置是否支持扫垂直的条码 (增强识别率,相应的也会增加性能消耗)
.setSupportLuminanceInvert(true)//设置是否支持识别反色码,黑白颜色反转(增强识别率,相应的也会增加性能消耗)
.setAreaRectRatio(0.8f)//设置识别区域比例默认0.8,设置的比例最终会在预览区域裁剪基于此比例的一个矩形进行扫码识别
// .setAreaRectVerticalOffset(0)//设置识别区域垂直方向偏移量默认为0为0表示居中可以为负数
// .setAreaRectHorizontalOffset(0)//设置识别区域水平方向偏移量默认为0为0表示居中可以为负数
.setFullAreaScan(false);//设置是否全区域识别默认false
//获取CameraScan里面有扫码相关的配置设置。CameraScan里面包含部分支持链式调用的方法即调用返回是CameraScan本身的一些配置建议在startCamera之前调用。
getCameraScan().setPlayBeep(true)//设置是否播放音效默认为false
.setVibrate(true)//设置是否震动默认为false
// .setCameraConfig(new CameraConfig())//设置相机配置信息CameraConfig可覆写options方法自定义配置
// .setCameraConfig(new ResolutionCameraConfig(this))//设置CameraConfig可以根据自己的需求去自定义配置
.setNeedAutoZoom(false)//二维码太小时可自动缩放默认为false
.setNeedTouchZoom(true)//支持多指触摸捏合缩放默认为true
.setDarkLightLux(45f)//设置光线足够暗的阈值单位lux需要通过{@link #bindFlashlightView(View)}绑定手电筒才有效
.setBrightLightLux(100f)//设置光线足够明亮的阈值单位lux需要通过{@link #bindFlashlightView(View)}绑定手电筒才有效
.bindFlashlightView(ivFlashlight)//绑定手电筒,绑定后可根据光线传感器,动态显示或隐藏手电筒按钮
.setOnScanResultCallback(this)//设置扫码结果回调,需要自己处理或者需要连扫时,可设置回调,自己去处理相关逻辑
.setAnalyzer(new MultiFormatAnalyzer(decodeConfig))//设置分析器,DecodeConfig可以配置一些解码时的配置信息如果内置的不满足您的需求你也可以自定义实现
.setAnalyzeImage(true);//设置是否分析图片默认为true。如果设置为false相当于关闭了扫码识别功能
}
/**
* 扫码结果回调
* @param result
* @return 返回false表示不拦截将关闭扫码界面并将结果返回给调用界面
* 返回true表示拦截需自己处理逻辑。当isAnalyze为true时默认会继续分析图像也就是连扫
* 如果只是想拦截扫码结果回调,并不想继续分析图像(不想连扫),请在拦截扫码逻辑处通过调
* 用{@link CameraScan#setAnalyzeImage(boolean)}
* 因为{@link CameraScan#setAnalyzeImage(boolean)}方法能动态控制是否继续分析图像。
*
*/
@Override
public boolean onScanResultCallback(Result result) {
if(isContinuousScan){
showToast(result.getText());
}
/*
* 因为setAnalyzeImage方法能动态控制是否继续分析图像。
*
* 1. 因为分析图像默认为true如果想支持连扫返回true即可。
* 当连扫的处理逻辑比较复杂时请在处理逻辑前调用getCameraScan().setAnalyzeImage(false)
* 来停止分析图像等逻辑处理完后再调用getCameraScan().setAnalyzeImage(true)来继续分析图像。
*
* 2. 如果只是想拦截扫码结果回调自己处理逻辑,但并不想继续分析图像(即不想连扫),可通过
* 调用getCameraScan().setAnalyzeImage(false)来停止分析图像。
*/
return isContinuousScan;
}
private void showToast(String text){
if(toast != null){
toast.cancel();
}
toast = Toast.makeText(this,text,Toast.LENGTH_SHORT);
toast.show();
}
public void onClick(View v){
switch (v.getId()){
case R.id.ivLeft:
finish();
break;
}
}
}

View File

@ -1,119 +0,0 @@
package com.king.zxing.app;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;
import com.google.zxing.Result;
import com.king.zxing.CameraScan;
import com.king.zxing.DecodeConfig;
import com.king.zxing.DecodeFormatManager;
import com.king.zxing.DefaultCameraScan;
import com.king.zxing.ViewfinderView;
import com.king.zxing.analyze.MultiFormatAnalyzer;
import com.king.zxing.app.util.StatusBarUtils;
import com.king.zxing.config.ResolutionCameraConfig;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.camera.view.PreviewView;
import androidx.fragment.app.Fragment;
/**
* 自定义扫码当直接使用CaptureActivity
* 自定义扫码,切记自定义扫码需在{@link Activity}或者{@link Fragment}相对应的生命周期里面调用{@link #mCameraScan}对应的生命周期
* @author <a href="mailto:jenly1314@gmail.com">Jenly</a>
*/
public class CustomFullScanActivity extends AppCompatActivity implements CameraScan.OnScanResultCallback {
private boolean isContinuousScan;
private CameraScan mCameraScan;
private PreviewView previewView;
private ViewfinderView viewfinderView;
private View ivFlash;
private Toast toast;
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.custom_activity);
initUI();
}
private void initUI(){
Toolbar toolbar = findViewById(R.id.toolbar);
StatusBarUtils.immersiveStatusBar(this,toolbar,0.2f);
TextView tvTitle = findViewById(R.id.tvTitle);
tvTitle.setText(getIntent().getStringExtra(MainActivity.KEY_TITLE));
previewView = findViewById(R.id.previewView);
viewfinderView = findViewById(R.id.viewfinderView);
ivFlash = findViewById(R.id.ivFlash);
ivFlash.setVisibility(View.INVISIBLE);
isContinuousScan = getIntent().getBooleanExtra(MainActivity.KEY_IS_CONTINUOUS,false);
//初始化解码配置
DecodeConfig decodeConfig = new DecodeConfig();
decodeConfig.setHints(DecodeFormatManager.QR_CODE_HINTS)//如果只有识别二维码的需求这样设置效率会更高不设置默认为DecodeFormatManager.DEFAULT_HINTS
.setFullAreaScan(true);//设置是否全区域识别默认false
mCameraScan = new DefaultCameraScan(this,previewView);
mCameraScan.setOnScanResultCallback(this)
.setAnalyzer(new MultiFormatAnalyzer(decodeConfig))
.setVibrate(true)
.setCameraConfig(new ResolutionCameraConfig(this, ResolutionCameraConfig.IMAGE_QUALITY_720P))
.startCamera();
}
@Override
protected void onDestroy() {
mCameraScan.release();
super.onDestroy();
}
/**
* 扫码结果回调
* @param result 扫码结果
* @return
*/
@Override
public boolean onScanResultCallback(Result result) {
if(isContinuousScan){
showToast(result.getText());
}
//如果需支持连扫返回true即可
return isContinuousScan;
}
private void showToast(String text){
if(toast == null){
toast = Toast.makeText(this,text,Toast.LENGTH_SHORT);
}else{
toast.setDuration(Toast.LENGTH_SHORT);
toast.setText(text);
}
toast.show();
}
public void onClick(View v){
switch (v.getId()){
case R.id.ivLeft:
finish();
break;
}
}
}

View File

@ -1,63 +0,0 @@
/*
* 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.app;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;
import com.king.zxing.CaptureActivity;
import com.king.zxing.app.util.StatusBarUtils;
import androidx.appcompat.widget.Toolbar;
/**
* @author Jenly <a href="mailto:jenly1314@gmail.com">Jenly</a>
*/
public class EasyCaptureActivity extends CaptureActivity {
@Override
public int getLayoutId() {
return R.layout.easy_capture_activity;
}
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
Toolbar toolbar = findViewById(R.id.toolbar);
StatusBarUtils.immersiveStatusBar(this,toolbar,0.2f);
TextView tvTitle = findViewById(R.id.tvTitle);
tvTitle.setText(getIntent().getStringExtra(MainActivity.KEY_TITLE));
}
@Override
public void initCameraScan() {
super.initCameraScan();
getCameraScan()
.setPlayBeep(true)
.setVibrate(true);
}
public void onClick(View v){
switch (v.getId()){
case R.id.ivLeft:
finish();
break;
}
}
}

View File

@ -0,0 +1,108 @@
package com.king.zxing.app
import android.app.Activity
import android.content.Intent
import android.widget.Toast
import com.google.zxing.Result
import com.king.camera.scan.AnalyzeResult
import com.king.camera.scan.CameraScan
import com.king.camera.scan.analyze.Analyzer
import com.king.camera.scan.util.PointUtils
import com.king.view.viewfinderview.ViewfinderView.ViewfinderStyle
import com.king.zxing.DecodeConfig
import com.king.zxing.DecodeFormatManager
import com.king.zxing.BarcodeCameraScanActivity
import com.king.zxing.analyze.QRCodeAnalyzer
/**
* 扫二维码全屏识别示例
* @author <a href="mailto:jenly1314@gmail.com">Jenly</a>
*/
class FullScreenQRCodeScanActivity : BarcodeCameraScanActivity() {
override fun initUI() {
super.initUI()
// 设置取景框样式
viewfinderView.setViewfinderStyle(ViewfinderStyle.POPULAR)
}
override fun initCameraScan(cameraScan: CameraScan<Result>) {
super.initCameraScan(cameraScan)
// 根据需要设置CameraScan相关配置
cameraScan.setPlayBeep(true)
}
override fun createAnalyzer(): Analyzer<Result>? {
// 初始化解码配置
val decodeConfig = DecodeConfig().apply {
// 如果只有识别二维码的需求这样设置效率会更高不设置默认为DecodeFormatManager.DEFAULT_HINTS
hints = DecodeFormatManager.QR_CODE_HINTS
// 设置是否全区域识别默认false
isFullAreaScan = true
}
// BarcodeCameraScanActivity默认使用的MultiFormatAnalyzer这里可以改为使用QRCodeAnalyzer
return QRCodeAnalyzer(decodeConfig)
}
/**
* 布局ID通过覆写此方法可以自定义布局
*
* @return 布局ID
*/
override fun getLayoutId(): Int {
return super.getLayoutId()
}
override fun onScanResultCallback(result: AnalyzeResult<Result>) {
// 停止分析
cameraScan.setAnalyzeImage(false)
// 显示结果点
displayResultPoint(result)
// 返回结果
val intent = Intent()
intent.putExtra(CameraScan.SCAN_RESULT, result.result.text)
setResult(Activity.RESULT_OK, intent)
finish()
}
/**
* 显示结果点
*/
private fun displayResultPoint(result: AnalyzeResult<Result>) {
val frameMetadata = result.frameMetadata
var width = frameMetadata.width
var height = frameMetadata.height
if (frameMetadata.rotation == 90 || frameMetadata.rotation == 270) {
width = frameMetadata.height
height = frameMetadata.width
}
val resultPoints = result.result.resultPoints
val size = resultPoints.size
if (size > 0) {
var x = 0f
var y = 0f
resultPoints.forEach {
x += it.x
y += it.y
}
var centerX = x / size
var centerY = y / size
//将实际的结果中心点坐标转换成界面预览的坐标
val point = PointUtils.transform(
centerX.toInt(),
centerY.toInt(),
width,
height,
viewfinderView.width,
viewfinderView.height
)
//显示结果点信息
viewfinderView.showResultPoints(listOf(point))
}
}
}

View File

@ -16,7 +16,6 @@
package com.king.zxing.app;
import android.Manifest;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.provider.MediaStore;
@ -25,52 +24,30 @@ import android.view.View;
import android.widget.Button;
import android.widget.Toast;
import com.king.zxing.CameraScan;
import com.king.zxing.CaptureActivity;
import com.king.camera.scan.CameraScan;
import com.king.camera.scan.util.LogUtils;
import com.king.camera.scan.util.PermissionUtils;
import com.king.zxing.util.CodeUtils;
import com.king.zxing.util.LogUtils;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.app.ActivityOptionsCompat;
import pub.devrel.easypermissions.AfterPermissionGranted;
import pub.devrel.easypermissions.EasyPermissions;
/**
* 扫码Demo示例说明
*
* 快速实现扫码识别有以下几种方式:
*
* 1、直接使用CaptureActivity或者CaptureFragment。(默认的扫码实现)
*
* 2、通过继承CaptureActivity或者CaptureFragment并自定义布局。适用于大多场景并无需关心扫码相关逻辑自定义布局时需覆写getLayoutId方法
*
* 3、在你项目的Activity或者Fragment中实例化一个CameraScan即可。适用于想在扫码界面写交互逻辑又因为项目架构或其它原因无法直接或间接继承CaptureActivity或CaptureFragment时使用
*
* 4、继承CameraScan自己实现一个可参照默认实现类DefaultCameraScan其它步骤同方式3。扩展高级用法谨慎使用
*
* 扫码示例
*/
public class MainActivity extends AppCompatActivity implements EasyPermissions.PermissionCallbacks{
public class MainActivity extends AppCompatActivity {
public static final String KEY_TITLE = "key_title";
public static final String KEY_IS_QR_CODE = "key_code";
public static final String KEY_IS_CONTINUOUS = "key_continuous_scan";
public static final int REQUEST_CODE_SCAN = 0X01;
public static final int REQUEST_CODE_PHOTO = 0X02;
public static final int REQUEST_CODE_SCAN = 0x01;
public static final int REQUEST_CODE_PHOTO = 0x02;
public static final int RC_CAMERA = 0X01;
public static final int RC_READ_PHOTO = 0X02;
private Class<?> cls;
private String title;
private boolean isContinuousScan;
public static final int REQUEST_CODE_READ_EXTERNAL_STORAGE = 0x99;
private Toast toast;
@ -86,8 +63,8 @@ public class MainActivity extends AppCompatActivity implements EasyPermissions.P
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if(resultCode == RESULT_OK && data!=null){
switch (requestCode){
if (resultCode == RESULT_OK && data != null) {
switch (requestCode) {
case REQUEST_CODE_SCAN:
String result = CameraScan.parseScanResult(data);
showToast(result);
@ -100,23 +77,24 @@ public class MainActivity extends AppCompatActivity implements EasyPermissions.P
}
}
private void showToast(String text){
if(toast != null){
toast.cancel();
private void showToast(String text) {
if (toast == null) {
toast = Toast.makeText(this, text, Toast.LENGTH_SHORT);
}else {
toast.setText(text);
}
toast = Toast.makeText(this,text,Toast.LENGTH_SHORT);
toast.show();
}
private void parsePhoto(Intent data){
private void parsePhoto(Intent data) {
try {
Bitmap bitmap = MediaStore.Images.Media.getBitmap(getContentResolver(),data.getData());
Bitmap bitmap = MediaStore.Images.Media.getBitmap(getContentResolver(), data.getData());
//异步解析
asyncThread(() -> {
final String result = CodeUtils.parseCode(bitmap);
runOnUiThread(() -> {
LogUtils.d("result:" + result);
Toast.makeText(getContext(),result,Toast.LENGTH_SHORT).show();
showToast(result);
});
});
@ -127,133 +105,86 @@ public class MainActivity extends AppCompatActivity implements EasyPermissions.P
}
private Context getContext(){
return this;
}
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
// Forward results to EasyPermissions
EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this);
}
@Override
public void onPermissionsGranted(int requestCode, List<String> list) {
// Some permissions have been granted
}
@Override
public void onPermissionsDenied(int requestCode, List<String> list) {
// Some permissions have been denied
// ...
}
/**
* 检测拍摄权限
*/
@AfterPermissionGranted(RC_CAMERA)
private void checkCameraPermissions(){
String[] perms = {Manifest.permission.CAMERA};
if (EasyPermissions.hasPermissions(this, perms)) {//有权限
startScan(cls,title);
} else {
// Do not have permissions, request them now
EasyPermissions.requestPermissions(this, getString(R.string.permission_camera),
RC_CAMERA, perms);
if (requestCode == REQUEST_CODE_READ_EXTERNAL_STORAGE && PermissionUtils.requestPermissionsResult(
Manifest.permission.READ_EXTERNAL_STORAGE,
permissions,
grantResults)) {
startPickPhoto();
}
}
private void asyncThread(Runnable runnable){
private void asyncThread(Runnable runnable) {
executor.execute(runnable);
}
/**
* 扫码
*
* @param cls
* @param title
*/
private void startScan(Class<?> cls,String title){
ActivityOptionsCompat optionsCompat = ActivityOptionsCompat.makeCustomAnimation(this,R.anim.in,R.anim.out);
private void startScan(Class<?> cls) {
ActivityOptionsCompat optionsCompat = ActivityOptionsCompat.makeCustomAnimation(this, R.anim.in, R.anim.out);
Intent intent = new Intent(this, cls);
intent.putExtra(KEY_TITLE,title);
intent.putExtra(KEY_IS_CONTINUOUS,isContinuousScan);
ActivityCompat.startActivityForResult(this,intent,REQUEST_CODE_SCAN,optionsCompat.toBundle());
ActivityCompat.startActivityForResult(this, intent, REQUEST_CODE_SCAN, optionsCompat.toBundle());
}
/**
* 生成二维码/条形码
*
* @param isQRCode
*/
private void startCode(boolean isQRCode){
Intent intent = new Intent(this,CodeActivity.class);
intent.putExtra(KEY_IS_QR_CODE,isQRCode);
intent.putExtra(KEY_TITLE,isQRCode ? getString(R.string.qr_code) : getString(R.string.bar_code));
private void startGenerateCodeActivity(boolean isQRCode, String title) {
Intent intent = new Intent(this, CodeActivity.class);
intent.putExtra(KEY_IS_QR_CODE, isQRCode);
intent.putExtra(KEY_TITLE, title);
startActivity(intent);
}
private void startPhotoCode(){
/**
* 点击选择图片识别 - 进行动态权限校验
*/
private void pickPhotoClicked() {
if (PermissionUtils.checkPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE)) {
startPickPhoto();
} else {
PermissionUtils.requestPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE, REQUEST_CODE_READ_EXTERNAL_STORAGE);
}
}
/**
* 开始选择图片
*/
private void startPickPhoto() {
Intent pickIntent = new Intent(Intent.ACTION_PICK,
MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
pickIntent.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "image/*");
startActivityForResult(pickIntent, REQUEST_CODE_PHOTO);
}
@AfterPermissionGranted(RC_READ_PHOTO)
private void checkExternalStoragePermissions(){
String[] perms = {Manifest.permission.READ_EXTERNAL_STORAGE,Manifest.permission.WRITE_EXTERNAL_STORAGE};
if (EasyPermissions.hasPermissions(this, perms)) {//有权限
startPhotoCode();
}else{
EasyPermissions.requestPermissions(this, getString(R.string.permission_external_storage),
RC_READ_PHOTO, perms);
}
}
public void onClick(View v){
isContinuousScan = false;
switch (v.getId()){
case R.id.btn0:
this.cls = CaptureActivity.class;
this.title = ((Button)v).getText().toString();
checkCameraPermissions();
public void onClick(View v) {
switch (v.getId()) {
case R.id.btnMultiFormat:
startScan(MultiFormatScanActivity.class);
break;
case R.id.btn1:
this.cls = CustomCaptureActivity.class;
this.title = ((Button)v).getText().toString();
isContinuousScan = true;
checkCameraPermissions();
case R.id.btnQRCode:
startScan(QRCodeScanActivity.class);
break;
case R.id.btn2:
this.cls = CaptureFragmentActivity.class;
this.title = ((Button)v).getText().toString();
checkCameraPermissions();
case R.id.btnFullQRCode:
startScan(FullScreenQRCodeScanActivity.class);
break;
case R.id.btn3:
this.cls = EasyCaptureActivity.class;
this.title = ((Button)v).getText().toString();
checkCameraPermissions();
case R.id.btnPickPhoto:
pickPhotoClicked();
break;
case R.id.btn4:
this.cls = CustomFullScanActivity.class;
this.title = ((Button)v).getText().toString();
checkCameraPermissions();
case R.id.btnGenerateQrCode:
startGenerateCodeActivity(true, ((Button) v).getText().toString());
break;
case R.id.btn5:
startCode(false);
break;
case R.id.btn6:
startCode(true);
break;
case R.id.btn7:
checkExternalStoragePermissions();
break;
case R.id.btn8:
this.cls = QRCodeActivity.class;
this.title = ((Button)v).getText().toString();
checkCameraPermissions();
case R.id.btnGenerateBarcode:
startGenerateCodeActivity(false, ((Button) v).getText().toString());
break;
}

View File

@ -0,0 +1,64 @@
package com.king.zxing.app
import android.widget.Toast
import com.google.zxing.Result
import com.king.camera.scan.AnalyzeResult
import com.king.camera.scan.CameraScan
import com.king.camera.scan.analyze.Analyzer
import com.king.zxing.DecodeConfig
import com.king.zxing.BarcodeCameraScanActivity
import com.king.zxing.analyze.MultiFormatAnalyzer
/**
* 连续扫码(识别多种格式)示例
* @author <a href="mailto:jenly1314@gmail.com">Jenly</a>
*/
class MultiFormatScanActivity : BarcodeCameraScanActivity() {
var toast: Toast? = null
override fun initCameraScan(cameraScan: CameraScan<Result>) {
super.initCameraScan(cameraScan)
// 根据需要设置CameraScan相关配置
cameraScan.setPlayBeep(true)
}
override fun createAnalyzer(): Analyzer<Result>? {
// 初始化解码配置
val decodeConfig = DecodeConfig().apply {
// 设置是否支持扫垂直的条码
isSupportVerticalCode = true
// 设置是否支持识别反色码,黑白颜色反转
isSupportLuminanceInvert = true
}
// 多格式分析器(支持的条码格式主要包含:一维码和二维码)
return MultiFormatAnalyzer(decodeConfig)
}
/**
* 布局ID通过覆写此方法可以自定义布局
*
* @return 布局ID
*/
override fun getLayoutId(): Int {
return super.getLayoutId()
}
override fun onScanResultCallback(result: AnalyzeResult<Result>) {
// 停止分析
cameraScan.setAnalyzeImage(false)
// 处理扫码结果相关逻辑此处弹Toast只是为了演示
showToast(result.result.text)
// 继续分析
cameraScan.setAnalyzeImage(true)
}
private fun showToast(text: String) {
if(toast == null) {
toast = Toast.makeText(this, text, Toast.LENGTH_SHORT)
} else {
toast?.setText(text)
}
toast?.show()
}
}

View File

@ -1,92 +0,0 @@
package com.king.zxing.app;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;
import com.google.zxing.Result;
import com.king.zxing.CameraScan;
import com.king.zxing.CaptureActivity;
import com.king.zxing.DecodeConfig;
import com.king.zxing.DecodeFormatManager;
import com.king.zxing.analyze.MultiFormatAnalyzer;
import com.king.zxing.app.util.StatusBarUtils;
import com.king.zxing.config.AspectRatioCameraConfig;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.Toolbar;
/**
* @author <a href="mailto:jenly1314@gmail.com">Jenly</a>
*/
public class QRCodeActivity extends CaptureActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Toolbar toolbar = findViewById(R.id.toolbar);
StatusBarUtils.immersiveStatusBar(this,toolbar,0.2f);
TextView tvTitle = findViewById(R.id.tvTitle);
tvTitle.setText(getIntent().getStringExtra(MainActivity.KEY_TITLE));
}
@Override
public int getLayoutId() {
return R.layout.qr_code_activity;
}
@Override
public void initCameraScan() {
super.initCameraScan();
//初始化解码配置
DecodeConfig decodeConfig = new DecodeConfig();
decodeConfig.setHints(DecodeFormatManager.QR_CODE_HINTS)//如果只有识别二维码的需求这样设置效率会更高不设置默认为DecodeFormatManager.DEFAULT_HINTS
.setFullAreaScan(false)//设置是否全区域识别默认false
.setAreaRectRatio(0.8f)//设置识别区域比例默认0.8,设置的比例最终会在预览区域裁剪基于此比例的一个矩形进行扫码识别
.setAreaRectVerticalOffset(0)//设置识别区域垂直方向偏移量默认为0为0表示居中可以为负数
.setAreaRectHorizontalOffset(0);//设置识别区域水平方向偏移量默认为0为0表示居中可以为负数
//在启动预览之前,设置分析器,只识别二维码
getCameraScan()
.setCameraConfig(new AspectRatioCameraConfig(this))//设置相机配置,使用 AspectRatioCameraConfig
.setVibrate(true)//设置是否震动默认为false
.setAnalyzer(new MultiFormatAnalyzer(decodeConfig));//设置分析器,如果内置实现的一些分析器不满足您的需求,你也可以自定义去实现
}
/**
* 扫码结果回调
* @param result
* @return 返回false表示不拦截将关闭扫码界面并将结果返回给调用界面
* 返回true表示拦截需自己处理逻辑。当isAnalyze为true时默认会继续分析图像也就是连扫
* 如果只是想拦截扫码结果回调,并不想继续分析图像(不想连扫),请在拦截扫码逻辑处通过调
* 用{@link CameraScan#setAnalyzeImage(boolean)}
* 因为{@link CameraScan#setAnalyzeImage(boolean)}方法能动态控制是否继续分析图像。
*
*/
@Override
public boolean onScanResultCallback(Result result) {
/*
* 因为setAnalyzeImage方法能动态控制是否继续分析图像。
*
* 1. 因为分析图像默认为true如果想支持连扫返回true即可。
* 当连扫的处理逻辑比较复杂时请在处理逻辑前调用getCameraScan().setAnalyzeImage(false)
* 来停止分析图像等逻辑处理完后再调用getCameraScan().setAnalyzeImage(true)来继续分析图像。
*
* 2. 如果只是想拦截扫码结果回调自己处理逻辑,但并不想继续分析图像(即不想连扫),可通过
* 调用getCameraScan().setAnalyzeImage(false)来停止分析图像。
*/
return super.onScanResultCallback(result);
}
public void onClick(View v){
switch (v.getId()){
case R.id.ivLeft:
finish();
break;
}
}
}

View File

@ -0,0 +1,65 @@
package com.king.zxing.app;
import android.app.Activity;
import android.content.Intent;
import com.google.zxing.Result;
import com.king.camera.scan.AnalyzeResult;
import com.king.camera.scan.CameraScan;
import com.king.camera.scan.analyze.Analyzer;
import com.king.zxing.DecodeConfig;
import com.king.zxing.DecodeFormatManager;
import com.king.zxing.BarcodeCameraScanActivity;
import com.king.zxing.analyze.QRCodeAnalyzer;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
/**
* 扫二维码识别示例
* @author <a href="mailto:jenly1314@gmail.com">Jenly</a>
*/
public class QRCodeScanActivity extends BarcodeCameraScanActivity {
@Override
public void initCameraScan(@NonNull CameraScan<Result> cameraScan) {
super.initCameraScan(cameraScan);
// 根据需要设置CameraScan相关配置
cameraScan.setPlayBeep(true);
}
@Nullable
@Override
public Analyzer<Result> createAnalyzer() {
//初始化解码配置
DecodeConfig decodeConfig = new DecodeConfig();
decodeConfig.setHints(DecodeFormatManager.QR_CODE_HINTS)//如果只有识别二维码的需求这样设置效率会更高不设置默认为DecodeFormatManager.DEFAULT_HINTS
.setFullAreaScan(false)//设置是否全区域识别默认false
.setAreaRectRatio(0.8f)//设置识别区域比例默认0.8,设置的比例最终会在预览区域裁剪基于此比例的一个矩形进行扫码识别
.setAreaRectVerticalOffset(0)//设置识别区域垂直方向偏移量默认为0为0表示居中可以为负数
.setAreaRectHorizontalOffset(0);//设置识别区域水平方向偏移量默认为0为0表示居中可以为负数
// BarcodeCameraScanActivity默认使用的MultiFormatAnalyzer这里可以改为使用QRCodeAnalyzer
return new QRCodeAnalyzer(decodeConfig);
}
/**
* 布局ID通过覆写此方法可以自定义布局
*
* @return 布局ID
*/
@Override
public int getLayoutId() {
return R.layout.activity_qrcode_scan;
}
@Override
public void onScanResultCallback(@NonNull AnalyzeResult<Result> result) {
// 停止分析
getCameraScan().setAnalyzeImage(false);
// 返回结果
Intent intent = new Intent();
intent.putExtra(CameraScan.SCAN_RESULT, result.getResult().getText());
setResult(Activity.RESULT_OK, intent);
finish();
}
}

View File

@ -1,89 +0,0 @@
/*
* 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.app.util;
import android.app.Activity;
import android.content.Context;
import android.graphics.Color;
import android.os.Build;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
import android.widget.LinearLayout;
import com.king.zxing.app.R;
import androidx.annotation.FloatRange;
import androidx.appcompat.widget.Toolbar;
/**
* @author Jenly <a href="mailto:jenly1314@gmail.com">Jenly</a>
*/
public final class StatusBarUtils {
private StatusBarUtils(){
throw new AssertionError();
}
public static void immersiveStatusBar(Activity activity, Toolbar toolbar) {
immersiveStatusBar(activity,toolbar,0.0f);
}
public static void immersiveStatusBar(Activity activity,Toolbar toolbar,@FloatRange(from = 0.0, to = 1.0) float alpha) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
return;
}
Window window = activity.getWindow();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
window.setStatusBarColor(Color.TRANSPARENT);
window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
} else {
window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
}
ViewGroup decorView = (ViewGroup) window.getDecorView();
ViewGroup contentView = window.getDecorView().findViewById(Window.ID_ANDROID_CONTENT);
View rootView = contentView.getChildAt(0);
if (rootView != null) {
rootView.setFitsSystemWindows(false);
}
if(toolbar!=null){
toolbar.setPadding(0,getStatusBarHeight(activity),0,0);
}
decorView.addView(createStatusBarView(activity,alpha));
}
private static View createStatusBarView(Activity activity,@FloatRange(from = 0.0, to = 1.0) float alpha) {
// 绘制一个和状态栏一样高的矩形
View statusBarView = new View(activity);
LinearLayout.LayoutParams params =
new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, getStatusBarHeight(activity));
statusBarView.setLayoutParams(params);
statusBarView.setBackgroundColor(Color.argb((int) (alpha * 255), 0, 0, 0));
statusBarView.setId(R.id.translucent_view);
return statusBarView;
}
/** 获取状态栏高度 */
public static int getStatusBarHeight(Context context) {
return context.getResources().getDimensionPixelSize(R.dimen.status_bar_height);
}
}

View File

@ -1,73 +0,0 @@
package com.king.zxing.app.util;
import android.content.ContentUris;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
import android.provider.DocumentsContract;
import android.provider.MediaStore;
import android.util.Log;
import com.king.zxing.util.LogUtils;
/**
* @author Jenly <a href="mailto:jenly1314@gmail.com">Jenly</a>
*/
public final class UriUtils {
private UriUtils(){
throw new AssertionError();
}
/**
* 获取图片
*/
public static String getImagePath(Context context,Intent data) {
String imagePath = null;
Uri uri = data.getData();
//获取系統版本
int currentapiVersion = Build.VERSION.SDK_INT;
if(currentapiVersion> Build.VERSION_CODES.KITKAT){
LogUtils.d("uri=intent.getData :" + uri);
if (DocumentsContract.isDocumentUri(context, uri)) {
String docId = DocumentsContract.getDocumentId(uri);
Log.d("getDocumentId(uri) :", "" + docId);
Log.d("uri.getAuthority() :", "" + uri.getAuthority());
if ("com.android.providers.media.documents".equals(uri.getAuthority())) {
String id = docId.split(":")[1];
String selection = MediaStore.Images.Media._ID + "=" + id;
imagePath = getImagePath(context,MediaStore.Images.Media.EXTERNAL_CONTENT_URI, selection);
} else if ("com.android.providers.downloads.documents".equals(uri.getAuthority())) {
Uri contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), Long.valueOf(docId));
imagePath = getImagePath(context,contentUri, null);
}
} else if ("content".equalsIgnoreCase(uri.getScheme())) {
imagePath = getImagePath(context,uri, null);
}
}else{
imagePath = getImagePath(context,uri, null);
}
return imagePath;
}
/**
* 通过uri和selection来获取真实的图片路径,从相册获取图片时要用
*/
private static String getImagePath(Context context,Uri uri, String selection) {
String path = null;
Cursor cursor = context.getContentResolver().query(uri, null, selection, null, null);
if (cursor != null) {
if (cursor.moveToFirst()) {
path = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA));
}
cursor.close();
}
return path;
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

View File

@ -26,122 +26,84 @@
android:layout_gravity="center_horizontal"
android:text="@string/app_name"/>
</androidx.appcompat.widget.Toolbar>
<Button
android:id="@+id/btn0"
android:id="@+id/btnMultiFormat"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:layout_marginTop="16dp"
android:layout_marginBottom="6dp"
android:text="默认扫码"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
android:layout_marginTop="40dp"
android:layout_marginBottom="10dp"
android:text="连续扫码(识别多种格式)"
app:layout_constraintTop_toBottomOf="@+id/toolbar"
style="@style/OnClick"/>
<Button
android:id="@+id/btn1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:layout_marginTop="6dp"
android:layout_marginBottom="6dp"
android:text="连续扫码"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/btn0"
style="@style/OnClick"/>
<Button
android:id="@+id/btn2"
android:id="@+id/btnQRCode"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:layout_marginTop="6dp"
android:layout_marginBottom="6dp"
android:text="Fragment扫码"
android:layout_marginTop="10dp"
android:layout_marginBottom="10dp"
android:text="扫二维码"
android:textAllCaps="false"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/btn1"
app:layout_constraintTop_toBottomOf="@+id/btnMultiFormat"
style="@style/OnClick"/>
<Button
android:id="@+id/btn3"
android:id="@+id/btnFullQRCode"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:layout_marginTop="6dp"
android:layout_marginBottom="6dp"
android:text="自定义布局扫码"
android:layout_marginTop="10dp"
android:layout_marginBottom="10dp"
android:text="扫二维码(全屏识别)"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/btn2"
app:layout_constraintTop_toBottomOf="@+id/btnQRCode"
style="@style/OnClick"/>
<Button
android:id="@+id/btn4"
android:id="@+id/btnPickPhoto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:layout_marginTop="6dp"
android:layout_marginBottom="6dp"
android:text="自定义全屏扫二维码"
android:layout_marginTop="10dp"
android:layout_marginBottom="10dp"
android:text="选择图片识别"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/btn3"
app:layout_constraintTop_toBottomOf="@+id/btnFullQRCode"
style="@style/OnClick"/>
<Button
android:id="@+id/btn5"
android:id="@+id/btnGenerateQrCode"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:layout_marginTop="6dp"
android:layout_marginBottom="6dp"
android:text="生成条形码"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/btn4"
style="@style/OnClick"/>
<Button
android:id="@+id/btn6"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:layout_marginTop="6dp"
android:layout_marginBottom="6dp"
android:layout_marginTop="10dp"
android:layout_marginBottom="10dp"
android:text="生成二维码"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/btn5"
app:layout_constraintTop_toBottomOf="@+id/btnPickPhoto"
style="@style/OnClick"/>
<Button
android:id="@+id/btn7"
android:id="@+id/btnGenerateBarcode"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:layout_marginTop="6dp"
android:layout_marginBottom="6dp"
android:text="识别一维码/二维码图片"
android:layout_marginTop="10dp"
android:layout_marginBottom="10dp"
android:text="生成线性条形码"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/btn6"
style="@style/OnClick"/>
<Button
android:id="@+id/btn8"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:layout_marginTop="6dp"
android:layout_marginBottom="6dp"
android:text="只识别二维码"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/btn7"
app:layout_constraintTop_toBottomOf="@+id/btnGenerateQrCode"
style="@style/OnClick"/>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto">
<androidx.camera.view.PreviewView
android:id="@+id/previewView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<com.king.view.viewfinderview.ViewfinderView
android:id="@+id/viewfinderView"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:vvLaserStyle="grid"
app:vvLabelTextLocation="bottom"
app:vvLabelText="@string/tips_scan_qr_code"/>
<ImageView
android:id="@+id/ivFlashlight"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="@dimen/camera_scan_flashlight_margin_top"
android:contentDescription="@null"
android:src="@drawable/camera_scan_flashlight_selector" />
</FrameLayout>

View File

@ -6,6 +6,7 @@
xmlns:app="http://schemas.android.com/apk/res-auto">
<include android:id="@+id/toolbar"
layout="@layout/toolbar"/>
<ImageView
android:id="@+id/ivCode"
android:layout_width="wrap_content"
@ -15,4 +16,13 @@
app:layout_constraintTop_toBottomOf="@+id/toolbar"
app:layout_constraintBottom_toBottomOf="parent"/>
<TextView
android:id="@+id/tvBarcodeFormat"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintBottom_toTopOf="@+id/ivCode"
android:layout_marginBottom="20dp"
android:gravity="center_horizontal"/>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -1,31 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.camera.view.PreviewView
android:id="@+id/previewView"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<com.king.zxing.ViewfinderView
android:id="@+id/viewfinderView"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:viewfinderStyle="popular"
app:laserStyle="image"
app:laserDrawableRatio="0.8"
app:laserDrawable="@drawable/ic_laser_line"/>
<ImageView
android:id="@+id/ivFlash"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/zxl_flashlight_selector"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginTop="170dp" />
<include layout="@layout/toolbar_capture"/>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -1,34 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.camera.view.PreviewView
android:id="@+id/previewView"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<com.king.zxing.ViewfinderView
android:id="@+id/viewfinderView"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:labelText="@string/tips_scan_code"
app:labelTextSize="@dimen/size_14sp"
app:laserColor="@color/colorAccent"
app:frameColor="@color/colorPrimary"
app:cornerColor="@color/colorPrimary"
app:labelTextLocation="bottom"
app:laserStyle="grid" />
<ImageView
android:id="@+id/ivFlashlight"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/zxl_flashlight_selector"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginTop="170dp" />
<include layout="@layout/toolbar_capture"/>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<include layout="@layout/zxl_capture"/>
<include layout="@layout/toolbar_capture"/>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -1,12 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:id="@+id/fragmentContent"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<include layout="@layout/toolbar_capture"/>
</FrameLayout>

View File

@ -1,34 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.camera.view.PreviewView
android:id="@+id/previewView"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<com.king.zxing.ViewfinderView
android:id="@+id/viewfinderView"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:labelText="@string/tips_scan_code"
app:labelTextSize="@dimen/size_14sp"
app:laserColor="@color/colorAccent"
app:frameColor="@color/colorPrimary"
app:cornerColor="@color/colorPrimary"
app:labelTextLocation="bottom"
app:laserStyle="grid" />
<ImageView
android:id="@+id/ivFlashlight"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/zxl_flashlight_selector"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginTop="170dp" />
<include layout="@layout/toolbar_capture"/>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<item name="statusbar_view" type="id"/>
<item name="translucent_view" type="id"/>
</resources>

View File

@ -1,8 +1,5 @@
<resources>
<string name="app_name">ZXingLite</string>
<string name="permission_camera">App扫码需要用到拍摄权限</string>
<string name="permission_external_storage">App需要用到读写权限</string>
<string name="tips_scan_code">将二维码/条形码放入框内,即可自动扫描</string>
<string name="qr_code">二维码</string>
<string name="bar_code">条形码</string>
<string name="tips_scan_qr_code">将二维码放入框内,即可自动扫描</string>
</resources>

View File

@ -1,5 +1,12 @@
## 版本记录
#### v3.0.02023-8-23
* 将通用基础类拆分移除并进行重构,后续维护更便捷
* 移除 **CameraScan** 相关核心类,改为依赖[CameraScan](https://github.com/jenly1314/CameraScan)
* 移除扫码取景视图 **ViewfinderView**,改为依赖[ViewfinderView](https://github.com/jenly1314/ViewfinderView)
* 移除**CaptureActivity**和****CaptureFragment**,新增**BarcodeCameraScanActivity**和****BarcodeCameraScanFragment**来替代
* 优化扫描分析过程的性能体验(优化帧数据分析过程)
#### v2.4.02023-4-15
* 优化CameraScan的缺省配置CameraConfig相关配置
* 优化ViewfinderView自定义属性新增laserDrawableRatio

View File

@ -14,8 +14,8 @@ org.gradle.jvmargs = -Xmx1536m
android.useAndroidX=true
android.enableJetifier=true
VERSION_NAME=2.4.0
VERSION_CODE=37
VERSION_NAME=3.0.0
VERSION_CODE=38
GROUP=com.github.jenly1314
POM_DESCRIPTION=ZXingLite for Android

View File

@ -10,6 +10,7 @@ dependencyResolutionManagement {
repositories {
google()
mavenCentral()
maven { url 'https://jitpack.io' }
}
}

View File

@ -1,7 +1,7 @@
//App
def app_version = [:]
app_version.versionCode = 37
app_version.versionName = "2.4.0"
app_version.versionCode = 38
app_version.versionName = "3.0.0"
ext.app_version = app_version
//build version
@ -31,17 +31,11 @@ versions.espresso = "3.4.0"
versions.bintray_release = "0.9.2"
versions.mavenPublish = '0.22.0'
versions.gralde = "7.4.2"
versions.kotlin = "1.8.0"
versions.coreKtx = "1.7.0"
//zxing
versions.zxing = "3.5.1"
versions.camerax = "1.2.1"
versions.desugar_jdk_libs = "1.2.2"
versions.easypermissions = "3.0.0"
ext.versions = versions
@ -61,24 +55,14 @@ test.runner = "androidx.test:runner:$versions.runner"
test.espresso = "androidx.test.espresso:espresso-core:$versions.espresso"
deps.test = test
//deps.kotlin = "org.jetbrains.kotlin:kotlin-stdlib:$versions.kotlin"
//
//deps.corektx = "androidx.core:core-ktx:$versions.coreKtx"
//zxing
deps.zxing = "com.google.zxing:core:$versions.zxing"
//CameraX
deps.camera_core = "androidx.camera:camera-core:$versions.camerax"
deps.camera_camera2 = "androidx.camera:camera-camera2:$versions.camerax"
deps.camera_lifecycle = "androidx.camera:camera-lifecycle:$versions.camerax"
deps.camera_view = "androidx.camera:camera-view:$versions.camerax"
deps.camera_scan = "com.github.jenly1314:CameraScan:1.0.0"
deps.viewfinderview = "com.github.jenly1314:viewfinderview:1.0.0"
//desugar_jdk
deps.desugar_jdk = "com.android.tools:desugar_jdk_libs:$versions.desugar_jdk_libs"
//permission
deps.easypermissions = "pub.devrel:easypermissions:$versions.easypermissions"
ext.deps = deps

View File

@ -42,9 +42,8 @@ dependencies {
implementation deps.androidx.appcompat
api deps.zxing
api deps.camera_core
api deps.camera_camera2
api deps.camera_lifecycle
api deps.camera_view
api deps.viewfinderview
api deps.camera_scan
}

View File

@ -1,11 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.king.zxing">
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.VIBRATE"/>
<uses-permission android:name="android.permission.FLASHLIGHT"/>
<application>
</application>
</manifest>

View File

@ -0,0 +1,59 @@
package com.king.zxing;
import android.view.View;
import com.google.zxing.Result;
import com.king.camera.scan.BaseCameraScanActivity;
import com.king.camera.scan.analyze.Analyzer;
import com.king.view.viewfinderview.ViewfinderView;
import com.king.zxing.analyze.MultiFormatAnalyzer;
import androidx.annotation.Nullable;
/**
* 基于zxing实现的扫码识别 - 相机扫描基类
* <p>
* 通过继承 {@link BarcodeCameraScanActivity}或{@link BarcodeCameraScanFragment}可快速实现扫码识别
*
* @author <a href="mailto:jenly1314@gmail.com">Jenly</a>
*/
public abstract class BarcodeCameraScanActivity extends BaseCameraScanActivity<Result> {
protected ViewfinderView viewfinderView;
@Override
public void initUI() {
int viewfinderViewId = getViewfinderViewId();
if (viewfinderViewId != View.NO_ID && viewfinderViewId != 0) {
viewfinderView = findViewById(viewfinderViewId);
}
super.initUI();
}
@Nullable
@Override
public Analyzer<Result> createAnalyzer() {
return new MultiFormatAnalyzer();
}
/**
* 布局ID通过覆写此方法可以自定义布局
*
* @return 布局ID
*/
@Override
public int getLayoutId() {
return R.layout.zxl_camera_scan;
}
/**
* {@link #viewfinderView} 的 ID
*
* @return 默认返回{@code R.id.viewfinderView}, 如果不需要扫码框可以返回{@link View#NO_ID}
*/
public int getViewfinderViewId() {
return R.id.viewfinderView;
}
}

View File

@ -0,0 +1,59 @@
package com.king.zxing;
import android.view.View;
import com.google.zxing.Result;
import com.king.camera.scan.BaseCameraScanFragment;
import com.king.camera.scan.analyze.Analyzer;
import com.king.view.viewfinderview.ViewfinderView;
import com.king.zxing.analyze.MultiFormatAnalyzer;
import androidx.annotation.Nullable;
/**
* 基于zxing实现的扫码识别 - 相机扫描基类
* <p>
* 通过继承 {@link BarcodeCameraScanActivity}或{@link BarcodeCameraScanFragment}可快速实现扫码识别
*
* @author <a href="mailto:jenly1314@gmail.com">Jenly</a>
*/
public abstract class BarcodeCameraScanFragment extends BaseCameraScanFragment<Result> {
protected ViewfinderView viewfinderView;
@Override
public void initUI() {
int viewfinderViewId = getViewfinderViewId();
if (viewfinderViewId != View.NO_ID && viewfinderViewId != 0) {
viewfinderView = getRootView().findViewById(viewfinderViewId);
}
super.initUI();
}
@Nullable
@Override
public Analyzer<Result> createAnalyzer() {
return new MultiFormatAnalyzer();
}
/**
* 布局ID通过覆写此方法可以自定义布局
*
* @return 布局ID
*/
@Override
public int getLayoutId() {
return R.layout.zxl_camera_scan;
}
/**
* {@link #viewfinderView} 的 ID
*
* @return 默认返回{@code R.id.viewfinderView}, 如果不需要扫码框可以返回{@link View#NO_ID}
*/
public int getViewfinderViewId() {
return R.id.viewfinderView;
}
}

View File

@ -1,233 +0,0 @@
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 com.king.zxing.config.CameraConfig;
import androidx.annotation.Nullable;
import androidx.camera.core.CameraSelector;
/**
* 相机扫描基类定义;内置的默认实现见:{@link DefaultCameraScan}
* <p>
* 快速实现扫描识别主要有以下几种方式:
* <p>
* 1、通过继承 {@link CaptureActivity}或者{@link CaptureFragment}或其子类,可快速实现扫描识别。
* 适用于大多数场景自定义布局时需覆写getLayoutId方法
* <p>
* 2、在你项目的Activity或者Fragment中实例化一个{@link DefaultCameraScan}。(适用于想在扫码界面写交互逻辑,又因为项目
* 架构或其它原因,无法直接或间接继承{@link CaptureActivity}或{@link CaptureFragment}时使用)
* <p>
* 3、继承{@link CameraScan}自己实现一个,可参照默认实现类{@link DefaultCameraScan}其他步骤同方式2。高级用法谨慎使用
*
* @author <a href="mailto:jenly1314@gmail.com">Jenly</a>
*/
public abstract class CameraScan implements ICamera, ICameraControl {
/**
* 扫描返回结果的key解析方式可参见{@link #parseScanResult(Intent)}
*/
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;
/**
* 纵横比4:3
*/
public static final float ASPECT_RATIO_4_3 = 4.0F / 3.0F;
/**
* 纵横比16:9
*/
public static final float ASPECT_RATIO_16_9 = 16.0F / 9.0F;
/**
* 是否需要支持自动缩放
*/
private boolean isNeedAutoZoom = false;
/**
* 是否需要支持触摸缩放
*/
private boolean isNeedTouchZoom = true;
/**
* 是否需要支持触摸缩放
*
* @return 返回是否需要支持触摸缩放
*/
protected boolean isNeedTouchZoom() {
return isNeedTouchZoom;
}
/**
* 设置是否需要支持触摸缩放
*
* @param needTouchZoom 是否需要支持触摸缩放
* @return {@link CameraScan}
*/
public CameraScan setNeedTouchZoom(boolean needTouchZoom) {
isNeedTouchZoom = needTouchZoom;
return this;
}
/**
* 是否需要支持自动缩放
*
* @return 是否需要支持自动缩放
*/
protected boolean isNeedAutoZoom() {
return isNeedAutoZoom;
}
/**
* 设置是否需要支持自动缩放
*
* @param needAutoZoom 是否需要支持自动缩放
* @return {@link CameraScan}
*/
public CameraScan setNeedAutoZoom(boolean needAutoZoom) {
isNeedAutoZoom = needAutoZoom;
return this;
}
/**
* 设置相机配置,请在{@link #startCamera()}之前调用
*
* @param cameraConfig 相机配置
* @return {@link CameraScan}
*/
public abstract CameraScan setCameraConfig(CameraConfig cameraConfig);
/**
* 设置是否分析图像,通过此方法可以动态控制是否分析图像,常用于中断扫码识别。如:连扫时,扫到结果,然后停止分析图像
* <p>
* 1. 因为分析图像默认为true如果想支持连扫在{@link OnScanResultCallback#onScanResultCallback(Result)}返回true拦截即可。
* 当连扫的处理逻辑比较复杂时请在处理逻辑前通过调用setAnalyzeImage(false)来停止分析图像,
* 等逻辑处理完后再调用getCameraScan().setAnalyzeImage(true)来继续分析图像。
* <p>
* 2. 如果只是想拦截扫码结果回调自己处理逻辑,但并不想继续分析图像(即不想连扫),可通过
* 调用getCameraScan().setAnalyzeImage(false)来停止分析图像。
*
* @param analyze 是否分析图像
* @return {@link CameraScan}
*/
public abstract CameraScan setAnalyzeImage(boolean analyze);
/**
* 设置分析器,如果内置的一些分析器不满足您的需求,你也可以自定义{@link Analyzer}
* 自定义时,切记需在{@link #startCamera()}之前调用才有效。
* <p>
* 内置了一些{@link Analyzer}的实现类如下:
*
* @param analyzer 分析器
* @return {@link CameraScan}
* @see {@link MultiFormatAnalyzer}
* @see {@link AreaRectAnalyzer}
* @see {@link ImageAnalyzer}
* @see {@link BarcodeFormatAnalyzer}
* @see {@link QRCodeReader}
*/
public abstract CameraScan setAnalyzer(Analyzer analyzer);
/**
* 设置是否振动
*
* @param vibrate 是否振动
* @return {@link CameraScan}
*/
public abstract CameraScan setVibrate(boolean vibrate);
/**
* 设置是否播放提示音
*
* @param playBeep 是否播放蜂鸣提示音
* @return {@link CameraScan}
*/
public abstract CameraScan setPlayBeep(boolean playBeep);
/**
* 设置扫码结果回调
*
* @param callback 扫码结果回调
* @return {@link CameraScan}
*/
public abstract CameraScan setOnScanResultCallback(OnScanResultCallback callback);
/**
* 绑定手电筒,绑定后可根据光线传感器,动态显示或隐藏手电筒
*
* @param v 手电筒视图
* @return {@link CameraScan}
*/
public abstract CameraScan bindFlashlightView(@Nullable View v);
/**
* 设置光线足够暗的阈值单位lux需要通过{@link #bindFlashlightView(View)}绑定手电筒才有效
*
* @param lightLux 光线亮度阈值
* @return {@link CameraScan}
*/
public abstract CameraScan setDarkLightLux(float lightLux);
/**
* 设置光线足够明亮的阈值单位lux需要通过{@link #bindFlashlightView(View)}绑定手电筒才有效
*
* @param lightLux 光线亮度阈值
* @return {@link CameraScan}
*/
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

@ -1,241 +0,0 @@
/*
* 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.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.camera.view.PreviewView;
/**
* 相机扫描基类;{@link CaptureActivity} 内部持有{@link CameraScan},便于快速实现扫描识别。
* <p>
* 快速实现扫描识别主要有以下几种方式:
* <p>
* 1、通过继承 {@link CaptureActivity}或者{@link CaptureFragment}或其子类,可快速实现扫描识别。
* 适用于大多数场景自定义布局时需覆写getLayoutId方法
* <p>
* 2、在你项目的Activity或者Fragment中实例化一个{@link DefaultCameraScan}。(适用于想在扫码界面写交互逻辑,又因为项目
* 架构或其它原因,无法直接或间接继承{@link CaptureActivity}或{@link CaptureFragment}时使用)
* <p>
* 3、继承{@link CameraScan}自己实现一个,可参照默认实现类{@link DefaultCameraScan}其他步骤同方式2。高级用法谨慎使用
*
* @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;
/**
* CameraScan
*/
private CameraScan mCameraScan;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (isContentView()) {
setContentView(getLayoutId());
}
initUI();
}
/**
* 初始化
*/
public void initUI() {
previewView = findViewById(getPreviewViewId());
int viewfinderViewId = getViewfinderViewId();
if (viewfinderViewId != 0 && viewfinderViewId != View.NO_ID) {
viewfinderView = findViewById(viewfinderViewId);
}
int ivFlashlightId = getFlashlightId();
if (ivFlashlightId != 0 && ivFlashlightId != View.NO_ID) {
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)}
*
* @return 默认返回true
*/
public boolean isContentView() {
return true;
}
/**
* 布局ID通过覆写此方法可以自定义布局
*
* @return 布局ID
*/
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可通过覆写此方法自定义ID
*
* @return 默认返回{@code R.id.previewView}
*/
public int getPreviewViewId() {
return R.id.previewView;
}
/**
* 获取 {@link #ivFlashlight} 的ID
*
* @return 默认返回{@code R.id.ivFlashlight}, 如果不需要手电筒按钮可以返回0
*/
public int getFlashlightId() {
return R.id.ivFlashlight;
}
/**
* 获取 {@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

@ -1,285 +0,0 @@
/*
* 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.NonNull;
import androidx.annotation.Nullable;
import androidx.camera.view.PreviewView;
import androidx.fragment.app.Fragment;
/**
* 相机扫描基类;{@link CaptureFragment} 内部持有{@link CameraScan},便于快速实现扫描识别。
* <p>
* 快速实现扫描识别主要有以下几种方式:
* <p>
* 1、通过继承 {@link CaptureActivity}或者{@link CaptureFragment}或其子类,可快速实现扫描识别。
* 适用于大多数场景自定义布局时需覆写getLayoutId方法
* <p>
* 2、在你项目的Activity或者Fragment中实例化一个{@link DefaultCameraScan}。(适用于想在扫码界面写交互逻辑,又因为项目
* 架构或其它原因,无法直接或间接继承{@link CaptureActivity}或{@link CaptureFragment}时使用)
* <p>
* 3、继承{@link CameraScan}自己实现一个,可参照默认实现类{@link DefaultCameraScan}其他步骤同方式2。高级用法谨慎使用
*
* @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;
/**
* CameraScan
*/
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) {
if (isContentView()) {
mRootView = createRootView(inflater, container);
}
return mRootView;
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
initUI();
}
/**
* 初始化
*/
public void initUI() {
previewView = mRootView.findViewById(getPreviewViewId());
int viewfinderViewId = getViewfinderViewId();
if (viewfinderViewId != 0 && viewfinderViewId != View.NO_ID) {
viewfinderView = mRootView.findViewById(viewfinderViewId);
}
int ivFlashlightId = getFlashlightId();
if (ivFlashlightId != 0 && ivFlashlightId != View.NO_ID) {
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 onDestroyView() {
releaseCamera();
super.onDestroyView();
}
/**
* 返回true时会自动初始化{@link #createRootView(LayoutInflater, ViewGroup)}返回为false是需自己去初始化{@link #createRootView(LayoutInflater, ViewGroup)}
*
* @return 默认返回true
*/
public boolean isContentView() {
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 布局ID
*/
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可通过覆写此方法自定义ID
*
* @return 默认返回{@code R.id.previewView}
*/
public int getPreviewViewId() {
return R.id.previewView;
}
/**
* 获取 {@link #ivFlashlight} 的ID
*
* @return 默认返回{@code R.id.ivFlashlight}, 如果不需要手电筒按钮可以返回0
*/
public int getFlashlightId() {
return R.id.ivFlashlight;
}
/**
* 获取 {@link CameraScan}
*
* @return {@link #mCameraScan}
*/
public CameraScan getCameraScan() {
return mCameraScan;
}
/**
* 接收扫码结果回调
*
* @param result 扫码结果
* @return 返回true表示拦截将不自动执行后续逻辑为false表示不拦截默认不拦截
*/
@Override
public boolean onScanResultCallback(Result result) {
return false;
}
//--------------------------------------------
/**
* 获取根视图
*
* @return {@link #mRootView}
*/
public View getRootView() {
return mRootView;
}
}

View File

@ -6,7 +6,6 @@ import com.google.zxing.BarcodeFormat;
import com.google.zxing.DecodeHintType;
import com.google.zxing.common.GlobalHistogramBinarizer;
import com.google.zxing.common.HybridBinarizer;
import com.king.zxing.config.CameraConfig;
import java.util.Map;
@ -35,7 +34,6 @@ import androidx.annotation.FloatRange;
* {@link #setAreaRectRatio(float)} 设置识别区域比例,默认{@link #DEFAULT_AREA_RECT_RATIO},设置的比例最终会在预览区域裁剪基于此比例的一个矩形进行扫码识别,优先级最低
* <p>
* 因为{@link androidx.camera.view.PreviewView}的预览区域是经过裁剪的所以这里的区域并不是用户所能预览到的区域而是指Camera预览的真实区域
* 您还可以通过{@link CameraScan#setCameraConfig(CameraConfig)}去自定义配置{@link CameraConfig}的配置信息控制预览相关配置信息
* <p>
* 即判定区域分析的优先级顺序为:{@link #setFullAreaScan(boolean)} -> {@link #setAnalyzeAreaRect(Rect)} -> {@link #setAreaRectRatio(float)}
* <p></>
@ -247,7 +245,6 @@ public class DecodeConfig {
* {@link #setAreaRectRatio(float)} 设置识别区域比例,默认{@link #DEFAULT_AREA_RECT_RATIO},设置的比例最终会在预览区域裁剪基于此比例的一个矩形进行扫码识别,优先级最低
* <p>
* 因为{@link androidx.camera.view.PreviewView}的预览区域是经过裁剪的所以这里的区域并不是用户所能预览到的区域而是指Camera预览的真实区域
* 您还可以通过{@link CameraScan#setCameraConfig(CameraConfig)}去自定义配置{@link CameraConfig}的配置信息控制预览相关配置信息
* <p>
* 即判定区域分析的优先级顺序为:{@link #setFullAreaScan(boolean)} -> {@link #setAnalyzeAreaRect(Rect)} -> {@link #setAreaRectRatio(float)}
* @return {@link DecodeConfig}
@ -277,7 +274,6 @@ public class DecodeConfig {
* {@link #setAreaRectRatio(float)} 设置识别区域比例,默认{@link #DEFAULT_AREA_RECT_RATIO},设置的比例最终会在预览区域裁剪基于此比例的一个矩形进行扫码识别,优先级最低
* <p>
* 因为{@link androidx.camera.view.PreviewView}的预览区域是经过裁剪的所以这里的区域并不是用户所能预览到的区域而是指Camera预览的真实区域
* 您还可以通过{@link CameraScan#setCameraConfig(CameraConfig)}去自定义配置{@link CameraConfig}的配置信息控制预览相关配置信息
* <p>
* 即判定区域分析的优先级顺序为:{@link #setFullAreaScan(boolean)} -> {@link #setAnalyzeAreaRect(Rect)} -> {@link #setAreaRectRatio(float)}
* @return {@link DecodeConfig}
@ -305,7 +301,6 @@ public class DecodeConfig {
* {@link #setAreaRectRatio(float)} 设置识别区域比例,默认{@link #DEFAULT_AREA_RECT_RATIO},设置的比例最终会在预览区域裁剪基于此比例的一个矩形进行扫码识别,优先级最低
* <p>
* 因为{@link androidx.camera.view.PreviewView}的预览区域是经过裁剪的所以这里的区域并不是用户所能预览到的区域而是指Camera预览的真实区域
* 您还可以通过{@link CameraScan#setCameraConfig(CameraConfig)}去自定义配置{@link CameraConfig}的配置信息控制预览相关配置信息
* <p>
* 即判定区域分析的优先级顺序为:{@link #setFullAreaScan(boolean)} -> {@link #setAnalyzeAreaRect(Rect)} -> {@link #setAreaRectRatio(float)}
* @return {@link DecodeConfig}

View File

@ -57,6 +57,10 @@ public final class DecodeFormatManager {
addDecodeHintTypes(DEFAULT_HINTS, getDefaultFormats());
}
private DecodeFormatManager() {
throw new AssertionError();
}
/**
* 所有支持的{@link BarcodeFormat}
*

View File

@ -1,604 +0,0 @@
package com.king.zxing;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.util.DisplayMetrics;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.View;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.Result;
import com.google.zxing.ResultPoint;
import com.google.zxing.common.detector.MathUtils;
import com.king.zxing.analyze.Analyzer;
import com.king.zxing.analyze.MultiFormatAnalyzer;
import com.king.zxing.config.AspectRatioCameraConfig;
import com.king.zxing.config.CameraConfig;
import com.king.zxing.config.ResolutionCameraConfig;
import com.king.zxing.manager.AmbientLightManager;
import com.king.zxing.manager.BeepManager;
import com.king.zxing.util.LogUtils;
import java.util.concurrent.Executors;
import androidx.annotation.FloatRange;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.camera.core.Camera;
import androidx.camera.core.CameraInfo;
import androidx.camera.core.CameraSelector;
import androidx.camera.core.FocusMeteringAction;
import androidx.camera.core.ImageAnalysis;
import androidx.camera.core.MeteringPoint;
import androidx.camera.core.Preview;
import androidx.camera.core.TorchState;
import androidx.camera.core.ZoomState;
import androidx.camera.lifecycle.ProcessCameraProvider;
import androidx.camera.view.PreviewView;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.MutableLiveData;
/**
* 相机扫描基类;{@link DefaultCameraScan} 为 {@link CameraScan} 的默认实现
* <p>
* 快速实现扫描识别主要有以下几种方式:
* <p>
* 1、通过继承 {@link CaptureActivity}或者{@link CaptureFragment}或其子类,可快速实现扫描识别。
* 适用于大多数场景自定义布局时需覆写getLayoutId方法
* <p>
* 2、在你项目的Activity或者Fragment中实例化一个{@link DefaultCameraScan}。(适用于想在扫码界面写交互逻辑,又因为项目
* 架构或其它原因,无法直接或间接继承{@link CaptureActivity}或{@link CaptureFragment}时使用)
* <p>
* 3、继承{@link CameraScan}自己实现一个,可参照默认实现类{@link DefaultCameraScan}其他步骤同方式2。高级用法谨慎使用
*
* @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 static final float ZOOM_STEP_SIZE = 0.1F;
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 mImageWidth;
private int mImageHeight;
/**
* 最后自动缩放时间
*/
private long mLastAutoZoomTime;
/**
* 最后点击时间,根据两次点击时间间隔用于区分单机和触摸缩放事件
*/
private long mLastHoveTapTime;
/**
* 是否是点击事件
*/
private boolean isClickTap;
/**
* 按下时X坐标
*/
private float mDownX;
/**
* 按下时Y坐标
*/
private float mDownY;
public DefaultCameraScan(@NonNull FragmentActivity activity, @NonNull PreviewView previewView) {
this.mFragmentActivity = activity;
this.mLifecycleOwner = activity;
this.mContext = activity;
this.mPreviewView = previewView;
initData();
}
public DefaultCameraScan(@NonNull Fragment fragment, @NonNull PreviewView previewView) {
this.mFragmentActivity = fragment.getActivity();
this.mLifecycleOwner = fragment;
this.mContext = fragment.getContext();
this.mPreviewView = previewView;
initData();
}
/**
* 缩放手势检测
*/
private ScaleGestureDetector.OnScaleGestureListener mOnScaleGestureListener = new ScaleGestureDetector.SimpleOnScaleGestureListener() {
@Override
public boolean onScale(ScaleGestureDetector detector) {
float scale = detector.getScaleFactor();
if (mCamera != null) {
float ratio = mCamera.getCameraInfo().getZoomState().getValue().getZoomRatio();
// 根据缩放的手势和当前比例进行缩放
zoomTo(ratio * scale);
return true;
}
return false;
}
};
/**
* 初始化
*/
@SuppressLint("ClickableViewAccessibility")
private void initData() {
mResultLiveData = new MutableLiveData<>();
mResultLiveData.observe(mLifecycleOwner, result -> {
if (result != null) {
handleAnalyzeResult(result);
} else if (mOnScanResultCallback != null) {
mOnScanResultCallback.onScanResultFailure();
}
});
mOrientation = mContext.getResources().getConfiguration().orientation;
ScaleGestureDetector scaleGestureDetector = new ScaleGestureDetector(mContext, mOnScaleGestureListener);
mPreviewView.setOnTouchListener((v, event) -> {
handlePreviewViewClickTap(event);
if (isNeedTouchZoom()) {
return scaleGestureDetector.onTouchEvent(event);
}
return false;
});
mBeepManager = new BeepManager(mContext);
mAmbientLightManager = new AmbientLightManager(mContext);
mAmbientLightManager.register();
mAmbientLightManager.setOnLightSensorEventListener((dark, lightLux) -> {
if (flashlightView != null) {
if (dark) {
if (flashlightView.getVisibility() != View.VISIBLE) {
flashlightView.setVisibility(View.VISIBLE);
flashlightView.setSelected(isTorchEnabled());
}
} else if (flashlightView.getVisibility() == View.VISIBLE && !isTorchEnabled()) {
flashlightView.setVisibility(View.INVISIBLE);
flashlightView.setSelected(false);
}
}
});
}
/**
* 处理预览视图点击事件;如果触发的点击事件被判定对焦操作,则开始自动对焦
*
* @param event 事件
*/
private void handlePreviewViewClickTap(MotionEvent event) {
if (event.getPointerCount() == 1) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
isClickTap = true;
mDownX = event.getX();
mDownY = event.getY();
mLastHoveTapTime = System.currentTimeMillis();
break;
case MotionEvent.ACTION_MOVE:
isClickTap = MathUtils.distance(mDownX, mDownY, event.getX(), event.getY()) < HOVER_TAP_SLOP;
break;
case MotionEvent.ACTION_UP:
if (isClickTap && mLastHoveTapTime + HOVER_TAP_TIMEOUT > System.currentTimeMillis()) {
// 开始对焦和测光
startFocusAndMetering(event.getX(), event.getY());
}
break;
}
}
}
/**
* 开始对焦和测光
*
* @param x X轴坐标
* @param y Y轴坐标
*/
private void startFocusAndMetering(float x, float y) {
if (mCamera != null) {
MeteringPoint point = mPreviewView.getMeteringPointFactory().createPoint(x, y);
FocusMeteringAction focusMeteringAction = new FocusMeteringAction.Builder(point).build();
if (mCamera.getCameraInfo().isFocusMeteringSupported(focusMeteringAction)) {
mCamera.getCameraControl().startFocusAndMetering(focusMeteringAction);
LogUtils.d("startFocusAndMetering:" + x + "," + y);
}
}
}
@Override
public CameraScan setCameraConfig(CameraConfig cameraConfig) {
if (cameraConfig != null) {
this.mCameraConfig = cameraConfig;
}
return this;
}
/**
* 初始化相机配置
*/
/**
* 初始化相机配置
*/
private void initCameraConfig(Context context) {
if (mCameraConfig == null) {
DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
int size = Math.min(displayMetrics.widthPixels, displayMetrics.heightPixels);
// 根据分辨率初始化缺省配置CameraConfig在此前提下尽可能的找到比屏幕分辨率小一级的配置在适配、性能与体验之间得有所取舍找到平衡点。
if (size > ResolutionCameraConfig.IMAGE_QUALITY_1080P) {
mCameraConfig = new ResolutionCameraConfig(context);
} else if (size > ResolutionCameraConfig.IMAGE_QUALITY_720P) {
mCameraConfig = new ResolutionCameraConfig(context, ResolutionCameraConfig.IMAGE_QUALITY_720P);
} else {
mCameraConfig = new AspectRatioCameraConfig(context);
}
}
}
@Override
public void startCamera() {
initCameraConfig(mContext);
if (mAnalyzer == null) {
mAnalyzer = new MultiFormatAnalyzer();
}
mCameraProviderFuture = ProcessCameraProvider.getInstance(mContext);
mCameraProviderFuture.addListener(() -> {
try {
Preview preview = mCameraConfig.options(new Preview.Builder());
//相机选择器
CameraSelector cameraSelector = mCameraConfig.options(new CameraSelector.Builder());
//设置SurfaceProvider
preview.setSurfaceProvider(mPreviewView.getSurfaceProvider());
//图像分析
ImageAnalysis imageAnalysis = mCameraConfig.options(new ImageAnalysis.Builder()
.setOutputImageFormat(ImageAnalysis.OUTPUT_IMAGE_FORMAT_YUV_420_888)
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST));
imageAnalysis.setAnalyzer(Executors.newSingleThreadExecutor(), image -> {
mImageWidth = image.getWidth();
mImageHeight = image.getHeight();
if (isAnalyze && !isAnalyzeResult && mAnalyzer != null) {
Result result = mAnalyzer.analyze(image, mOrientation);
mResultLiveData.postValue(result);
}
image.close();
});
if (mCamera != null) {
mCameraProviderFuture.get().unbindAll();
}
// 绑定到生命周期
mCamera = mCameraProviderFuture.get().bindToLifecycle(mLifecycleOwner, cameraSelector, preview, imageAnalysis);
} catch (Exception e) {
LogUtils.e(e);
}
}, ContextCompat.getMainExecutor(mContext));
}
/**
* 处理分析结果
*
* @param result
*/
private synchronized void handleAnalyzeResult(Result result) {
if (isAnalyzeResult || !isAnalyze) {
return;
}
isAnalyzeResult = true;
if (mBeepManager != null) {
mBeepManager.playBeepSoundAndVibrate();
}
if (result.getBarcodeFormat() == BarcodeFormat.QR_CODE && isNeedAutoZoom() && mLastAutoZoomTime + 100 < System.currentTimeMillis()) {
ResultPoint[] points = result.getResultPoints();
if (points != null && points.length >= 2) {
float distance1 = ResultPoint.distance(points[0], points[1]);
float maxDistance = distance1;
if (points.length >= 3) {
float distance2 = ResultPoint.distance(points[1], points[2]);
float distance3 = ResultPoint.distance(points[0], points[2]);
maxDistance = Math.max(Math.max(distance1, distance2), distance3);
}
if (handleAutoZoom((int) maxDistance, result)) {
return;
}
}
}
scanResultCallback(result);
}
/**
* 处理自动缩放
*
* @param distance
* @param result
* @return
*/
private boolean handleAutoZoom(int distance, Result result) {
int size = Math.min(mImageWidth, mImageHeight);
if (distance * 4 < size) {
mLastAutoZoomTime = System.currentTimeMillis();
zoomIn();
scanResultCallback(result);
return true;
}
return false;
}
/**
* 扫描结果回调
*
* @param result 扫描结果
*/
private void scanResultCallback(Result result) {
if (mOnScanResultCallback != null && mOnScanResultCallback.onScanResultCallback(result)) {
/*
* 如果拦截了结果则重置分析结果状态并当isAnalyze为true时默认会继续分析图像也就是连扫
* 如果只是想拦截扫码结果回调,并不想继续分析图像(不想连扫),请在拦截扫码逻辑处通过调用
* setAnalyzeImage(false)因为setAnalyzeImage方法能动态控制是否继续分析图像。
*/
isAnalyzeResult = false;
return;
}
if (mFragmentActivity != null) {
Intent intent = new Intent();
intent.putExtra(SCAN_RESULT, result.getText());
mFragmentActivity.setResult(Activity.RESULT_OK, intent);
mFragmentActivity.finish();
}
}
@Override
public void stopCamera() {
if (mCameraProviderFuture != null) {
try {
mCameraProviderFuture.get().unbindAll();
} catch (Exception e) {
LogUtils.e(e);
}
}
}
@Override
public CameraScan setAnalyzeImage(boolean analyze) {
isAnalyze = analyze;
return this;
}
@Override
public CameraScan setAnalyzer(Analyzer analyzer) {
mAnalyzer = analyzer;
return this;
}
@Override
public void zoomIn() {
if (mCamera != null) {
float ratio = getCameraInfo().getZoomState().getValue().getZoomRatio() + ZOOM_STEP_SIZE;
float maxRatio = getCameraInfo().getZoomState().getValue().getMaxZoomRatio();
if (ratio <= maxRatio) {
mCamera.getCameraControl().setZoomRatio(ratio);
}
}
}
@Override
public void zoomOut() {
if (mCamera != null) {
float ratio = getCameraInfo().getZoomState().getValue().getZoomRatio() - ZOOM_STEP_SIZE;
float minRatio = getCameraInfo().getZoomState().getValue().getMinZoomRatio();
if (ratio >= minRatio) {
mCamera.getCameraControl().setZoomRatio(ratio);
}
}
}
@Override
public void zoomTo(float ratio) {
if (mCamera != null) {
ZoomState zoomState = getCameraInfo().getZoomState().getValue();
float maxRatio = zoomState.getMaxZoomRatio();
float minRatio = zoomState.getMinZoomRatio();
float zoom = Math.max(Math.min(ratio, maxRatio), minRatio);
mCamera.getCameraControl().setZoomRatio(zoom);
}
}
@Override
public void lineZoomIn() {
if (mCamera != null) {
float zoom = getCameraInfo().getZoomState().getValue().getLinearZoom() + ZOOM_STEP_SIZE;
if (zoom <= 1f) {
mCamera.getCameraControl().setLinearZoom(zoom);
}
}
}
@Override
public void lineZoomOut() {
if (mCamera != null) {
float zoom = getCameraInfo().getZoomState().getValue().getLinearZoom() - ZOOM_STEP_SIZE;
if (zoom >= 0f) {
mCamera.getCameraControl().setLinearZoom(zoom);
}
}
}
@Override
public void lineZoomTo(@FloatRange(from = 0.0, to = 1.0) float linearZoom) {
if (mCamera != null) {
mCamera.getCameraControl().setLinearZoom(linearZoom);
}
}
@Override
public void enableTorch(boolean torch) {
if (mCamera != null && hasFlashUnit()) {
mCamera.getCameraControl().enableTorch(torch);
}
}
@Override
public boolean isTorchEnabled() {
if (mCamera != null) {
return mCamera.getCameraInfo().getTorchState().getValue() == TorchState.ON;
}
return false;
}
@Override
public boolean hasFlashUnit() {
if (mCamera != null) {
return mCamera.getCameraInfo().hasFlashUnit();
}
return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_FLASH);
}
@Override
public CameraScan setVibrate(boolean vibrate) {
if (mBeepManager != null) {
mBeepManager.setVibrate(vibrate);
}
return this;
}
@Override
public CameraScan setPlayBeep(boolean playBeep) {
if (mBeepManager != null) {
mBeepManager.setPlayBeep(playBeep);
}
return this;
}
@Override
public CameraScan setOnScanResultCallback(OnScanResultCallback callback) {
this.mOnScanResultCallback = callback;
return this;
}
@Nullable
@Override
public Camera getCamera() {
return mCamera;
}
/**
* CameraInfo
*
* @return {@link CameraInfo}
*/
private CameraInfo getCameraInfo() {
return mCamera.getCameraInfo();
}
@Override
public void release() {
isAnalyze = false;
flashlightView = null;
if (mAmbientLightManager != null) {
mAmbientLightManager.unregister();
}
if (mBeepManager != null) {
mBeepManager.close();
}
stopCamera();
}
@Override
public CameraScan bindFlashlightView(@Nullable View v) {
flashlightView = v;
if (mAmbientLightManager != null) {
mAmbientLightManager.setLightSensorEnabled(v != null);
}
return this;
}
@Override
public CameraScan setDarkLightLux(float lightLux) {
if (mAmbientLightManager != null) {
mAmbientLightManager.setDarkLightLux(lightLux);
}
return this;
}
@Override
public CameraScan setBrightLightLux(float lightLux) {
if (mAmbientLightManager != null) {
mAmbientLightManager.setBrightLightLux(lightLux);
}
return this;
}
}

View File

@ -1,36 +0,0 @@
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 {@link Camera}
*/
@Nullable
Camera getCamera();
/**
* 释放
*/
void release();
}

View File

@ -1,66 +0,0 @@
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 线性缩放比例范围在0.0 ~ 1.0之间
*/
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

@ -1,935 +0,0 @@
package com.king.zxing;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.LinearGradient;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Shader;
import android.graphics.drawable.Drawable;
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.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.List;
import androidx.annotation.ColorInt;
import androidx.annotation.ColorRes;
import androidx.annotation.DrawableRes;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
/**
* 取景视图:主要用于渲染扫描效果
*
* @author <a href="mailto:jenly1314@gmail.com">Jenly</a>
*/
public class ViewfinderView extends View {
/**
* 默认范围比例,之所以默认为 1.2 是因为内切圆半径和外切圆半径之和的二分之一1 + √2) / 2 ≈ 1.2
*/
private final float DEFAULT_RANGE_RATIO = 1.2F;
private final float MAX_ZOOM_RATIO = 1.2F;
/**
* 画笔
*/
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 int pointColor;
private int pointStrokeColor;
private Bitmap pointBitmap;
private boolean isShowPointAnim = true;
private float pointRadius;
private float pointStrokeRatio;
private float pointStrokeRadius;
/**
* 当前缩放比例
*/
private float currentZoomRatio = 1.0f;
/**
* 最后一次缩放比例(即上一次缩放比例)
*/
private float lastZoomRatio;
/**
* 缩放速度
*/
private float zoomSpeed = 0.02f;
private int zoomCount;
/**
* 结果点有效点击范围半径
*/
private float pointRangeRadius;
private Bitmap laserBitmap;
private float laserBitmapRatio;
private float laserBitmapWidth;
private int viewfinderStyle = ViewfinderStyle.CLASSIC;
private List<Point> pointList;
private boolean isShowPoints = false;
private OnItemClickListener onItemClickListener;
private GestureDetector gestureDetector;
/**
* 取景框样式
*/
@IntDef({ViewfinderStyle.CLASSIC, ViewfinderStyle.POPULAR})
@Retention(RetentionPolicy.SOURCE)
public @interface ViewfinderStyle {
/**
* 经典样式:经典的扫码风格(带扫码框)
*/
int CLASSIC = 0;
/**
* 流行样式:类似于新版的微信全屏扫码(不带扫码框)
*/
int POPULAR = 1;
}
/**
* 扫描线样式
*/
public enum LaserStyle {
NONE(0), LINE(1), GRID(2), IMAGE(3);
private final 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 final 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 final 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);
}
/**
* 初始化
*
* @param context
* @param 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, FrameGravity.CENTER.mValue));
pointColor = array.getColor(R.styleable.ViewfinderView_pointColor, ContextCompat.getColor(context, R.color.viewfinder_point));
pointStrokeColor = array.getColor(R.styleable.ViewfinderView_pointStrokeColor, Color.WHITE);
pointRadius = array.getDimension(R.styleable.ViewfinderView_pointRadius, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 15, getResources().getDisplayMetrics()));
pointStrokeRatio = array.getFloat(R.styleable.ViewfinderView_pointStrokeRatio, DEFAULT_RANGE_RATIO);
isShowPointAnim = array.getBoolean(R.styleable.ViewfinderView_showPointAnim, true);
Drawable pointDrawable = array.getDrawable(R.styleable.ViewfinderView_pointDrawable);
Drawable laserDrawable = array.getDrawable(R.styleable.ViewfinderView_laserDrawable);
laserBitmapRatio = array.getFloat(R.styleable.ViewfinderView_laserDrawableRatio, 0.625f);
viewfinderStyle = array.getInt(R.styleable.ViewfinderView_viewfinderStyle, ViewfinderStyle.CLASSIC);
array.recycle();
if (pointDrawable != null) {
pointBitmap = getBitmapFormDrawable(pointDrawable);
pointRangeRadius = (pointBitmap.getWidth() + pointBitmap.getHeight()) / 4 * DEFAULT_RANGE_RATIO;
} else {
pointStrokeRadius = pointRadius * pointStrokeRatio;
pointRangeRadius = pointStrokeRadius * DEFAULT_RANGE_RATIO;
}
if (laserDrawable != null) {
laserBitmap = getBitmapFormDrawable(laserDrawable);
}
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setAntiAlias(true);
textPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
gestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() {
@Override
public boolean onSingleTapUp(MotionEvent e) {
if (isShowPoints && checkSingleTap(e.getX(), e.getY())) {
return true;
}
return super.onSingleTapUp(e);
}
});
}
private Bitmap getBitmapFormDrawable(@NonNull Drawable drawable) {
Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), drawable.getOpacity() != PixelFormat.OPAQUE ? Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565);
Canvas canvas = new Canvas(bitmap);
drawable.setBounds(0, 0, bitmap.getWidth(), bitmap.getHeight());
drawable.draw(canvas);
return bitmap;
}
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;
}
public void setLaserStyle(LaserStyle laserStyle) {
this.laserStyle = laserStyle;
}
/**
* 设置激光扫描自定义图片
*
* @param drawableResId
*/
public void setLaserDrawable(@DrawableRes int drawableResId) {
setLaserBitmap(BitmapFactory.decodeResource(getResources(), drawableResId));
}
/**
* 设置激光扫描自定义图片
*
* @param laserBitmap
*/
public void setLaserBitmap(Bitmap laserBitmap) {
this.laserBitmap = laserBitmap;
scaleLaserBitmap();
}
public void setPointImageResource(@DrawableRes int drawable) {
setPointBitmap(BitmapFactory.decodeResource(getResources(), drawable));
}
public void setPointBitmap(Bitmap bitmap) {
pointBitmap = bitmap;
pointRangeRadius = (pointBitmap.getWidth() + pointBitmap.getHeight()) / 4 * DEFAULT_RANGE_RATIO;
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
initFrame(getWidth(),getHeight());
}
private void scaleLaserBitmap() {
if (laserBitmap != null && laserBitmapWidth > 0) {
float ratio = laserBitmapWidth / laserBitmap.getWidth();
Matrix matrix = new Matrix();
matrix.postScale(ratio, ratio);
int w = laserBitmap.getWidth();
int h = laserBitmap.getHeight();
laserBitmap = Bitmap.createBitmap(laserBitmap, 0, 0, w, h, matrix, true);
}
}
private void initFrame(int width, int height) {
int size = (int) (Math.min(width, height) * frameRatio);
if (laserBitmapWidth <= 0) {
laserBitmapWidth = Math.min(width, height) * laserBitmapRatio;
scaleLaserBitmap();
}
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 (isShowPoints) {
// 显示结果点
drawMask(canvas, getWidth(), getHeight());
drawResultPoints(canvas, pointList);
if (isShowPointAnim && pointBitmap == null) {
// 显示动画并且结果点标记的图片为空时,支持缩放动画
calcZoomPointAnim();
}
return;
}
if (frame == null) {
return;
}
if (scannerStart == 0 || scannerEnd == 0) {
scannerStart = frame.top;
scannerEnd = frame.bottom - scannerLineHeight;
}
if (viewfinderStyle == ViewfinderStyle.CLASSIC) {// CLASSIC样式经典样式带扫码框
// 绘制模糊区域
drawExterior(canvas, frame, getWidth(), getHeight());
// 绘制扫描动画
drawLaserScanner(canvas, frame);
// 绘制取景区域框
drawFrame(canvas, frame);
// 绘制取景区域边角
drawCorner(canvas, frame);
// 绘制提示信息
drawTextInfo(canvas, frame);
// 间隔更新取景区域
postInvalidateDelayed(scannerAnimationDelay, frame.left, frame.top, frame.right, frame.bottom);
} else if (viewfinderStyle == ViewfinderStyle.POPULAR) {// POPULAR样式类似于新版的微信全屏扫码不带扫码框
// 绘制扫描动画
drawLaserScanner(canvas, frame);
postInvalidateDelayed(scannerAnimationDelay);
}
}
/**
* 绘制文本
*
* @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 drawImageScanner(Canvas canvas, Rect frame) {
if (laserBitmap != null) {
paint.setColor(Color.WHITE);
canvas.drawBitmap(laserBitmap, (getWidth() - laserBitmap.getWidth()) / 2, scannerStart, paint);
if (scannerStart < scannerEnd) {
scannerStart += scannerLineMoveDistance;
} else {
scannerStart = frame.top;
}
} else {
drawLineScanner(canvas, frame);
}
}
/**
* 绘制激光扫描线
*
* @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;
case IMAGE:// 图片
drawImageScanner(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);
}
}
/**
* 绘制遮罩层
*
* @param canvas
* @param width
* @param height
*/
private void drawMask(Canvas canvas, int width, int height) {
if (maskColor != 0) {
paint.setColor(maskColor);
canvas.drawRect(0, 0, width, height, paint);
}
}
/**
* 根据结果点集合绘制结果点
*
* @param canvas
* @param points
*/
private void drawResultPoints(Canvas canvas, List<Point> points) {
paint.setColor(Color.WHITE);
if (points != null) {
for (Point point : points) {
drawResultPoint(canvas, point, currentZoomRatio);
}
}
}
/**
* 计算点的缩放动画
*/
private void calcZoomPointAnim() {
if (currentZoomRatio <= 1F) {
lastZoomRatio = currentZoomRatio;
currentZoomRatio += zoomSpeed;
if (zoomCount < 2) {
// 记住缩放回合次数
zoomCount++;
} else {
zoomCount = 0;
}
} else if (currentZoomRatio >= MAX_ZOOM_RATIO) {
lastZoomRatio = currentZoomRatio;
currentZoomRatio -= zoomSpeed;
} else {
if (lastZoomRatio > currentZoomRatio) {
lastZoomRatio = currentZoomRatio;
currentZoomRatio -= zoomSpeed;
} else {
lastZoomRatio = currentZoomRatio;
currentZoomRatio += zoomSpeed;
}
}
// 每间隔3秒触发一套缩放动画一套动画缩放三个回合(即每次zoomCount累加到2后重置为0时)
postInvalidateDelayed(zoomCount == 0 && lastZoomRatio == 1f ? 3000 : scannerAnimationDelay * 2);
}
/**
* 绘制结果点
*
* @param canvas
* @param point
*/
private void drawResultPoint(Canvas canvas, Point point, float currentZoomRatio) {
if (pointBitmap != null) {
float left = point.x - pointBitmap.getWidth() / 2.0f;
float top = point.y - pointBitmap.getHeight() / 2.0f;
canvas.drawBitmap(pointBitmap, left, top, paint);
} else {
paint.setColor(pointStrokeColor);
canvas.drawCircle(point.x, point.y, pointStrokeRadius * currentZoomRatio, paint);
paint.setColor(pointColor);
canvas.drawCircle(point.x, point.y, pointRadius * currentZoomRatio, paint);
}
}
@SuppressLint("ClickableViewAccessibility")
@Override
public boolean onTouchEvent(MotionEvent event) {
if (isShowPoints) {
gestureDetector.onTouchEvent(event);
}
return isShowPoints || super.onTouchEvent(event);
}
private boolean checkSingleTap(float x, float y) {
if (pointList != null) {
for (int i = 0; i < pointList.size(); i++) {
Point point = pointList.get(i);
float distance = getDistance(x, y, point.x, point.y);
if (distance <= pointRangeRadius) {
if (onItemClickListener != null) {
onItemClickListener.onItemClick(i);
}
return true;
}
}
}
return true;
}
/**
* 获取两点之间的距离
*
* @param x1
* @param y1
* @param x2
* @param y2
* @return
*/
private float getDistance(float x1, float y1, float x2, float y2) {
return (float) Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2));
}
/**
* 是否显示结果点
*
* @return
*/
public boolean isShowPoints() {
return isShowPoints;
}
/**
* 显示扫码动画
*/
public void showScanner() {
isShowPoints = false;
invalidate();
}
/**
* 显示结果点
*
* @param points
*/
public void showResultPoints(List<Point> points) {
pointList = points;
isShowPoints = true;
zoomCount = 0;
lastZoomRatio = 0;
currentZoomRatio = 1;
invalidate();
}
/**
* 设置点击Item监听
*
* @param listener
*/
public void setOnItemClickListener(OnItemClickListener listener) {
onItemClickListener = listener;
}
/**
* Item点击监听
*/
public interface OnItemClickListener {
void onItemClick(int position);
}
}

View File

@ -1,27 +0,0 @@
package com.king.zxing.analyze;
import android.content.res.Configuration;
import com.google.zxing.Result;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.camera.core.ImageProxy;
/**
* 分析器
*
* @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

@ -6,7 +6,6 @@ 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;

View File

@ -8,8 +8,8 @@ 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.camera.scan.util.LogUtils;
import com.king.zxing.DecodeConfig;
import com.king.zxing.util.LogUtils;
import java.util.Map;

View File

@ -1,12 +1,16 @@
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.BitmapUtils;
import com.king.zxing.util.LogUtils;
import com.king.camera.scan.AnalyzeResult;
import com.king.camera.scan.FrameMetadata;
import com.king.camera.scan.analyze.Analyzer;
import java.nio.ByteBuffer;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicBoolean;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@ -17,7 +21,11 @@ import androidx.camera.core.ImageProxy;
*
* @author <a href="mailto:jenly1314@gmail.com">Jenly</a>
*/
public abstract class ImageAnalyzer implements Analyzer {
public abstract class ImageAnalyzer implements Analyzer<Result> {
private final Queue<byte[]> queue = new ConcurrentLinkedQueue<>();
private final AtomicBoolean joinQueue = new AtomicBoolean(false);
/**
* 分析图像数据
@ -30,26 +38,109 @@ public abstract class ImageAnalyzer implements Analyzer {
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) {
int width = image.getWidth();
int height = image.getHeight();
@SuppressLint("UnsafeOptInUsageError")
byte[] data = BitmapUtils.yuv420ThreePlanesToNV21(image.getImage().getPlanes(), width, height).array();
if (orientation == Configuration.ORIENTATION_PORTRAIT) {
byte[] rotatedData = new byte[data.length];
public void analyze(@NonNull ImageProxy imageProxy, @NonNull OnAnalyzeListener<AnalyzeResult<Result>> listener) {
if (!joinQueue.get()) {
int imageSize = imageProxy.getWidth() * imageProxy.getHeight();
byte[] bytes = new byte[imageSize + 2 * (imageSize / 4)];
queue.add(bytes);
joinQueue.set(true);
}
if (queue.isEmpty()) {
return;
}
final byte[] nv21Data = queue.poll();
try {
int rotation = imageProxy.getImageInfo().getRotationDegrees();
int width = imageProxy.getWidth();
int height = imageProxy.getHeight();
yuv_420_888toNv21(imageProxy, nv21Data);
Result result;
if (rotation == 90 || rotation == 270) {
byte[] rotatedData = new byte[nv21Data.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];
rotatedData[x * height + height - y - 1] = nv21Data[x + y * width];
}
}
return analyze(rotatedData, height, width);
result = analyze(rotatedData, height, width);
} else {
result = analyze(nv21Data, width, height);
}
if (result != null) {
FrameMetadata frameMetadata = new FrameMetadata(
width,
height,
rotation);
joinQueue.set(false);
listener.onSuccess(new AnalyzeResult<>(nv21Data, ImageFormat.NV21, frameMetadata, result));
} else {
queue.add(nv21Data);
listener.onFailure(null);
}
} catch (Exception e) {
queue.add(nv21Data);
listener.onSuccess(null);
}
}
/**
* YUV420_888转NV21
*
* @param image
* @param nv21
*/
private void yuv_420_888toNv21(@NonNull ImageProxy image, byte[] nv21) {
ImageProxy.PlaneProxy yPlane = image.getPlanes()[0];
ImageProxy.PlaneProxy uPlane = image.getPlanes()[1];
ImageProxy.PlaneProxy vPlane = image.getPlanes()[2];
ByteBuffer yBuffer = yPlane.getBuffer();
ByteBuffer uBuffer = uPlane.getBuffer();
ByteBuffer vBuffer = vPlane.getBuffer();
yBuffer.rewind();
uBuffer.rewind();
vBuffer.rewind();
int ySize = yBuffer.remaining();
int position = 0;
// Add the full y buffer to the array. If rowStride > 1, some padding may be skipped.
for (int row = 0; row < image.getHeight(); row++) {
yBuffer.get(nv21, position, image.getWidth());
position += image.getWidth();
yBuffer.position(Math.min(ySize, yBuffer.position() - image.getWidth() + yPlane.getRowStride()));
}
int chromaHeight = image.getHeight() / 2;
int chromaWidth = image.getWidth() / 2;
int vRowStride = vPlane.getRowStride();
int uRowStride = uPlane.getRowStride();
int vPixelStride = vPlane.getPixelStride();
int uPixelStride = uPlane.getPixelStride();
// Interleave the u and v frames, filling up the rest of the buffer. Use two line buffers to
// perform faster bulk gets from the byte buffers.
byte[] vLineBuffer = new byte[vRowStride];
byte[] uLineBuffer = new byte[uRowStride];
for (int row = 0; row < chromaHeight; row++) {
vBuffer.get(vLineBuffer, 0, Math.min(vRowStride, vBuffer.remaining()));
uBuffer.get(uLineBuffer, 0, Math.min(uRowStride, uBuffer.remaining()));
int vLineBufferPosition = 0;
int uLineBufferPosition = 0;
for (int col = 0; col < chromaWidth; col++) {
nv21[position++] = vLineBuffer[vLineBufferPosition];
nv21[position++] = uLineBuffer[uLineBufferPosition];
vLineBufferPosition += vPixelStride;
uLineBufferPosition += uPixelStride;
}
return analyze(data, width, height);
} else {
LogUtils.w("imageFormat: " + image.getFormat());
}
return null;
}
}

View File

@ -8,8 +8,8 @@ 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.camera.scan.util.LogUtils;
import com.king.zxing.DecodeConfig;
import com.king.zxing.util.LogUtils;
import java.util.Map;

View File

@ -4,12 +4,12 @@ import com.google.zxing.DecodeHintType;
import com.google.zxing.Reader;
import com.google.zxing.qrcode.QRCodeReader;
import com.king.zxing.DecodeConfig;
import com.king.zxing.DecodeFormatManager;
import java.util.Map;
import androidx.annotation.Nullable;
/**
* 二维码分析器
*
@ -18,7 +18,7 @@ import androidx.annotation.Nullable;
public class QRCodeAnalyzer extends BarcodeFormatAnalyzer {
public QRCodeAnalyzer() {
this((DecodeConfig) null);
this(new DecodeConfig().setHints(DecodeFormatManager.QR_CODE_HINTS));
}
public QRCodeAnalyzer(@Nullable Map<DecodeHintType, Object> hints) {

View File

@ -1,73 +0,0 @@
package com.king.zxing.config;
import android.content.Context;
import android.util.DisplayMetrics;
import com.king.zxing.CameraScan;
import com.king.zxing.util.LogUtils;
import java.util.Locale;
import androidx.annotation.NonNull;
import androidx.camera.core.AspectRatio;
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 final class AspectRatioCameraConfig extends CameraConfig {
/**
* 纵横比
*/
private int mAspectRatio;
public AspectRatioCameraConfig(Context context) {
super();
initTargetAspectRatio(context);
}
/**
* 初始化 {@link #mAspectRatio}
*
* @param context
*/
private void initTargetAspectRatio(Context context) {
DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
int width = displayMetrics.widthPixels;
int height = displayMetrics.heightPixels;
LogUtils.d(String.format(Locale.getDefault(), "displayMetrics: %dx%d", width, height));
float ratio = Math.max(width, height) / (float) Math.min(width, height);
if (Math.abs(ratio - CameraScan.ASPECT_RATIO_4_3) < Math.abs(ratio - CameraScan.ASPECT_RATIO_16_9)) {
mAspectRatio = AspectRatio.RATIO_4_3;
} else {
mAspectRatio = AspectRatio.RATIO_16_9;
}
LogUtils.d("aspectRatio: " + mAspectRatio);
}
@NonNull
@Override
public Preview options(@NonNull Preview.Builder builder) {
return super.options(builder);
}
@NonNull
@Override
public CameraSelector options(@NonNull CameraSelector.Builder builder) {
return super.options(builder);
}
@NonNull
@Override
public ImageAnalysis options(@NonNull ImageAnalysis.Builder builder) {
builder.setTargetAspectRatio(mAspectRatio);
return super.options(builder);
}
}

View File

@ -1,80 +0,0 @@
package com.king.zxing.config;
import androidx.annotation.NonNull;
import androidx.camera.core.CameraSelector;
import androidx.camera.core.ImageAnalysis;
import androidx.camera.core.Preview;
/**
* 相机配置:主要用于提供相机预览时可自定义一些配置,便于拓展;
* <p>
* 库中内置实现{@link CameraConfig}的有{@link AspectRatioCameraConfig}和{@link ResolutionCameraConfig}
* <p>
* 这里简单说下各自的特点:
* <p>
* {@link CameraConfig} - 默认的相机配置
* <p>
* {@link AspectRatioCameraConfig} - 根据纵横比配置相机,使输出分析的图像尽可能的接近屏幕的比例
* <p>
* {@link ResolutionCameraConfig} - 根据尺寸配置相机的目标图像大小,使输出分析的图像的分辨率尽可能的接近屏幕尺寸
* <p>
* 当使用默认的 {@link CameraConfig}在某些机型上体验欠佳时,你可以尝试使用{@link AspectRatioCameraConfig}或
* {@link ResolutionCameraConfig}会有意想不到奇效。
* <p>
* 你也可以自定义或覆写 {@link CameraConfig} 中的 {@link #options} 方法,根据需要定制配置。
*
* @author <a href="mailto:jenly1314@gmail.com">Jenly</a>
*/
public class CameraConfig {
public CameraConfig() {
}
/**
* 配置 {@link Preview.Builder};可参考:{@link AspectRatioCameraConfig} 或 {@link ResolutionCameraConfig}
* <p>
* 如配置目标旋转角度为90度{@code builder.setTargetRotation(Surface.ROTATION_90)}
* <p>
* 切记,外部请勿直接调用 {@link #options(Preview.Builder)}
*
* @param builder
* @return
*/
@NonNull
public Preview options(@NonNull Preview.Builder builder) {
return builder.build();
}
/**
* 配置 {@link CameraSelector.Builder};可参考:{@link AspectRatioCameraConfig} 或 {@link ResolutionCameraConfig}
* <p>
* 如配置前置摄像头:{@code builder.requireLensFacing(CameraSelector.LENS_FACING_FRONT)}
* <p>
* 切记,外部请勿直接调用 {@link #options(CameraSelector.Builder)}
*
* @param builder
* @return
*/
@NonNull
public CameraSelector options(@NonNull CameraSelector.Builder builder) {
return builder.build();
}
/**
* 配置 {@link ImageAnalysis.Builder};可参考:{@link AspectRatioCameraConfig} 或 {@link ResolutionCameraConfig}
* <p>
* 如配置目标旋转角度为90度{@code builder.setTargetRotation(Surface.ROTATION_90)}
* <p>
* 切记,外部请勿直接调用 {@link #options(ImageAnalysis.Builder)}
*
* @param builder
* @return
*/
@NonNull
public ImageAnalysis options(@NonNull ImageAnalysis.Builder builder) {
return builder.build();
}
}

View File

@ -1,109 +0,0 @@
package com.king.zxing.config;
import android.content.Context;
import android.util.DisplayMetrics;
import android.util.Size;
import com.king.zxing.CameraScan;
import com.king.zxing.util.LogUtils;
import java.util.Locale;
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 ResolutionCameraConfig extends CameraConfig {
/**
* 1080P
*/
public static final int IMAGE_QUALITY_1080P = 1080;
/**
* 720P
*/
public static final int IMAGE_QUALITY_720P = 720;
/**
* 目标尺寸
*/
private Size mTargetSize;
/**
* 构造
*
* @param context 上下文
*/
public ResolutionCameraConfig(Context context) {
this(context, IMAGE_QUALITY_1080P);
}
/**
* 构造
*
* @param context 上下文
* @param imageQuality 图像质量;此参数只是期望的图像质量,最终以实际计算结果为准
*/
public ResolutionCameraConfig(Context context, int imageQuality) {
super();
initTargetResolutionSize(context, imageQuality);
}
/**
* 初始化 {@link #mTargetSize}
*
* @param context 上下文
* @param imageQuality 图像质量;此参数只是期望的图像质量,最终以实际计算结果为准
*/
private void initTargetResolutionSize(Context context, int imageQuality) {
DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
int width = displayMetrics.widthPixels;
int height = displayMetrics.heightPixels;
LogUtils.d(String.format(Locale.getDefault(), "displayMetrics: %dx%d", width, height));
// 因为为了保持流畅性和性能尽可能的限制在imageQuality默认1080p在此前提下尽可能的找到屏幕接近的分辨率
if (width < height) {
float ratio = height / (float) width;
int size = Math.min(width, imageQuality);
if (Math.abs(ratio - CameraScan.ASPECT_RATIO_4_3) < Math.abs(ratio - CameraScan.ASPECT_RATIO_16_9)) {
mTargetSize = new Size(size, Math.round(size * CameraScan.ASPECT_RATIO_4_3));
} else {
mTargetSize = new Size(size, Math.round(size * CameraScan.ASPECT_RATIO_16_9));
}
} else {
int size = Math.min(height, imageQuality);
float ratio = width / (float) height;
if (Math.abs(ratio - CameraScan.ASPECT_RATIO_4_3) < Math.abs(ratio - CameraScan.ASPECT_RATIO_16_9)) {
mTargetSize = new Size(Math.round(size * CameraScan.ASPECT_RATIO_4_3), size);
} else {
mTargetSize = new Size(Math.round(size * CameraScan.ASPECT_RATIO_16_9), size);
}
}
LogUtils.d("targetSize: " + mTargetSize);
}
@NonNull
@Override
public Preview options(@NonNull Preview.Builder builder) {
return super.options(builder);
}
@NonNull
@Override
public CameraSelector options(@NonNull CameraSelector.Builder builder) {
return super.options(builder);
}
@NonNull
@Override
public ImageAnalysis options(@NonNull ImageAnalysis.Builder builder) {
builder.setTargetResolution(mTargetSize);
return super.options(builder);
}
}

View File

@ -1,141 +0,0 @@
package com.king.zxing.manager;
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

@ -1,101 +0,0 @@
package com.king.zxing.manager;
import android.content.Context;
import android.content.res.AssetFileDescriptor;
import android.media.MediaPlayer;
import android.os.Build;
import android.os.VibrationEffect;
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.hasVibrator()) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
vibrator.vibrate(VibrationEffect.createOneShot(VIBRATE_DURATION, VibrationEffect.DEFAULT_AMPLITUDE));
} else {
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.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

@ -1,273 +0,0 @@
package com.king.zxing.util;
import android.annotation.SuppressLint;
import android.content.ContentResolver;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.ImageFormat;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.graphics.YuvImage;
import android.media.ExifInterface;
import android.media.Image;
import android.net.Uri;
import android.os.Build;
import android.provider.MediaStore;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.camera.core.ImageProxy;
/**
* Utils functions for bitmap conversions.
*
* @see <a href="https://github.com/googlesamples/mlkit/blob/master/android/vision-quickstart/app/src/main/java/com/google/mlkit/vision/demo/BitmapUtils.java">BitmapUtils</a>
*/
public class BitmapUtils {
private BitmapUtils() {
throw new AssertionError();
}
/**
* Converts NV21 format byte buffer to bitmap.
*/
@Nullable
public static Bitmap getBitmap(ByteBuffer data, int width, int height, int rotationDegrees) {
data.rewind();
byte[] imageInBuffer = new byte[data.limit()];
data.get(imageInBuffer, 0, imageInBuffer.length);
try {
YuvImage image = new YuvImage(
imageInBuffer, ImageFormat.NV21, width, height, null);
ByteArrayOutputStream stream = new ByteArrayOutputStream();
image.compressToJpeg(new Rect(0, 0, width, height), 80, stream);
Bitmap bmp = BitmapFactory.decodeByteArray(stream.toByteArray(), 0, stream.size());
stream.close();
return rotateBitmap(bmp, rotationDegrees, false, false);
} catch (Exception e) {
LogUtils.e("Error: " + e.getMessage());
}
return null;
}
/**
* Converts a YUV_420_888 image from CameraX API to a bitmap.
*/
@Nullable
public static Bitmap getBitmap(ImageProxy image) {
@SuppressLint("UnsafeOptInUsageError")
ByteBuffer nv21Buffer = yuv420ThreePlanesToNV21(image.getImage().getPlanes(), image.getWidth(), image.getHeight());
return getBitmap(nv21Buffer, image.getWidth(), image.getHeight(), image.getImageInfo().getRotationDegrees());
}
/**
* Rotates a bitmap if it is converted from a bytebuffer.
*/
private static Bitmap rotateBitmap(
Bitmap bitmap, int rotationDegrees, boolean flipX, boolean flipY) {
Matrix matrix = new Matrix();
// Rotate the image back to straight.
matrix.postRotate(rotationDegrees);
// Mirror the image along the X or Y axis.
matrix.postScale(flipX ? -1.0f : 1.0f, flipY ? -1.0f : 1.0f);
Bitmap rotatedBitmap =
Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
// Recycle the old bitmap if it has changed.
if (rotatedBitmap != bitmap) {
bitmap.recycle();
}
return rotatedBitmap;
}
@RequiresApi(api = Build.VERSION_CODES.N)
@Nullable
public static Bitmap getBitmapFromContentUri(ContentResolver contentResolver, Uri imageUri)
throws IOException {
Bitmap decodedBitmap = MediaStore.Images.Media.getBitmap(contentResolver, imageUri);
if (decodedBitmap == null) {
return null;
}
int orientation = getExifOrientationTag(contentResolver, imageUri);
int rotationDegrees = 0;
boolean flipX = false;
boolean flipY = false;
// See e.g. https://magnushoff.com/articles/jpeg-orientation/ for a detailed explanation on each
// orientation.
switch (orientation) {
case ExifInterface.ORIENTATION_FLIP_HORIZONTAL:
flipX = true;
break;
case ExifInterface.ORIENTATION_ROTATE_90:
rotationDegrees = 90;
break;
case ExifInterface.ORIENTATION_TRANSPOSE:
rotationDegrees = 90;
flipX = true;
break;
case ExifInterface.ORIENTATION_ROTATE_180:
rotationDegrees = 180;
break;
case ExifInterface.ORIENTATION_FLIP_VERTICAL:
flipY = true;
break;
case ExifInterface.ORIENTATION_ROTATE_270:
rotationDegrees = -90;
break;
case ExifInterface.ORIENTATION_TRANSVERSE:
rotationDegrees = -90;
flipX = true;
break;
case ExifInterface.ORIENTATION_UNDEFINED:
case ExifInterface.ORIENTATION_NORMAL:
default:
// No transformations necessary in this case.
}
return rotateBitmap(decodedBitmap, rotationDegrees, flipX, flipY);
}
@RequiresApi(api = Build.VERSION_CODES.N)
private static int getExifOrientationTag(ContentResolver resolver, Uri imageUri) {
// We only support parsing EXIF orientation tag from local file on the device.
// See also:
// https://android-developers.googleblog.com/2016/12/introducing-the-exifinterface-support-library.html
if (!ContentResolver.SCHEME_CONTENT.equals(imageUri.getScheme())
&& !ContentResolver.SCHEME_FILE.equals(imageUri.getScheme())) {
return 0;
}
ExifInterface exif;
try (InputStream inputStream = resolver.openInputStream(imageUri)) {
if (inputStream == null) {
return 0;
}
exif = new ExifInterface(inputStream);
} catch (IOException e) {
LogUtils.e("failed to open file to read rotation meta data: " + imageUri, e);
return 0;
}
return exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL);
}
/**
* Converts YUV_420_888 to NV21 bytebuffer.
*
* <p>The NV21 format consists of a single byte array containing the Y, U and V values. For an
* image of size S, the first S positions of the array contain all the Y values. The remaining
* positions contain interleaved V and U values. U and V are subsampled by a factor of 2 in both
* dimensions, so there are S/4 U values and S/4 V values. In summary, the NV21 array will contain
* S Y values followed by S/4 VU values: YYYYYYYYYYYYYY(...)YVUVUVUVU(...)VU
*
* <p>YUV_420_888 is a generic format that can describe any YUV image where U and V are subsampled
* by a factor of 2 in both dimensions. {@link Image#getPlanes} returns an array with the Y, U and
* V planes. The Y plane is guaranteed not to be interleaved, so we can just copy its values into
* the first part of the NV21 array. The U and V planes may already have the representation in the
* NV21 format. This happens if the planes share the same buffer, the V buffer is one position
* before the U buffer and the planes have a pixelStride of 2. If this is case, we can just copy
* them to the NV21 array.
*/
public static ByteBuffer yuv420ThreePlanesToNV21(Image.Plane[] yuv420888planes, int width, int height) {
int imageSize = width * height;
byte[] out = new byte[imageSize + 2 * (imageSize / 4)];
if (areUVPlanesNV21(yuv420888planes, width, height)) {
// Copy the Y values.
yuv420888planes[0].getBuffer().get(out, 0, imageSize);
ByteBuffer uBuffer = yuv420888planes[1].getBuffer();
ByteBuffer vBuffer = yuv420888planes[2].getBuffer();
// Get the first V value from the V buffer, since the U buffer does not contain it.
vBuffer.get(out, imageSize, 1);
// Copy the first U value and the remaining VU values from the U buffer.
uBuffer.get(out, imageSize + 1, 2 * imageSize / 4 - 1);
} else {
// Fallback to copying the UV values one by one, which is slower but also works.
// Unpack Y.
unpackPlane(yuv420888planes[0], width, height, out, 0, 1);
// Unpack U.
unpackPlane(yuv420888planes[1], width, height, out, imageSize + 1, 2);
// Unpack V.
unpackPlane(yuv420888planes[2], width, height, out, imageSize, 2);
}
return ByteBuffer.wrap(out);
}
/**
* Checks if the UV plane buffers of a YUV_420_888 image are in the NV21 format.
*/
private static boolean areUVPlanesNV21(Image.Plane[] planes, int width, int height) {
int imageSize = width * height;
ByteBuffer uBuffer = planes[1].getBuffer();
ByteBuffer vBuffer = planes[2].getBuffer();
// Backup buffer properties.
int vBufferPosition = vBuffer.position();
int uBufferLimit = uBuffer.limit();
// Advance the V buffer by 1 byte, since the U buffer will not contain the first V value.
vBuffer.position(vBufferPosition + 1);
// Chop off the last byte of the U buffer, since the V buffer will not contain the last U value.
uBuffer.limit(uBufferLimit - 1);
// Check that the buffers are equal and have the expected number of elements.
boolean areNV21 =
(vBuffer.remaining() == (2 * imageSize / 4 - 2)) && (vBuffer.compareTo(uBuffer) == 0);
// Restore buffers to their initial state.
vBuffer.position(vBufferPosition);
uBuffer.limit(uBufferLimit);
return areNV21;
}
/**
* Unpack an image plane into a byte array.
*
* <p>The input plane data will be copied in 'out', starting at 'offset' and every pixel will be
* spaced by 'pixelStride'. Note that there is no row padding on the output.
*/
private static void unpackPlane(
Image.Plane plane, int width, int height, byte[] out, int offset, int pixelStride) {
ByteBuffer buffer = plane.getBuffer();
buffer.rewind();
// Compute the size of the current plane.
// We assume that it has the aspect ratio as the original image.
int numRow = (buffer.limit() + plane.getRowStride() - 1) / plane.getRowStride();
if (numRow == 0) {
return;
}
int scaleFactor = height / numRow;
int numCol = width / scaleFactor;
// Extract the data in the output buffer.
int outputPos = offset;
int rowStart = 0;
for (int row = 0; row < numRow; row++) {
int inputPos = rowStart;
for (int col = 0; col < numCol; col++) {
out[outputPos] = buffer.get(inputPos);
outputPos += pixelStride;
inputPos += plane.getPixelStride();
}
rowStart += plane.getRowStride();
}
}
}

View File

@ -43,6 +43,7 @@ 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.camera.scan.util.LogUtils;
import com.king.zxing.DecodeFormatManager;
import java.util.HashMap;

View File

@ -1,327 +0,0 @@
/*
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;
import java.util.Locale;
/**
* @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(Locale.getDefault(), 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

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

View File

@ -1,12 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="30dp"
android:height="30dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path
android:fillColor="#FFFFFF"
android:pathData="m755.2,209.9c-17.2,-17.4 -40.2,-26.9 -64.5,-26.9l-357.2,0c-50.3,0 -91.3,40.9 -91.3,91.3l0,105.1c0,77.3 36.3,149.7 97.7,196.1l0,300.3c0,50.3 40.9,91.3 91.3,91.3l161.5,0c50.3,0 91.3,-40.9 91.3,-91.3l0,-300.3c61.4,-46.4 97.7,-118.7 97.7,-196.1l0,-104.9c0.2,-24.4 -9.3,-47.3 -26.5,-64.6zM333.5,230.8l357.1,0c11.6,0 22.5,4.5 30.7,12.8c8.2,8.2 12.7,19.1 12.6,30.8l0,42.9l-443.9,0l0,-43c0,-24 19.5,-43.5 43.5,-43.5zM646.7,543.3c-6.6,4.4 -10.5,11.9 -10.5,19.8l0,312.6c0,24 -19.5,43.5 -43.5,43.5l-161.4,0c-24,0 -43.5,-19.5 -43.5,-43.5l0,-312.6c0,-7.9 -3.9,-15.4 -10.5,-19.8c-54,-36.5 -86.3,-96.8 -87.1,-161.7l443.7,0c-0.8,64.9 -33.2,125.2 -87.2,161.7z" />
<path
android:fillColor="#FFFFFF"
android:pathData="m543,690.4l-62,0c-0.7,0 -1.2,-0.5 -1.2,-1.2l0,-169.4c0,-0.7 0.5,-1.2 1.2,-1.2l62,0c0.7,0 1.2,0.5 1.2,1.2l0,169.4c0,0.7 -0.5,1.2 -1.2,1.2z" />
</vector>

View File

@ -1,27 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="30dp"
android:height="30dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path
android:fillColor="#FFFFFF"
android:pathData="m755.2,209.9c-17.2,-17.4 -40.2,-26.9 -64.5,-26.9l-357.2,0c-50.3,0 -91.3,40.9 -91.3,91.3l0,105.1c0,77.3 36.3,149.7 97.7,196.1l0,300.3c0,50.3 40.9,91.3 91.3,91.3l161.5,0c50.3,0 91.3,-40.9 91.3,-91.3l0,-300.3c61.4,-46.4 97.7,-118.7 97.7,-196.1l0,-104.9c0.2,-24.4 -9.3,-47.3 -26.5,-64.6zM333.5,230.8l357.1,0c11.6,0 22.5,4.5 30.7,12.8c8.2,8.2 12.7,19.1 12.6,30.8l0,42.9l-443.9,0l0,-43c0,-24 19.5,-43.5 43.5,-43.5zM646.7,543.3c-6.6,4.4 -10.5,11.9 -10.5,19.8l0,312.6c0,24 -19.5,43.5 -43.5,43.5l-161.4,0c-24,0 -43.5,-19.5 -43.5,-43.5l0,-312.6c0,-7.9 -3.9,-15.4 -10.5,-19.8c-54,-36.5 -86.3,-96.8 -87.1,-161.7l443.7,0c-0.8,64.9 -33.2,125.2 -87.2,161.7z" />
<path
android:fillColor="#FFFFFF"
android:pathData="m543,690.4l-62,0c-0.7,0 -1.2,-0.5 -1.2,-1.2l0,-169.4c0,-0.7 0.5,-1.2 1.2,-1.2l62,0c0.7,0 1.2,0.5 1.2,1.2l0,169.4c0,0.7 -0.5,1.2 -1.2,1.2z" />
<path
android:fillColor="#FFFFFF"
android:pathData="M682.5,77.5a53.5,26.5 116.5,1 0,45 28.4a53.5,26.5 116.5,1 0,-45 -28.4z"
android:strokeWidth="1"
android:strokeColor="#FFFFFF" />
<path
android:fillColor="#FFFFFF"
android:pathData="M482,80a30,68 0,1 0,60 0a30,68 0,1 0,-60 0z"
android:strokeWidth="1"
android:strokeColor="#FFFFFF" />
<path
android:fillColor="#FFFFFF"
android:pathData="M296.5,105.6a53.5,26.5 63.5,1 0,45 -28.4a53.5,26.5 63.5,1 0,-45 28.4z"
android:strokeWidth="1"
android:strokeColor="#FFFFFF" />
</vector>

View File

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

View File

@ -8,7 +8,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent" />
<com.king.zxing.ViewfinderView
<com.king.view.viewfinderview.ViewfinderView
android:id="@+id/viewfinderView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
@ -18,7 +18,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="@dimen/zxl_flashlight_margin_top"
android:layout_marginTop="@dimen/camera_scan_flashlight_margin_top"
android:contentDescription="@null"
android:src="@drawable/zxl_flashlight_selector" />
android:src="@drawable/camera_scan_flashlight_selector" />
</FrameLayout>

View File

@ -1,59 +0,0 @@
<resources>
<declare-styleable name="ViewfinderView">
<attr name="maskColor" format="color" />
<attr name="frameColor" format="color" />
<attr name="cornerColor" format="color"/>
<attr name="laserColor" format="color"/>
<attr name="labelText" format="string"/>
<attr name="labelTextColor" format="color"/>
<attr name="labelTextSize" format="dimension"/>
<attr name="labelTextPadding" format="dimension"/>
<attr name="labelTextWidth" format="dimension"/>
<attr name="labelTextLocation" format="enum">
<enum name="top" value="0"/>
<enum name="bottom" value="1"/>
</attr>
<attr name="frameWidth" format="dimension"/>
<attr name="frameHeight" format="dimension"/>
<attr name="gridColumn" format="integer"/>
<attr name="gridHeight" format="dimension"/>
<attr name="laserStyle" format="enum">
<enum name="none" value="0"/>
<enum name="line" value="1"/>
<enum name="grid" value="2"/>
<enum name="image" value="3"/>
</attr>
<attr name="cornerRectWidth" format="dimension"/>
<attr name="cornerRectHeight" format="dimension"/>
<attr name="scannerLineMoveDistance" format="dimension"/>
<attr name="scannerLineHeight" format="dimension"/>
<attr name="frameLineWidth" format="dimension"/>
<attr name="scannerAnimationDelay" format="integer"/>
<attr name="frameRatio" format="float"/>
<attr name="framePaddingLeft" format="dimension"/>
<attr name="framePaddingTop" format="dimension"/>
<attr name="framePaddingRight" format="dimension"/>
<attr name="framePaddingBottom" format="dimension"/>
<attr name="frameGravity" format="enum">
<enum name="center" value="0"/>
<enum name="left" value="1"/>
<enum name="top" value="2"/>
<enum name="right" value="3"/>
<enum name="bottom" value="4"/>
</attr>
<attr name="pointColor" format="color"/>
<attr name="pointStrokeColor" format="color"/>
<attr name="pointRadius" format="dimension"/>
<attr name="pointStrokeRatio" format="float"/>
<attr name="pointDrawable" format="reference"/>
<attr name="showPointAnim" format="boolean"/>
<attr name="laserDrawable" format="reference"/>
<attr name="laserDrawableRatio" format="float"/>
<attr name="viewfinderStyle" format="enum">
<enum name="classic" value="0"/>
<enum name="popular" value="1"/>
</attr>
</declare-styleable>
</resources>

View File

@ -1,14 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="viewfinder_mask">#60000000</color>
<color name="viewfinder_frame">#7F1FB3E2</color>
<color name="viewfinder_corner">#FF1FB3E2</color>
<color name="viewfinder_laser">#FF1FB3E2</color>
<color name="viewfinder_point">#FF1FB3E2</color>
<color name="viewfinder_text_color">#FFC0C0C0</color>
<color name="zxl_capture_status_bar_color">#00000000</color>
<color name="zxl_capture_navigation_bar_color">#00000000</color>
</resources>

View File

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

View File

@ -1,15 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="CaptureTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<item name="windowNoTitle">true</item>
<item name="windowActionBar">false</item>
<item name="colorPrimary">@android:color/black</item>
<item name="colorPrimaryDark">@android:color/black</item>
<item name="android:windowTranslucentStatus">true</item>
<item name="android:windowTranslucentNavigation">true</item>
<item name="android:statusBarColor">@color/zxl_capture_status_bar_color</item>
<item name="android:navigationBarColor">@color/zxl_capture_navigation_bar_color</item>
</style>
</resources>