liuyuqi-dellpc 4 years ago
commit
91f7a4b5cc
65 changed files with 4746 additions and 0 deletions
  1. 16 0
      .gitignore
  2. 1 0
      app/.gitignore
  3. 41 0
      app/build.gradle
  4. 21 0
      app/proguard-rules.pro
  5. 26 0
      app/src/androidTest/java/me/yoqi/android/barcode/ExampleInstrumentedTest.java
  6. 24 0
      app/src/main/AndroidManifest.xml
  7. 124 0
      app/src/main/java/me/yoqi/android/barcode/MainActivity.java
  8. 30 0
      app/src/main/res/drawable-v24/ic_launcher_foreground.xml
  9. BIN
      app/src/main/res/drawable-xxhdpi/ic_zxing_rect.9.png
  10. BIN
      app/src/main/res/drawable-xxhdpi/ic_zxing_scan.9.png
  11. 170 0
      app/src/main/res/drawable/ic_launcher_background.xml
  12. 9 0
      app/src/main/res/drawable/ic_zxingscanview_error.xml
  13. 59 0
      app/src/main/res/layout/activity_main.xml
  14. 5 0
      app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
  15. 5 0
      app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
  16. BIN
      app/src/main/res/mipmap-hdpi/ic_launcher.png
  17. BIN
      app/src/main/res/mipmap-hdpi/ic_launcher_round.png
  18. BIN
      app/src/main/res/mipmap-mdpi/ic_launcher.png
  19. BIN
      app/src/main/res/mipmap-mdpi/ic_launcher_round.png
  20. BIN
      app/src/main/res/mipmap-xhdpi/ic_launcher.png
  21. BIN
      app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
  22. BIN
      app/src/main/res/mipmap-xxhdpi/ic_launcher.png
  23. BIN
      app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
  24. BIN
      app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
  25. BIN
      app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
  26. 16 0
      app/src/main/res/values-night/themes.xml
  27. 18 0
      app/src/main/res/values/colors.xml
  28. 8 0
      app/src/main/res/values/strings.xml
  29. 22 0
      app/src/main/res/values/themes.xml
  30. 17 0
      app/src/test/java/me/yoqi/android/barcode/ExampleUnitTest.java
  31. 24 0
      build.gradle
  32. 19 0
      gradle.properties
  33. BIN
      gradle/wrapper/gradle-wrapper.jar
  34. 6 0
      gradle/wrapper/gradle-wrapper.properties
  35. 172 0
      gradlew
  36. 84 0
      gradlew.bat
  37. 3 0
      settings.gradle
  38. 1 0
      zxingscanview/.gitignore
  39. 40 0
      zxingscanview/build.gradle
  40. 0 0
      zxingscanview/consumer-rules.pro
  41. 21 0
      zxingscanview/proguard-rules.pro
  42. 26 0
      zxingscanview/src/androidTest/java/me/yoqi/android/zxingscanview/ExampleInstrumentedTest.java
  43. 9 0
      zxingscanview/src/main/AndroidManifest.xml
  44. 131 0
      zxingscanview/src/main/java/com/google/zxing/client/android/camera/AutoFocusManager.java
  45. 236 0
      zxingscanview/src/main/java/com/google/zxing/client/android/camera/CameraConfigurationManager.java
  46. 464 0
      zxingscanview/src/main/java/com/google/zxing/client/android/camera/CameraConfigurationUtils.java
  47. 333 0
      zxingscanview/src/main/java/com/google/zxing/client/android/camera/CameraManager.java
  48. 34 0
      zxingscanview/src/main/java/com/google/zxing/client/android/camera/CameraPreferences.java
  49. 47 0
      zxingscanview/src/main/java/com/google/zxing/client/android/camera/FrontLightMode.java
  50. 56 0
      zxingscanview/src/main/java/com/google/zxing/client/android/camera/PreviewCallback.java
  51. 27 0
      zxingscanview/src/main/java/com/google/zxing/client/android/camera/open/CameraFacing.java
  52. 55 0
      zxingscanview/src/main/java/com/google/zxing/client/android/camera/open/OpenCamera.java
  53. 99 0
      zxingscanview/src/main/java/com/google/zxing/client/android/camera/open/OpenCameraInterface.java
  54. 108 0
      zxingscanview/src/main/java/com/google/zxing/client/android/compat/Compat.java
  55. 36 0
      zxingscanview/src/main/java/com/google/zxing/client/android/decode/BarcodeType.java
  56. 117 0
      zxingscanview/src/main/java/com/google/zxing/client/android/decode/DecodeHandler.java
  57. 135 0
      zxingscanview/src/main/java/com/google/zxing/client/android/decode/DecodeThread.java
  58. 30 0
      zxingscanview/src/main/java/com/google/zxing/client/android/decode/ID.java
  59. 140 0
      zxingscanview/src/main/java/com/google/zxing/client/android/decode/ScanHandler.java
  60. 157 0
      zxingscanview/src/main/java/com/google/zxing/client/android/manager/AmbientLightManager.java
  61. 225 0
      zxingscanview/src/main/java/com/google/zxing/client/android/manager/ScanFeedbackManager.java
  62. 652 0
      zxingscanview/src/main/java/me/yoqi/android/zxingscanview/widget/ZxingForegroundView.java
  63. 572 0
      zxingscanview/src/main/java/me/yoqi/android/zxingscanview/widget/ZxingScanView.java
  64. 58 0
      zxingscanview/src/main/res/values/attrs.xml
  65. 17 0
      zxingscanview/src/test/java/me/yoqi/android/zxingscanview/ExampleUnitTest.java

+ 16 - 0
.gitignore

@@ -0,0 +1,16 @@
+*.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
+/.idea/

+ 1 - 0
app/.gitignore

@@ -0,0 +1 @@
+/build

+ 41 - 0
app/build.gradle

@@ -0,0 +1,41 @@
+plugins {
+    id 'com.android.application'
+}
+
+android {
+    compileSdkVersion 29
+
+    defaultConfig {
+        applicationId "me.yoqi.android.barcode"
+        minSdkVersion 16
+        targetSdkVersion 29
+        versionCode 1
+        versionName "1.0"
+
+        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+    }
+
+    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 {
+
+    implementation 'androidx.appcompat:appcompat:1.2.0'
+    implementation 'com.google.android.material:material:1.2.1'
+    implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
+//    条码二维码
+    implementation project(path: ':zxingscanview')
+
+    testImplementation 'junit:junit:4.+'
+    androidTestImplementation 'androidx.test.ext:junit:1.1.2'
+    androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
+}

+ 21 - 0
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

+ 26 - 0
app/src/androidTest/java/me/yoqi/android/barcode/ExampleInstrumentedTest.java

@@ -0,0 +1,26 @@
+package me.yoqi.android.barcode;
+
+import android.content.Context;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.*;
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
+ */
+@RunWith(AndroidJUnit4.class)
+public class ExampleInstrumentedTest {
+    @Test
+    public void useAppContext() {
+        // Context of the app under test.
+        Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+        assertEquals("me.yoqi.android.barcode", appContext.getPackageName());
+    }
+}

+ 24 - 0
app/src/main/AndroidManifest.xml

@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="me.yoqi.android.barcode">
+
+
+    <application
+        android:allowBackup="true"
+        android:icon="@mipmap/ic_launcher"
+        android:label="@string/app_name"
+        android:roundIcon="@mipmap/ic_launcher_round"
+        android:supportsRtl="true"
+        android:theme="@style/Theme.Barcode">
+        <activity
+            android:name=".MainActivity"
+            android:theme="@style/Theme.Barcode.FullScreen">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+</manifest>

+ 124 - 0
app/src/main/java/me/yoqi/android/barcode/MainActivity.java

@@ -0,0 +1,124 @@
+package me.yoqi.android.barcode;
+
+import android.Manifest;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.graphics.Bitmap;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.Toast;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.appcompat.widget.Toolbar;
+import androidx.core.app.ActivityCompat;
+
+import com.google.zxing.Result;
+import com.google.zxing.ResultMetadataType;
+import com.google.zxing.client.result.ParsedResult;
+import com.google.zxing.client.result.ResultParser;
+
+import java.text.DateFormat;
+import java.util.Collection;
+import java.util.Date;
+import java.util.EnumSet;
+import java.util.Map;
+
+import me.yoqi.android.zxingscanview.widget.ZxingForegroundView;
+import me.yoqi.android.zxingscanview.widget.ZxingScanView;
+
+public class MainActivity extends AppCompatActivity implements ZxingScanView.OnScanListener {
+
+    private static final int PERMISSIONS_REQUEST_CAMERA = 108;
+    private static final Collection<ResultMetadataType> DISPLAYABLE_METADATA_TYPES =
+            EnumSet.of(ResultMetadataType.ISSUE_NUMBER,
+                    ResultMetadataType.SUGGESTED_PRICE,
+                    ResultMetadataType.ERROR_CORRECTION_LEVEL,
+                    ResultMetadataType.POSSIBLE_COUNTRY);
+    private ZxingScanView mVScan;
+    private ZxingForegroundView mVForeground;
+
+    public static void getInstance(Context context) {
+        context.startActivity(new Intent(context, MainActivity.class));
+    }
+
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_main);
+
+        Toolbar toolbar = findViewById(R.id.zsv_toolbar);
+        toolbar.setVisibility(View.GONE);
+
+        mVScan = findViewById(R.id.zsv_zsv_scan);
+        mVForeground = findViewById(R.id.zsv_zfv_foreground);
+        mVScan.addOnScanListener(this);
+    }
+
+    // Listener
+    @Override
+    public void onError(ZxingScanView scanView) {
+        switch (scanView.getErrorCode()) {
+            case ZxingScanView.ERROR_CODE_0:
+                // 相机无法打开
+                Toast.makeText(this, R.string.zsv_error, Toast.LENGTH_SHORT).show();
+                break;
+            case ZxingScanView.ERROR_CODE_1:
+                // 缺少打开相机的权限
+                if (!ActivityCompat.shouldShowRequestPermissionRationale(this,
+                        Manifest.permission.CAMERA)) {
+                    ActivityCompat.requestPermissions(this,
+                            new String[]{Manifest.permission.CAMERA},
+                            PERMISSIONS_REQUEST_CAMERA);
+                }
+                break;
+        }
+    }
+
+    @Override
+    public void onResult(ZxingScanView scanView, Result result, Bitmap barcode,
+                         float scaleFactor) {
+        ParsedResult parsedResult = ResultParser.parseResult(result);
+        final String format = "format:" + result.getBarcodeFormat().toString();
+        final String type = "type:" + parsedResult.getType().toString();
+        DateFormat formatter = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT);
+        final String date = "date:" + formatter.format(new Date(result.getTimestamp()));
+        String meta = "";
+        Map<ResultMetadataType, Object> metadata = result.getResultMetadata();
+        if (metadata != null) {
+            StringBuilder metadataText = new StringBuilder(20);
+            for (Map.Entry<ResultMetadataType, Object> entry : metadata.entrySet()) {
+                if (DISPLAYABLE_METADATA_TYPES.contains(entry.getKey())) {
+                    metadataText.append(entry.getValue()).append('\n');
+                }
+            }
+            if (metadataText.length() > 0) {
+                metadataText.setLength(metadataText.length() - 1);
+                meta = metadataText.toString();
+            }
+        }
+        CharSequence displayContents = parsedResult.getDisplayResult();
+        Toast.makeText(this, format + "\n" + type + "\n" + date + "\n" + meta + "\n" + displayContents,
+                Toast.LENGTH_SHORT).show();
+        // 重新扫描
+        scanView.restartScanDelay(3000);
+    }
+
+    @Override
+    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
+                                           @NonNull int[] grantResults) {
+        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
+        switch (requestCode) {
+            case PERMISSIONS_REQUEST_CAMERA: {
+                if (grantResults.length > 0
+                        && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
+                    mVScan.open();
+                } else {
+                    mVForeground.setMode(ZxingForegroundView.MODE_ERROR);
+                }
+            }
+        }
+    }
+}

+ 30 - 0
app/src/main/res/drawable-v24/ic_launcher_foreground.xml

@@ -0,0 +1,30 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:aapt="http://schemas.android.com/aapt"
+    android:width="108dp"
+    android:height="108dp"
+    android:viewportWidth="108"
+    android:viewportHeight="108">
+    <path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
+        <aapt:attr name="android:fillColor">
+            <gradient
+                android:endX="85.84757"
+                android:endY="92.4963"
+                android:startX="42.9492"
+                android:startY="49.59793"
+                android:type="linear">
+                <item
+                    android:color="#44000000"
+                    android:offset="0.0" />
+                <item
+                    android:color="#00000000"
+                    android:offset="1.0" />
+            </gradient>
+        </aapt:attr>
+    </path>
+    <path
+        android:fillColor="#FFFFFF"
+        android:fillType="nonZero"
+        android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
+        android:strokeWidth="1"
+        android:strokeColor="#00000000" />
+</vector>

BIN
app/src/main/res/drawable-xxhdpi/ic_zxing_rect.9.png


BIN
app/src/main/res/drawable-xxhdpi/ic_zxing_scan.9.png


+ 170 - 0
app/src/main/res/drawable/ic_launcher_background.xml

@@ -0,0 +1,170 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="108dp"
+    android:height="108dp"
+    android:viewportWidth="108"
+    android:viewportHeight="108">
+    <path
+        android:fillColor="#3DDC84"
+        android:pathData="M0,0h108v108h-108z" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M9,0L9,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,0L19,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M29,0L29,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M39,0L39,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M49,0L49,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M59,0L59,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M69,0L69,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M79,0L79,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M89,0L89,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M99,0L99,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,9L108,9"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,19L108,19"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,29L108,29"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,39L108,39"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,49L108,49"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,59L108,59"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,69L108,69"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,79L108,79"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,89L108,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,99L108,99"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,29L89,29"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,39L89,39"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,49L89,49"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,59L89,59"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,69L89,69"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,79L89,79"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M29,19L29,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M39,19L39,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M49,19L49,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M59,19L59,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M69,19L69,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M79,19L79,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+</vector>

+ 9 - 0
app/src/main/res/drawable/ic_zxingscanview_error.xml

@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="168dp"
+    android:height="168dp"
+    android:viewportHeight="168"
+    android:viewportWidth="168">
+    <path
+        android:fillColor="#FE439B"
+        android:pathData="M84,0C37.608,0 0,37.608 0,84s37.608,84 84,84s84,-37.608 84,-84S130.392,0 84,0zM122.891,108.749l-14.142,14.142L84,98.142l-24.749,24.749l-14.142,-14.142L69.858,84L45.109,59.251l14.142,-14.142L84,69.858l24.749,-24.749l14.142,14.142L98.142,84L122.891,108.749z" />
+</vector>

+ 59 - 0
app/src/main/res/layout/activity_main.xml

@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <androidx.appcompat.widget.Toolbar
+        android:id="@+id/zsv_toolbar"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
+        app:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
+        app:title="@string/zsv_label" />
+
+    <me.yoqi.android.zxingscanview.widget.ZxingScanView
+        android:id="@+id/zsv_zsv_scan"
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/zsv_toolbar"
+        app:zsvAmbientLight="close"
+        app:zsvFeedback="auto"
+        app:zsvScanHeight="400dp"
+        app:zsvScanWidth="300dp" />
+
+    <TextView
+        android:layout_width="19dp"
+        android:layout_height="206dp"
+        android:layout_marginEnd="20dp"
+        android:layout_marginRight="20dp"
+        android:ems="1"
+        android:text="@string/alert_title"
+        android:textColor="@color/design_default_color_error"
+        android:textSize="20dp"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintVertical_bias="0.5" />
+
+    <me.yoqi.android.zxingscanview.widget.ZxingForegroundView
+        android:id="@+id/zsv_zfv_foreground"
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/zsv_toolbar"
+        app:layout_constraintVertical_bias="0.0"
+        app:zfvCoverColor="@color/colorRipple"
+        app:zfvErrorDrawable="@drawable/ic_zxingscanview_error"
+        app:zfvResultPointsColor="@color/colorAccent"
+        app:zfvScanFlagDrawable="@drawable/ic_zxing_scan"
+        app:zfvScanRectDrawable="@drawable/ic_zxing_rect"
+        app:zfvZxingScanView="@id/zsv_zsv_scan"
+        tools:layout_editor_absoluteX="-16dp" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>

+ 5 - 0
app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+    <background android:drawable="@drawable/ic_launcher_background" />
+    <foreground android:drawable="@drawable/ic_launcher_foreground" />
+</adaptive-icon>

+ 5 - 0
app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+    <background android:drawable="@drawable/ic_launcher_background" />
+    <foreground android:drawable="@drawable/ic_launcher_foreground" />
+</adaptive-icon>

BIN
app/src/main/res/mipmap-hdpi/ic_launcher.png


BIN
app/src/main/res/mipmap-hdpi/ic_launcher_round.png


BIN
app/src/main/res/mipmap-mdpi/ic_launcher.png


BIN
app/src/main/res/mipmap-mdpi/ic_launcher_round.png


BIN
app/src/main/res/mipmap-xhdpi/ic_launcher.png


BIN
app/src/main/res/mipmap-xhdpi/ic_launcher_round.png


BIN
app/src/main/res/mipmap-xxhdpi/ic_launcher.png


BIN
app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png


BIN
app/src/main/res/mipmap-xxxhdpi/ic_launcher.png


BIN
app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png


+ 16 - 0
app/src/main/res/values-night/themes.xml

@@ -0,0 +1,16 @@
+<resources xmlns:tools="http://schemas.android.com/tools">
+    <!-- Base application theme. -->
+    <style name="Theme.Barcode" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
+        <!-- Primary brand color. -->
+        <item name="colorPrimary">@color/purple_200</item>
+        <item name="colorPrimaryVariant">@color/purple_700</item>
+        <item name="colorOnPrimary">@color/black</item>
+        <!-- Secondary brand color. -->
+        <item name="colorSecondary">@color/teal_200</item>
+        <item name="colorSecondaryVariant">@color/teal_200</item>
+        <item name="colorOnSecondary">@color/black</item>
+        <!-- Status bar color. -->
+        <item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
+        <!-- Customize your theme here. -->
+    </style>
+</resources>

+ 18 - 0
app/src/main/res/values/colors.xml

