天天看點

Flutter 二維碼掃描插件前言效果示範一、Zxing是什麼?二、插件Android部分三、插件Flutter部分總結

文章目錄

  • 前言
  • 效果示範
  • 一、Zxing是什麼?
  • 二、插件Android部分
    • 1. 建立Flutter插件
    • 2. 引入Zxing依賴庫
    • 3. 權限配置
    • 4. ZXingLite代碼引入
    • 5. 編寫QrScannerPlugin
  • 三、插件Flutter部分
  • 總結
    • Github位址

前言

在Dart Packages找了一圈二維碼掃描插件,發現幾個問題:

1. 擴充性低,自己想定制一下掃描框的樣式,改起來很麻煩;

2. 攝像頭不能拉進,導緻比較小的二維碼識别不了。

于是,在GitHub上找了一下Android的二維碼掃描插件(我的項目裡面隻針對Android裝置),ZXingLite比較符合我的要求,識别率高,代碼可讀性高,友善自己改寫。在此,感謝作者無私開源。

效果示範

二維碼掃描 生成二維碼
Flutter 二維碼掃描插件前言效果示範一、Zxing是什麼?二、插件Android部分三、插件Flutter部分總結
Flutter 二維碼掃描插件前言效果示範一、Zxing是什麼?二、插件Android部分三、插件Flutter部分總結

一、Zxing是什麼?

Zxing(“zebra crossing”)是Google開源的,适用于Java、Android的條形碼/二維碼掃描庫。

二、插件Android部分

1. 建立Flutter插件

注意不要使用Kotlin,使用java進行Android部分代碼開發。當然你也可以自己用Kotlin改寫。

Flutter 二維碼掃描插件前言效果示範一、Zxing是什麼?二、插件Android部分三、插件Flutter部分總結

2. 引入Zxing依賴庫

修改插件的Android目錄下的build.gradle:

android {
    compileSdkVersion 29

    defaultConfig {
        minSdkVersion 21     // camera2最低版本要求
    }
    lintOptions {
        disable 'InvalidPackage'
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}

dependencies {
    implementation 'androidx.appcompat:appcompat:1.1.0'
	// zxing
    implementation 'com.google.zxing:core:3.4.1'
	// camera2
    implementation 'androidx.camera:camera-camera2:1.0.0-rc01'
    implementation 'androidx.camera:camera-lifecycle:1.0.0-rc01'
    implementation 'androidx.camera:camera-view:1.0.0-alpha19'
}
           

3. 權限配置

修改插件的Android目錄下的src/main/AndroidManifest.xml:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
  package="com.vincent.qr_scanner">

    <uses-permission android:name="android.permission.CAMERA"/>
    <uses-permission android:name="android.permission.VIBRATE"/>
    <uses-permission android:name="android.permission.FLASHLIGHT"/>

    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

    <application>
        <activity
            android:name="com.vincent.qr_scanner.CaptureActivity"
            android:screenOrientation="portrait"
            android:theme="@style/CaptureTheme"/>
    </application>

</manifest>
           

4. ZXingLite代碼引入

兩種方式處理:

  1. 通過gradle依賴引入
//AndroidX 版本
implementation 'com.king.zxing:zxing-lite:1.1.9-androidx'

//Android 版本
implementation 'com.king.zxing:zxing-lite:1.1.9'
           
  1. 直接複制其libs下面的代碼。

如果使用第一種方案,gradle配置中,隻需要設定minSdkVersion為21就可以了。JDK和dependencies 都不用設定。

我采用的是第二種方案,友善自己做一些擴充。

5. 編寫QrScannerPlugin

需要注意的幾個點:

  1. 為了擷取到Activity執行個體,該類需要實作ActivityAware相關方法
@Override
  public void onAttachedToActivity(@NonNull ActivityPluginBinding binding) {
  	// 擷取activity執行個體
    this.activity = binding.getActivity();
    // 設定Activity傳回回調
    binding.addActivityResultListener(this);
  }
           
  1. 為了接收掃描傳回結果,該類需要實作ActivityResultListener相關方法
@Override
public boolean onActivityResult(int requestCode, int resultCode, Intent data) {
  if (resultCode == Activity.RESULT_OK && data != null) {
    if (requestCode == REQUEST_CODE) {
  	  // 掃描傳回的結果
      String result = CameraScan.parseScanResult(data);
      this.result.success(result);
      return true;
    } else if (requestCode == REQUEST_IMAGE) {
  	  // 選擇相冊後的結果
      final String path = UriUtils.getImagePath(activity.getApplicationContext(), data);
      String code = CodeReader.parseCode(path);
      result.success(code);
      return true;
    }
  }
  return false;
}
           
  1. 實作onMethodCall方法
@Override
public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) {
  switch (call.method) {
    case "scan":
      Intent codeIntent = new Intent(activity, CaptureActivity.class);
      activity.startActivityForResult(codeIntent, REQUEST_CODE);
      this.result = result;
      break;
    case "pickImage":
      if(!PermissionUtils.checkPermission(activity.getApplicationContext(), Manifest.permission.READ_EXTERNAL_STORAGE)){
        PermissionUtils.requestPermission(activity, Manifest.permission.READ_EXTERNAL_STORAGE, 1);
        result.success(null);
        return;
      }
      Intent imgIntent = new Intent();
      imgIntent.setAction(Intent.ACTION_PICK);
      imgIntent.setType("image/*");
      activity.startActivityForResult(imgIntent, REQUEST_IMAGE);
      this.result = result;
      break;
    case "scanPath":
      if(!PermissionUtils.checkPermission(activity.getApplicationContext(), Manifest.permission.READ_EXTERNAL_STORAGE)){
        PermissionUtils.requestPermission(activity, Manifest.permission.READ_EXTERNAL_STORAGE, 1);
        result.success(null);
        return;
      }
      String path = call.argument("path");
      result.success(CodeReader.parseCode(path));
      break;
    case "scanBitmap":
      byte[] bytes = call.argument("bytes");
      Bitmap bitmap = BitmapFactory.decodeByteArray(bytes , 0, bytes != null ? bytes.length : 0);
      result.success(CodeReader.parseCode(bitmap));
      break;
    case "createQRCode":
      String qrCode = call.argument("code");
      int qrWidth = call.argument("width");
      long qrColor =  call.argument("color");
      Bitmap qrBt = CodeCreator.createQRCode(qrCode, qrWidth, (int) qrColor);
      if (qrBt != null) {
        ByteArrayOutputStream qrBo = new ByteArrayOutputStream();
        qrBt.compress(Bitmap.CompressFormat.JPEG, 90, qrBo);
        result.success(qrBo.toByteArray());
        return;
      }
      result.success(null);
      break;
    case "createBarCode":
      String barCode = call.argument("code");
      int barWidth = call.argument("width");
      int barHeight = call.argument("height");
      boolean showText = call.argument("showText");
      int fontSize = call.argument("fontSize");
      long barColor =  call.argument("color");
      Bitmap barBt = CodeCreator.createBarCode(barCode, BarcodeFormat.CODE_128, barWidth, barHeight, null, showText, fontSize, (int) barColor);
      if (barBt != null) {
        ByteArrayOutputStream barBo = new ByteArrayOutputStream();
        barBt.compress(Bitmap.CompressFormat.JPEG, 90, barBo);
        result.success(barBo.toByteArray());
        return;
      }
      result.success(null);
      break;
    default:
      result.notImplemented();
  }
}
           

