更新CameraX至v1.2.2

This commit is contained in:
Jenly
2023-04-15 16:45:34 +08:00
parent 863f1ab1b4
commit 2359695964
33 changed files with 734 additions and 319 deletions

View File

@@ -25,43 +25,44 @@ ZXingLite for Android 是ZXing的精简极速版基于ZXing库优化扫码和
## 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 | 扫描区边角的高 |
| 属性 | 属性类型 | 默认值 | 属性说明 |
|:------------------------|:----------| :------ | :------ |
| 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 | | 扫描激光自定义图片 |
| viewfinderStyle | enum | classic | 取景框样式支持classic经典样式带扫码框那种、popular流行样式不带扫码框 |
| 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流行样式不带扫码框 |
## 引入
@@ -83,7 +84,7 @@ allprojects {
```gradle
// AndroidX 版本
implementation 'com.github.jenly1314:zxing-lite:2.3.1'
implementation 'com.github.jenly1314:zxing-lite:2.4.0'
```
@@ -175,7 +176,7 @@ implementation 'com.king.zxing:zxing-lite:1.1.9'
// 获取CameraScan扫码相关的配置设置。CameraScan里面包含部分支持链式调用的方法即调用返回是CameraScan本身的一些配置建议在startCamera之前调用。
getCameraScan().setPlayBeep(true)//设置是否播放音效默认为false
.setVibrate(true)//设置是否震动默认为false
.setCameraConfig(new CameraConfig())//设置相机配置信息CameraConfig可覆写options方法自定义配置
.setCameraConfig(new ResolutionCameraConfig(this))//设置相机配置信息CameraConfig可覆写options方法自定义配置
.setNeedAutoZoom(false)//二维码太小时可自动缩放默认为false
.setNeedTouchZoom(true)//支持多指触摸捏合缩放默认为true
.setDarkLightLux(45f)//设置光线足够暗的阈值单位lux需要通过{@link #bindFlashlightView(View)}绑定手电筒才有效
@@ -404,6 +405,12 @@ dependencies {
## 版本记录
#### v2.4.02023-4-15
* 优化CameraScan的缺省配置CameraConfig相关配置
* 优化ViewfinderView自定义属性新增laserDrawableRatio
* 优化ImageAnalyzer中YUV数据的处理
* 更新CameraX至v1.2.2
#### v2.3.12023-3-4
* 更新CameraX至v1.2.1
* 更新Gradle至v7.5

View File

@@ -1,18 +1,22 @@
apply plugin: 'com.android.application'
//apply plugin: 'kotlin-android'
//apply plugin: 'kotlin-android-extensions'
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
}
android {
compileSdkVersion build_versions.compileSdk
buildToolsVersion build_versions.buildTools
namespace 'com.king.zxing.app'
compileSdk build_versions.compileSdk
defaultConfig {
applicationId "com.king.zxing.app"
minSdkVersion build_versions.minSdk
targetSdkVersion build_versions.targetSdk
minSdk build_versions.minSdk
targetSdk build_versions.targetSdk
versionCode app_version.versionCode
versionName app_version.versionName
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
@@ -24,22 +28,16 @@ android {
abortOnError false
}
compileOptions {
coreLibraryDesugaringEnabled true
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
}
// kotlinOptions {
// jvmTarget = '1.8'
// }
}
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
testImplementation deps.test.junit
androidTestImplementation deps.test.runner
androidTestImplementation deps.test.android_ext_junit
androidTestImplementation deps.test.espresso
implementation deps.androidx.design

Binary file not shown.

View File

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

View File

@@ -77,7 +77,7 @@ public class CustomCaptureActivity extends CaptureActivity {
getCameraScan().setPlayBeep(true)//设置是否播放音效默认为false
.setVibrate(true)//设置是否震动默认为false
// .setCameraConfig(new CameraConfig())//设置相机配置信息CameraConfig可覆写options方法自定义配置
.setCameraConfig(new ResolutionCameraConfig(this))//设置CameraConfig可以根据自己的需求去自定义配置
// .setCameraConfig(new ResolutionCameraConfig(this))//设置CameraConfig可以根据自己的需求去自定义配置
.setNeedAutoZoom(false)//二维码太小时可自动缩放默认为false
.setNeedTouchZoom(true)//支持多指触摸捏合缩放默认为true
.setDarkLightLux(45f)//设置光线足够暗的阈值单位lux需要通过{@link #bindFlashlightView(View)}绑定手电筒才有效
@@ -117,12 +117,10 @@ public class CustomCaptureActivity extends CaptureActivity {
}
private void showToast(String text){
if(toast == null){
toast = Toast.makeText(this,text,Toast.LENGTH_SHORT);
}else{
toast.setText(text);
toast.setDuration(Toast.LENGTH_SHORT);
if(toast != null){
toast.cancel();
}
toast = Toast.makeText(this,text,Toast.LENGTH_SHORT);
toast.show();
}

View File

@@ -32,6 +32,8 @@ 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;
@@ -72,6 +74,8 @@ public class MainActivity extends AppCompatActivity implements EasyPermissions.P
private Toast toast;
private ExecutorService executor = Executors.newSingleThreadExecutor();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -97,12 +101,10 @@ public class MainActivity extends AppCompatActivity implements EasyPermissions.P
}
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);
if(toast != null){
toast.cancel();
}
toast = Toast.makeText(this,text,Toast.LENGTH_SHORT);
toast.show();
}
@@ -165,7 +167,7 @@ public class MainActivity extends AppCompatActivity implements EasyPermissions.P
}
private void asyncThread(Runnable runnable){
new Thread(runnable).start();
executor.execute(runnable);
}
/**

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

@@ -13,7 +13,10 @@
android:id="@+id/viewfinderView"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:viewfinderStyle="popular"/>
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"

View File

@@ -1,53 +1,10 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
apply from: 'versions.gradle'
repositories {
google()
mavenCentral()
}
dependencies {
classpath "com.android.tools.build:gradle:$versions.gralde"
// classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$versions.kotlin"
// classpath "com.novoda:bintray-release:$versions.bintray_release"
classpath "com.vanniktech:gradle-maven-publish-plugin:$versions.mavenPublish"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
google()
mavenCentral()
}
}
allprojects {
plugins.withId("com.vanniktech.maven.publish") {
mavenPublish {
sonatypeHost = "S01"
}
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
allprojects {
tasks.withType(Javadoc) {
options{
encoding "UTF-8"
charSet 'UTF-8'
links "http://docs.oracle.com/javase/8/docs/api"
}
options.addStringOption('Xdoclint:none', '-quiet')
failOnError false
}
}// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
id 'com.android.application' version '7.4.2' apply false
id 'com.android.library' version '7.4.2' apply false
id 'org.jetbrains.kotlin.android' version '1.8.0' apply false
// id 'org.jetbrains.dokka' version '1.7.0' apply false
id 'com.vanniktech.maven.publish' version '0.22.0' apply false
}

View File

@@ -1,5 +1,11 @@
## 版本记录
#### v2.4.02023-4-15
* 优化CameraScan的缺省配置CameraConfig相关配置
* 优化ViewfinderView自定义属性新增laserDrawableRatio
* 优化ImageAnalyzer中YUV数据的处理
* 更新CameraX至v1.2.2
#### v2.3.12023-3-4
* 更新CameraX至v1.2.1
* 更新Gradle至v7.5

View File

@@ -14,8 +14,8 @@ org.gradle.jvmargs = -Xmx1536m
android.useAndroidX=true
android.enableJetifier=true
VERSION_NAME=2.3.1
VERSION_CODE=36
VERSION_NAME=2.4.0
VERSION_CODE=37
GROUP=com.github.jenly1314
POM_DESCRIPTION=ZXingLite for Android
@@ -35,6 +35,8 @@ POM_DEVELOPER_ID=jenly
POM_DEVELOPER_NAME=Jenly Yu
POM_DEVELOPER_URL=https://github.com/jenly1314/
SONATYPE_HOST=S01
RELEASE_REPOSITORY_URL=https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/
SNAPSHOT_REPOSITORY_URL=https://s01.oss.sonatype.org/content/repositories/snapshots/

View File

@@ -1 +1,18 @@
include ':app', ':zxing-lite'
pluginManagement {
repositories {
gradlePluginPortal()
google()
mavenCentral()
}
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
}
}
rootProject.name = "ZXingLite"
include ':app'
include ':zxing-lite'

View File

@@ -1,7 +1,7 @@
//App
def app_version = [:]
app_version.versionCode = 36
app_version.versionName = "2.3.1"
app_version.versionCode = 37
app_version.versionName = "2.4.0"
ext.app_version = app_version
//build version
@@ -22,16 +22,17 @@ versions.appcompat = "1.1.0"
versions.constraintLayout = "2.0.4"
//test
versions.junit = "1.1.0"
versions.junit = "4.13.2"
versions.androidExtJunit = "1.1.3"
versions.test = "1.2.0"
versions.runner = "1.2.0"
versions.espresso = "3.2.0"
versions.espresso = "3.4.0"
versions.bintray_release = "0.9.2"
versions.mavenPublish = '0.18.0'
versions.gralde = "7.4.1"
versions.kotlin = "1.6.0"
versions.coreKtx = "1.6.0"
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"
@@ -53,7 +54,8 @@ deps.androidx = androidx
//test
def test = [:]
test.junit = "androidx.test.ext:junit:$versions.junit"
test.junit = "junit:junit:$versions.junit"
test.android_ext_junit = "androidx.test.ext:junit:$versions.androidExtJunit"
test.test = "androidx.test:core:$versions.test"
test.runner = "androidx.test:runner:$versions.runner"
test.espresso = "androidx.test.espresso:espresso-core:$versions.espresso"

View File

@@ -1,52 +1,46 @@
apply plugin: 'com.android.library'
//apply from: 'bintray.gradle'
apply plugin: "com.vanniktech.maven.publish"
plugins {
id 'com.android.library'
id 'com.vanniktech.maven.publish'
}
android {
compileSdkVersion build_versions.compileSdk
buildToolsVersion build_versions.buildTools
namespace 'com.king.zxing'
compileSdk build_versions.compileSdk
defaultConfig {
minSdkVersion build_versions.minSdk
targetSdkVersion build_versions.targetSdk
versionCode app_version.versionCode
versionName app_version.versionName
minSdk build_versions.minSdk
targetSdk build_versions.targetSdk
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles "consumer-rules.pro"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
lintOptions {
abortOnError false
warning 'InvalidPackage'
}
compileOptions {
coreLibraryDesugaringEnabled true
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
}
lintOptions {
abortOnError false
}
}
//task javadoc(type: Javadoc) {
// source = android.sourceSets.main.java.srcDirs
// classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
//}
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
testImplementation deps.test.junit
androidTestImplementation deps.test.runner
androidTestImplementation deps.test.android_ext_junit
androidTestImplementation deps.test.espresso
coreLibraryDesugaring deps.desugar_jdk
compileOnly deps.androidx.appcompat
implementation deps.androidx.appcompat
api deps.zxing
api deps.camera_core
api deps.camera_camera2

View File

View File

@@ -46,6 +46,15 @@ public abstract class CameraScan implements ICamera, ICameraControl {
*/
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;
/**
* 是否需要支持自动缩放
*/
@@ -59,7 +68,7 @@ public abstract class CameraScan implements ICamera, ICameraControl {
/**
* 是否需要支持触摸缩放
*
* @return
* @return 返回是否需要支持触摸缩放
*/
protected boolean isNeedTouchZoom() {
return isNeedTouchZoom;
@@ -68,8 +77,8 @@ public abstract class CameraScan implements ICamera, ICameraControl {
/**
* 设置是否需要支持触摸缩放
*
* @param needTouchZoom
* @return
* @param needTouchZoom 是否需要支持触摸缩放
* @return {@link CameraScan}
*/
public CameraScan setNeedTouchZoom(boolean needTouchZoom) {
isNeedTouchZoom = needTouchZoom;
@@ -79,7 +88,7 @@ public abstract class CameraScan implements ICamera, ICameraControl {
/**
* 是否需要支持自动缩放
*
* @return
* @return 是否需要支持自动缩放
*/
protected boolean isNeedAutoZoom() {
return isNeedAutoZoom;
@@ -88,8 +97,8 @@ public abstract class CameraScan implements ICamera, ICameraControl {
/**
* 设置是否需要支持自动缩放
*
* @param needAutoZoom
* @return
* @param needAutoZoom 是否需要支持自动缩放
* @return {@link CameraScan}
*/
public CameraScan setNeedAutoZoom(boolean needAutoZoom) {
isNeedAutoZoom = needAutoZoom;
@@ -99,7 +108,8 @@ public abstract class CameraScan implements ICamera, ICameraControl {
/**
* 设置相机配置,请在{@link #startCamera()}之前调用
*
* @param cameraConfig
* @param cameraConfig 相机配置
* @return {@link CameraScan}
*/
public abstract CameraScan setCameraConfig(CameraConfig cameraConfig);
@@ -113,7 +123,8 @@ public abstract class CameraScan implements ICamera, ICameraControl {
* 2. 如果只是想拦截扫码结果回调自己处理逻辑,但并不想继续分析图像(即不想连扫),可通过
* 调用getCameraScan().setAnalyzeImage(false)来停止分析图像。
*
* @param analyze
* @param analyze 是否分析图像
* @return {@link CameraScan}
*/
public abstract CameraScan setAnalyzeImage(boolean analyze);
@@ -123,7 +134,8 @@ public abstract class CameraScan implements ICamera, ICameraControl {
* <p>
* 内置了一些{@link Analyzer}的实现类如下:
*
* @param analyzer
* @param analyzer 分析器
* @return {@link CameraScan}
* @see {@link MultiFormatAnalyzer}
* @see {@link AreaRectAnalyzer}
* @see {@link ImageAnalyzer}
@@ -133,47 +145,56 @@ public abstract class CameraScan implements ICamera, ICameraControl {
public abstract CameraScan setAnalyzer(Analyzer analyzer);
/**
* 设置是否
* 设置是否
*
* @param vibrate
* @param vibrate 是否振动
* @return {@link CameraScan}
*/
public abstract CameraScan setVibrate(boolean vibrate);
/**
* 设置是否播放提示音
*
* @param playBeep
* @param playBeep 是否播放蜂鸣提示音
* @return {@link CameraScan}
*/
public abstract CameraScan setPlayBeep(boolean playBeep);
/**
* 设置扫码结果回调
*
* @param callback
* @param callback 扫码结果回调
* @return {@link CameraScan}
*/
public abstract CameraScan setOnScanResultCallback(OnScanResultCallback callback);
/**
* 绑定手电筒,绑定后可根据光线传感器,动态显示或隐藏手电筒
*
* @param v
* @param v 手电筒视图
* @return {@link CameraScan}
*/
public abstract CameraScan bindFlashlightView(@Nullable View v);
/**
* 设置光线足够暗的阈值单位lux需要通过{@link #bindFlashlightView(View)}绑定手电筒才有效
*
* @param lightLux
* @param lightLux 光线亮度阈值
* @return {@link CameraScan}
*/
public abstract CameraScan setDarkLightLux(float lightLux);
/**
* 设置光线足够明亮的阈值单位lux需要通过{@link #bindFlashlightView(View)}绑定手电筒才有效
*
* @param lightLux
* @param lightLux 光线亮度阈值
* @return {@link CameraScan}
*/
public abstract CameraScan setBrightLightLux(float lightLux);
/**
* 扫描结果回调
*/
public interface OnScanResultCallback {
/**
* 扫码结果回调
@@ -193,14 +214,13 @@ public abstract class CameraScan implements ICamera, ICameraControl {
default void onScanResultFailure() {
}
}
/**
* 解析扫结果
* 解析扫结果
*
* @param data
* @return
* @param data 需解析的意图数据
* @return 返回解析结果
*/
@Nullable
public static String parseScanResult(Intent data) {

View File

@@ -45,12 +45,25 @@ import androidx.camera.view.PreviewView;
*/
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
@@ -68,11 +81,11 @@ public class CaptureActivity extends AppCompatActivity implements CameraScan.OnS
public void initUI() {
previewView = findViewById(getPreviewViewId());
int viewfinderViewId = getViewfinderViewId();
if (viewfinderViewId != 0) {
if (viewfinderViewId != 0 && viewfinderViewId != View.NO_ID) {
viewfinderView = findViewById(viewfinderViewId);
}
int ivFlashlightId = getFlashlightId();
if (ivFlashlightId != 0) {
if (ivFlashlightId != 0 && ivFlashlightId != View.NO_ID) {
ivFlashlight = findViewById(ivFlashlightId);
if (ivFlashlight != null) {
ivFlashlight.setOnClickListener(v -> onClickFlashlight());
@@ -144,8 +157,8 @@ public class CaptureActivity extends AppCompatActivity implements CameraScan.OnS
/**
* 请求Camera权限回调结果
*
* @param permissions
* @param grantResults
* @param permissions 权限
* @param grantResults 授权结果
*/
public void requestCameraPermissionResult(@NonNull String[] permissions, @NonNull int[] grantResults) {
if (PermissionUtils.requestPermissionsResult(Manifest.permission.CAMERA, permissions, grantResults)) {
@@ -173,7 +186,7 @@ public class CaptureActivity extends AppCompatActivity implements CameraScan.OnS
/**
* 布局ID通过覆写此方法可以自定义布局
*
* @return
* @return 布局ID
*/
public int getLayoutId() {
return R.layout.zxl_capture;
@@ -188,11 +201,10 @@ public class CaptureActivity extends AppCompatActivity implements CameraScan.OnS
return R.id.viewfinderView;
}
/**
* 预览界面{@link #previewView} 的ID
* 预览界面{@link #previewView} 的ID可通过覆写此方法自定义ID
*
* @return
* @return 默认返回{@code R.id.previewView}
*/
public int getPreviewViewId() {
return R.id.previewView;
@@ -208,7 +220,7 @@ public class CaptureActivity extends AppCompatActivity implements CameraScan.OnS
}
/**
* Get {@link CameraScan}
* 获取 {@link CameraScan}
*
* @return {@link #mCameraScan}
*/

View File

@@ -26,6 +26,7 @@ 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;
@@ -46,14 +47,29 @@ import androidx.fragment.app.Fragment;
*/
public class CaptureFragment extends Fragment implements CameraScan.OnScanResultCallback {
/**
* 相机权限请求代码
*/
private static final int CAMERA_PERMISSION_REQUEST_CODE = 0X86;
/**
* 根视图
*/
private View mRootView;
/**
* 预览视图
*/
protected PreviewView previewView;
/**
* 取景视图
*/
protected ViewfinderView viewfinderView;
/**
* 手电筒视图
*/
protected View ivFlashlight;
/**
* CameraScan
*/
private CameraScan mCameraScan;
public static CaptureFragment newInstance() {
@@ -70,21 +86,26 @@ public class CaptureFragment extends Fragment implements CameraScan.OnScanResult
if (isContentView()) {
mRootView = createRootView(inflater, container);
}
initUI();
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) {
if (viewfinderViewId != 0 && viewfinderViewId != View.NO_ID) {
viewfinderView = mRootView.findViewById(viewfinderViewId);
}
int ivFlashlightId = getFlashlightId();
if (ivFlashlightId != 0) {
if (ivFlashlightId != 0 && ivFlashlightId != View.NO_ID) {
ivFlashlight = mRootView.findViewById(ivFlashlightId);
if (ivFlashlight != null) {
ivFlashlight.setOnClickListener(v -> onClickFlashlight());
@@ -156,8 +177,8 @@ public class CaptureFragment extends Fragment implements CameraScan.OnScanResult
/**
* 请求Camera权限回调结果
*
* @param permissions
* @param grantResults
* @param permissions 权限
* @param grantResults 授权结果
*/
public void requestCameraPermissionResult(@NonNull String[] permissions, @NonNull int[] grantResults) {
if (PermissionUtils.requestPermissionsResult(Manifest.permission.CAMERA, permissions, grantResults)) {
@@ -168,9 +189,9 @@ public class CaptureFragment extends Fragment implements CameraScan.OnScanResult
}
@Override
public void onDestroy() {
public void onDestroyView() {
releaseCamera();
super.onDestroy();
super.onDestroyView();
}
/**
@@ -197,7 +218,7 @@ public class CaptureFragment extends Fragment implements CameraScan.OnScanResult
/**
* 布局ID通过覆写此方法可以自定义布局
*
* @return
* @return 布局ID
*/
public int getLayoutId() {
return R.layout.zxl_capture;
@@ -213,9 +234,9 @@ public class CaptureFragment extends Fragment implements CameraScan.OnScanResult
}
/**
* 预览界面{@link #previewView} 的ID
* 预览界面{@link #previewView} 的ID可通过覆写此方法自定义ID
*
* @return
* @return 默认返回{@code R.id.previewView}
*/
public int getPreviewViewId() {
return R.id.previewView;
@@ -231,7 +252,7 @@ public class CaptureFragment extends Fragment implements CameraScan.OnScanResult
}
/**
* Get {@link CameraScan}
* 获取 {@link CameraScan}
*
* @return {@link #mCameraScan}
*/
@@ -252,6 +273,11 @@ public class CaptureFragment extends Fragment implements CameraScan.OnScanResult
//--------------------------------------------
/**
* 获取根视图
*
* @return {@link #mRootView}
*/
public View getRootView() {
return mRootView;
}

View File

@@ -95,6 +95,12 @@ public class DecodeConfig {
}
/**
* 获取配置的解码支持类型 {@link DecodeHintType}
*
* @return
* @see {@link DecodeFormatManager}
*/
public Map<DecodeHintType, Object> getHints() {
return hints;
}
@@ -105,7 +111,7 @@ public class DecodeConfig {
* @param hints {@link DecodeFormatManager}
* <p>
* 内置的一些解码可参见如下:
* @return
* @return {@link DecodeConfig}
* @see {@link DecodeFormatManager#DEFAULT_HINTS}
* @see {@link DecodeFormatManager#ALL_HINTS}
* @see {@link DecodeFormatManager#CODE_128_HINTS}
@@ -124,7 +130,7 @@ public class DecodeConfig {
/**
* 是否支持识别反色码,黑白颜色反转
*
* @return
* @return 是否支持识别反色码
*/
public boolean isSupportLuminanceInvert() {
return isSupportLuminanceInvert;
@@ -134,7 +140,7 @@ public class DecodeConfig {
* 设置是否支持识别反色码,黑白颜色反转
*
* @param supportLuminanceInvert 默认为{@code false},想要增强支持扫码识别反色码时可使用,相应的也会增加性能消耗。
* @return
* @return {@link DecodeConfig}
*/
public DecodeConfig setSupportLuminanceInvert(boolean supportLuminanceInvert) {
isSupportLuminanceInvert = supportLuminanceInvert;
@@ -144,7 +150,7 @@ public class DecodeConfig {
/**
* 是否支持扫垂直的条码
*
* @return
* @return 是否支持扫垂直的条码
*/
public boolean isSupportVerticalCode() {
return isSupportVerticalCode;
@@ -154,7 +160,7 @@ public class DecodeConfig {
* 设置是否支持扫垂直的条码
*
* @param supportVerticalCode 默认为{@code false},想要增强支持扫码识别垂直的条码时可使用,相应的也会增加性能消耗。
* @return
* @return {@link DecodeConfig}
*/
public DecodeConfig setSupportVerticalCode(boolean supportVerticalCode) {
isSupportVerticalCode = supportVerticalCode;
@@ -164,7 +170,7 @@ public class DecodeConfig {
/**
* 是否支持使用多解码
*
* @return
* @return 是否支持使用多解码
*/
public boolean isMultiDecode() {
return isMultiDecode;
@@ -185,7 +191,7 @@ public class DecodeConfig {
/**
* 是否支持识别反色码(条码黑白颜色反转的码)使用多解码
*
* @return
* @return 是否支持识别反色码
*/
public boolean isSupportLuminanceInvertMultiDecode() {
return isSupportLuminanceInvertMultiDecode;
@@ -195,7 +201,7 @@ public class DecodeConfig {
* 设置是否支持识别反色码(条码黑白颜色反转的码)使用多解码
*
* @param supportLuminanceInvertMultiDecode 默认为{@code false},想要增强支持扫码识别反色码时可使用,相应的也会增加性能消耗。
* @return
* @return {@link DecodeConfig}
* @see {@link HybridBinarizer} , {@link GlobalHistogramBinarizer}
*/
public DecodeConfig setSupportLuminanceInvertMultiDecode(boolean supportLuminanceInvertMultiDecode) {
@@ -206,7 +212,7 @@ public class DecodeConfig {
/**
* 是否支持垂直的条码,使用多解码
*
* @return
* @return 是否支持垂直的条码,使用多解码
*/
public boolean isSupportVerticalCodeMultiDecode() {
return isSupportVerticalCodeMultiDecode;
@@ -216,7 +222,7 @@ public class DecodeConfig {
* 设置是否支持垂直的条码,使用多解码;解码时,对应的二值化的实现: {@link HybridBinarizer} , {@link GlobalHistogramBinarizer}
*
* @param supportVerticalCodeMultiDecode 默认为{@code false},想要增强支持扫码识别垂直的条码时可使用,相应的也会增加性能消耗。
* @return
* @return {@link DecodeConfig}
*/
public DecodeConfig setSupportVerticalCodeMultiDecode(boolean supportVerticalCodeMultiDecode) {
isSupportVerticalCodeMultiDecode = supportVerticalCodeMultiDecode;
@@ -226,7 +232,7 @@ public class DecodeConfig {
/**
* 需要分析识别区域
*
* @return
* @return 分析识别区域
*/
public Rect getAnalyzeAreaRect() {
return analyzeAreaRect;
@@ -244,7 +250,7 @@ public class DecodeConfig {
* 您还可以通过{@link CameraScan#setCameraConfig(CameraConfig)}去自定义配置{@link CameraConfig}的配置信息控制预览相关配置信息
* <p>
* 即判定区域分析的优先级顺序为:{@link #setFullAreaScan(boolean)} -> {@link #setAnalyzeAreaRect(Rect)} -> {@link #setAreaRectRatio(float)}
* @return
* @return {@link DecodeConfig}
*/
public DecodeConfig setAnalyzeAreaRect(Rect analyzeAreaRect) {
this.analyzeAreaRect = analyzeAreaRect;
@@ -254,7 +260,7 @@ public class DecodeConfig {
/**
* 是否支持全区域扫码识别
*
* @return
* @return 是否支持全区域扫码识别
*/
public boolean isFullAreaScan() {
return isFullAreaScan;
@@ -274,7 +280,7 @@ public class DecodeConfig {
* 您还可以通过{@link CameraScan#setCameraConfig(CameraConfig)}去自定义配置{@link CameraConfig}的配置信息控制预览相关配置信息
* <p>
* 即判定区域分析的优先级顺序为:{@link #setFullAreaScan(boolean)} -> {@link #setAnalyzeAreaRect(Rect)} -> {@link #setAreaRectRatio(float)}
* @return
* @return {@link DecodeConfig}
*/
public DecodeConfig setFullAreaScan(boolean fullAreaScan) {
isFullAreaScan = fullAreaScan;
@@ -284,7 +290,7 @@ public class DecodeConfig {
/**
* 识别区域比例,默认{@link #DEFAULT_AREA_RECT_RATIO},设置的比例最终会在预览区域裁剪基于此比例的一个矩形进行扫码识别
*
* @return
* @return 识别区域比例,默认{@link #DEFAULT_AREA_RECT_RATIO}
*/
public float getAreaRectRatio() {
return areaRectRatio;
@@ -302,7 +308,7 @@ public class DecodeConfig {
* 您还可以通过{@link CameraScan#setCameraConfig(CameraConfig)}去自定义配置{@link CameraConfig}的配置信息控制预览相关配置信息
* <p>
* 即判定区域分析的优先级顺序为:{@link #setFullAreaScan(boolean)} -> {@link #setAnalyzeAreaRect(Rect)} -> {@link #setAreaRectRatio(float)}
* @return
* @return {@link DecodeConfig}
*/
public DecodeConfig setAreaRectRatio(@FloatRange(from = 0.5, to = 1.0) float areaRectRatio) {
this.areaRectRatio = areaRectRatio;
@@ -312,7 +318,7 @@ public class DecodeConfig {
/**
* 识别区域垂直方向偏移量支持负数大于0时居中心向下偏移小于0时居中心向上偏移
*
* @return
* @return 识别区域垂直方向偏移量
*/
public int getAreaRectVerticalOffset() {
return areaRectVerticalOffset;
@@ -322,7 +328,7 @@ public class DecodeConfig {
* 设置识别区域垂直方向偏移量支持负数大于0时居中心向下偏移小于0时居中心向上偏移
*
* @param areaRectVerticalOffset
* @return
* @return {@link DecodeConfig}
*/
public DecodeConfig setAreaRectVerticalOffset(int areaRectVerticalOffset) {
this.areaRectVerticalOffset = areaRectVerticalOffset;
@@ -332,7 +338,7 @@ public class DecodeConfig {
/**
* 识别区域水平方向偏移量支持负数大于0时居中心向右偏移小于0时居中心向左偏移
*
* @return
* @return 识别区域水平方向偏移量
*/
public int getAreaRectHorizontalOffset() {
return areaRectHorizontalOffset;
@@ -342,7 +348,7 @@ public class DecodeConfig {
* 设置识别区域水平方向偏移量支持负数大于0时居中心向右偏移小于0时居中心向左偏移
*
* @param areaRectHorizontalOffset
* @return
* @return {@link DecodeConfig}
*/
public DecodeConfig setAreaRectHorizontalOffset(int areaRectHorizontalOffset) {
this.areaRectHorizontalOffset = areaRectHorizontalOffset;

View File

@@ -41,7 +41,6 @@ public final class DecodeFormatManager {
* 二维码
*/
public static final Map<DecodeHintType, Object> TWO_DIMENSIONAL_HINTS = new EnumMap<>(DecodeHintType.class);
/**
* 默认
*/
@@ -86,7 +85,7 @@ public final class DecodeFormatManager {
}
/**
* 维码
* 维码
* 包括如下几种格式:
* {@link BarcodeFormat#CODABAR}
* {@link BarcodeFormat#CODE_39}

View File

@@ -17,7 +17,9 @@ 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;
@@ -67,14 +69,12 @@ public class DefaultCameraScan extends CameraScan {
* 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;
/**
* 每次缩放改变的步长
*/
@@ -83,40 +83,73 @@ public class DefaultCameraScan extends CameraScan {
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) {
@@ -199,7 +232,7 @@ public class DefaultCameraScan extends CameraScan {
/**
* 处理预览视图点击事件;如果触发的点击事件被判定对焦操作,则开始自动对焦
*
* @param event
* @param event 事件
*/
private void handlePreviewViewClickTap(MotionEvent event) {
if (event.getPointerCount() == 1) {
@@ -226,8 +259,8 @@ public class DefaultCameraScan extends CameraScan {
/**
* 开始对焦和测光
*
* @param x
* @param y
* @param x X轴坐标
* @param y Y轴坐标
*/
private void startFocusAndMetering(float x, float y) {
if (mCamera != null) {
@@ -240,19 +273,6 @@ public class DefaultCameraScan extends CameraScan {
}
}
/**
* 初始化配置
*/
private void initConfig() {
if (mCameraConfig == null) {
mCameraConfig = new CameraConfig();
}
if (mAnalyzer == null) {
mAnalyzer = new MultiFormatAnalyzer();
}
}
@Override
public CameraScan setCameraConfig(CameraConfig cameraConfig) {
if (cameraConfig != null) {
@@ -261,12 +281,35 @@ public class DefaultCameraScan extends CameraScan {
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() {
initConfig();
initCameraConfig(mContext);
if (mAnalyzer == null) {
mAnalyzer = new MultiFormatAnalyzer();
}
mCameraProviderFuture = ProcessCameraProvider.getInstance(mContext);
mCameraProviderFuture.addListener(() -> {
try {
Preview preview = mCameraConfig.options(new Preview.Builder());
@@ -336,6 +379,7 @@ public class DefaultCameraScan extends CameraScan {
/**
* 处理自动缩放
*
* @param distance
* @param result
* @return
@@ -353,7 +397,8 @@ public class DefaultCameraScan extends CameraScan {
/**
* 扫描结果回调
* @param result
*
* @param result 扫描结果
*/
private void scanResultCallback(Result result) {
if (mOnScanResultCallback != null && mOnScanResultCallback.onScanResultCallback(result)) {
@@ -473,11 +518,6 @@ public class DefaultCameraScan extends CameraScan {
return false;
}
/**
* 是否支持闪光灯
*
* @return
*/
@Override
public boolean hasFlashUnit() {
if (mCamera != null) {

View File

@@ -21,9 +21,9 @@ public interface ICamera {
void stopCamera();
/**
* 获取{@link Camera}
* 获取 {@link Camera}
*
* @return
* @return {@link Camera}
*/
@Nullable
Camera getCamera();

View File

@@ -22,7 +22,7 @@ public interface ICameraControl {
/**
* 缩放到指定比例
*
* @param ratio
* @param ratio 缩放比例
*/
void zoomTo(float ratio);
@@ -39,28 +39,28 @@ public interface ICameraControl {
/**
* 线性缩放到指定比例
*
* @param linearZoom
* @param linearZoom 线性缩放比例范围在0.0 ~ 1.0之间
*/
void lineZoomTo(@FloatRange(from = 0.0, to = 1.0) float linearZoom);
/**
* 设置闪光灯(手电筒)是否开启
*
* @param torch
* @param torch 是否开启闪光灯(手电筒)
*/
void enableTorch(boolean torch);
/**
* 闪光灯(手电筒)是否开启
*
* @return
* @return 闪光灯(手电筒)是否开启
*/
boolean isTorchEnabled();
/**
* 是否支持闪光灯
*
* @return
* @return 是否支持闪光灯
*/
boolean hasFlashUnit();
}

View File

@@ -8,6 +8,7 @@ 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;
@@ -26,8 +27,6 @@ import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import com.king.zxing.util.LogUtils;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.List;
@@ -216,6 +215,10 @@ public class ViewfinderView extends View {
private Bitmap laserBitmap;
private float laserBitmapRatio;
private float laserBitmapWidth;
private int viewfinderStyle = ViewfinderStyle.CLASSIC;
private List<Point> pointList;
@@ -248,7 +251,7 @@ public class ViewfinderView extends View {
*/
public enum LaserStyle {
NONE(0), LINE(1), GRID(2), IMAGE(3);
private int mValue;
private final int mValue;
LaserStyle(int value) {
mValue = value;
@@ -270,7 +273,7 @@ public class ViewfinderView extends View {
public enum TextLocation {
TOP(0), BOTTOM(1);
private int mValue;
private final int mValue;
TextLocation(int value) {
mValue = value;
@@ -292,7 +295,7 @@ public class ViewfinderView extends View {
public enum FrameGravity {
CENTER(0), LEFT(1), TOP(2), RIGHT(3), BOTTOM(4);
private int mValue;
private final int mValue;
FrameGravity(int value) {
mValue = value;
@@ -369,6 +372,7 @@ public class ViewfinderView extends View {
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();
@@ -433,6 +437,24 @@ public class ViewfinderView extends View {
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));
}
@@ -448,10 +470,26 @@ public class ViewfinderView extends View {
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;
}
@@ -582,7 +620,7 @@ public class ViewfinderView extends View {
private void drawImageScanner(Canvas canvas, Rect frame) {
if (laserBitmap != null) {
paint.setColor(Color.WHITE);
canvas.drawBitmap(laserBitmap, frame.left, scannerStart, paint);
canvas.drawBitmap(laserBitmap, (getWidth() - laserBitmap.getWidth()) / 2, scannerStart, paint);
if (scannerStart < scannerEnd) {
scannerStart += scannerLineMoveDistance;
} else {
@@ -591,7 +629,6 @@ public class ViewfinderView extends View {
} else {
drawLineScanner(canvas, frame);
}
}
/**

View File

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

View File

@@ -5,22 +5,23 @@ 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 java.nio.ByteBuffer;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.camera.core.ImageProxy;
/**
* 图像分析器
*
* @author <a href="mailto:jenly1314@gmail.com">Jenly</a>
*/
public abstract class ImageAnalyzer implements Analyzer {
/**
* 分析图像数据
*
* @param data
* @param width
* @param height
@@ -29,25 +30,23 @@ 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){
@SuppressLint("UnsafeExperimentalUsageError")
ByteBuffer buffer = image.getPlanes()[0].getBuffer();
byte[] data = new byte[buffer.remaining()];
buffer.get(data);
public Result analyze(@NonNull ImageProxy image, int orientation) {
if (image.getFormat() == ImageFormat.YUV_420_888) {
int width = image.getWidth();
int height = image.getHeight();
if(orientation == Configuration.ORIENTATION_PORTRAIT){
@SuppressLint("UnsafeOptInUsageError")
byte[] data = BitmapUtils.yuv420ThreePlanesToNV21(image.getImage().getPlanes(), width, height).array();
if (orientation == Configuration.ORIENTATION_PORTRAIT) {
byte[] rotatedData = new byte[data.length];
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++){
for (int x = 0; x < width; x++) {
rotatedData[x * height + height - y - 1] = data[x + y * width];
}
}
return analyze(rotatedData,height,width);
return analyze(rotatedData, height, width);
}
return analyze(data,width,height);
}else{
return analyze(data, width, height);
} else {
LogUtils.w("imageFormat: " + image.getFormat());
}
return null;

View File

@@ -11,15 +11,17 @@ import androidx.annotation.Nullable;
/**
* 二维码分析器
*
* @author <a href="mailto:jenly1314@gmail.com">Jenly</a>
*/
public class QRCodeAnalyzer extends BarcodeFormatAnalyzer {
public QRCodeAnalyzer() {
this((DecodeConfig)null);
this((DecodeConfig) null);
}
public QRCodeAnalyzer(@Nullable Map<DecodeHintType,Object> hints){
public QRCodeAnalyzer(@Nullable Map<DecodeHintType, Object> hints) {
this(new DecodeConfig().setHints(hints));
}

View File

@@ -3,8 +3,11 @@ 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;
@@ -18,6 +21,9 @@ import androidx.camera.core.Preview;
*/
public final class AspectRatioCameraConfig extends CameraConfig {
/**
* 纵横比
*/
private int mAspectRatio;
public AspectRatioCameraConfig(Context context) {
@@ -34,14 +40,15 @@ public final class AspectRatioCameraConfig extends CameraConfig {
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) / Math.min(width, height);
if (Math.abs(ratio - 4.0F / 3.0F) < Math.abs(ratio - 16.0F / 9.0F)) {
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);
LogUtils.d("aspectRatio: " + mAspectRatio);
}
@NonNull

View File

@@ -4,6 +4,7 @@ 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;
@@ -29,7 +30,9 @@ public class ResolutionCameraConfig extends CameraConfig {
*/
public static final int IMAGE_QUALITY_720P = 720;
/**
* 目标尺寸
*/
private Size mTargetSize;
/**
@@ -62,29 +65,27 @@ public class ResolutionCameraConfig extends CameraConfig {
DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
int width = displayMetrics.widthPixels;
int height = displayMetrics.heightPixels;
LogUtils.d(String.format(Locale.getDefault(), "displayMetrics: %dx%d", width, height));
LogUtils.d(String.format(Locale.getDefault(), "displayMetrics:%d x %d", width, height));
// 因为为了保持流畅性和性能尽可能的限制在imageQuality默认1080p在此前提下尽可能的找到屏幕接近的分辨率
if (width < height) {
float ratio = height / (float) width;
int size = Math.min(width, imageQuality);
float ratio = width / (float) height;
if (ratio > 0.7F) {
// 一般应用于平板
mTargetSize = new Size(size, (int) (size / 3.0F * 4.0F));
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, (int) (size / 9.0F * 16.0F));
mTargetSize = new Size(size, Math.round(size * CameraScan.ASPECT_RATIO_16_9));
}
} else {
int size = Math.min(height, imageQuality);
float ratio = height / (float) width;
if (ratio > 0.7F) {
// 一般应用于平板
mTargetSize = new Size((int) (size / 3.0F * 4.0F), size);
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((int) (size / 9.0F * 16.0F), size);
mTargetSize = new Size(Math.round(size * CameraScan.ASPECT_RATIO_16_9), size);
}
}
LogUtils.d("targetSize:" + mTargetSize);
LogUtils.d("targetSize: " + mTargetSize);
}
@NonNull

View File

@@ -7,6 +7,8 @@ import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
/**
* 环境光线管理器:主要通过传感器来监听光线的亮度变化
*
* @author <a href="mailto:jenly1314@gmail.com">Jenly</a>
*/
public class AmbientLightManager implements SensorEventListener {

View File

@@ -13,6 +13,8 @@ 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 {

View File

@@ -0,0 +1,273 @@
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

@@ -48,6 +48,7 @@
<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"/>