@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <color name="purple_200">#FFBB86FC</color>
+    <color name="purple_500">#FF6200EE</color>
+    <color name="purple_700">#FF3700B3</color>
+    <color name="teal_200">#FF03DAC5</color>
+    <color name="teal_700">#FF018786</color>
+    <color name="black">#FF000000</color>
+    <color name="white">#FFFFFFFF</color>
+
+    <!--    自定义-->
+    <color name="colorWindowBackground">@android:color/white</color>
+    <color name="colorPrimary">#4788f4</color>
+    <color name="colorPrimaryDark">#4272c4</color>
+    <color name="colorAccent">#fe439b</color>
+    <color name="colorDark">#28000000</color>
+    <color name="colorRipple">#804788f4</color>
+</resources>

+ 8 - 0
app/src/main/res/values/strings.xml

@@ -0,0 +1,8 @@
+<resources>
+    <string name="app_name">Barcode</string>
+    <!--条码扫描-->
+    <string name="zsv_label">条码扫描</string>
+    <string name="zsv_error">很遗憾,相机出现问题。你可能需要重启设备。</string>
+    <string name="alert_title">横屏扫描二维码</string>
+
+</resources>

+ 22 - 0
app/src/main/res/values/themes.xml

@@ -0,0 +1,22 @@
+<resources xmlns:tools="http://schemas.android.com/tools">
+    <!-- Base application theme. -->
+    <style name="Theme.Barcode" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
+        <!-- Primary brand color. -->
+        <item name="colorPrimary">@color/purple_500</item>
+        <item name="colorPrimaryVariant">@color/purple_700</item>
+        <item name="colorOnPrimary">@color/white</item>
+        <!-- Secondary brand color. -->
+        <item name="colorSecondary">@color/teal_200</item>
+        <item name="colorSecondaryVariant">@color/teal_700</item>
+        <item name="colorOnSecondary">@color/black</item>
+        <!-- Status bar color. -->
+        <item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
+        <!-- Customize your theme here. -->
+    </style>
+
+    <style name="Theme.Barcode.FullScreen" parent="Theme.Barcode">
+        <item name="android:windowFullscreen">true</item>
+        <item name="windowActionBar">false</item>
+        <item name="windowNoTitle">true</item>
+    </style>
+</resources>

+ 17 - 0
app/src/test/java/me/yoqi/android/barcode/ExampleUnitTest.java

@@ -0,0 +1,17 @@
+package me.yoqi.android.barcode;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
+ */
+public class ExampleUnitTest {
+    @Test
+    public void addition_isCorrect() {
+        assertEquals(4, 2 + 2);
+    }
+}

+ 24 - 0
build.gradle

@@ -0,0 +1,24 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+buildscript {
+    repositories {
+        google()
+        jcenter()
+    }
+    dependencies {
+        classpath "com.android.tools.build:gradle:4.1.0"
+
+        // NOTE: Do not place your application dependencies here; they belong
+        // in the individual module build.gradle files
+    }
+}
+
+allprojects {
+    repositories {
+        google()
+        jcenter()
+    }
+}
+
+task clean(type: Delete) {
+    delete rootProject.buildDir
+}

+ 19 - 0
gradle.properties

@@ -0,0 +1,19 @@
+# Project-wide Gradle settings.
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
+# AndroidX package structure to make it clearer which packages are bundled with the
+# Android operating system, and which are packaged with your app"s APK
+# https://developer.android.com/topic/libraries/support-library/androidx-rn
+android.useAndroidX=true
+# Automatically convert third-party libraries to use AndroidX
+android.enableJetifier=true

BIN
gradle/wrapper/gradle-wrapper.jar


+ 6 - 0
gradle/wrapper/gradle-wrapper.properties

@@ -0,0 +1,6 @@
+#Sun Nov 15 13:22:39 CST 2020
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip

+ 172 - 0
gradlew