三、插件Flutter部分

class QrScanner {
  static const MethodChannel _channel =
      const MethodChannel('vincent/qr_scanner');

  /// 開啟掃描
  static Future<String> scan() async {
    return await _channel.invokeMethod('scan');
  }

  /// 選擇圖檔識别
  static Future<String> pickImage() async {
    return await _channel.invokeMethod('pickImage');
  }

  /// 識别圖檔檔案位址
  static Future<String> scanPath(String imgPath) async {
    assert(imgPath != null && File(imgPath).existsSync());
    return await _channel.invokeMethod('scanPath', {'path': imgPath});
  }

  /// 識别圖檔資料流
  static Future<String> scanBitmap(Uint8List uint8list) async {
    assert(uint8list != null && uint8list.isNotEmpty);
    return await _channel.invokeMethod('scanBitmap', {'bytes' : uint8list});
  }

  /// 生成二維碼
  /// - [code] 碼
  /// - [width] 二維碼寬高
  /// - [color] 16進制顔色
  static Future<Uint8List> createQRCode(String code, {
    int width = 200,
    int color = 0xFF000000
  }) async {
    return await _channel.invokeMethod('createQRCode', {
      'code': code,
      'width': width,
      'color': color
    });
  }

  /// 生成條形碼
  /// - [code] 碼
  /// - [width] 條形碼的寬
  /// - [height] 條形碼的高
  /// - [showText] 是否顯示文字
  /// - [fontSize] 文字大小
  /// - [color] 16進制顔色
  static Future<Uint8List> createBarCode(String code, {
    int width = 200,
    int height = 200,
    bool showText = false,
    int fontSize = 14,
    int color = 0xFF000000
  }) async {
    return await _channel.invokeMethod('createBarCode', {
      'code': code,
      'width': width,
      'height': height,
      'showText': showText,
      'fontSize': fontSize,
      'color': color
    });
  }
}
           

總結

Github位址

qr_scanner,README.MD檔案裡面詳細的說明插件使用方式