diff --git a/README.md b/README.md index d792b9c..d03f2ec 100644 --- a/README.md +++ b/README.md @@ -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 | #60000000 | 扫描区外遮罩的颜色 | -| frameColor | color | #7F1FB3E2 | 扫描区边框的颜色 | -| cornerColor | color | #FF1FB3E2 | 扫描区边角的颜色 | -| laserColor | color | #FF1FB3E2 | 扫描区激光线的颜色 | -| labelText | string | | 扫描提示文本信息 | -| labelTextColor | color | #FFC0C0C0 | 提示文本字体颜色 | -| 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 | #FF1FB3E2 | 结果点的颜色 | -| pointStrokeColor | color | #FFFFFFFF | 结果点描边的颜色 | -| 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即可。(适用于想在扫码界面写交互逻辑,又因为项目架构或其它原因,无法直接或间接继承CaptureActivity或CaptureFragment时使用)实现示例:[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 - - - - - - - - -``` - -或在你的布局中添加 - -```Xml - -``` - -### 代码示例 - -**工具类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 cameraScan) { + super.initCameraScan(cameraScan); + // 根据需要设置CameraScan相关配置 + cameraScan.setPlayBeep(true); } + @Nullable @Override - public void initCameraScan() { - super.initCameraScan(); - - //初始化解码配置 + public Analyzer 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) { + // 停止分析 + 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 - -``` -#### 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.0:2023-8-23 +* 将通用基础类拆分移除并进行重构,后续维护更便捷 +* 移除 **CameraScan** 相关核心类,改为依赖[CameraScan](https://github.com/jenly1314/CameraScan) +* 移除扫码取景视图 **ViewfinderView**,改为依赖[ViewfinderView](https://github.com/jenly1314/ViewfinderView) +* 移除**CaptureActivity**和****CaptureFragment**,新增**BarcodeCameraScanActivity**和****BarcodeCameraScanFragment**来替代 +* 优化扫描分析过程的性能体验(优化帧数据分析过程) + #### v2.4.0:2023-4-15 * 优化CameraScan的缺省配置(CameraConfig相关配置) * 优化ViewfinderView自定义属性(新增laserDrawableRatio) diff --git a/app/build.gradle b/app/build.gradle index 9e7598a..f04cd7a 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -46,10 +46,5 @@ dependencies { coreLibraryDesugaring deps.desugar_jdk -// implementation deps.kotlin -// implementation deps.corektx - - implementation deps.easypermissions - implementation project(':zxing-lite') } diff --git a/app/release/app-release.apk b/app/release/app-release.apk index c09d027..743df93 100644 Binary files a/app/release/app-release.apk and b/app/release/app-release.apk differ diff --git a/app/release/output-metadata.json b/app/release/output-metadata.json index 51233fe..23dd689 100644 --- a/app/release/output-metadata.json +++ b/app/release/output-metadata.json @@ -11,8 +11,8 @@ "type": "SINGLE", "filters": [], "attributes": [], - "versionCode": 37, - "versionName": "2.4.0", + "versionCode": 38, + "versionName": "3.0.0", "outputFile": "app-release.apk" } ], diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index bf02d13..435e287 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -24,34 +24,21 @@ - - + android:theme="@style/CameraScanTheme"/> + android:theme="@style/CameraScanTheme"/> - - + android:theme="@style/CameraScanTheme"/> Jenly - */ -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; - } - } -} diff --git a/app/src/main/java/com/king/zxing/app/CodeActivity.java b/app/src/main/java/com/king/zxing/app/CodeActivity.java index 14f9477..cc42f19 100644 --- a/app/src/main/java/com/king/zxing/app/CodeActivity.java +++ b/app/src/main/java/com/king/zxing/app/CodeActivity.java @@ -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 Jenly */ 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: diff --git a/app/src/main/java/com/king/zxing/app/CustomCaptureActivity.java b/app/src/main/java/com/king/zxing/app/CustomCaptureActivity.java deleted file mode 100644 index 56e89f9..0000000 --- a/app/src/main/java/com/king/zxing/app/CustomCaptureActivity.java +++ /dev/null @@ -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 Jenly - */ -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; - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/king/zxing/app/CustomFullScanActivity.java b/app/src/main/java/com/king/zxing/app/CustomFullScanActivity.java deleted file mode 100644 index dd77a50..0000000 --- a/app/src/main/java/com/king/zxing/app/CustomFullScanActivity.java +++ /dev/null @@ -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 Jenly - */ -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; - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/king/zxing/app/EasyCaptureActivity.java b/app/src/main/java/com/king/zxing/app/EasyCaptureActivity.java deleted file mode 100644 index 674ccb4..0000000 --- a/app/src/main/java/com/king/zxing/app/EasyCaptureActivity.java +++ /dev/null @@ -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 Jenly - */ -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; - } - } -} diff --git a/app/src/main/java/com/king/zxing/app/FullScreenQRCodeScanActivity.kt b/app/src/main/java/com/king/zxing/app/FullScreenQRCodeScanActivity.kt new file mode 100644 index 0000000..44e5ccf --- /dev/null +++ b/app/src/main/java/com/king/zxing/app/FullScreenQRCodeScanActivity.kt @@ -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 Jenly + */ +class FullScreenQRCodeScanActivity : BarcodeCameraScanActivity() { + + override fun initUI() { + super.initUI() + + // 设置取景框样式 + viewfinderView.setViewfinderStyle(ViewfinderStyle.POPULAR) + + } + + + override fun initCameraScan(cameraScan: CameraScan) { + super.initCameraScan(cameraScan) + // 根据需要设置CameraScan相关配置 + cameraScan.setPlayBeep(true) + } + + override fun createAnalyzer(): Analyzer? { + // 初始化解码配置 + 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) { + // 停止分析 + 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) { + 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)) + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/king/zxing/app/MainActivity.java b/app/src/main/java/com/king/zxing/app/MainActivity.java index c160660..92f5edc 100644 --- a/app/src/main/java/com/king/zxing/app/MainActivity.java +++ b/app/src/main/java/com/king/zxing/app/MainActivity.java @@ -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 list) { - // Some permissions have been granted - - } - - @Override - public void onPermissionsDenied(int requestCode, List 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; } diff --git a/app/src/main/java/com/king/zxing/app/MultiFormatScanActivity.kt b/app/src/main/java/com/king/zxing/app/MultiFormatScanActivity.kt new file mode 100644 index 0000000..1c6f6e8 --- /dev/null +++ b/app/src/main/java/com/king/zxing/app/MultiFormatScanActivity.kt @@ -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 Jenly + */ +class MultiFormatScanActivity : BarcodeCameraScanActivity() { + + var toast: Toast? = null + + override fun initCameraScan(cameraScan: CameraScan) { + super.initCameraScan(cameraScan) + // 根据需要设置CameraScan相关配置 + cameraScan.setPlayBeep(true) + } + + override fun createAnalyzer(): Analyzer? { + // 初始化解码配置 + 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) { + // 停止分析 + 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() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/king/zxing/app/QRCodeActivity.java b/app/src/main/java/com/king/zxing/app/QRCodeActivity.java deleted file mode 100644 index 7643f6c..0000000 --- a/app/src/main/java/com/king/zxing/app/QRCodeActivity.java +++ /dev/null @@ -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 Jenly - */ -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; - } - } -} diff --git a/app/src/main/java/com/king/zxing/app/QRCodeScanActivity.java b/app/src/main/java/com/king/zxing/app/QRCodeScanActivity.java new file mode 100644 index 0000000..88bf074 --- /dev/null +++ b/app/src/main/java/com/king/zxing/app/QRCodeScanActivity.java @@ -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 Jenly + */ +public class QRCodeScanActivity extends BarcodeCameraScanActivity { + + @Override + public void initCameraScan(@NonNull CameraScan cameraScan) { + super.initCameraScan(cameraScan); + // 根据需要设置CameraScan相关配置 + cameraScan.setPlayBeep(true); + } + + @Nullable + @Override + public Analyzer 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) { + // 停止分析 + getCameraScan().setAnalyzeImage(false); + // 返回结果 + Intent intent = new Intent(); + intent.putExtra(CameraScan.SCAN_RESULT, result.getResult().getText()); + setResult(Activity.RESULT_OK, intent); + finish(); + } +} diff --git a/app/src/main/java/com/king/zxing/app/util/StatusBarUtils.java b/app/src/main/java/com/king/zxing/app/util/StatusBarUtils.java deleted file mode 100644 index 1c251a5..0000000 --- a/app/src/main/java/com/king/zxing/app/util/StatusBarUtils.java +++ /dev/null @@ -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 Jenly - */ -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); - } -} diff --git a/app/src/main/java/com/king/zxing/app/util/UriUtils.java b/app/src/main/java/com/king/zxing/app/util/UriUtils.java deleted file mode 100644 index 93a3ea6..0000000 --- a/app/src/main/java/com/king/zxing/app/util/UriUtils.java +++ /dev/null @@ -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 Jenly - */ -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; - } - -} diff --git a/app/src/main/res/drawable-xxhdpi/ic_laser_line.png b/app/src/main/res/drawable-xxhdpi/ic_laser_line.png deleted file mode 100644 index 09a75b6..0000000 Binary files a/app/src/main/res/drawable-xxhdpi/ic_laser_line.png and /dev/null differ diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index e60378b..32549fc 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -26,122 +26,84 @@ android:layout_gravity="center_horizontal" android:text="@string/app_name"/> +