@@ -0,0 +1,172 @@
+#!/usr/bin/env sh
+
+##############################################################################
+##
+##  Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+    ls=`ls -ld "$PRG"`
+    link=`expr "$ls" : '.*-> \(.*\)$'`
+    if expr "$link" : '/.*' > /dev/null; then
+        PRG="$link"
+    else
+        PRG=`dirname "$PRG"`"/$link"
+    fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+    echo "$*"
+}
+
+die () {
+    echo
+    echo "$*"
+    echo
+    exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+  CYGWIN* )
+    cygwin=true
+    ;;
+  Darwin* )
+    darwin=true
+    ;;
+  MINGW* )
+    msys=true
+    ;;
+  NONSTOP* )
+    nonstop=true
+    ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+        # IBM's JDK on AIX uses strange locations for the executables
+        JAVACMD="$JAVA_HOME/jre/sh/java"
+    else
+        JAVACMD="$JAVA_HOME/bin/java"
+    fi
+    if [ ! -x "$JAVACMD" ] ; then
+        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+    fi
+else
+    JAVACMD="java"
+    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+    MAX_FD_LIMIT=`ulimit -H -n`
+    if [ $? -eq 0 ] ; then
+        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+            MAX_FD="$MAX_FD_LIMIT"
+        fi
+        ulimit -n $MAX_FD
+        if [ $? -ne 0 ] ; then
+            warn "Could not set maximum file descriptor limit: $MAX_FD"
+        fi
+    else
+        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+    fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+    JAVACMD=`cygpath --unix "$JAVACMD"`
+
+    # We build the pattern for arguments to be converted via cygpath
+    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+    SEP=""
+    for dir in $ROOTDIRSRAW ; do
+        ROOTDIRS="$ROOTDIRS$SEP$dir"
+        SEP="|"
+    done
+    OURCYGPATTERN="(^($ROOTDIRS))"
+    # Add a user-defined pattern to the cygpath arguments
+    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+    fi
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    i=0
+    for arg in "$@" ; do
+        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option
+
+        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
+            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+        else
+            eval `echo args$i`="\"$arg\""
+        fi
+        i=$((i+1))
+    done
+    case $i in
+        (0) set -- ;;
+        (1) set -- "$args0" ;;
+        (2) set -- "$args0" "$args1" ;;
+        (3) set -- "$args0" "$args1" "$args2" ;;
+        (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+        (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+        (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+        (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+        (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+        (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+    esac
+fi
+
+# Escape application args
+save () {
+    for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+    echo " "
+}
+APP_ARGS=$(save "$@")
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
+if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
+  cd "$(dirname "$0")"
+fi
+
+exec "$JAVACMD" "$@"

+ 84 - 0
gradlew.bat

@@ -0,0 +1,84 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem  Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windows variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega

+ 3 - 0
settings.gradle

@@ -0,0 +1,3 @@
+include ':zxingscanview'
+include ':app'
+rootProject.name = "Barcode"

+ 1 - 0
zxingscanview/.gitignore

@@ -0,0 +1 @@
+/build

+ 40 - 0
zxingscanview/build.gradle

@@ -0,0 +1,40 @@
+plugins {
+    id 'com.android.library'
+}
+
+android {
+    compileSdkVersion 29
+
+    defaultConfig {
+        minSdkVersion 16
+        targetSdkVersion 29
+        versionCode 1
+        versionName "1.0"
+
+        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+        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 {
+
+    implementation 'androidx.appcompat:appcompat:1.2.0'
+    implementation 'com.google.android.material:material:1.2.1'
+
+    api 'com.google.zxing:core:3.3.3'
+
+    androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
+    testImplementation 'junit:junit:4.+'
+    androidTestImplementation 'androidx.test.ext:junit:1.1.2'
+}

+ 0 - 0
zxingscanview/consumer-rules.pro


+ 21 - 0
zxingscanview/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

+ 26 - 0
zxingscanview/src/androidTest/java/me/yoqi/android/zxingscanview/ExampleInstrumentedTest.java

@@ -0,0 +1,26 @@
+package me.yoqi.android.zxingscanview;
+
+import android.content.Context;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.*;
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
+ */
+@RunWith(AndroidJUnit4.class)
+public class ExampleInstrumentedTest {
+    @Test
+    public void useAppContext() {
+        // Context of the app under test.
+        Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+        assertEquals("me.yoqi.android.zxingscanview.test", appContext.getPackageName());
+    }
+}

+ 9 - 0
zxingscanview/src/main/AndroidManifest.xml

@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="me.yoqi.android.zxingscanview">
+    <!--    摄像头权限-->
+    <uses-permission android:name="android.permission.CAMERA" />
+    <uses-permission android:name="android.permission.WAKE_LOCK" />
+    <uses-permission android:name="android.permission.VIBRATE" />
+
+</manifest>

+ 131 - 0
zxingscanview/src/main/java/com/google/zxing/client/android/camera/AutoFocusManager.java

@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2012 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.client.android.camera;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.hardware.Camera;
+import android.os.AsyncTask;
+import android.preference.PreferenceManager;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.concurrent.RejectedExecutionException;
+
+final class AutoFocusManager implements Camera.AutoFocusCallback {
+
+    private static final String TAG = AutoFocusManager.class.getSimpleName();
+
+    private static final long AUTO_FOCUS_INTERVAL_MS = 2000L;
+    private static final Collection<String> FOCUS_MODES_CALLING_AF;
+
+    static {
+        FOCUS_MODES_CALLING_AF = new ArrayList<>(2);
+        FOCUS_MODES_CALLING_AF.add(Camera.Parameters.FOCUS_MODE_AUTO);
+        FOCUS_MODES_CALLING_AF.add(Camera.Parameters.FOCUS_MODE_MACRO);
+    }
+
+    private final boolean useAutoFocus;
+    private final Camera camera;
+    private boolean stopped;
+    private boolean focusing;
+    private AsyncTask<?, ?, ?> outstandingTask;
+
+    AutoFocusManager(Context context, Camera camera) {
+        this.camera = camera;
+        SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(context);
+        String currentFocusMode = camera.getParameters().getFocusMode();
+        useAutoFocus =
+                sharedPrefs.getBoolean(CameraPreferences.KEY_AUTO_FOCUS, true) &&
+                        FOCUS_MODES_CALLING_AF.contains(currentFocusMode);
+        Log.i(TAG, "Current focus mode '" + currentFocusMode + "'; use auto focus? " + useAutoFocus);
+        start();
+    }
+
+    @Override
+    public synchronized void onAutoFocus(boolean success, Camera theCamera) {
+        focusing = false;
+        autoFocusAgainLater();
+    }
+
+    private synchronized void autoFocusAgainLater() {
+        if (!stopped && outstandingTask == null) {
+            AutoFocusTask newTask = new AutoFocusTask();
+            try {
+                newTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+                outstandingTask = newTask;
+            } catch (RejectedExecutionException ree) {
+                Log.w(TAG, "Could not request auto focus", ree);
+            }
+        }
+    }
+
+    synchronized void start() {
+        if (useAutoFocus) {
+            outstandingTask = null;
+            if (!stopped && !focusing) {
+                try {
+                    camera.autoFocus(this);
+                    focusing = true;
+                } catch (RuntimeException re) {
+                    // Have heard RuntimeException reported in Android 4.0.x+; continue?
+                    Log.w(TAG, "Unexpected exception while focusing", re);
+                    // Try again later to keep cycle going
+                    autoFocusAgainLater();
+                }
+            }
+        }
+    }
+
+    private synchronized void cancelOutstandingTask() {
+        if (outstandingTask != null) {
+            if (outstandingTask.getStatus() != AsyncTask.Status.FINISHED) {
+                outstandingTask.cancel(true);
+            }
+            outstandingTask = null;
+        }
+    }
+
+    synchronized void stop() {
+        stopped = true;
+        if (useAutoFocus) {
+            cancelOutstandingTask();
+            // Doesn't hurt to call this even if not focusing
+            try {
+                camera.cancelAutoFocus();
+            } catch (RuntimeException re) {
+                // Have heard RuntimeException reported in Android 4.0.x+; continue?
+                Log.w(TAG, "Unexpected exception while cancelling focusing", re);
+            }
+        }
+    }
+
+    private final class AutoFocusTask extends AsyncTask<Object, Object, Object> {
+        @Override
+        protected Object doInBackground(Object... voids) {
+            try {
+                Thread.sleep(AUTO_FOCUS_INTERVAL_MS);
+            } catch (InterruptedException e) {
+                // continue
+            }
+            start();
+            return null;
+        }
+    }
+
+}

+ 236 - 0
zxingscanview/src/main/java/com/google/zxing/client/android/camera/CameraConfigurationManager.java

@@ -0,0 +1,236 @@
+/*
+ * Copyright (C) 2010 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.client.android.camera;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.graphics.Point;
+import android.hardware.Camera;
+import android.preference.PreferenceManager;
+import android.util.Log;
+import android.view.Display;
+import android.view.Surface;
+import android.view.WindowManager;
+
+import com.google.zxing.client.android.camera.open.CameraFacing;
+import com.google.zxing.client.android.camera.open.OpenCamera;
+
+/**
+ * A class which deals with reading, parsing, and setting the camera parameters which are used to
+ * configure the camera hardware.
+ */
+final class CameraConfigurationManager {
+
+    private static final String TAG = "CameraConfiguration";
+
+    private final Context context;
+    private int cwNeededRotation;
+    private int cwRotationFromDisplayToCamera;
+    private Point screenResolution;
+    private Point cameraResolution;
+    private Point bestPreviewSize;
+    private Point previewSizeOnScreen;
+
+    CameraConfigurationManager(Context context) {
+        this.context = context;
+    }
+
+    /**
+     * Reads, one time, values from the camera that are needed by the app.
+     */
+    void initFromCameraParameters(OpenCamera camera) {
+        Camera.Parameters parameters = camera.getCamera().getParameters();
+        WindowManager manager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+        Display display = manager.getDefaultDisplay();
+
+        int displayRotation = display.getRotation();
+        int cwRotationFromNaturalToDisplay;
+        switch (displayRotation) {
+            case Surface.ROTATION_0:
+                cwRotationFromNaturalToDisplay = 0;
+                break;
+            case Surface.ROTATION_90:
+                cwRotationFromNaturalToDisplay = 90;
+                break;
+            case Surface.ROTATION_180:
+                cwRotationFromNaturalToDisplay = 180;
+                break;
+            case Surface.ROTATION_270:
+                cwRotationFromNaturalToDisplay = 270;
+                break;
+            default:
+                // Have seen this return incorrect values like -90
+                if (displayRotation % 90 == 0) {
+                    cwRotationFromNaturalToDisplay = (360 + displayRotation) % 360;
+                } else {
+                    throw new IllegalArgumentException("Bad rotation: " + displayRotation);
+                }
+        }
+        Log.i(TAG, "Display at: " + cwRotationFromNaturalToDisplay);
+
+        int cwRotationFromNaturalToCamera = camera.getOrientation();
+        Log.i(TAG, "Camera at: " + cwRotationFromNaturalToCamera);
+
+        // Still not 100% sure about this. But acts like we need to flip this:
+        if (camera.getFacing() == CameraFacing.FRONT) {
+            cwRotationFromNaturalToCamera = (360 - cwRotationFromNaturalToCamera) % 360;
+            Log.i(TAG, "Front camera overriden to: " + cwRotationFromNaturalToCamera);
+        }
+
+        cwRotationFromDisplayToCamera =
+                (360 + cwRotationFromNaturalToCamera - cwRotationFromNaturalToDisplay) % 360;
+        Log.i(TAG, "Final display orientation: " + cwRotationFromDisplayToCamera);
+        if (camera.getFacing() == CameraFacing.FRONT) {
+            Log.i(TAG, "Compensating rotation for front camera");
+            cwNeededRotation = (360 - cwRotationFromDisplayToCamera) % 360;
+        } else {
+            cwNeededRotation = cwRotationFromDisplayToCamera;
+        }
+        Log.i(TAG, "Clockwise rotation from display to camera: " + cwNeededRotation);
+
+        Point theScreenResolution = new Point();
+        display.getSize(theScreenResolution);
+        screenResolution = theScreenResolution;
+        Log.i(TAG, "Screen resolution in current orientation: " + screenResolution);
+        cameraResolution = CameraConfigurationUtils.findBestPreviewSizeValue(parameters, screenResolution);
+        Log.i(TAG, "Camera resolution: " + cameraResolution);
+        bestPreviewSize = CameraConfigurationUtils.findBestPreviewSizeValue(parameters, screenResolution);
+        Log.i(TAG, "Best available preview size: " + bestPreviewSize);
+
+        boolean isScreenPortrait = screenResolution.x < screenResolution.y;
+        boolean isPreviewSizePortrait = bestPreviewSize.x < bestPreviewSize.y;
+
+        if (isScreenPortrait == isPreviewSizePortrait) {
+            previewSizeOnScreen = bestPreviewSize;
+        } else {
+            previewSizeOnScreen = new Point(bestPreviewSize.y, bestPreviewSize.x);
+        }
+        Log.i(TAG, "Preview size on screen: " + previewSizeOnScreen);
+    }
+
+    void setDesiredCameraParameters(OpenCamera camera, boolean safeMode) {
+
+        Camera theCamera = camera.getCamera();
+        Camera.Parameters parameters = theCamera.getParameters();
+
+        if (parameters == null) {
+            Log.w(TAG, "Device error: no camera parameters are available. Proceeding without configuration.");
+            return;
+        }
+
+        Log.i(TAG, "Initial camera parameters: " + parameters.flatten());
+
+        if (safeMode) {
+            Log.w(TAG, "In camera config safe mode -- most settings will not be honored");
+        }
+
+        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
+
+        initializeTorch(parameters, prefs, safeMode);
+
+        CameraConfigurationUtils.setFocus(
+                parameters,
+                prefs.getBoolean(CameraPreferences.KEY_AUTO_FOCUS, true),
+                prefs.getBoolean(CameraPreferences.KEY_DISABLE_CONTINUOUS_FOCUS, true),
+                safeMode);
+
+        if (!safeMode) {
+            if (prefs.getBoolean(CameraPreferences.KEY_INVERT_SCAN, false)) {
+                CameraConfigurationUtils.setInvertColor(parameters);
+            }
+
+            if (!prefs.getBoolean(CameraPreferences.KEY_DISABLE_BARCODE_SCENE_MODE, true)) {
+                CameraConfigurationUtils.setBarcodeSceneMode(parameters);
+            }
+
+            if (!prefs.getBoolean(CameraPreferences.KEY_DISABLE_METERING, true)) {
+                CameraConfigurationUtils.setVideoStabilization(parameters);
+                CameraConfigurationUtils.setFocusArea(parameters);
+                CameraConfigurationUtils.setMetering(parameters);
+            }
+
+        }
+
+        parameters.setPreviewSize(bestPreviewSize.x, bestPreviewSize.y);
+
+        theCamera.setParameters(parameters);
+
+        theCamera.setDisplayOrientation(cwRotationFromDisplayToCamera);
+
+        Camera.Parameters afterParameters = theCamera.getParameters();
+        Camera.Size afterSize = afterParameters.getPreviewSize();
+        if (afterSize != null && (bestPreviewSize.x != afterSize.width || bestPreviewSize.y != afterSize.height)) {
+            Log.w(TAG, "Camera said it supported preview size " + bestPreviewSize.x + 'x' + bestPreviewSize.y +
+                    ", but after setting it, preview size is " + afterSize.width + 'x' + afterSize.height);
+            bestPreviewSize.x = afterSize.width;
+            bestPreviewSize.y = afterSize.height;
+        }
+    }
+
+    Point getBestPreviewSize() {
+        return bestPreviewSize;
+    }
+
+    Point getPreviewSizeOnScreen() {
+        return previewSizeOnScreen;
+    }
+
+    Point getCameraResolution() {
+        return cameraResolution;
+    }
+
+    Point getScreenResolution() {
+        return screenResolution;
+    }
+
+    int getCWNeededRotation() {
+        return cwNeededRotation;
+    }
+
+    boolean getTorchState(Camera camera) {
+        if (camera != null) {
+            Camera.Parameters parameters = camera.getParameters();
+            if (parameters != null) {
+                String flashMode = parameters.getFlashMode();
+                return flashMode != null &&
+                        (Camera.Parameters.FLASH_MODE_ON.equals(flashMode) ||
+                                Camera.Parameters.FLASH_MODE_TORCH.equals(flashMode));
+            }
+        }
+        return false;
+    }
+
+    void setTorch(Camera camera, boolean newSetting) {
+        Camera.Parameters parameters = camera.getParameters();
+        doSetTorch(parameters, newSetting, false);
+        camera.setParameters(parameters);
+    }
+
+    private void initializeTorch(Camera.Parameters parameters, SharedPreferences prefs, boolean safeMode) {
+        boolean currentSetting = FrontLightMode.readPref(prefs) == FrontLightMode.ON;
+        doSetTorch(parameters, currentSetting, safeMode);
+    }
+
+    private void doSetTorch(Camera.Parameters parameters, boolean newSetting, boolean safeMode) {
+        CameraConfigurationUtils.setTorch(parameters, newSetting);
+        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
+        if (!safeMode && !prefs.getBoolean(CameraPreferences.KEY_DISABLE_EXPOSURE, true)) {
+            CameraConfigurationUtils.setBestExposure(parameters, newSetting);
+        }
+    }
+
+}

+ 464 - 0
zxingscanview/src/main/java/com/google/zxing/client/android/camera/CameraConfigurationUtils.java

@@ -0,0 +1,464 @@
+/*
+ * Copyright (C) 2014 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.client.android.camera;
+
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.hardware.Camera;
+import android.os.Build;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
+import java.util.regex.Pattern;
+
+/**
+ * Utility methods for configuring the Android camera.
+ *
+ * @author Sean Owen
+ */
+final class CameraConfigurationUtils {
+
+    private static final String TAG = "CameraConfiguration";
+
+    private static final Pattern SEMICOLON = Pattern.compile(";");
+
+    private static final int MIN_PREVIEW_PIXELS = 480 * 320; // normal screen
+    private static final float MAX_EXPOSURE_COMPENSATION = 1.5f;
+    private static final float MIN_EXPOSURE_COMPENSATION = 0.0f;
+    private static final double MAX_ASPECT_DISTORTION = 0.15;
+    private static final int MIN_FPS = 10;
+    private static final int MAX_FPS = 20;
+    private static final int AREA_PER_1000 = 400;
+
+    private CameraConfigurationUtils() {
+    }
+
+    public static void setFocus(Camera.Parameters parameters,
+                                boolean autoFocus,
+                                boolean disableContinuous,
+                                boolean safeMode) {
+        List<String> supportedFocusModes = parameters.getSupportedFocusModes();
+        String focusMode = null;
+        if (autoFocus) {
+            if (safeMode || disableContinuous) {
+                focusMode = findSettableValue("focus mode",
+                        supportedFocusModes,
+                        Camera.Parameters.FOCUS_MODE_AUTO);
+            } else {
+                focusMode = findSettableValue("focus mode",
+                        supportedFocusModes,
+                        Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE,
+                        Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO,
+                        Camera.Parameters.FOCUS_MODE_AUTO);
+            }
+        }
+        // Maybe selected auto-focus but not available, so fall through here:
+        if (!safeMode && focusMode == null) {
+            focusMode = findSettableValue("focus mode",
+                    supportedFocusModes,
+                    Camera.Parameters.FOCUS_MODE_MACRO,
+                    Camera.Parameters.FOCUS_MODE_EDOF);
+        }
+        if (focusMode != null) {
+            if (focusMode.equals(parameters.getFocusMode())) {
+                Log.i(TAG, "Focus mode already set to " + focusMode);
+            } else {
+                parameters.setFocusMode(focusMode);
+            }
+        }
+    }
+
+    public static void setTorch(Camera.Parameters parameters, boolean on) {
+        List<String> supportedFlashModes = parameters.getSupportedFlashModes();
+        String flashMode;
+        if (on) {
+            flashMode = findSettableValue("flash mode",
+                    supportedFlashModes,
+                    Camera.Parameters.FLASH_MODE_TORCH,
+                    Camera.Parameters.FLASH_MODE_ON);
+        } else {
+            flashMode = findSettableValue("flash mode",
+                    supportedFlashModes,
+                    Camera.Parameters.FLASH_MODE_OFF);
+        }
+        if (flashMode != null) {
+            if (flashMode.equals(parameters.getFlashMode())) {
+                Log.i(TAG, "Flash mode already set to " + flashMode);
+            } else {
+                Log.i(TAG, "Setting flash mode to " + flashMode);
+                parameters.setFlashMode(flashMode);
+            }
+        }
+    }
+
+    public static void setBestExposure(Camera.Parameters parameters, boolean lightOn) {
+        int minExposure = parameters.getMinExposureCompensation();
+        int maxExposure = parameters.getMaxExposureCompensation();
+        float step = parameters.getExposureCompensationStep();
+        if ((minExposure != 0 || maxExposure != 0) && step > 0.0f) {
+            // Set low when light is on
+            float targetCompensation = lightOn ? MIN_EXPOSURE_COMPENSATION : MAX_EXPOSURE_COMPENSATION;
+            int compensationSteps = Math.round(targetCompensation / step);
+            float actualCompensation = step * compensationSteps;
+            // Clamp value:
+            compensationSteps = Math.max(Math.min(compensationSteps, maxExposure), minExposure);
+            if (parameters.getExposureCompensation() == compensationSteps) {
+                Log.i(TAG, "Exposure compensation already set to " + compensationSteps + " / " + actualCompensation);
+            } else {
+                Log.i(TAG, "Setting exposure compensation to " + compensationSteps + " / " + actualCompensation);
+                parameters.setExposureCompensation(compensationSteps);
+            }
+        } else {
+            Log.i(TAG, "Camera does not support exposure compensation");
+        }
+    }
+
+    public static void setBestPreviewFPS(Camera.Parameters parameters) {
+        setBestPreviewFPS(parameters, MIN_FPS, MAX_FPS);
+    }
+
+    public static void setBestPreviewFPS(Camera.Parameters parameters, int minFPS, int maxFPS) {
+        List<int[]> supportedPreviewFpsRanges = parameters.getSupportedPreviewFpsRange();
+        Log.i(TAG, "Supported FPS ranges: " + toString(supportedPreviewFpsRanges));
+        if (supportedPreviewFpsRanges != null && !supportedPreviewFpsRanges.isEmpty()) {
+            int[] suitableFPSRange = null;
+            for (int[] fpsRange : supportedPreviewFpsRanges) {
+                int thisMin = fpsRange[Camera.Parameters.PREVIEW_FPS_MIN_INDEX];
+                int thisMax = fpsRange[Camera.Parameters.PREVIEW_FPS_MAX_INDEX];
+                if (thisMin >= minFPS * 1000 && thisMax <= maxFPS * 1000) {
+                    suitableFPSRange = fpsRange;
+                    break;
+                }
+            }
+            if (suitableFPSRange == null) {
+                Log.i(TAG, "No suitable FPS range?");
+            } else {
+                int[] currentFpsRange = new int[2];
+                parameters.getPreviewFpsRange(currentFpsRange);
+                if (Arrays.equals(currentFpsRange, suitableFPSRange)) {
+                    Log.i(TAG, "FPS range already set to " + Arrays.toString(suitableFPSRange));
+                } else {
+                    Log.i(TAG, "Setting FPS range to " + Arrays.toString(suitableFPSRange));
+                    parameters.setPreviewFpsRange(suitableFPSRange[Camera.Parameters.PREVIEW_FPS_MIN_INDEX],
+                            suitableFPSRange[Camera.Parameters.PREVIEW_FPS_MAX_INDEX]);
+                }
+            }
+        }
+    }
+
+    public static void setFocusArea(Camera.Parameters parameters) {
+        if (parameters.getMaxNumFocusAreas() > 0) {
+            Log.i(TAG, "Old focus areas: " + toString(parameters.getFocusAreas()));
+            List<Camera.Area> middleArea = buildMiddleArea(AREA_PER_1000);
+            Log.i(TAG, "Setting focus area to : " + toString(middleArea));
+            parameters.setFocusAreas(middleArea);
+        } else {
+            Log.i(TAG, "Device does not support focus areas");
+        }
+    }
+
+    public static void setMetering(Camera.Parameters parameters) {
+        if (parameters.getMaxNumMeteringAreas() > 0) {
+            Log.i(TAG, "Old metering areas: " + parameters.getMeteringAreas());
+            List<Camera.Area> middleArea = buildMiddleArea(AREA_PER_1000);
+            Log.i(TAG, "Setting metering area to : " + toString(middleArea));
+            parameters.setMeteringAreas(middleArea);
+        } else {
+            Log.i(TAG, "Device does not support metering areas");
+        }
+    }
+
+    private static List<Camera.Area> buildMiddleArea(int areaPer1000) {
+        return Collections.singletonList(
+                new Camera.Area(new Rect(-areaPer1000, -areaPer1000, areaPer1000, areaPer1000), 1));
+    }
+
+    public static void setVideoStabilization(Camera.Parameters parameters) {
+        if (parameters.isVideoStabilizationSupported()) {
+            if (parameters.getVideoStabilization()) {
+                Log.i(TAG, "Video stabilization already enabled");
+            } else {
+                Log.i(TAG, "Enabling video stabilization...");
+                parameters.setVideoStabilization(true);
+            }
+        } else {
+            Log.i(TAG, "This device does not support video stabilization");
+        }
+    }
+
+    public static void setBarcodeSceneMode(Camera.Parameters parameters) {
+        if (Camera.Parameters.SCENE_MODE_BARCODE.equals(parameters.getSceneMode())) {
+            Log.i(TAG, "Barcode scene mode already set");
+            return;
+        }
+        String sceneMode = findSettableValue("scene mode",
+                parameters.getSupportedSceneModes(),
+                Camera.Parameters.SCENE_MODE_BARCODE);
+        if (sceneMode != null) {
+            parameters.setSceneMode(sceneMode);
+        }
+    }
+
+    public static void setZoom(Camera.Parameters parameters, double targetZoomRatio) {
+        if (parameters.isZoomSupported()) {
+            Integer zoom = indexOfClosestZoom(parameters, targetZoomRatio);
+            if (zoom == null) {
+                return;
+            }
+            if (parameters.getZoom() == zoom) {
+                Log.i(TAG, "Zoom is already set to " + zoom);
+            } else {
+                Log.i(TAG, "Setting zoom to " + zoom);
+                parameters.setZoom(zoom);
+            }
+        } else {
+            Log.i(TAG, "Zoom is not supported");
+        }
+    }
+
+    private static Integer indexOfClosestZoom(Camera.Parameters parameters, double targetZoomRatio) {
+        List<Integer> ratios = parameters.getZoomRatios();
+        Log.i(TAG, "Zoom ratios: " + ratios);
+        int maxZoom = parameters.getMaxZoom();
+        if (ratios == null || ratios.isEmpty() || ratios.size() != maxZoom + 1) {
+            Log.w(TAG, "Invalid zoom ratios!");
+            return null;
+        }
+        double target100 = 100.0 * targetZoomRatio;
+        double smallestDiff = Double.POSITIVE_INFINITY;
+        int closestIndex = 0;
+        for (int i = 0; i < ratios.size(); i++) {
+            double diff = Math.abs(ratios.get(i) - target100);
+            if (diff < smallestDiff) {
+                smallestDiff = diff;
+                closestIndex = i;
+            }
+        }
+        Log.i(TAG, "Chose zoom ratio of " + (ratios.get(closestIndex) / 100.0));
+        return closestIndex;
+    }
+
+    public static void setInvertColor(Camera.Parameters parameters) {
+        if (Camera.Parameters.EFFECT_NEGATIVE.equals(parameters.getColorEffect())) {
+            Log.i(TAG, "Negative effect already set");
+            return;
+        }
+        String colorMode = findSettableValue("color effect",
+                parameters.getSupportedColorEffects(),
+                Camera.Parameters.EFFECT_NEGATIVE);
+        if (colorMode != null) {
+            parameters.setColorEffect(colorMode);
+        }
+    }
+
+    public static Point findBestPreviewSizeValue(Camera.Parameters parameters, Point screenResolution) {
+        final int width = screenResolution.x < screenResolution.y ?
+                screenResolution.y : screenResolution.x;
+        final int height = screenResolution.x < screenResolution.y ?
+                screenResolution.x : screenResolution.y;
+        List<Camera.Size> rawSupportedSizes = parameters.getSupportedPreviewSizes();
+        if (rawSupportedSizes == null) {
+            Log.w(TAG, "Device returned no supported preview sizes; using default");
+            Camera.Size defaultSize = parameters.getPreviewSize();
+            if (defaultSize == null) {
+                return new Point(screenResolution);
+            }
+            return new Point(defaultSize.width, defaultSize.height);
+        }
+
+        // Sort by size, descending
+        List<Camera.Size> supportedPreviewSizes = new ArrayList<>(rawSupportedSizes);
+        Collections.sort(supportedPreviewSizes, new Comparator<Camera.Size>() {
+            @Override
+            public int compare(Camera.Size a, Camera.Size b) {
+                int aPixels = a.height * a.width;
+                int bPixels = b.height * b.width;
+                if (bPixels < aPixels) {
+                    return -1;
+                }
+                if (bPixels > aPixels) {
+                    return 1;
+                }
+                return 0;
+            }
+        });
+
+        if (Log.isLoggable(TAG, Log.INFO)) {
+            StringBuilder previewSizesString = new StringBuilder();
+            for (Camera.Size supportedPreviewSize : supportedPreviewSizes) {
+                previewSizesString.append(supportedPreviewSize.width).append('x')
+                        .append(supportedPreviewSize.height).append(' ');
+            }
+            Log.i(TAG, "Supported preview sizes: " + previewSizesString);
+        }
+
+        double screenAspectRatio = width / (double) height;
+
+        // Remove sizes that are unsuitable
+        Iterator<Camera.Size> it = supportedPreviewSizes.iterator();
+        while (it.hasNext()) {
+            Camera.Size supportedPreviewSize = it.next();
+            int realWidth = supportedPreviewSize.width;
+            int realHeight = supportedPreviewSize.height;
+            if (realWidth * realHeight < MIN_PREVIEW_PIXELS) {
+                it.remove();
+                continue;
+            }
+
+            boolean isCandidatePortrait = realWidth < realHeight;
+            int maybeFlippedWidth = isCandidatePortrait ? realHeight : realWidth;
+            int maybeFlippedHeight = isCandidatePortrait ? realWidth : realHeight;
+            double aspectRatio = maybeFlippedWidth / (double) maybeFlippedHeight;
+            double distortion = Math.abs(aspectRatio - screenAspectRatio);
+            if (distortion > MAX_ASPECT_DISTORTION) {
+                it.remove();
+                continue;
+            }
+
+            if (maybeFlippedWidth == width && maybeFlippedHeight == height) {
+                Point exactPoint = new Point(realWidth, realHeight);
+                Log.i(TAG, "Found preview size exactly matching screen size: " + exactPoint);
+                return exactPoint;
+            }
+        }
+
+        // If no exact match, use largest preview size. This was not a great idea on older devices because
+        // of the additional computation needed. We're likely to get here on newer Android 4+ devices, where
+        // the CPU is much more powerful.
+        if (!supportedPreviewSizes.isEmpty()) {
+            Camera.Size largestPreview = supportedPreviewSizes.get(0);
+            Point largestSize = new Point(largestPreview.width, largestPreview.height);
+            Log.i(TAG, "Using largest suitable preview size: " + largestSize);
+            return largestSize;
+        } else {
+            long offset = Long.MAX_VALUE;
+            int position = -1;
+            for (Camera.Size size : supportedPreviewSizes) {
+                final long value1 = size.width * (long) height;
+                final long value2 = size.height * (long) width;
+                final long offsetTmp = value1 > value2 ? value1 - value2 : value2 - value1;
+                position++;
+                if (offsetTmp == 0)
+                    break;
+                else if (offsetTmp < offset)
+                    offset = offsetTmp;
+            }
+            Camera.Size suggestSize = supportedPreviewSizes.get(position);
+            Camera.Size defaultPreview = parameters.getPreviewSize();
+            if (defaultPreview == null) {
+                return new Point(suggestSize.width, suggestSize.height);
+            }
+            final long value1 = defaultPreview.width * (long) height;
+            final long value2 = defaultPreview.height * (long) width;
+            final long offsetTmp = value1 > value2 ? value1 - value2 : value2 - value1;
+            if (offsetTmp > offset)
+                return new Point(suggestSize.width, suggestSize.height);
+            else
+                return new Point(defaultPreview.width, defaultPreview.height);
+        }
+    }
+
+    private static String findSettableValue(String name,
+                                            Collection<String> supportedValues,
+                                            String... desiredValues) {
+        Log.i(TAG, "Requesting " + name + " value from among: " + Arrays.toString(desiredValues));
+        Log.i(TAG, "Supported " + name + " values: " + supportedValues);
+        if (supportedValues != null) {
+            for (String desiredValue : desiredValues) {
+                if (supportedValues.contains(desiredValue)) {
+                    Log.i(TAG, "Can set " + name + " to: " + desiredValue);
+                    return desiredValue;
+                }
+            }
+        }
+        Log.i(TAG, "No supported values match");
+        return null;
+    }
+
+    private static String toString(Collection<int[]> arrays) {
+        if (arrays == null || arrays.isEmpty()) {
+            return "[]";
+        }
+        StringBuilder buffer = new StringBuilder();
+        buffer.append('[');
+        Iterator<int[]> it = arrays.iterator();
+        while (it.hasNext()) {
+            buffer.append(Arrays.toString(it.next()));
+            if (it.hasNext()) {
+                buffer.append(", ");
+            }
+        }
+        buffer.append(']');
+        return buffer.toString();
+    }
+
+    private static String toString(Iterable<Camera.Area> areas) {
+        if (areas == null) {
+            return null;
+        }
+        StringBuilder result = new StringBuilder();
+        for (Camera.Area area : areas) {
+            result.append(area.rect).append(':').append(area.weight).append(' ');
+        }
+        return result.toString();
+    }
+
+    public static String collectStats(Camera.Parameters parameters) {
+        return collectStats(parameters.flatten());
+    }
+
+    public static String collectStats(CharSequence flattenedParams) {
+        StringBuilder result = new StringBuilder(1000);
+
+        result.append("BOARD=").append(Build.BOARD).append('\n');
+        result.append("BRAND=").append(Build.BRAND).append('\n');
+        result.append("CPU_ABI=").append(Build.CPU_ABI).append('\n');
+        result.append("DEVICE=").append(Build.DEVICE).append('\n');
+        result.append("DISPLAY=").append(Build.DISPLAY).append('\n');
+        result.append("FINGERPRINT=").append(Build.FINGERPRINT).append('\n');
+        result.append("HOST=").append(Build.HOST).append('\n');
+        result.append("ID=").append(Build.ID).append('\n');
+        result.append("MANUFACTURER=").append(Build.MANUFACTURER).append('\n');
+        result.append("MODEL=").append(Build.MODEL).append('\n');
+        result.append("PRODUCT=").append(Build.PRODUCT).append('\n');
+        result.append("TAGS=").append(Build.TAGS).append('\n');
+        result.append("TIME=").append(Build.TIME).append('\n');
+        result.append("TYPE=").append(Build.TYPE).append('\n');
+        result.append("USER=").append(Build.USER).append('\n');
+        result.append("VERSION.CODENAME=").append(Build.VERSION.CODENAME).append('\n');
+        result.append("VERSION.INCREMENTAL=").append(Build.VERSION.INCREMENTAL).append('\n');
+        result.append("VERSION.RELEASE=").append(Build.VERSION.RELEASE).append('\n');
+        result.append("VERSION.SDK_INT=").append(Build.VERSION.SDK_INT).append('\n');
+
+        if (flattenedParams != null) {
+            String[] params = SEMICOLON.split(flattenedParams);
+            Arrays.sort(params);
+            for (String param : params) {
+                result.append(param).append('\n');
+            }
+        }
+
+        return result.toString();
+    }
+
+}

+ 333 - 0
zxingscanview/src/main/java/com/google/zxing/client/android/camera/CameraManager.java

@@ -0,0 +1,333 @@
+/*
+ * Copyright (C) 2008 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.client.android.camera;
+
+import android.content.Context;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.hardware.Camera;
+import android.os.Handler;
+import android.util.Log;
+import android.view.SurfaceHolder;
+
+import com.google.zxing.PlanarYUVLuminanceSource;
+import com.google.zxing.client.android.camera.open.OpenCamera;
+import com.google.zxing.client.android.camera.open.OpenCameraInterface;
+
+import java.io.IOException;
+
+/**
+ * This object wraps the Camera service object and expects to be the only one talking to it. The
+ * implementation encapsulates the steps needed to take preview-sized images, which are used for
+ * both preview and decoding.
+ *
+ * @author dswitkin@google.com (Daniel Switkin)
+ */
+public final class CameraManager {
+
+    private static final String TAG = CameraManager.class.getSimpleName();
+
+    private static final int MIN_FRAME_WIDTH = 240;
+    private static final int MIN_FRAME_HEIGHT = 240;
+    private static final int MAX_FRAME_WIDTH = 1200; // = 5/8 * 1920
+    private static final int MAX_FRAME_HEIGHT = 675; // = 5/8 * 1080
+
+    private final Context context;
+    private final CameraConfigurationManager configManager;
+    /**
+     * Preview frames are delivered here, which we pass on to the registered handler. Make sure to
+     * clear the handler so it will only receive one message.
+     */
+    private final PreviewCallback previewCallback;
+    private OpenCamera camera;
+    private AutoFocusManager autoFocusManager;
+    private Rect framingRect;
+    private Rect framingRectInPreview;
+    private boolean initialized;
+    private boolean previewing;
+    private int requestedCameraId = OpenCameraInterface.NO_REQUESTED_CAMERA;
+    private int requestedFramingRectWidth;
+    private int requestedFramingRectHeight;
+
+    public CameraManager(Context context) {
+        this.context = context;
+        this.configManager = new CameraConfigurationManager(context);
+        previewCallback = new PreviewCallback(configManager);
+    }
+
+    private static int findDesiredDimensionInRange(int resolution, int hardMin, int hardMax) {
+        int dim = 5 * resolution / 8; // Target 5/8 of each dimension
+        if (dim < hardMin) {
+            return hardMin;
+        }
+        if (dim > hardMax) {
+            return hardMax;
+        }
+        return dim;
+    }
+
+    /**
+     * Opens the camera driver and initializes the hardware parameters.
+     *
+     * @param holder The surface object which the camera will draw preview frames into.
+     * @throws IOException Indicates the camera driver failed to open.
+     */
+    public synchronized void openDriver(SurfaceHolder holder) throws IOException {
+        OpenCamera theCamera = camera;
+        if (theCamera == null) {
+            theCamera = OpenCameraInterface.open(requestedCameraId);
+            if (theCamera == null) {
+                throw new IOException("Camera.open() failed to return object from driver");
+            }
+            camera = theCamera;
+        }
+
+        if (!initialized) {
+            initialized = true;
+            configManager.initFromCameraParameters(theCamera);
+            if (requestedFramingRectWidth > 0 && requestedFramingRectHeight > 0) {
+                setManualFramingRect(requestedFramingRectWidth, requestedFramingRectHeight);
+                requestedFramingRectWidth = 0;
+                requestedFramingRectHeight = 0;
+            }
+        }
+
+        Camera cameraObject = theCamera.getCamera();
+        Camera.Parameters parameters = cameraObject.getParameters();
+        String parametersFlattened = parameters == null ? null : parameters.flatten(); // Save these, temporarily
+        try {
+            configManager.setDesiredCameraParameters(theCamera, false);
+        } catch (RuntimeException re) {
+            // Driver failed
+            Log.w(TAG, "Camera rejected parameters. Setting only minimal safe-mode parameters");
+            Log.i(TAG, "Resetting to saved camera params: " + parametersFlattened);
+            // Reset:
+            if (parametersFlattened != null) {
+                parameters = cameraObject.getParameters();
+                parameters.unflatten(parametersFlattened);
+                try {
+                    cameraObject.setParameters(parameters);
+                    configManager.setDesiredCameraParameters(theCamera, true);
+                } catch (RuntimeException re2) {
+                    // Well, darn. Give up
+                    Log.w(TAG, "Camera rejected even safe-mode parameters! No configuration");
+                }
+            }
+        }
+        cameraObject.setPreviewDisplay(holder);
+
+    }
+
+    public synchronized boolean isOpen() {
+        return camera != null;
+    }
+
+    /**
+     * Closes the camera driver if still in use.
+     */
+    public synchronized void closeDriver() {
+        if (camera != null) {
+            camera.getCamera().release();
+            camera = null;
+            // Make sure to clear these each time we close the camera, so that any scanning rect
+            // requested by intent is forgotten.
+            framingRect = null;
+            framingRectInPreview = null;
+        }
+    }
+
+    /**
+     * Asks the camera hardware to begin drawing preview frames to the screen.
+     */
+    public synchronized void startPreview() {
+        OpenCamera theCamera = camera;
+        if (theCamera != null && !previewing) {
+            theCamera.getCamera().startPreview();
+            previewing = true;
+            autoFocusManager = new AutoFocusManager(context, theCamera.getCamera());
+        }
+    }
+
+    /**
+     * Tells the camera to stop drawing preview frames.
+     */
+    public synchronized void stopPreview() {
+        if (autoFocusManager != null) {
+            autoFocusManager.stop();
+            autoFocusManager = null;
+        }
+        if (camera != null && previewing) {
+            camera.getCamera().stopPreview();
+            previewCallback.setHandler(null, 0);
+            previewing = false;
+        }
+    }
+
+    /**
+     * Convenience method
+     *
+     * @param newSetting if {@code true}, light should be turned on if currently off. And vice versa.
+     */
+    public synchronized void setTorch(boolean newSetting) {
+        OpenCamera theCamera = camera;
+        if (theCamera != null) {
+            if (newSetting != configManager.getTorchState(theCamera.getCamera())) {
+                boolean wasAutoFocusManager = autoFocusManager != null;
+                if (wasAutoFocusManager) {
+                    autoFocusManager.stop();
+                    autoFocusManager = null;
+                }
+                configManager.setTorch(theCamera.getCamera(), newSetting);
+                if (wasAutoFocusManager) {
+                    autoFocusManager = new AutoFocusManager(context, theCamera.getCamera());
+                    autoFocusManager.start();
+                }
+            }
+        }
+    }
+
+    /**
+     * A single preview frame will be returned to the handler supplied. The data will arrive as byte[]
+     * in the message.obj field, with width and height encoded as message.arg1 and message.arg2,
+     * respectively.
+     *
+     * @param handler The handler to send the message to.
+     * @param message The what field of the message to be sent.
+     */
+    public synchronized void requestPreviewFrame(Handler handler, int message) {
+        OpenCamera theCamera = camera;
+        if (theCamera != null && previewing) {
+            previewCallback.setHandler(handler, message);
+            theCamera.getCamera().setOneShotPreviewCallback(previewCallback);
+        }
+    }
+
+    /**
+     * Calculates the framing rect which the UI should draw to show the user where to place the
+     * barcode. This target helps with alignment as well as forces the user to hold the device
+     * far enough away to ensure the image will be in focus.
+     *
+     * @return The rectangle to draw on screen in window coordinates.
+     */
+    public synchronized Rect getFramingRect() {
+        if (framingRect == null) {
+            if (camera == null) {
+                return null;
+            }
+            Point screenResolution = configManager.getScreenResolution();
+            if (screenResolution == null) {
+                // Called early, before init even finished
+                return null;
+            }
+
+            int width = findDesiredDimensionInRange(screenResolution.x, MIN_FRAME_WIDTH, MAX_FRAME_WIDTH);
+            int height = findDesiredDimensionInRange(screenResolution.y, MIN_FRAME_HEIGHT, MAX_FRAME_HEIGHT);
+
+            int leftOffset = (screenResolution.x - width) / 2;
+            int topOffset = (screenResolution.y - height) / 2;
+            framingRect = new Rect(leftOffset, topOffset, leftOffset + width, topOffset + height);
+            Log.d(TAG, "Calculated framing rect: " + framingRect);
+        }
+        return framingRect;
+    }
+
+    /**
+     * Like {@link #getFramingRect} but coordinates are in terms of the preview frame,
+     * not UI / screen.
+     *
+     * @return {@link Rect} expressing barcode scan area in terms of the preview size
+     */
+    public synchronized Rect getFramingRectInPreview() {
+        if (framingRectInPreview == null) {
+            Rect framingRect = getFramingRect();
+            if (framingRect == null) {
+                return null;
+            }
+            Rect rect = new Rect(framingRect);
+            Point cameraResolution = configManager.getCameraResolution();
+            Point screenResolution = configManager.getScreenResolution();
+            if (cameraResolution == null || screenResolution == null) {
+                // Called early, before init even finished
+                return null;
+            }
+            rect.left = rect.left * cameraResolution.x / screenResolution.x;
+            rect.right = rect.right * cameraResolution.x / screenResolution.x;
+            rect.top = rect.top * cameraResolution.y / screenResolution.y;
+            rect.bottom = rect.bottom * cameraResolution.y / screenResolution.y;
+            framingRectInPreview = rect;
+        }
+        return framingRectInPreview;
+    }
+
+
+    /**
+     * Allows third party apps to specify the camera ID, rather than determine
+     * it automatically based on available cameras and their orientation.
+     *
+     * @param cameraId camera ID of the camera to use. A negative value means "no preference".
+     */
+    public synchronized void setManualCameraId(int cameraId) {
+        requestedCameraId = cameraId;
+    }
+
+    /**
+     * Allows third party apps to specify the scanning rectangle dimensions, rather than determine
+     * them automatically based on screen resolution.
+     *
+     * @param width  The width in pixels to scan.
+     * @param height The height in pixels to scan.
+     */
+    public synchronized void setManualFramingRect(int width, int height) {
+        if (initialized) {
+            Point screenResolution = configManager.getScreenResolution();
+            if (width > screenResolution.x) {
+                width = screenResolution.x;
+            }
+            if (height > screenResolution.y) {
+                height = screenResolution.y;
+            }
+            int leftOffset = (screenResolution.x - width) / 2;
+            int topOffset = (screenResolution.y - height) / 2;
+            framingRect = new Rect(leftOffset, topOffset, leftOffset + width, topOffset + height);
+            Log.d(TAG, "Calculated manual framing rect: " + framingRect);
+            framingRectInPreview = null;
+        } else {
+            requestedFramingRectWidth = width;
+            requestedFramingRectHeight = height;
+        }
+    }
+
+    /**
+     * A factory method to build the appropriate LuminanceSource object based on the format
+     * of the preview buffers, as described by Camera.Parameters.
+     *
+     * @param data   A preview frame.
+     * @param width  The width of the image.
+     * @param height The height of the image.
+     * @return A PlanarYUVLuminanceSource instance.
+     */
+    public PlanarYUVLuminanceSource buildLuminanceSource(byte[] data, int width, int height) {
+        Rect rect = getFramingRectInPreview();
+        if (rect == null) {
+            return null;
+        }
+        // Go ahead and assume it's YUV rather than die.
+        return new PlanarYUVLuminanceSource(data, width, height, rect.left, rect.top,
+                rect.width(), rect.height(), false);
+    }
+
+}

+ 34 - 0
zxingscanview/src/main/java/com/google/zxing/client/android/camera/CameraPreferences.java

@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2015 AlexMofer
+ *
+ * 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.google.zxing.client.android.camera;
+
+/**
+ * Camera settings
+ * TODO
+ * Created by Alex on 2016/11/22.
+ */
+class CameraPreferences {
+
+    static final String KEY_FRONT_LIGHT_MODE = "preferences_front_light_mode";
+    static final String KEY_AUTO_FOCUS = "preferences_auto_focus";
+    static final String KEY_INVERT_SCAN = "preferences_invert_scan";
+
+    static final String KEY_DISABLE_CONTINUOUS_FOCUS = "preferences_disable_continuous_focus";
+    static final String KEY_DISABLE_EXPOSURE = "preferences_disable_exposure";
+    static final String KEY_DISABLE_METERING = "preferences_disable_metering";
+    static final String KEY_DISABLE_BARCODE_SCENE_MODE = "preferences_disable_barcode_scene_mode";
+}

+ 47 - 0
zxingscanview/src/main/java/com/google/zxing/client/android/camera/FrontLightMode.java

@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2012 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.client.android.camera;
+
+import android.content.SharedPreferences;
+
+/**
+ * Enumerates settings of the preference controlling the front light.
+ */
+enum FrontLightMode {
+
+    /**
+     * Always on.
+     */
+    ON,
+    /**
+     * On only when ambient light is low.
+     */
+    AUTO,
+    /**
+     * Always off.
+     */
+    OFF;
+
+    private static FrontLightMode parse(String modeString) {
+        return modeString == null ? OFF : valueOf(modeString);
+    }
+
+    public static FrontLightMode readPref(SharedPreferences sharedPrefs) {
+        return parse(sharedPrefs.getString(CameraPreferences.KEY_FRONT_LIGHT_MODE, OFF.toString()));
+    }
+
+}

+ 56 - 0
zxingscanview/src/main/java/com/google/zxing/client/android/camera/PreviewCallback.java

@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2010 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.client.android.camera;
+
+import android.graphics.Point;
+import android.hardware.Camera;
+import android.os.Handler;
+import android.os.Message;
+import android.util.Log;
+
+final class PreviewCallback implements Camera.PreviewCallback {
+
+    private static final String TAG = PreviewCallback.class.getSimpleName();
+
+    private final CameraConfigurationManager configManager;
+    private Handler previewHandler;
+    private int previewMessage;
+
+    PreviewCallback(CameraConfigurationManager configManager) {
+        this.configManager = configManager;
+    }
+
+    void setHandler(Handler previewHandler, int previewMessage) {
+        this.previewHandler = previewHandler;
+        this.previewMessage = previewMessage;
+    }
+
+    @Override
+    public void onPreviewFrame(byte[] data, Camera camera) {
+        Point cameraResolution = configManager.getCameraResolution();
+        Handler thePreviewHandler = previewHandler;
+        if (cameraResolution != null && thePreviewHandler != null) {
+            Message message = thePreviewHandler.obtainMessage(previewMessage, cameraResolution.x,
+                    cameraResolution.y, data);
+            message.sendToTarget();
+            previewHandler = null;
+        } else {
+            Log.d(TAG, "Got preview callback, but no handler or resolution available");
+        }
+    }
+
+}

+ 27 - 0
zxingscanview/src/main/java/com/google/zxing/client/android/camera/open/CameraFacing.java

@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2015 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.client.android.camera.open;
+
+/**
+ * Enumeration of directions a camera may face: front or back.
+ */
+public enum CameraFacing {
+
+    BACK,  // must be value 0!
+    FRONT, // must be value 1!
+
+}

+ 55 - 0
zxingscanview/src/main/java/com/google/zxing/client/android/camera/open/OpenCamera.java

@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2015 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.client.android.camera.open;
+
+import android.hardware.Camera;
+
+/**
+ * Represents an open {@link Camera} and its metadata, like facing direction and orientation.
+ */
+public final class OpenCamera {
+
+    private final int index;
+    private final Camera camera;
+    private final CameraFacing facing;
+    private final int orientation;
+
+    public OpenCamera(int index, Camera camera, CameraFacing facing, int orientation) {
+        this.index = index;
+        this.camera = camera;
+        this.facing = facing;
+        this.orientation = orientation;
+    }
+
+    public Camera getCamera() {
+        return camera;
+    }
+
+    public CameraFacing getFacing() {
+        return facing;
+    }
+
+    public int getOrientation() {
+        return orientation;
+    }
+
+    @Override
+    public String toString() {
+        return "Camera #" + index + " : " + facing + ',' + orientation;
+    }
+
+}

+ 99 - 0
zxingscanview/src/main/java/com/google/zxing/client/android/camera/open/OpenCameraInterface.java

@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2012 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.client.android.camera.open;
+
+import android.hardware.Camera;
+import android.util.Log;
+
+/**
+ * Abstraction over the {@link Camera} API that helps open them and return their metadata.
+ */
+public final class OpenCameraInterface {
+
+    /**
+     * For {@link #open(int)}, means no preference for which camera to open.
+     */
+    public static final int NO_REQUESTED_CAMERA = -1;
+    private static final String TAG = OpenCameraInterface.class.getName();
+
+    private OpenCameraInterface() {
+    }
+
+    /**
+     * Opens the requested camera with {@link Camera#open(int)}, if one exists.
+     *
+     * @param cameraId camera ID of the camera to use. A negative value
+     *                 or {@link #NO_REQUESTED_CAMERA} means "no preference", in which case a rear-facing
+     *                 camera is returned if possible or else any camera
+     * @return handle to {@link OpenCamera} that was opened
+     */
+    public static OpenCamera open(int cameraId) {
+
+        int numCameras = Camera.getNumberOfCameras();
+        if (numCameras == 0) {
+            Log.w(TAG, "No cameras!");
+            return null;
+        }
+
+        boolean explicitRequest = cameraId >= 0;
+
+        Camera.CameraInfo selectedCameraInfo = null;
+        int index;
+        if (explicitRequest) {
+            index = cameraId;
+            selectedCameraInfo = new Camera.CameraInfo();
+            Camera.getCameraInfo(index, selectedCameraInfo);
+        } else {
+            index = 0;
+            while (index < numCameras) {
+                Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
+                Camera.getCameraInfo(index, cameraInfo);
+                CameraFacing reportedFacing = CameraFacing.values()[cameraInfo.facing];
+                if (reportedFacing == CameraFacing.BACK) {
+                    selectedCameraInfo = cameraInfo;
+                    break;
+                }
+                index++;
+            }
+        }
+
+        Camera camera;
+        if (index < numCameras) {
+            Log.i(TAG, "Opening camera #" + index);
+            camera = Camera.open(index);
+        } else {
+            if (explicitRequest) {
+                Log.w(TAG, "Requested camera does not exist: " + cameraId);
+                camera = null;
+            } else {
+                Log.i(TAG, "No camera facing " + CameraFacing.BACK + "; returning camera #0");
+                camera = Camera.open(0);
+                selectedCameraInfo = new Camera.CameraInfo();
+                Camera.getCameraInfo(0, selectedCameraInfo);
+            }
+        }
+
+        if (camera == null) {
+            return null;
+        }
+        return new OpenCamera(index,
+                camera,
+                CameraFacing.values()[selectedCameraInfo.facing],
+                selectedCameraInfo.orientation);
+    }
+
+}

+ 108 - 0
zxingscanview/src/main/java/com/google/zxing/client/android/compat/Compat.java

@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2015 AlexMofer
+ *
+ * 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.google.zxing.client.android.compat;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.os.Process;
+
+/**
+ * 版本兼容器
+ * Created by Alex on 2016/11/28.
+ */
+
+public class Compat {
+
+    private static final CompatImpl IMPL;
+
+    static {
+        final int version = android.os.Build.VERSION.SDK_INT;
+        if (version >= 23) {
+            IMPL = new CompatAPI23();
+        } else if (version >= 21) {
+            IMPL = new CompatLollipop();
+        } else {
+            IMPL = new CompatBase();
+        }
+    }
+
+    private Compat() {
+    }
+
+    /**
+     * Determine whether <em>you</em> have been granted a particular permission.
+     *
+     * @param permission The name of the permission being checked.
+     * @return {@link android.content.pm.PackageManager#PERMISSION_GRANTED} if you have the
+     * permission, or {@link android.content.pm.PackageManager#PERMISSION_DENIED} if not.
+     * @see android.content.pm.PackageManager#checkPermission(String, String)
+     */
+    public static int checkSelfPermission(Context context, String permission) {
+        if (permission == null) {
+            throw new IllegalArgumentException("permission is null");
+        }
+        return IMPL.checkSelfPermission(context, permission);
+    }
+
+    /**
+     * Specifies the hotspot's location within the drawable.
+     *
+     * @param drawable The Drawable against which to invoke the method.
+     * @param x        The X coordinate of the center of the hotspot
+     * @param y        The Y coordinate of the center of the hotspot
+     */
+    public static void setHotspot(Drawable drawable, float x, float y) {
+        IMPL.setHotspot(drawable, x, y);
+    }
+
+    private interface CompatImpl {
+        int checkSelfPermission(Context context, String permission);
+
+        void setHotspot(Drawable drawable, float x, float y);
+    }
+
+    private static class CompatBase implements CompatImpl {
+
+        @Override
+        public int checkSelfPermission(Context context, String permission) {
+            return context.checkPermission(permission, android.os.Process.myPid(), Process.myUid());
+        }
+
+        @Override
+        public void setHotspot(Drawable drawable, float x, float y) {
+            // do nothing
+        }
+    }
+
+    @TargetApi(21)
+    private static class CompatLollipop extends CompatBase {
+
+        @Override
+        public void setHotspot(Drawable drawable, float x, float y) {
+            drawable.setHotspot(x, y);
+        }
+    }
+
+    @TargetApi(23)
+    private static class CompatAPI23 extends CompatBase {
+        @Override
+        public int checkSelfPermission(Context context, String permission) {
+            return context.checkSelfPermission(permission);
+        }
+    }
+}

+ 36 - 0
zxingscanview/src/main/java/com/google/zxing/client/android/decode/BarcodeType.java

@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2015 AlexMofer
+ *
+ * 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.google.zxing.client.android.decode;
+
+/**
+ * 条码类型
+ * Created by Alex on 2016/12/2.
+ */
+@SuppressWarnings("WeakerAccess")
+public class BarcodeType {
+    public static final int PRODUCT_1D = 1;// 一维码:商品
+    public static final int INDUSTRIAL_1D = 2;// 一维码:工业
+    public static final int QR = 4;// 二维码
+    public static final int DATA_MATRIX = 8;// Data Matrix
+    public static final int AZTEC = 16;// Aztec
+    public static final int PDF417 = 32;// PDF417
+    public static final int DEFAULT = PRODUCT_1D | INDUSTRIAL_1D | QR | DATA_MATRIX;
+
+    private BarcodeType() {
+        //no instance
+    }
+}

+ 117 - 0
zxingscanview/src/main/java/com/google/zxing/client/android/decode/DecodeHandler.java

@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2010 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.client.android.decode;
+
+import android.graphics.Bitmap;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+
+import com.google.zxing.BinaryBitmap;
+import com.google.zxing.DecodeHintType;
+import com.google.zxing.MultiFormatReader;
+import com.google.zxing.PlanarYUVLuminanceSource;
+import com.google.zxing.ReaderException;
+import com.google.zxing.Result;
+import com.google.zxing.client.android.camera.CameraManager;
+import com.google.zxing.common.HybridBinarizer;
+
+import java.io.ByteArrayOutputStream;
+import java.util.Map;
+
+
+final class DecodeHandler extends Handler {
+
+    private final CameraManager cameraManager;
+    private final Handler mHandler;
+    private final MultiFormatReader multiFormatReader;
+    private boolean running = true;
+
+    DecodeHandler(CameraManager cameraManager, Handler mHandler, Map<DecodeHintType, Object> hints) {
+        multiFormatReader = new MultiFormatReader();
+        multiFormatReader.setHints(hints);
+        this.cameraManager = cameraManager;
+        this.mHandler = mHandler;
+    }
+
+    private static void bundleThumbnail(PlanarYUVLuminanceSource source, Bundle bundle) {
+        int[] pixels = source.renderThumbnail();
+        int width = source.getThumbnailWidth();
+        int height = source.getThumbnailHeight();
+        Bitmap bitmap = Bitmap.createBitmap(pixels, 0, width, width, height, Bitmap.Config.ARGB_8888);
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        bitmap.compress(Bitmap.CompressFormat.JPEG, 50, out);
+        bundle.putByteArray(DecodeThread.BARCODE_BITMAP, out.toByteArray());
+        bundle.putFloat(DecodeThread.BARCODE_SCALED_FACTOR, (float) width / source.getWidth());
+    }
+
+    @Override
+    public void handleMessage(Message message) {
+        if (!running) {
+            return;
+        }
+        switch (message.what) {
+            case ID.decode:
+                decode((byte[]) message.obj, message.arg1, message.arg2);
+                break;
+            case ID.quit:
+                running = false;
+                Looper.myLooper().quit();
+                break;
+        }
+    }
+
+    /**
+     * Decode the data within the viewfinder rectangle, and time how long it took. For efficiency,
+     * reuse the same reader objects from one decode to the next.
+     *
+     * @param data   The YUV preview frame.
+     * @param width  The width of the preview frame.
+     * @param height The height of the preview frame.
+     */
+    private void decode(byte[] data, int width, int height) {
+        Result rawResult = null;
+        PlanarYUVLuminanceSource source = cameraManager.buildLuminanceSource(data, width, height);
+        if (source != null) {
+            BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
+            try {
+                rawResult = multiFormatReader.decodeWithState(bitmap);
+            } catch (ReaderException re) {
+                // continue
+            } finally {
+                multiFormatReader.reset();
+            }
+        }
+        if (rawResult != null) {
+            // Don't log the barcode contents for security.
+            if (mHandler != null) {
+                Message message = Message.obtain(mHandler, ID.decode_succeeded, rawResult);
+                Bundle bundle = new Bundle();
+                bundleThumbnail(source, bundle);
+                message.setData(bundle);
+                message.sendToTarget();
+            }
+        } else {
+            if (mHandler != null) {
+                Message message = Message.obtain(mHandler, ID.decode_failed);
+                message.sendToTarget();
+            }
+        }
+    }
+
+}

+ 135 - 0
zxingscanview/src/main/java/com/google/zxing/client/android/decode/DecodeThread.java

@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2008 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.client.android.decode;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.util.Log;
+
+import com.google.zxing.BarcodeFormat;
+import com.google.zxing.DecodeHintType;
+import com.google.zxing.ResultPointCallback;
+import com.google.zxing.client.android.camera.CameraManager;
+
+import java.util.Collection;
+import java.util.EnumMap;
+import java.util.EnumSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+
+/**
+ * This thread does all the heavy lifting of decoding the images.
+ *
+ * @author dswitkin@google.com (Daniel Switkin)
+ */
+class DecodeThread extends Thread {
+
+    public static final String BARCODE_BITMAP = "barcode_bitmap";
+    public static final String BARCODE_SCALED_FACTOR = "barcode_scaled_factor";
+    private static final Set<BarcodeFormat> PRODUCT_FORMATS;
+    private static final Set<BarcodeFormat> INDUSTRIAL_FORMATS;
+    private static final Set<BarcodeFormat> ONE_D_FORMATS;
+    private static final Set<BarcodeFormat> QR_CODE_FORMATS = EnumSet.of(BarcodeFormat.QR_CODE);
+    private static final Set<BarcodeFormat> DATA_MATRIX_FORMATS = EnumSet.of(BarcodeFormat.DATA_MATRIX);
+    private static final Set<BarcodeFormat> AZTEC_FORMATS = EnumSet.of(BarcodeFormat.AZTEC);
+    private static final Set<BarcodeFormat> PDF417_FORMATS = EnumSet.of(BarcodeFormat.PDF_417);
+
+    static {
+        PRODUCT_FORMATS = EnumSet.of(BarcodeFormat.UPC_A,
+                BarcodeFormat.UPC_E,
+                BarcodeFormat.EAN_13,
+                BarcodeFormat.EAN_8,
+                BarcodeFormat.RSS_14,
+                BarcodeFormat.RSS_EXPANDED);
+        INDUSTRIAL_FORMATS = EnumSet.of(BarcodeFormat.CODE_39,
+                BarcodeFormat.CODE_93,
+                BarcodeFormat.CODE_128,
+                BarcodeFormat.ITF,
+                BarcodeFormat.CODABAR);
+        ONE_D_FORMATS = EnumSet.copyOf(PRODUCT_FORMATS);
+        ONE_D_FORMATS.addAll(INDUSTRIAL_FORMATS);
+    }
+
+    public final CameraManager cameraManager;
+    public final Handler mHandler;
+    private final Map<DecodeHintType, Object> hints;
+    private final CountDownLatch handlerInitLatch;
+    private Handler handler;
+
+    public DecodeThread(CameraManager cameraManager, Handler mHandler,
+                        int barcodeType,
+                        Map<DecodeHintType, ?> baseHints,
+                        String characterSet,
+                        ResultPointCallback resultPointCallback) {
+        this.cameraManager = cameraManager;
+        this.mHandler = mHandler;
+
+        handlerInitLatch = new CountDownLatch(1);
+
+        hints = new EnumMap<>(DecodeHintType.class);
+        if (baseHints != null) {
+            hints.putAll(baseHints);
+        }
+
+        Collection<BarcodeFormat> decodeFormats = EnumSet.noneOf(BarcodeFormat.class);
+        if ((barcodeType & BarcodeType.PRODUCT_1D) == BarcodeType.PRODUCT_1D) {
+            decodeFormats.addAll(PRODUCT_FORMATS);
+        }
+        if ((barcodeType & BarcodeType.INDUSTRIAL_1D) == BarcodeType.INDUSTRIAL_1D) {
+            decodeFormats.addAll(INDUSTRIAL_FORMATS);
+        }
+        if ((barcodeType & BarcodeType.QR) == BarcodeType.QR) {
+            decodeFormats.addAll(QR_CODE_FORMATS);
+        }
+        if ((barcodeType & BarcodeType.DATA_MATRIX) == BarcodeType.DATA_MATRIX) {
+            decodeFormats.addAll(DATA_MATRIX_FORMATS);
+        }
+        if ((barcodeType & BarcodeType.AZTEC) == BarcodeType.AZTEC) {
+            decodeFormats.addAll(AZTEC_FORMATS);
+        }
+        if ((barcodeType & BarcodeType.PDF417) == BarcodeType.PDF417) {
+            decodeFormats.addAll(PDF417_FORMATS);
+        }
+
+        hints.put(DecodeHintType.POSSIBLE_FORMATS, decodeFormats);
+
+        if (characterSet != null) {
+            hints.put(DecodeHintType.CHARACTER_SET, characterSet);
+        }
+        hints.put(DecodeHintType.NEED_RESULT_POINT_CALLBACK, resultPointCallback);
+        Log.i("DecodeThread", "Hints: " + hints);
+    }
+
+    Handler getHandler() {
+        try {
+            handlerInitLatch.await();
+        } catch (InterruptedException ie) {
+            // continue?
+        }
+        return handler;
+    }
+
+    @Override
+    public void run() {
+        Looper.prepare();
+        handler = new DecodeHandler(cameraManager, mHandler, hints);
+        handlerInitLatch.countDown();
+        Looper.loop();
+    }
+
+}

+ 30 - 0
zxingscanview/src/main/java/com/google/zxing/client/android/decode/ID.java

@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2015 AlexMofer
+ *
+ * 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.google.zxing.client.android.decode;
+
+/**
+ * ID
+ * Created by Alex on 2016/11/25.
+ */
+
+class ID {
+    static final int decode = 8748800;
+    static final int decode_failed = 8748801;
+    static final int decode_succeeded = 8748802;
+    static final int quit = 8748804;
+    static final int restart_preview = 8748805;
+}

+ 140 - 0
zxingscanview/src/main/java/com/google/zxing/client/android/decode/ScanHandler.java

@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2008 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.client.android.decode;
+
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+
+import com.google.zxing.DecodeHintType;
+import com.google.zxing.Result;
+import com.google.zxing.ResultPointCallback;
+import com.google.zxing.client.android.camera.CameraManager;
+
+import java.util.Map;
+
+
+/**
+ * This class handles all the messaging which comprises the state machine for capture.
+ *
+ * @author dswitkin@google.com (Daniel Switkin)
+ */
+public final class ScanHandler extends Handler {
+
+    private final OnResultListener listener;
+    private final DecodeThread decodeThread;
+    private final CameraManager cameraManager;
+    private State state;
+
+    public ScanHandler(OnResultListener listener,
+                       int barcodeType,
+                       Map<DecodeHintType, ?> baseHints,
+                       String characterSet,
+                       CameraManager cameraManager, ResultPointCallback resultPointCallback) {
+        this.listener = listener;
+        // Start ourselves capturing previews and decoding.
+        this.cameraManager = cameraManager;
+        decodeThread = new DecodeThread(cameraManager,
+                this, barcodeType, baseHints, characterSet, resultPointCallback);
+        decodeThread.start();
+        state = State.SUCCESS;
+        restartPreviewAndDecode();
+    }
+
+    @Override
+    public void handleMessage(Message message) {
+        switch (message.what) {
+            case ID.restart_preview:
+                restartPreviewAndDecode();
+                break;
+            case ID.decode_succeeded:
+                state = State.SUCCESS;
+                Bundle bundle = message.getData();
+                Bitmap barcode = null;
+                float scaleFactor = 1.0f;
+                if (bundle != null) {
+                    byte[] compressedBitmap = bundle.getByteArray(DecodeThread.BARCODE_BITMAP);
+                    if (compressedBitmap != null) {
+                        barcode = BitmapFactory.decodeByteArray(compressedBitmap, 0, compressedBitmap.length, null);
+                        // Mutable copy:
+                        barcode = barcode.copy(Bitmap.Config.ARGB_8888, true);
+                    }
+                    scaleFactor = bundle.getFloat(DecodeThread.BARCODE_SCALED_FACTOR);
+                }
+                if (listener != null)
+                    listener.onResult((Result) message.obj, barcode, scaleFactor);
+                break;
+            case ID.decode_failed:
+                // We're decoding as fast as possible, so when one decode fails, start another.
+                state = State.PREVIEW;
+                cameraManager.requestPreviewFrame(decodeThread.getHandler(), ID.decode);
+                break;
+        }
+    }
+
+    public void quitSynchronously() {
+        state = State.DONE;
+        cameraManager.stopPreview();
+        Message quit = Message.obtain(decodeThread.getHandler(), ID.quit);
+        quit.sendToTarget();
+        try {
+            // Wait at most half a second; should be enough time, and onPause() will timeout quickly
+            decodeThread.join(500L);
+        } catch (InterruptedException e) {
+            // continue
+        }
+        // Be absolutely sure we don't send any queued up messages
+        removeMessages(ID.decode_succeeded);
+        removeMessages(ID.decode_failed);
+    }
+
+    private void restartPreviewAndDecode() {
+        if (state == State.SUCCESS) {
+            state = State.PREVIEW;
+            cameraManager.requestPreviewFrame(decodeThread.getHandler(), ID.decode);
+        }
+    }
+
+    /**
+     * 重新开始扫描
+     */
+    public void restartScan() {
+        sendEmptyMessage(ID.restart_preview);
+    }
+
+    /**
+     * 一段时间后重新你开始扫描
+     *
+     * @param delay 时间
+     */
+    public void restartScanDelay(long delay) {
+        sendEmptyMessageDelayed(ID.restart_preview, delay);
+    }
+
+    private enum State {
+        PREVIEW,
+        SUCCESS,
+        DONE
+    }
+
+    public interface OnResultListener {
+        void onResult(Result result, Bitmap barcode, float scaleFactor);
+    }
+
+}

+ 157 - 0
zxingscanview/src/main/java/com/google/zxing/client/android/manager/AmbientLightManager.java

@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2015 AlexMofer
+ *
+ * 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.google.zxing.client.android.manager;
+
+import android.content.Context;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+
+/**
+ * 背光管理器
+ * Created by Alex on 2016/11/28.
+ */
+@SuppressWarnings({"WeakerAccess", "unused"})
+public class AmbientLightManager implements SensorEventListener {
+    public static final int MODE_AUTO = 0;
+    public static final int MODE_OPEN = 1;
+    public static final int MODE_CLOSE = 2;
+    private static final float LUX_TOO_DARK = 45.0f;
+    private static final float LUX_BRIGHT_ENOUGH = 450.0f;
+    private final AmbientLightCallBack mCallBack;
+    private int mMode;
+    private boolean isResume = false;
+    private SensorManager sensorManager;
+    private float mMinLux;
+    private float mMaxLux;
+
+    public AmbientLightManager(Context context, AmbientLightCallBack callBack) {
+        this(context, callBack, MODE_AUTO);
+    }
+
+    public AmbientLightManager(Context context, AmbientLightCallBack callBack, int mode) {
+        mCallBack = callBack;
+        setMode(mode);
+        sensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
+        setMaxLux(LUX_BRIGHT_ENOUGH);
+        setMinLux(LUX_TOO_DARK);
+    }
+
+    /**
+     * 设置背光模式
+     *
+     * @param mode 背光模式
+     */
+    public void setMode(int mode) {
+        if (mode != MODE_AUTO && mode != MODE_CLOSE && mode != MODE_OPEN)
+            return;
+        if (mode == mMode)
+            return;
+        mMode = mode;
+        if (isResume) {
+            setTorch();
+        }
+    }
+
+    private void setTorch() {
+        switch (mMode) {
+            case MODE_AUTO:
+                if (sensorManager != null) {
+                    sensorManager.registerListener(this,
+                            sensorManager.getDefaultSensor(Sensor.TYPE_LIGHT),
+                            SensorManager.SENSOR_DELAY_NORMAL);
+                }
+                break;
+            case MODE_OPEN:
+                if (sensorManager != null) {
+                    sensorManager.unregisterListener(this);
+                }
+                if (mCallBack != null)
+                    mCallBack.onChange(true);
+                break;
+            case MODE_CLOSE:
+                if (sensorManager != null) {
+                    sensorManager.unregisterListener(this);
+                }
+                if (mCallBack != null)
+                    mCallBack.onChange(false);
+                break;
+        }
+    }
+
+    public void setMinLux(float mix) {
+        mMinLux = mix;
+    }
+
+    public void setMaxLux(float max) {
+        mMaxLux = max;
+    }
+
+    public void resume() {
+        if (isResume)
+            return;
+        isResume = true;
+        setTorch();
+    }
+
+    public void pause() {
+        if (!isResume)
+            return;
+        isResume = false;
+        if (sensorManager != null)
+            sensorManager.unregisterListener(this);
+    }
+
+    /**
+     * 销毁
+     */
+    public void release() {
+        if (sensorManager != null)
+            sensorManager.unregisterListener(this);
+        sensorManager = null;
+    }
+
+    @Override
+    public void onSensorChanged(SensorEvent sensorEvent) {
+        float ambientLightLux = sensorEvent.values[0];
+        if (mCallBack != null) {
+            if (ambientLightLux <= mMinLux) {
+                mCallBack.onChange(true);
+            } else if (ambientLightLux >= mMaxLux) {
+                mCallBack.onChange(false);
+            }
+        }
+    }
+
+    @Override
+    public void onAccuracyChanged(Sensor sensor, int accuracy) {
+
+    }
+
+    /**
+     * 背光状态变化回调
+     */
+    public interface AmbientLightCallBack {
+        /**
+         * 状态变化
+         *
+         * @param on 打开还是关闭
+         */
+        void onChange(boolean on);
+    }
+}

+ 225 - 0
zxingscanview/src/main/java/com/google/zxing/client/android/manager/ScanFeedbackManager.java

@@ -0,0 +1,225 @@
+/*
+ * Copyright (C) 2015 AlexMofer
+ *
+ * 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.google.zxing.client.android.manager;
+
+import android.Manifest;
+import android.annotation.SuppressLint;
+import android.app.Service;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.res.AssetFileDescriptor;
+import android.media.AudioManager;
+import android.media.MediaPlayer;
+import android.media.RingtoneManager;
+import android.net.Uri;
+import android.os.Vibrator;
+import android.view.View;
+
+import com.google.zxing.client.android.compat.Compat;
+
+/**
+ * 扫描反馈管理器
+ * Created by Alex on 2016/11/28.
+ */
+public class ScanFeedbackManager implements MediaPlayer.OnCompletionListener {
+
+    public static final int MODE_AUTO = 0;//自动模式
+    public static final int MODE_AUDIO_ONLY = 1;//铃声
+    public static final int MODE_VIBRATOR_ONLY = 2;//震动(需要权限)
+    public static final int MODE_AUDIO_VIBRATOR = 3;//铃声与震动
+    public static final int DEFAUT_MILLISECONDS = 200;
+    private final MediaPlayer player = new MediaPlayer();
+    private final AudioManager audioManager;
+    private final Vibrator vibrator;
+    private final Context context;
+    private String assetsFileName;
+    private int rawId = View.NO_ID;
+    private long mVibrateMilliseconds;
+    private boolean isAudio = false;
+    private boolean isVibrator = false;
+
+
+    public ScanFeedbackManager(Context context) {
+        this.context = context;
+        audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+        vibrator = (Vibrator) context.getSystemService(Service.VIBRATOR_SERVICE);
+        player.setOnCompletionListener(this);
+        setVibrateMilliseconds(DEFAUT_MILLISECONDS);
+    }
+
+    /**
+     * 设置音频Assets文件名
+     *
+     * @param fileName 文件名
+     */
+    public void setAudioAssetsFileName(String fileName) {
+        if (fileName == null || fileName.length() <= 0)
+            return;
+        assetsFileName = fileName;
+    }
+
+    /**
+     * 设置音频资源ID
+     *
+     * @param id 资源ID
+     */
+    public void setAudioRawId(int id) {
+        rawId = id;
+    }
+
+    /**
+     * 设置震动时长
+     *
+     * @param milliseconds 时长
+     */
+    public void setVibrateMilliseconds(long milliseconds) {
+        mVibrateMilliseconds = milliseconds;
+    }
+
+    /**
+     * 设置反馈模式
+     *
+     * @param mode 反馈模式
+     */
+    public void setMode(int mode) {
+        switch (mode) {
+            case MODE_AUDIO_VIBRATOR:
+                isAudio = true;
+                isVibrator = true;
+                break;
+            case MODE_AUDIO_ONLY:
+                isAudio = true;
+                isVibrator = false;
+                break;
+            case MODE_VIBRATOR_ONLY:
+                isAudio = false;
+                isVibrator = true;
+                break;
+            default:
+            case MODE_AUTO:
+                isAudio = false;
+                isVibrator = false;
+                break;
+        }
+    }
+
+    /**
+     * 执行扫描反馈
+     */
+    public void performScanFeedback() {
+        if (isAudio && isVibrator) {
+            playSoundEffect();
+            playVibrateEffect();
+        } else if (isAudio) {
+            playSoundEffect();
+        } else if (isVibrator) {
+            playVibrateEffect();
+        } else {
+            switch (audioManager.getRingerMode()) {
+                case AudioManager.RINGER_MODE_SILENT:
+                    break;
+                case AudioManager.RINGER_MODE_VIBRATE:
+                    playVibrateEffect();
+                    break;
+                case AudioManager.RINGER_MODE_NORMAL:
+                    if (audioManager.getStreamVolume(AudioManager.STREAM_NOTIFICATION) != 0) {
+                        playSoundEffect();
+                    }
+                    playVibrateEffect();
+                    break;
+            }
+        }
+    }
+
+    private void playSoundEffect() {
+        AssetFileDescriptor fileDescriptor = getDataSource();
+        try {
+            player.reset();
+            if (fileDescriptor != null) {
+                player.setDataSource(fileDescriptor.getFileDescriptor(),
+                        fileDescriptor.getStartOffset(),
+                        fileDescriptor.getLength());
+            } else {
+                Uri alert = RingtoneManager
+                        .getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
+                player.setDataSource(context, alert);
+            }
+            player.setAudioStreamType(AudioManager.STREAM_NOTIFICATION);
+            player.setVolume(getVolume(AudioManager.STREAM_NOTIFICATION),
+                    getVolume(AudioManager.STREAM_NOTIFICATION));
+            player.setLooping(false);
+            player.prepare();
+            player.start();
+        } catch (Exception e) {
+            e.printStackTrace();
+        } finally {
+            try {
+                if (fileDescriptor != null)
+                    fileDescriptor.close();
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+        }
+    }
+
+    @SuppressLint("MissingPermission")
+    private void playVibrateEffect() {
+        if (Compat.checkSelfPermission(context, Manifest.permission.VIBRATE)
+                == PackageManager.PERMISSION_DENIED)
+            return;
+        if (vibrator != null && vibrator.hasVibrator())
+            vibrator.vibrate(mVibrateMilliseconds);
+    }
+
+    private AssetFileDescriptor getDataSource() {
+        if (assetsFileName != null) {
+            try {
+                return context.getAssets().openFd(assetsFileName);
+            } catch (Exception e) {
+                return null;
+            }
+        }
+        if (rawId != View.NO_ID) {
+            try {
+                return context.getResources().openRawResourceFd(rawId);
+            } catch (Exception e) {
+                return null;
+            }
+        }
+        return null;
+    }
+
+    private float getVolume(@SuppressWarnings("SameParameterValue") int streamType) {
+        int maxVolume = audioManager.getStreamMaxVolume(streamType);
+        int curVolume = audioManager.getStreamVolume(streamType);
+        return curVolume * 1f / maxVolume;
+    }
+
+    @Override
+    public void onCompletion(MediaPlayer mediaPlayer) {
+        mediaPlayer.reset();
+    }
+
+    /**
+     * 销毁
+     */
+    public void release() {
+        if (player.isPlaying())
+            player.stop();
+        player.release();
+    }
+}

+ 652 - 0
zxingscanview/src/main/java/me/yoqi/android/zxingscanview/widget/ZxingForegroundView.java

@@ -0,0 +1,652 @@
+package me.yoqi.android.zxingscanview.widget;
+
+import android.animation.ValueAnimator;
+import android.annotation.SuppressLint;
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.drawable.Animatable;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewParent;
+import android.view.animation.CycleInterpolator;
+import android.view.animation.Interpolator;
+
+import com.google.zxing.Result;
+import com.google.zxing.ResultPoint;
+import com.google.zxing.client.android.compat.Compat;
+
+import java.util.ListIterator;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import me.yoqi.android.zxingscanview.R;
+
+/**
+ * 前景视图
+ *
+ * @author liuyuqi.gov@msn.cn
+ * @date 11/15/2020
+ */
+public class ZxingForegroundView extends View {
+
+    public static final int MODE_OPEN = 0;
+    public static final int MODE_ERROR = 1;
+    private static final long DEFAULT_FRAME_DELAY = 10;//丢失超过30帧就会报警
+    private final OnScanListener scanListener = new OnScanListener();
+    private final OnStateListener stateListener = new OnStateListener();
+    private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+    private final Rect mCoverRect = new Rect();
+    private final ValueAnimator mLoadingAnimator = ValueAnimator.ofFloat(0f, 1f);// 载入动画
+    private final CopyOnWriteArrayList<ResultPointItem> mResultPoints = new CopyOnWriteArrayList<>();
+    private final Interpolator mInterpolator = new CycleInterpolator(1);
+    private Drawable mOpenDrawable;
+    private Drawable mErrorDrawable;
+    private ZxingScanView mScanView;
+    private int mScanViewId;
+    private int mode;
+    private int mCoverColor;
+    private Drawable mScanRectDrawable;
+    private Drawable mScanFlagDrawable;
+    private float mOffset = 0;
+    private boolean mShowResultPoints;
+    private long mResultPointsAnimatorDuration;
+    private int mMaxResultPointsNumber;
+    private int mResultPointsColor;
+    private float mResultPointsSize;
+
+    public ZxingForegroundView(Context context) {
+        super(context);
+        initView(null);
+    }
+
+    public ZxingForegroundView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        initView(attrs);
+    }
+
+    public ZxingForegroundView(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        initView(attrs);
+    }
+
+    @TargetApi(21)
+    public ZxingForegroundView(Context context, AttributeSet attrs, int defStyleAttr,
+                               int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+        initView(attrs);
+    }
+
+    private void initView(AttributeSet attrs) {
+        Drawable open;
+        Drawable error;
+        int scanId = 0;
+        int mode = MODE_OPEN;
+        int coverColor = 0x80000000;
+        Drawable scanRect;
+        Drawable scanFlag;
+        int duration = 2000;
+        int repeatMode = 1;
+        boolean showResultPoints;
+        int resultPointsAnimatorDuration = 500;
+        int maxNumber = 10;
+        int resultPointsColor = 0xc0ffbd21;
+        float resultPointsSize = 6;
+        TypedArray custom = getContext().obtainStyledAttributes(attrs,
+                R.styleable.ZxingForegroundView);
+        open = custom.getDrawable(R.styleable.ZxingForegroundView_zfvOpenDrawable);
+        error = custom.getDrawable(R.styleable.ZxingForegroundView_zfvErrorDrawable);
+        scanId = custom.getResourceId(R.styleable.ZxingForegroundView_zfvZxingScanView, scanId);
+        mode = custom.getInt(R.styleable.ZxingForegroundView_zfvMode, mode);
+        coverColor = custom.getColor(R.styleable.ZxingForegroundView_zfvCoverColor, coverColor);
+        scanRect = custom.getDrawable(R.styleable.ZxingForegroundView_zfvScanRectDrawable);
+        scanFlag = custom.getDrawable(R.styleable.ZxingForegroundView_zfvScanFlagDrawable);
+        duration = custom.getInteger(R.styleable.ZxingForegroundView_zfvFlagAnimatorDuration,
+                duration);
+        repeatMode = custom.getInt(R.styleable.ZxingForegroundView_zfvFlagAnimatorRepeatMode,
+                repeatMode);
+        showResultPoints = custom.getBoolean(R.styleable.ZxingForegroundView_zfvShowResultPoints,
+                false);
+        resultPointsAnimatorDuration = custom.getInteger(
+                R.styleable.ZxingForegroundView_zfvResultPointsAnimatorDuration,
+                resultPointsAnimatorDuration);
+        maxNumber = custom.getInteger(R.styleable.ZxingForegroundView_zfvMaxResultPointsNumber,
+                maxNumber);
+        resultPointsColor = custom.getColor(R.styleable.ZxingForegroundView_zfvResultPointsColor,
+                resultPointsColor);
+        resultPointsSize = custom.getDimension(R.styleable.ZxingForegroundView_zfvResultPointsSize,
+                resultPointsSize);
+        custom.recycle();
+        mScanViewId = scanId;
+        setOpenDrawable(open);
+        setErrorDrawable(error);
+        setMode(mode);
+        setCoverColor(coverColor);
+        setScanRectDrawable(scanRect);
+        setScanFlagDrawable(scanFlag);
+        setFlagAnimatorDuration(duration);
+        if (repeatMode == 1) {
+            setFlagAnimatorRepeatMode(ValueAnimator.RESTART);
+        } else {
+            setFlagAnimatorRepeatMode(ValueAnimator.REVERSE);
+        }
+        setShowResultPoints(showResultPoints);
+        setResultPointsAnimatorDuration(resultPointsAnimatorDuration);
+        setMaxResultPointsNumber(maxNumber);
+        setResultPointsColor(resultPointsColor);
+        setResultPointsSize(resultPointsSize);
+        mLoadingAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+            @Override
+            public void onAnimationUpdate(ValueAnimator animator) {
+                mOffset = (float) animator.getAnimatedValue();
+                if (mShowResultPoints)
+                    editResultPoints();
+                if (mScanFlagDrawable != null)
+                    invalidate();
+            }
+        });
+        mLoadingAnimator.setRepeatCount(ValueAnimator.INFINITE);
+    }
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        bindScanView();
+        start();
+    }
+
+    private void bindScanView() {
+        if (mScanViewId == 0)
+            return;
+        ViewParent parent = getParent();
+        if (parent instanceof View) {
+            View vParent = (View) parent;
+            View child = vParent.findViewById(mScanViewId);
+            if (child instanceof ZxingScanView) {
+                bindScanView((ZxingScanView) child);
+            }
+        }
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        end();
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        super.onDraw(canvas);
+        drawState(canvas);
+    }
+
+    private void drawState(Canvas canvas) {
+        if (mScanView == null)
+            return;
+        if (isInEditMode() || mScanView.isOpen()) {
+            drawScan(canvas);
+            return;
+        }
+        if (mScanView.getErrorCode() == ZxingScanView.ERROR_CODE_NULL) {
+            drawOpen(canvas);
+        } else {
+            switch (mode) {
+                default:
+                case MODE_OPEN:
+                    drawOpen(canvas);
+                    break;
+                case MODE_ERROR:
+                    drawError(canvas);
+                    break;
+            }
+        }
+    }
+
+    private void drawOpen(Canvas canvas) {
+        if (mOpenDrawable == null)
+            return;
+        final int width = mOpenDrawable.getIntrinsicWidth();
+        final int height = mOpenDrawable.getIntrinsicHeight();
+        mOpenDrawable.setBounds(0, 0, width, height);
+        final float xMove = (getWidth() - width) * 0.5f;
+        final float yMove = (getHeight() - height) * 0.5f;
+        canvas.save();
+        canvas.translate(xMove, yMove);
+        mOpenDrawable.draw(canvas);
+        canvas.restore();
+    }
+
+    private void drawError(Canvas canvas) {
+        if (mErrorDrawable == null)
+            return;
+        final int width = mErrorDrawable.getIntrinsicWidth();
+        final int height = mErrorDrawable.getIntrinsicHeight();
+        mErrorDrawable.setBounds(0, 0, width, height);
+        final float xMove = (getWidth() - width) * 0.5f;
+        final float yMove = (getHeight() - height) * 0.5f;
+        canvas.save();
+        canvas.translate(xMove, yMove);
+        mErrorDrawable.draw(canvas);
+        canvas.restore();
+    }
+
+    private void drawScan(Canvas canvas) {
+        final int scanWidth = mScanView.getScanWidth() > 0 ?
+                (mScanView.getScanWidth() > getWidth() ? getWidth() : mScanView.getScanWidth())
+                : getWidth();
+        final int scanHeight = mScanView.getScanHeight() > 0 ?
+                (mScanView.getScanHeight() > getHeight() ? getHeight() : mScanView.getScanHeight())
+                : getHeight();
+        final int coverX = (getWidth() - scanWidth) / 2;
+        final int coverY = (getHeight() - scanHeight) / 2;
+        mPaint.setColor(mCoverColor);
+        if (coverX > 0 && coverY > 0) {
+            mCoverRect.set(0, 0, getWidth(), coverY);
+            canvas.drawRect(mCoverRect, mPaint);
+            mCoverRect.set(0, getHeight() - coverY, getWidth(), getHeight());
+            canvas.drawRect(mCoverRect, mPaint);
+            mCoverRect.set(0, coverY, coverX, getHeight() - coverY);
+            canvas.drawRect(mCoverRect, mPaint);
+            mCoverRect.set(getWidth() - coverX, coverY, getWidth(), getHeight() - coverY);
+            canvas.drawRect(mCoverRect, mPaint);
+        } else if (coverX > 0) {
+            mCoverRect.set(0, 0, coverX, getHeight());
+            canvas.drawRect(mCoverRect, mPaint);
+            mCoverRect.set(getWidth() - coverX, 0, getWidth(), getHeight());
+            canvas.drawRect(mCoverRect, mPaint);
+        } else if (coverY > 0) {
+            mCoverRect.set(0, 0, getWidth(), coverY);
+            canvas.drawRect(mCoverRect, mPaint);
+            mCoverRect.set(0, getHeight() - coverY, getWidth(), getHeight());
+            canvas.drawRect(mCoverRect, mPaint);
+        }
+        if (mScanRectDrawable != null) {
+            mScanRectDrawable.setBounds(0, 0, scanWidth, scanHeight);
+            canvas.save();
+            canvas.translate(coverX, coverY);
+            mScanRectDrawable.draw(canvas);
+            canvas.restore();
+        }
+        drawScanPoint(canvas, scanWidth, scanHeight);
+        drawScanFlag(canvas, mScanFlagDrawable, scanWidth, scanHeight, mOffset);
+    }
+
+    private void drawScanPoint(Canvas canvas, int scanWidth, int scanHeight) {
+        if (!mShowResultPoints)
+            return;
+        final float scaleX = scanWidth / (float) getWidth();
+        final float scaleY = scanHeight / (float) getHeight();
+        final int coverX = (getWidth() - scanWidth) / 2;
+        final int coverY = (getHeight() - scanHeight) / 2;
+        ListIterator iterator = mResultPoints.listIterator();
+        //noinspection WhileLoopReplaceableByForEach
+        while (iterator.hasNext()) {
+            ResultPointItem point = (ResultPointItem) iterator.next();
+            final float offset = mInterpolator.getInterpolation(1 - point.getValue());
+            mPaint.setColor(getColor(mResultPointsColor, offset));
+            // TODO 扫描基准点问题导致XY不对
+            canvas.drawCircle(coverX + (int) (point.point.getX() * scaleX),
+                    coverY + (int) (point.point.getY() * scaleY),
+                    mResultPointsSize * offset, mPaint);
+        }
+    }
+
+    private int getColor(int color, float offset) {
+        int red = Color.red(color);
+        int green = Color.green(color);
+        int blue = Color.blue(color);
+        int alpha = (int) Math.ceil(255 * offset);
+        return Color.argb(alpha, red, green, blue);
+    }
+
+    private void editResultPoints() {
+        ListIterator<ResultPointItem> iterator = mResultPoints.listIterator();
+        //noinspection WhileLoopReplaceableByForEach
+        while (iterator.hasNext()) {
+            ResultPointItem point = iterator.next();
+            if (!point.cutDuration(DEFAULT_FRAME_DELAY))
+                mResultPoints.remove(point);
+        }
+    }
+
+    /**
+     * 绘制扫描标志
+     *
+     * @param canvas     画布
+     * @param drawable   扫描图
+     * @param scanWidth  扫描区域宽
+     * @param scanHeight 扫描区域高
+     * @param offset     动画偏移值
+     */
+    protected void drawScanFlag(Canvas canvas, Drawable drawable, int scanWidth, int scanHeight, float offset) {
+        final int coverX = (getWidth() - scanWidth) / 2;
+        final int coverY = (getHeight() - scanHeight) / 2;
+        if (drawable != null) {
+            drawable.setBounds(0, 0, scanWidth, drawable.getIntrinsicHeight());
+            canvas.save();
+            canvas.translate(coverX, coverY);
+            canvas.translate(0, (scanHeight - drawable.getIntrinsicHeight()) * offset);
+            drawable.draw(canvas);
+            canvas.restore();
+        }
+    }
+
+    @SuppressLint("ClickableViewAccessibility")
+    @Override
+    public boolean onTouchEvent(MotionEvent ev) {
+        switch (ev.getAction()) {
+            case MotionEvent.ACTION_DOWN:
+                final float x = ev.getX();
+                final float y = ev.getY();
+                if (mOpenDrawable != null) {
+                    final int width = mOpenDrawable.getIntrinsicWidth();
+                    final int height = mOpenDrawable.getIntrinsicHeight();
+                    final float offsetX = (getWidth() - width) * 0.5f;
+                    final float offsetY = (getHeight() - height) * 0.5f;
+                    final float dX = x - offsetX;
+                    final float dY = y - offsetY;
+                    if (dX >= 0 && dX <= width && dY >= 0 && dY <= height)
+                        Compat.setHotspot(mOpenDrawable, ev.getX(), ev.getY());
+                }
+                if (mErrorDrawable != null) {
+                    final int width = mErrorDrawable.getIntrinsicWidth();
+                    final int height = mErrorDrawable.getIntrinsicHeight();
+                    final float offsetX = (getWidth() - width) * 0.5f;
+                    final float offsetY = (getHeight() - height) * 0.5f;
+                    final float dX = x - offsetX;
+                    final float dY = y - offsetY;
+                    if (dX >= 0 && dX <= width && dY >= 0 && dY <= height)
+                        Compat.setHotspot(mErrorDrawable, ev.getX(), ev.getY());
+                }
+                break;
+        }
+        return super.onTouchEvent(ev);
+    }
+
+
+    @Override
+    protected void drawableStateChanged() {
+        if (mOpenDrawable != null && mOpenDrawable.isStateful()) {
+            mOpenDrawable.setState(getDrawableState());
+        }
+        if (mErrorDrawable != null && mErrorDrawable.isStateful()) {
+            mErrorDrawable.setState(getDrawableState());
+        }
+        super.drawableStateChanged();
+    }
+
+    @Override
+    protected boolean verifyDrawable(@SuppressWarnings("NullableProblems") Drawable who) {
+        if (mOpenDrawable == null && mErrorDrawable == null)
+            return super.verifyDrawable(who);
+        return who == mOpenDrawable || who == mErrorDrawable || super.verifyDrawable(who);
+    }
+
+    /**
+     * 设置开启图片
+     *
+     * @param drawable 开启图片
+     */
+    public void setOpenDrawable(Drawable drawable) {
+        if (mOpenDrawable == drawable)
+            return;
+        if (mOpenDrawable != null) {
+            if (mOpenDrawable instanceof Animatable)
+                ((Animatable) mOpenDrawable).stop();
+            mOpenDrawable.setCallback(null);
+        }
+        mOpenDrawable = drawable;
+        if (mOpenDrawable != null) {
+            mOpenDrawable.setCallback(this);
+            if (mOpenDrawable instanceof Animatable) {
+                Animatable animatable = (Animatable) mOpenDrawable;
+                if (!animatable.isRunning())
+                    animatable.start();
+            }
+        }
+        invalidate();
+    }
+
+    /**
+     * 设置错误图片
+     *
+     * @param drawable 错误图片
+     */
+    public void setErrorDrawable(Drawable drawable) {
+        if (mErrorDrawable == drawable)
+            return;
+        if (mErrorDrawable != null) {
+            if (mErrorDrawable instanceof Animatable)
+                ((Animatable) mErrorDrawable).stop();
+            mErrorDrawable.setCallback(null);
+        }
+        mErrorDrawable = drawable;
+        if (mErrorDrawable != null) {
+            mErrorDrawable.setCallback(this);
+            if (mErrorDrawable instanceof Animatable) {
+                Animatable animatable = (Animatable) mErrorDrawable;
+                if (!animatable.isRunning())
+                    animatable.start();
+            }
+        }
+        invalidate();
+    }
+
+    /**
+     * 绑定扫描视图
+     *
+     * @param view 扫描视图
+     */
+    public void bindScanView(ZxingScanView view) {
+        if (mScanView == view)
+            return;
+        if (mScanView != null) {
+            mScanView.removeOnScanListener(scanListener);
+            mScanView.removeOnStateListener(stateListener);
+        }
+        mScanView = view;
+        if (mScanView != null) {
+            mScanView.addOnScanListener(scanListener);
+            mScanView.addOnStateListener(stateListener);
+        }
+        invalidate();
+    }
+
+    /**
+     * 设置模式
+     *
+     * @param mode 模式,可用参数:{@link ZxingForegroundView#MODE_OPEN}、
+     *             {@link ZxingForegroundView#MODE_ERROR}
+     */
+    public void setMode(int mode) {
+        if (mode != MODE_OPEN && mode != MODE_ERROR)
+            return;
+        this.mode = mode;
+        invalidate();
+    }
+
+    /**
+     * 设置覆盖颜色
+     *
+     * @param color 颜色
+     */
+    public void setCoverColor(int color) {
+        if (mCoverColor == color)
+            return;
+        mCoverColor = color;
+        invalidate();
+    }
+
+    /**
+     * 设置扫描区域背景图
+     *
+     * @param drawable 背景图
+     */
+    public void setScanRectDrawable(Drawable drawable) {
+        if (mScanRectDrawable == drawable)
+            return;
+        mScanRectDrawable = drawable;
+        invalidate();
+    }
+
+    /**
+     * 设置扫描标志图
+     *
+     * @param drawable 图
+     */
+    public void setScanFlagDrawable(Drawable drawable) {
+        if (mScanFlagDrawable == drawable)
+            return;
+        mScanFlagDrawable = drawable;
+        invalidate();
+    }
+
+    /**
+     * 开始动画
+     */
+    public void start() {
+        mLoadingAnimator.start();
+    }
+
+    /**
+     * 结束动画
+     */
+    public void end() {
+        mLoadingAnimator.end();
+    }
+
+    /**
+     * 设置标志动画时长
+     *
+     * @param duration 时长
+     */
+    public void setFlagAnimatorDuration(long duration) {
+        mLoadingAnimator.setDuration(duration);
+    }
+
+    /**
+     * 设置标志动画循环模式
+     *
+     * @param mode 循环模式,可用参数:{@link ValueAnimator#RESTART}、
+     *             {@link ValueAnimator#REVERSE}
+     */
+    public void setFlagAnimatorRepeatMode(int mode) {
+        mLoadingAnimator.setRepeatMode(mode);
+    }
+
+    /**
+     * 设置是否显示结果点
+     *
+     * @param show 是否显示
+     */
+    public void setShowResultPoints(boolean show) {
+        if (mShowResultPoints == show)
+            return;
+        mShowResultPoints = show;
+        invalidate();
+    }
+
+    /**
+     * 设置结果点动画时长
+     *
+     * @param duration 时长
+     */
+    public void setResultPointsAnimatorDuration(long duration) {
+        mResultPointsAnimatorDuration = duration;
+    }
+
+    /**
+     * 设置最大结果点数目
+     *
+     * @param max 最大数目
+     */
+    public void setMaxResultPointsNumber(int max) {
+        mMaxResultPointsNumber = max;
+    }
+
+    /**
+     * 设置结果点的颜色
+     *
+     * @param color 颜色
+     */
+    public void setResultPointsColor(int color) {
+        mResultPointsColor = color;
+    }
+
+    /**
+     * 设置结果点的大小
+     *
+     * @param size 大小
+     */
+    public void setResultPointsSize(float size) {
+        mResultPointsSize = size;
+    }
+
+    private class OnScanListener implements ZxingScanView.OnScanListener {
+        @Override
+        public void onError(ZxingScanView scanView) {
+            invalidate();
+        }
+
+        @Override
+        public void onResult(ZxingScanView scanView, Result result, Bitmap barcode,
+                             float scaleFactor) {
+            // do nothing
+        }
+    }
+
+    private class OnStateListener implements ZxingScanView.OnStateListener {
+        @Override
+        public void onPrepareOpen(ZxingScanView scanView) {
+            invalidate();
+        }
+
+        @Override
+        public void onOpened(ZxingScanView scanView) {
+            invalidate();
+        }
+
+        @Override
+        public void foundPossibleResultPoint(ZxingScanView scanView, ResultPoint point) {
+            if (!mShowResultPoints)
+                return;
+            if (mResultPoints.size() < mMaxResultPointsNumber)
+                mResultPoints.add(new ResultPointItem(point, mResultPointsAnimatorDuration));
+        }
+
+        @Override
+        public void onPrepareClose(ZxingScanView scanView) {
+            invalidate();
+        }
+
+        @Override
+        public void onClosed(ZxingScanView scanView) {
+            invalidate();
+        }
+    }
+
+    private class ResultPointItem {
+        ResultPoint point;
+        long duration;
+
+        ResultPointItem(ResultPoint point, long duration) {
+            this.point = point;
+            this.duration = duration;
+        }
+
+        boolean cutDuration(@SuppressWarnings("SameParameterValue") long value) {
+            duration -= value;
+            return duration >= 0;
+        }
+
+        float getValue() {
+            return duration / (float) mResultPointsAnimatorDuration;
+        }
+    }
+}

+ 572 - 0
zxingscanview/src/main/java/me/yoqi/android/zxingscanview/widget/ZxingScanView.java

@@ -0,0 +1,572 @@
+package me.yoqi.android.zxingscanview.widget;
+
+
+import android.Manifest;
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.util.AttributeSet;
+import android.view.KeyEvent;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.ViewGroup;
+
+import com.google.zxing.BarcodeFormat;
+import com.google.zxing.DecodeHintType;
+import com.google.zxing.Result;
+import com.google.zxing.ResultPoint;
+import com.google.zxing.client.android.camera.CameraManager;
+import com.google.zxing.client.android.camera.open.OpenCameraInterface;
+import com.google.zxing.client.android.compat.Compat;
+import com.google.zxing.client.android.decode.BarcodeType;
+import com.google.zxing.client.android.decode.ScanHandler;
+import com.google.zxing.client.android.manager.AmbientLightManager;
+import com.google.zxing.client.android.manager.ScanFeedbackManager;
+
+import java.util.ArrayList;
+import java.util.Map;
+
+import me.yoqi.android.zxingscanview.R;
+
+
+/**
+ * @author liuyuqi.gov@msn.cn
+ * @date 11/15/2020
+ */
+public class ZxingScanView extends SurfaceView {
+
+    public static final int ERROR_CODE_NULL = -1;//无错误
+    public static final int ERROR_CODE_0 = 0;//开启摄像头失败
+    public static final int ERROR_CODE_1 = 1;//无开启摄像头权限
+    private CameraManager mCameraManager;
+    private AmbientLightManager mAmbientLightManager;
+    private ScanFeedbackManager mScanFeedbackManager;
+    private int mScanWidth;
+    private int mScanHeight;
+    private int mCameraId;
+    private int mErrorCode = ERROR_CODE_NULL;
+    private ArrayList<OnScanListener> mListeners = new ArrayList<>();
+    private ArrayList<OnStateListener> mStateListeners = new ArrayList<>();
+    private ScanHandler mScanHandler;
+    private OnResultListener resultListener = new OnResultListener();
+    private ResultPointCallback resultPointCallback = new ResultPointCallback();
+    private int mBarcodeType;
+    private String mCharacterSet;
+    private Map<DecodeHintType, ?> mBaseHints;
+
+
+    public ZxingScanView(Context context) {
+        super(context);
+        initView(null);
+    }
+
+    public ZxingScanView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        initView(attrs);
+    }
+
+    public ZxingScanView(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        initView(attrs);
+    }
+
+    @TargetApi(21)
+    @SuppressWarnings("unused")
+    public ZxingScanView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+        initView(attrs);
+    }
+
+    /**
+     * 为结果图添加识别效果
+     *
+     * @param result      扫描结果
+     * @param barcode     结果图片
+     * @param scaleFactor 缩放比
+     * @param color       颜色
+     */
+    @SuppressWarnings("unused")
+    public static void addResultPoints(Result result, Bitmap barcode, float scaleFactor, int color) {
+        ResultPoint[] points = result.getResultPoints();
+        if (points != null && points.length > 0) {
+            Canvas canvas = new Canvas(barcode);
+            Paint paint = new Paint();
+            paint.setColor(color);
+            if (points.length == 2) {
+                paint.setStrokeWidth(4.0f);
+                drawLine(canvas, paint, points[0], points[1], scaleFactor);
+            } else if (points.length == 4 &&
+                    (result.getBarcodeFormat() == BarcodeFormat.UPC_A ||
+                            result.getBarcodeFormat() == BarcodeFormat.EAN_13)) {
+                // Hacky special case -- draw two lines, for the barcode and metadata
+                drawLine(canvas, paint, points[0], points[1], scaleFactor);
+                drawLine(canvas, paint, points[2], points[3], scaleFactor);
+            } else {
+                paint.setStrokeWidth(10.0f);
+                for (ResultPoint point : points) {
+                    if (point != null) {
+                        canvas.drawPoint(scaleFactor * point.getX(), scaleFactor * point.getY(), paint);
+                    }
+                }
+            }
+        }
+    }
+
+    private static void drawLine(Canvas canvas, Paint paint, ResultPoint a, ResultPoint b, float scaleFactor) {
+        if (a != null && b != null) {
+            canvas.drawLine(scaleFactor * a.getX(),
+                    scaleFactor * a.getY(),
+                    scaleFactor * b.getX(),
+                    scaleFactor * b.getY(),
+                    paint);
+        }
+    }
+
+    private void initView(AttributeSet attrs) {
+        int mode = AmbientLightManager.MODE_AUTO;
+        int feedback = ScanFeedbackManager.MODE_AUTO;
+        String fileName;
+        int rawId = NO_ID;
+        int scanWidth = ViewGroup.LayoutParams.MATCH_PARENT;
+        int scanHeight = ViewGroup.LayoutParams.MATCH_PARENT;
+        int cameraId = OpenCameraInterface.NO_REQUESTED_CAMERA;
+        int milliseconds = ScanFeedbackManager.DEFAUT_MILLISECONDS;
+        int barcodeType = BarcodeType.DEFAULT;
+        String characterSet;
+        TypedArray custom = getContext().obtainStyledAttributes(attrs, R.styleable.ZxingScanView);
+        mode = custom.getInt(R.styleable.ZxingScanView_zsvAmbientLight, mode);
+        feedback = custom.getInt(R.styleable.ZxingScanView_zsvFeedback, feedback);
+        fileName = custom.getString(R.styleable.ZxingScanView_zsvAudioAssetsFileName);
+        rawId = custom.getResourceId(R.styleable.ZxingScanView_zsvAudioRaw, rawId);
+        milliseconds = custom.getInteger(R.styleable.ZxingScanView_zsvVibrateMilliseconds,
+                milliseconds);
+        scanWidth = custom.getLayoutDimension(R.styleable.ZxingScanView_zsvScanWidth, scanWidth);
+        scanHeight = custom.getLayoutDimension(R.styleable.ZxingScanView_zsvScanHeight, scanHeight);
+        cameraId = custom.getInteger(R.styleable.ZxingScanView_zsvCameraId, cameraId);
+        barcodeType = custom.getInt(R.styleable.ZxingScanView_zsvBarcode, barcodeType);
+        characterSet = custom.getString(R.styleable.ZxingScanView_zsvCharacterSet);
+        custom.recycle();
+        setScanWidth(scanWidth);
+        setScanHeight(scanHeight);
+        setCameraId(cameraId);
+        setScanBarcodeType(barcodeType);
+        setScanCharacterSet(characterSet);
+        setFocusable(true);
+        setFocusableInTouchMode(true);
+        setKeepScreenOn(true);
+        if (isInEditMode())
+            return;
+        mAmbientLightManager = new AmbientLightManager(getContext(),
+                new AmbientLightCallBack(), mode);
+        mScanFeedbackManager = new ScanFeedbackManager(getContext());
+        setFeedbackMode(feedback);
+        setFeedbackAudioAssetsFileName(fileName);
+        setFeedbackAudioRawId(rawId);
+        setFeedbackVibrateMilliseconds(milliseconds);
+        getHolder().addCallback(new CameraCallBack());
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        mAmbientLightManager.release();
+        mScanFeedbackManager.release();
+    }
+
+    @Override
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        switch (keyCode) {
+            case KeyEvent.KEYCODE_FOCUS:
+            case KeyEvent.KEYCODE_CAMERA:
+                return true;
+            case KeyEvent.KEYCODE_VOLUME_DOWN:
+                setAmbientLightMode(AmbientLightManager.MODE_CLOSE);
+                return true;
+            case KeyEvent.KEYCODE_VOLUME_UP:
+                setAmbientLightMode(AmbientLightManager.MODE_OPEN);
+                return true;
+        }
+        return super.onKeyDown(keyCode, event);
+    }
+
+    private void openDriver(SurfaceHolder surfaceHolder) {
+        notifyListenerPrepareOpen();
+        if (surfaceHolder == null)
+            return;// 已经销毁
+        if (isOpen())
+            return;// 摄像头已经打开
+        if (Compat.checkSelfPermission(getContext(), Manifest.permission.CAMERA)
+                == PackageManager.PERMISSION_DENIED) {
+            mErrorCode = ERROR_CODE_1;
+            notifyListenerError();
+            return;
+        }
+        mCameraManager = new CameraManager(getContext());
+        if (mCameraId != OpenCameraInterface.NO_REQUESTED_CAMERA)
+            mCameraManager.setManualCameraId(mCameraId);
+        final int width = mScanWidth == ViewGroup.LayoutParams.MATCH_PARENT ? getWidth() :
+                (mScanWidth > getWidth() ? getWidth() : mScanWidth);
+        final int height = mScanHeight == ViewGroup.LayoutParams.MATCH_PARENT ? getHeight() :
+                (mScanHeight > getHeight() ? getHeight() : mScanHeight);
+        mCameraManager.setManualFramingRect(width, height);
+        try {
+            mCameraManager.openDriver(surfaceHolder);
+            mCameraManager.startPreview();
+            mScanHandler = new ScanHandler(resultListener, mBarcodeType, mBaseHints,
+                    mCharacterSet, mCameraManager,
+                    resultPointCallback);
+        } catch (Exception e) {
+            mErrorCode = ERROR_CODE_0;
+            notifyListenerError();
+            return;
+        }
+        mAmbientLightManager.resume();
+        notifyListenerOpened();
+    }
+
+    private void closeDriver() {
+        notifyListenerPrepareClose();
+        mErrorCode = ERROR_CODE_NULL;
+        if (mScanHandler != null) {
+            mScanHandler.quitSynchronously();
+            mScanHandler = null;
+        }
+        if (mCameraManager != null)
+            mCameraManager.stopPreview();
+        mAmbientLightManager.pause();
+        if (mCameraManager != null)
+            mCameraManager.closeDriver();
+        mCameraManager = null;
+        notifyListenerClosed();
+    }
+
+    private void notifyListenerError() {
+        for (OnScanListener listener : mListeners) {
+            listener.onError(this);
+        }
+    }
+
+    private void notifyListenerPrepareOpen() {
+        for (OnStateListener listener : mStateListeners) {
+            listener.onPrepareOpen(this);
+        }
+    }
+
+    private void notifyListenerOpened() {
+        for (OnStateListener listener : mStateListeners) {
+            listener.onOpened(this);
+        }
+    }
+
+    private void notifyListenerPrepareClose() {
+        for (OnStateListener listener : mStateListeners) {
+            listener.onPrepareClose(this);
+        }
+    }
+
+    private void notifyListenerClosed() {
+        for (OnStateListener listener : mStateListeners) {
+            listener.onClosed(this);
+        }
+    }
+
+    private void notifyListenerResult(Result result, Bitmap barcode, float scaleFactor) {
+        for (OnScanListener listener : mListeners) {
+            listener.onResult(this, result, barcode, scaleFactor);
+        }
+    }
+
+    private void notifyListenerFoundPossibleResultPoint(ResultPoint point) {
+        for (OnStateListener listener : mStateListeners) {
+            listener.foundPossibleResultPoint(this, point);
+        }
+    }
+
+    public void open() {
+        openDriver(getHolder());
+    }
+
+    /**
+     * 扫描是否已打开
+     *
+     * @return 是否打开
+     */
+    public boolean isOpen() {
+        return mCameraManager != null && mCameraManager.isOpen();
+    }
+
+    /**
+     * 添加扫描监听
+     *
+     * @param listener 监听器
+     */
+    public void addOnScanListener(OnScanListener listener) {
+        if (listener != null)
+            mListeners.add(listener);
+    }
+
+    /**
+     * 移除扫描监听器
+     *
+     * @param listener 监听器
+     * @return 是否移除成功
+     */
+    public boolean removeOnScanListener(OnScanListener listener) {
+        return listener != null && mListeners.remove(listener);
+    }
+
+    /**
+     * 添加状态监听
+     *
+     * @param listener 状态监听
+     */
+    public void addOnStateListener(OnStateListener listener) {
+        if (listener != null)
+            mStateListeners.add(listener);
+    }
+
+    /**
+     * 移除状态监听
+     *
+     * @param listener 状态监听
+     * @return 是否成功移除
+     */
+    public boolean removeOnStateListener(OnStateListener listener) {
+        return listener != null && mStateListeners.remove(listener);
+    }
+
+    /**
+     * 设置背光模式
+     *
+     * @param mode 背光模式,可用参数:{@link AmbientLightManager#MODE_AUTO}、
+     *             {@link AmbientLightManager#MODE_OPEN}、{@link AmbientLightManager#MODE_CLOSE}
+     */
+    public void setAmbientLightMode(int mode) {
+        mAmbientLightManager.setMode(mode);
+    }
+
+    /**
+     * 设置扫描反馈模式
+     *
+     * @param mode 扫描反馈模式,可用参数:{@link ScanFeedbackManager#MODE_AUTO}、
+     *             {@link ScanFeedbackManager#MODE_AUDIO_ONLY}、
+     *             {@link ScanFeedbackManager#MODE_VIBRATOR_ONLY}、
+     *             {@link ScanFeedbackManager#MODE_AUDIO_VIBRATOR}
+     */
+    public void setFeedbackMode(int mode) {
+        mScanFeedbackManager.setMode(mode);
+    }
+
+    /**
+     * 设置音频Assets文件名
+     *
+     * @param fileName 文件名
+     */
+    public void setFeedbackAudioAssetsFileName(String fileName) {
+        mScanFeedbackManager.setAudioAssetsFileName(fileName);
+    }
+
+    /**
+     * 设置音频资源ID
+     *
+     * @param id 资源ID
+     */
+    public void setFeedbackAudioRawId(int id) {
+        mScanFeedbackManager.setAudioRawId(id);
+    }
+
+    /**
+     * 设置震动时长
+     *
+     * @param milliseconds 时长
+     */
+    public void setFeedbackVibrateMilliseconds(long milliseconds) {
+        mScanFeedbackManager.setVibrateMilliseconds(milliseconds);
+    }
+
+    /**
+     * 设置摄像头ID
+     * 下次创建CameraManager时生效
+     *
+     * @param id 摄像头ID
+     */
+    public void setCameraId(int id) {
+        mCameraId = id;
+    }
+
+    /**
+     * 获取错误代码
+     *
+     * @return 错误代码
+     */
+    public int getErrorCode() {
+        return mErrorCode;
+    }
+
+    /**
+     * 获取扫描宽度
+     *
+     * @return 扫描宽度
+     */
+    public int getScanWidth() {
+        return mScanWidth;
+    }
+
+    /**
+     * 设置扫描宽度
+     * 下次创建CameraManager时生效
+     *
+     * @param width 扫描宽度
+     */
+    public void setScanWidth(int width) {
+        mScanWidth = width;
+    }
+
+    /**
+     * 获取扫描高度
+     *
+     * @return 扫描高度
+     */
+    public int getScanHeight() {
+        return mScanHeight;
+    }
+
+    /**
+     * 设置扫描高度
+     * 下次创建CameraManager时生效
+     *
+     * @param height 扫描高度
+     */
+    public void setScanHeight(int height) {
+        mScanHeight = height;
+    }
+
+    /**
+     * 设置扫码类型
+     *
+     * @param type 类型
+     */
+    public void setScanBarcodeType(int type) {
+        mBarcodeType = type;
+    }
+
+    public void setScanCharacterSet(String characterSet) {
+        mCharacterSet = characterSet;
+    }
+
+    @SuppressWarnings("unused")
+    public void setScanBaseHints(Map<DecodeHintType, ?> hints) {
+        mBaseHints = hints;
+    }
+
+    /**
+     * 重新开始扫描
+     */
+    @SuppressWarnings("unused")
+    public void restartScan() {
+        if (mScanHandler != null) {
+            mScanHandler.restartScan();
+        }
+    }
+
+    /**
+     * 一段时间后重新你开始扫描
+     *
+     * @param delay 时间
+     */
+    @SuppressWarnings("unused")
+    public void restartScanDelay(long delay) {
+        if (mScanHandler != null) {
+            mScanHandler.restartScanDelay(delay);
+        }
+    }
+
+    /**
+     * 扫描监听
+     */
+    public interface OnScanListener {
+        /**
+         * 出现错误
+         *
+         * @param scanView ZxingScanView
+         */
+        void onError(ZxingScanView scanView);
+
+        /**
+         * 扫描结果
+         *
+         * @param scanView    ZxingScanView
+         * @param result      结果
+         * @param barcode     图片
+         * @param scaleFactor 缩放比
+         */
+        void onResult(ZxingScanView scanView, Result result, Bitmap barcode, float scaleFactor);
+    }
+
+    /**
+     * 状态监听器
+     */
+    public interface OnStateListener {
+        void onPrepareOpen(ZxingScanView scanView);
+
+        void onOpened(ZxingScanView scanView);
+
+        void foundPossibleResultPoint(ZxingScanView scanView, ResultPoint point);
+
+        void onPrepareClose(ZxingScanView scanView);
+
+        void onClosed(ZxingScanView scanView);
+    }
+
+    private class CameraCallBack implements SurfaceHolder.Callback {
+        @Override
+        public void surfaceCreated(SurfaceHolder surfaceHolder) {
+            openDriver(surfaceHolder);
+        }
+
+        @Override
+        public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
+            if (mCameraManager != null) {
+                final int scanWidth = mScanWidth == ViewGroup.LayoutParams.MATCH_PARENT ? width :
+                        (mScanWidth > width ? width : mScanWidth);
+                final int scanHeight = mScanHeight == ViewGroup.LayoutParams.MATCH_PARENT ? height :
+                        (mScanHeight > height ? height : mScanHeight);
+                mCameraManager.setManualFramingRect(scanWidth, scanHeight);
+            }
+        }
+
+        @Override
+        public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
+            closeDriver();
+        }
+    }
+
+    private class AmbientLightCallBack implements AmbientLightManager.AmbientLightCallBack {
+        @Override
+        public void onChange(boolean on) {
+            if (mCameraManager != null)
+                mCameraManager.setTorch(on);
+        }
+    }
+
+    private class OnResultListener implements ScanHandler.OnResultListener {
+
+        @Override
+        public void onResult(Result result, Bitmap barcode, float scaleFactor) {
+            mScanFeedbackManager.performScanFeedback();
+            notifyListenerResult(result, barcode, scaleFactor);
+        }
+    }
+
+    private class ResultPointCallback implements com.google.zxing.ResultPointCallback {
+
+        @Override
+        public void foundPossibleResultPoint(ResultPoint point) {
+            notifyListenerFoundPossibleResultPoint(point);
+        }
+    }
+}

