diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..aa724b7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..61a9130 --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 0000000..ceb5c79 --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,22 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..d5a4aa8 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/CameraXQRDecoder/.gitignore b/CameraXQRDecoder/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/CameraXQRDecoder/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/CameraXQRDecoder/build.gradle b/CameraXQRDecoder/build.gradle new file mode 100644 index 0000000..0bd7e0e --- /dev/null +++ b/CameraXQRDecoder/build.gradle @@ -0,0 +1,39 @@ +plugins { + id 'com.android.library' +} + +android { + compileSdk 30 + + defaultConfig { + minSdk 21 + targetSdk 30 + versionCode 1 + versionName "1.0" + consumerProguardFiles "consumer-rules.pro" + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } +} + +dependencies { + compileOnly 'androidx.appcompat:appcompat:1.0.0+' + compileOnly 'com.google.android.material:material:1.0.0+' + + def camerax_version = "1.0.0" + api "androidx.camera:camera-core:${camerax_version}" + api "androidx.camera:camera-camera2:${camerax_version}" + api "androidx.camera:camera-lifecycle:${camerax_version}" + api "androidx.camera:camera-view:1.0.0-alpha25" + api "androidx.camera:camera-extensions:1.0.0-alpha25" + api 'com.google.zxing:core:3.3.2' +} \ No newline at end of file diff --git a/CameraXQRDecoder/consumer-rules.pro b/CameraXQRDecoder/consumer-rules.pro new file mode 100644 index 0000000..e69de29 diff --git a/CameraXQRDecoder/proguard-rules.pro b/CameraXQRDecoder/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/CameraXQRDecoder/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/CameraXQRDecoder/src/main/AndroidManifest.xml b/CameraXQRDecoder/src/main/AndroidManifest.xml new file mode 100644 index 0000000..323d992 --- /dev/null +++ b/CameraXQRDecoder/src/main/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/CameraXQRDecoder/src/main/java/com/kongzue/cameraxqrdecoder/QrDecoderView.java b/CameraXQRDecoder/src/main/java/com/kongzue/cameraxqrdecoder/QrDecoderView.java new file mode 100644 index 0000000..2fa1817 --- /dev/null +++ b/CameraXQRDecoder/src/main/java/com/kongzue/cameraxqrdecoder/QrDecoderView.java @@ -0,0 +1,186 @@ +package com.kongzue.cameraxqrdecoder; + +import static androidx.camera.core.ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST; + +import static com.kongzue.cameraxqrdecoder.util.Utils.isNull; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.util.AttributeSet; +import android.util.DisplayMetrics; +import android.util.Log; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; +import androidx.camera.core.AspectRatio; +import androidx.camera.core.CameraSelector; +import androidx.camera.core.ImageAnalysis; +import androidx.camera.core.Preview; +import androidx.camera.core.impl.CameraInternal; +import androidx.camera.lifecycle.ProcessCameraProvider; +import androidx.camera.view.PreviewView; +import androidx.core.content.ContextCompat; + +import com.google.common.util.concurrent.ListenableFuture; +import com.kongzue.cameraxqrdecoder.interfaces.OnWorkFinish; +import com.kongzue.cameraxqrdecoder.util.QRcodeAnalyzerImpl; +import com.kongzue.cameraxqrdecoder.util.QrDecoderPermissionUtil; + +import java.util.Objects; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +/** + * @author: Kongzue + * @github: https://github.com/kongzue/ + * @homepage: http://kongzue.com/ + * @mail: myzcxhh@live.cn + * @createTime: 2021/8/28 9:32 + */ +public class QrDecoderView extends FrameLayout { + + public static boolean DEBUGMODE = true; + public static final String ERROR_NO_PERMISSION = "无法初始化:请检查权限,未获得权限:android.permission.CAMERA"; + + boolean isFlashOpen; + ExecutorService cameraExecutor; + boolean finish; + CameraInternal cameraInternal; + OnWorkFinish onWorkFinish; + boolean keepScan; + + public QrDecoderView(@NonNull Context context) { + super(context); + } + + public QrDecoderView(@NonNull Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + } + + public QrDecoderView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + public QrDecoderView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + public void start(OnWorkFinish onWorkFinish) { + if (QrDecoderPermissionUtil.checkPermissions(getContext(), new String[]{"android.permission.CAMERA"})) { + this.onWorkFinish = onWorkFinish; + addScanQRView(); + } else { + error(ERROR_NO_PERMISSION); + } + } + + public boolean isFlashOpen() { + return isFlashOpen; + } + + public QrDecoderView setFlashOpen(boolean flashOpen) { + isFlashOpen = flashOpen; + if (cameraInternal != null) { + cameraInternal.getCameraControl().enableTorch(isFlashOpen); + } + return this; + } + + String oldResult; + + private void addScanQRView() { + PreviewView viewFinder = new PreviewView(getContext()); + addView(viewFinder, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); + + viewFinder.post(new Runnable() { + @Override + public void run() { + cameraExecutor = Executors.newSingleThreadExecutor(); + + CameraSelector.Builder builder = new CameraSelector.Builder(); + builder.requireLensFacing(CameraSelector.LENS_FACING_BACK); + CameraSelector cameraSelector = builder.build(); + + ListenableFuture cameraProviderFuture = ProcessCameraProvider.getInstance(getContext()); + DisplayMetrics metrics = new DisplayMetrics(); + viewFinder.getDisplay().getRealMetrics(metrics); + int screenAspectRatio = aspectRatio(metrics.widthPixels, metrics.heightPixels); //屏幕纵横比 + int rotation = viewFinder.getDisplay().getRotation(); + + cameraProviderFuture.addListener(new Runnable() { + @SuppressLint("RestrictedApi") + @Override + public void run() { + try { + ProcessCameraProvider cameraProvider = cameraProviderFuture.get(); + + Preview.Builder pBuilder = new Preview.Builder(); + pBuilder.setTargetAspectRatio(screenAspectRatio); + pBuilder.setTargetRotation(rotation); + Preview preview = pBuilder.build(); + + preview.setSurfaceProvider(viewFinder.getSurfaceProvider()); + + ImageAnalysis.Builder iBuilder = new ImageAnalysis.Builder(); + iBuilder.setBackpressureStrategy(STRATEGY_KEEP_ONLY_LATEST); + iBuilder.setTargetAspectRatio(screenAspectRatio); + iBuilder.setTargetRotation(rotation); + + ImageAnalysis analysis = iBuilder.build(); + analysis.setAnalyzer(cameraExecutor, new QRcodeAnalyzerImpl(new OnWorkFinish() { + @Override + public void finish(String result) { + if (!finish && !isNull(result) && !Objects.equals(oldResult, result)) { + if (!keepScan) finish = true; + if (onWorkFinish != null) { + onWorkFinish.finish(result); + } + oldResult = result; + } + } + })); + + cameraProvider.unbindAll(); + cameraProvider.bindToLifecycle((AppCompatActivity) getContext(), cameraSelector, preview, analysis); + + cameraInternal = preview.getCamera(); + } catch (Exception e) { + if (DEBUGMODE) e.printStackTrace(); + e.printStackTrace(); + } + } + }, ContextCompat.getMainExecutor(getContext())); + } + }); + } + + double RATIO_4_3_VALUE = 4.0 / 3.0; + double RATIO_16_9_VALUE = 16.0 / 9.0; + + private int aspectRatio(int width, int height) { + double previewRatio = Math.max(width, height) / Math.min(width, height); + if (Math.abs(previewRatio - RATIO_4_3_VALUE) <= Math.abs(previewRatio - RATIO_16_9_VALUE)) { + return AspectRatio.RATIO_4_3; + } + return AspectRatio.RATIO_16_9; + } + + private void error(String errorNoPermission) { + if (DEBUGMODE) { + Log.e("QrDecoderView", errorNoPermission); + } + } + + public boolean isKeepScan() { + return keepScan; + } + + public QrDecoderView setKeepScan(boolean keepScan) { + this.keepScan = keepScan; + return this; + } +} diff --git a/CameraXQRDecoder/src/main/java/com/kongzue/cameraxqrdecoder/interfaces/OnWorkFinish.java b/CameraXQRDecoder/src/main/java/com/kongzue/cameraxqrdecoder/interfaces/OnWorkFinish.java new file mode 100644 index 0000000..bdde7c6 --- /dev/null +++ b/CameraXQRDecoder/src/main/java/com/kongzue/cameraxqrdecoder/interfaces/OnWorkFinish.java @@ -0,0 +1,14 @@ +package com.kongzue.cameraxqrdecoder.interfaces; + +/** + * @author: Kongzue + * @github: https://github.com/kongzue/ + * @homepage: http://kongzue.com/ + * @mail: myzcxhh@live.cn + * @createTime: 2021/8/28 9:41 + */ +public abstract class OnWorkFinish { + public abstract void finish(D d); + public void failed(Object e){}; + public void close(){}; +} diff --git a/CameraXQRDecoder/src/main/java/com/kongzue/cameraxqrdecoder/util/QRcodeAnalyzerImpl.java b/CameraXQRDecoder/src/main/java/com/kongzue/cameraxqrdecoder/util/QRcodeAnalyzerImpl.java new file mode 100644 index 0000000..616a2aa --- /dev/null +++ b/CameraXQRDecoder/src/main/java/com/kongzue/cameraxqrdecoder/util/QRcodeAnalyzerImpl.java @@ -0,0 +1,81 @@ +package com.kongzue.cameraxqrdecoder.util; + +import android.graphics.ImageFormat; +import androidx.annotation.NonNull; +import androidx.camera.core.ImageAnalysis; +import androidx.camera.core.ImageProxy; + +import com.google.zxing.BarcodeFormat; +import com.google.zxing.BinaryBitmap; +import com.google.zxing.DecodeHintType; +import com.google.zxing.MultiFormatReader; +import com.google.zxing.PlanarYUVLuminanceSource; +import com.google.zxing.Result; +import com.google.zxing.common.HybridBinarizer; +import com.kongzue.cameraxqrdecoder.QrDecoderView; +import com.kongzue.cameraxqrdecoder.interfaces.OnWorkFinish; + +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +/** + * @author: hotstu + * @github: https://github.com/hotstu/QRCodeCameraX + */ +public class QRcodeAnalyzerImpl implements ImageAnalysis.Analyzer { + + OnWorkFinish onWorkFinish; + + public QRcodeAnalyzerImpl(OnWorkFinish onWorkFinish) { + this.onWorkFinish = onWorkFinish; + Map> map = new HashMap(); + map.put(DecodeHintType.POSSIBLE_FORMATS, Arrays.asList(BarcodeFormat.QR_CODE)); + reader.setHints(map); + } + + byte[] mYBuffer = new byte[0]; + MultiFormatReader reader = new MultiFormatReader(); + + @Override + public void analyze(@NonNull ImageProxy image) { + if (ImageFormat.YUV_420_888 != image.getFormat()) { + image.close(); + return; + } + int height = image.getHeight(); + int width = image.getWidth(); + PlanarYUVLuminanceSource source = new PlanarYUVLuminanceSource(toYBuffer(image), width, height, 0, 0, width, height, false); + image.close(); + BinaryBitmap bitmap =new BinaryBitmap(new HybridBinarizer(source)); + try { + Result result = reader.decode(bitmap); + onWorkFinish.finish(result.getText()); + } catch ( Exception e) { + if (QrDecoderView.DEBUGMODE)e.printStackTrace(); + onWorkFinish.failed(e); + } + } + + private byte[] toYBuffer(ImageProxy image) { + ImageProxy.PlaneProxy yPlane = image.getPlanes()[0]; + ByteBuffer yBuffer = yPlane.getBuffer(); + yBuffer.rewind(); + int ySize = yBuffer.remaining(); + int position = 0; + if (mYBuffer.length != ySize) { + mYBuffer = new byte[ySize]; + } + for (int row = 0; row < image.getHeight(); row++) { + yBuffer.get(mYBuffer, position, image.getWidth()); + position += image.getWidth(); + yBuffer.position(Math.min(ySize, yBuffer.position() - image.getWidth() + yPlane.getRowStride())); + } + return mYBuffer; + } + + + +} diff --git a/CameraXQRDecoder/src/main/java/com/kongzue/cameraxqrdecoder/util/QrDecoderPermissionUtil.java b/CameraXQRDecoder/src/main/java/com/kongzue/cameraxqrdecoder/util/QrDecoderPermissionUtil.java new file mode 100644 index 0000000..31166ef --- /dev/null +++ b/CameraXQRDecoder/src/main/java/com/kongzue/cameraxqrdecoder/util/QrDecoderPermissionUtil.java @@ -0,0 +1,29 @@ +package com.kongzue.cameraxqrdecoder.util; + +import android.content.Context; +import android.content.pm.PackageManager; +import android.os.Build; +import androidx.core.content.ContextCompat; + +/** + * @author: Kongzue + * @github: https://github.com/kongzue/ + * @homepage: http://kongzue.com/ + * @mail: myzcxhh@live.cn + * @createTime: 2021/8/28 9:34 + */ +public class QrDecoderPermissionUtil { + + public static boolean checkPermissions(Context context,String[] permissions) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { + return true; + } + + for (String permission : permissions) { + if (ContextCompat.checkSelfPermission(context, permission) != PackageManager.PERMISSION_GRANTED) { + return false; + } + } + return true; + } +} diff --git a/CameraXQRDecoder/src/main/java/com/kongzue/cameraxqrdecoder/util/Utils.java b/CameraXQRDecoder/src/main/java/com/kongzue/cameraxqrdecoder/util/Utils.java new file mode 100644 index 0000000..8db2396 --- /dev/null +++ b/CameraXQRDecoder/src/main/java/com/kongzue/cameraxqrdecoder/util/Utils.java @@ -0,0 +1,19 @@ +package com.kongzue.cameraxqrdecoder.util; + +/** + * @author: Kongzue + * @github: https://github.com/kongzue/ + * @homepage: http://kongzue.com/ + * @mail: myzcxhh@live.cn + * @createTime: 2021/8/28 9:42 + */ +public class Utils { + + public static boolean isNull(String s) { + if (s == null || s.trim().isEmpty() || "null".equals(s) || "(null)".equals(s)) { + return true; + } + return false; + } + +} diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..ab4f258 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,41 @@ +plugins { + id 'com.android.application' +} + +android { + compileSdk 30 + + defaultConfig { + applicationId "com.kongzue.cameraxqrdecoderdemo" + minSdk 21 + targetSdk 30 + versionCode 1 + versionName "1.0" + + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } +} + +dependencies { + + //kongzue 自家组件库,非必须 + implementation 'com.github.kongzue:BaseFramework:6.7.9.6' + + //自家对话框库,非必须 + def dialogx_version = "0.0.43.beta6" + implementation "com.github.kongzue.DialogX:DialogX:${dialogx_version}" + + implementation 'androidx.appcompat:appcompat:1.3.1' + implementation 'com.google.android.material:material:1.4.0' + implementation project(path: ':CameraXQRDecoder') +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..aad1a20 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/kongzue/cameraxqrdecoderdemo/MainActivity.java b/app/src/main/java/com/kongzue/cameraxqrdecoderdemo/MainActivity.java new file mode 100644 index 0000000..bb9ca3f --- /dev/null +++ b/app/src/main/java/com/kongzue/cameraxqrdecoderdemo/MainActivity.java @@ -0,0 +1,63 @@ +package com.kongzue.cameraxqrdecoderdemo; + +import androidx.appcompat.app.AppCompatActivity; + +import android.os.Bundle; +import android.view.View; +import android.widget.Button; + +import com.kongzue.baseframework.BaseActivity; +import com.kongzue.baseframework.interfaces.DarkNavigationBarTheme; +import com.kongzue.baseframework.interfaces.DarkStatusBarTheme; +import com.kongzue.baseframework.interfaces.Layout; +import com.kongzue.baseframework.util.JumpParameter; +import com.kongzue.baseframework.util.OnPermissionResponseListener; +import com.kongzue.cameraxqrdecoder.QrDecoderView; +import com.kongzue.cameraxqrdecoder.interfaces.OnWorkFinish; +import com.kongzue.dialogx.dialogs.PopTip; + +@Layout(R.layout.activity_main) +@DarkStatusBarTheme(value = true) +@DarkNavigationBarTheme(value = true) +public class MainActivity extends BaseActivity { + + private QrDecoderView qrCodeView; + private Button btnFlash; + + @Override + public void initViews() { + qrCodeView = findViewById(R.id.qrCodeView); + btnFlash = findViewById(R.id.btn_flash); + } + + @Override + public void initDatas(JumpParameter parameter) { + requestPermission(new String[]{"android.permission.CAMERA"}, new OnPermissionResponseListener() { + @Override + public void onSuccess(String[] permissions) { + qrCodeView.setKeepScan(true).start(new OnWorkFinish() { + @Override + public void finish(String s) { + PopTip.show(s); + } + }); + } + + @Override + public void onFail() { + + } + }); + } + + @Override + public void setEvents() { + btnFlash.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + qrCodeView.setFlashOpen(!qrCodeView.isFlashOpen()); + btnFlash.setText(qrCodeView.isFlashOpen()?"闪光灯:开":"闪光灯:关"); + } + }); + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000..2b068d1 --- /dev/null +++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..07d5da9 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..f9dbfde --- /dev/null +++ b/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,23 @@ + + + + + +