+ 58 - 0
zxingscanview/src/main/res/values/attrs.xml

@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <declare-styleable name="ZxingScanView">
+        <attr name="zsvAmbientLight" format="enum">
+            <enum name="auto" value="0" />
+            <enum name="open" value="1" />
+            <enum name="close" value="2" />
+        </attr>
+        <attr name="zsvFeedback" format="enum">
+            <enum name="auto" value="0" />
+            <enum name="audio_only" value="1" />
+            <enum name="vibrator_only" value="2" />
+            <enum name="audio_vibrator" value="3" />
+        </attr>
+        <attr name="zsvAudioAssetsFileName" format="string" />
+        <attr name="zsvAudioRaw" format="reference" />
+        <attr name="zsvVibrateMilliseconds" format="integer" />
+        <attr name="zsvScanWidth" format="dimension">
+            <enum name="match_parent" value="-1" />
+        </attr>
+        <attr name="zsvScanHeight" format="dimension">
+            <enum name="match_parent" value="-1" />
+        </attr>
+        <attr name="zsvCameraId" format="integer" />
+        <attr name="zsvBarcode">
+            <flag name="Product_1D" value="1" />
+            <flag name="Industrial_1D" value="2" />
+            <flag name="QR" value="4" />
+            <flag name="Data_Matrix" value="8" />
+            <flag name="Aztec" value="16" />
+            <flag name="PDF417" value="32" />
+        </attr>
+        <attr name="zsvCharacterSet" format="string" />
+    </declare-styleable>
+
+    <declare-styleable name="ZxingForegroundView">
+        <attr name="zfvOpenDrawable" format="reference" />
+        <attr name="zfvErrorDrawable" format="reference" />
+        <attr name="zfvZxingScanView" format="reference" />
+        <attr name="zfvMode" format="enum">
+            <enum name="open" value="0" />
+            <enum name="error" value="1" />
+        </attr>
+        <attr name="zfvCoverColor" format="color" />
+        <attr name="zfvScanRectDrawable" format="reference" />
+        <attr name="zfvScanFlagDrawable" format="reference" />
+        <attr name="zfvFlagAnimatorDuration" format="integer" />
+        <attr name="zfvFlagAnimatorRepeatMode" format="enum">
+            <enum name="Restart" value="1" />
+            <enum name="Reverse" value="2" />
+        </attr>
+        <attr name="zfvShowResultPoints" format="boolean" />
+        <attr name="zfvResultPointsAnimatorDuration" format="integer" />
+        <attr name="zfvMaxResultPointsNumber" format="integer" />
+        <attr name="zfvResultPointsColor" format="color" />
+        <attr name="zfvResultPointsSize" format="dimension" />
+    </declare-styleable>
+</resources>

+ 17 - 0
zxingscanview/src/test/java/me/yoqi/android/zxingscanview/ExampleUnitTest.java

@@ -0,0 +1,17 @@
+package me.yoqi.android.zxingscanview;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
+ */
+public class ExampleUnitTest {
+    @Test
+    public void addition_isCorrect() {
+        assertEquals(4, 2 + 2);
+    }
+}