Android App 如何快速接入二維碼掃描和生成功能

二維碼又稱QR Code,QR全稱Quick Response,是一種比一維碼更高級的條碼格式,能存儲漢字、數字和圖片等信息。

App開發中,我們常常會遇到二維碼掃描功能和二維碼生成功能的需求。目前,常用的方式是集成zxing這個開源項目的掃碼功能,( 開源項目地址)。下面介紹集成方法。

接入步驟:

1.引入jar包

app build.gradle中加入依賴

dependencies {
 ...
 implementation 'com.google.zxing:core:3.3.0'
}

2.如何生成二維碼

直接上代碼

/**
*
* @param content 字符串內容
* @param width 二維碼寬度
* @param height 二維碼高度
* @param character_set  編碼方式(一般使用UTF-8)
* @param error_correction_level    容錯率 L:7% M:15% Q:25% H:35%
* @param margin 空白邊距(二維碼與邊框的空白區域)
* @param color_black  黑色色塊
* @param color_white  白色色塊
* @return BitMap
*/
public static Bitmap createQRCodeBitmap(String content, int width,int height,
  String character_set,String error_correction_level, String margin,int color_black, int color_white) {
  // 字符串內容判空
  if (TextUtils.isEmpty(content)) {
      return null;
  }
  // 寬和高>=0
  if (width < 0 || height < 0) {
      return null;
  }

  try {
    /** 1.設置二維碼相關配置 */
    Hashtable<EncodeHintType, String> hints = new Hashtable<>();
    // 字符轉碼格式設置
    if (!TextUtils.isEmpty(character_set)) {
      hints.put(EncodeHintType.CHARACTER_SET, character_set);
    }

     // 容錯率設置
    if (!TextUtils.isEmpty(error_correction_level)) {
      hints.put(EncodeHintType.ERROR_CORRECTION, error_correction_level);
    }
    // 空白邊距設置

    if (!TextUtils.isEmpty(margin)) {
      hints.put(EncodeHintType.MARGIN, margin);
    }

    /** 2.將配置參數傳入到QRCodeWriter的encode方法生成BitMatrix(位矩陣)對象 */
    BitMatrix bitMatrix = new QRCodeWriter().encode(content, BarcodeFormat.QR_CODE, width, height, hints);
    /** 3.創建像素數組,并根據BitMatrix(位矩陣)對象為數組元素賦顏色值 */
    int[] pixels = new int[width * height];
    for (int y = 0; y < height; y++) {
      for (int x = 0; x < width; x++) {
        //bitMatrix.get(x,y)方法返回true是黑色色塊,false是白色色塊
        if (bitMatrix.get(x, y)) {
          pixels[y * width + x] = color_black;//黑色色塊像素設置
        } else {
          pixels[y * width + x] = color_white;// 白色色塊像素設置
        }
      }
    }

     /** 4.創建Bitmap對象,根據像素數組設置Bitmap每個像素點的顏色值,并返回Bitmap對象 */
    Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
    bitmap.setPixels(pixels, 0, width, 0, 0, width, height);
    return bitmap;
  } catch (WriterException e) {
    e.printStackTrace();
    return null;
  }
}
主要步驟:

設置二維碼相關配置,包括傳入的二維碼長寬、容錯率和空白邊距大小。
將配置參數傳入到QRCodeWriter的encode方法并生成BitMatrix(位矩陣)對象。
位矩陣對象中bitMatrix.get(x, y)方法可判斷是黑色色塊還是白色色塊,根據不同色塊給數組元素賦我們傳入的顏色值。
根據像素數組每個像素點的顏色值創建Bitmap對象并返回,即二維碼。

主要參數介紹:

character_set
字符集/字符轉碼格式,通常使用UTF-8,格式不對可能導致亂碼。傳null時,默認使用 “ISO-8859-1”

error_correction_level
容錯率,也就是糾錯水平,二維碼破損一部分也能掃碼就歸功于容錯率,容錯率可分為L、 M、 Q、 H四個等級,其分別占比為:L:7% M:15% Q:25% H:35%。傳null時,默認使用 “L”,當然容錯率越高,二維碼能存儲的內容也隨之變小。

margin
二維碼和邊框的空白區域寬度

color_black、color_white
黑色色塊和白素色塊,我們常見的二維碼一般是黑白兩色的,也就是這兩個色塊。

3.如何識別圖像中的二維碼

識別圖像中的二維碼代碼:

/**
 * 掃描二維碼圖片的方法
 * @param path
 * @return
 */
public Result scanningImage(String path)   
  if(TextUtils.isEmpty(path)){
     return null;
  }

  Hashtable<DecodeHintType, String> hints = new Hashtable<>();
  hints.put(DecodeHintType.CHARACTER_SET, "UTF8"); //設置二維碼內容的編碼
  BitmapFactory.Options options = new BitmapFactory.Options();
  options.inJustDecodeBounds = true; // 先獲取原大小
  scanBitmap = BitmapFactory.decodeFile(path, options);
  options.inJustDecodeBounds = false; // 獲取新的大小
  int sampleSize = (int) (options.outHeight / (float) 200);

  if (sampleSize <= 0)
    sampleSize = 1;

  options.inSampleSize = sampleSize;
  scanBitmap = BitmapFactory.decodeFile(path, options);
  RGBLuminanceSource source = new RGBLuminanceSource(scanBitmap);
  BinaryBitmap bitmap1 = new BinaryBitmap(new HybridBinarizer(source));
  QRCodeReader reader = new QRCodeReader();
  
  try {
    return reader.decode(bitmap1, hints);
  } catch (NotFoundException e) {
    e.printStackTrace();
  } catch (ChecksumException e) {
    e.printStackTrace();
  } catch (FormatException e) {
    e.printStackTrace();
  }
  return null;
}

4.如何識別攝像頭掃描的二維碼

實現原理,在攝像頭的PreviewCallback的回調函數中,可以直接獲取攝像頭捕獲二進制YUV數據,將數據轉換為BinaryBitmap對象。

將YUV數據轉換為BinaryBitmap對象的方法:

首先引入類 PlanarYUVLuminanceSource, 代碼如下:

import android.graphics.Bitmap;
import com.google.zxing.LuminanceSource;
/**
 * This object extends LuminanceSource around an array of YUV data returned from the camera driver,
 * with the option to crop to a rectangle within the full data. This can be used to exclude
 * superfluous pixels around the perimeter and speed up decoding.
 *
 * It works for any pixel format where the Y channel is planar and appears first, including
 * YCbCr_420_SP and YCbCr_422_SP.
 *
 * @author dswitkin@google.com (Daniel Switkin)
 */
public final class PlanarYUVLuminanceSource extends LuminanceSource {
  private final byte[] yuvData;
  private final int dataWidth;
  private final int dataHeight;
  private final int left;
  private final int top;
  public PlanarYUVLuminanceSource(byte[] yuvData, int dataWidth, int dataHeight, int left, int top,int width, int height) {
    super(width, height);
    if (left + width > dataWidth || top + height > dataHeight) {
      throw new IllegalArgumentException("Crop rectangle does not fit within image data.");
    }
    this.yuvData = yuvData;
    this.dataWidth = dataWidth;
    this.dataHeight = dataHeight;
    this.left = left;
    this.top = top;
  }

  @Override
  public byte[] getRow(int y, byte[] row) {
    if (y < 0 || y >= getHeight()) {
       throw new IllegalArgumentException("Requested row is outside the image: " + y);
    }
    int width = getWidth();
    if (row == null || row.length < width) {
      row = new byte[width];
    }
    int offset = (y + top) * dataWidth + left;
    System.arraycopy(yuvData, offset, row, 0, width);
    return row;
  }

  @Override
  public byte[] getMatrix() {
    int width = getWidth();
    int height = getHeight();
    // If the caller asks for the entire underlying image, save the copy and give them the
    // original data. The docs specifically warn that result.length must be ignored.
    if (width == dataWidth && height == dataHeight) {
      return yuvData;
    }
    int area = width * height;
    byte[] matrix = new byte[area];
    int inputOffset = top * dataWidth + left;
    // If the width matches the full width of the underlying data, perform a single copy.
    if (width == dataWidth) {
      System.arraycopy(yuvData, inputOffset, matrix, 0, area);
      return matrix;
    }
    // Otherwise copy one cropped row at a time.
    byte[] yuv = yuvData;
    for (int y = 0; y < height; y++) {
      int outputOffset = y * width;
      System.arraycopy(yuv, inputOffset, matrix, outputOffset, width);
      inputOffset += dataWidth;
    }
    return matrix;
  }
  @Override
  public boolean isCropSupported() {
    return true;
  }
  public int getDataWidth() {
    return dataWidth;
  }
  public int getDataHeight() {
    return dataHeight;
  }
  public Bitmap renderCroppedGreyscaleBitmap() {
    int width = getWidth();
    int height = getHeight();
    int[] pixels = new int[width * height];
    byte[] yuv = yuvData;
    int inputOffset = top * dataWidth + left;
    for (int y = 0; y < height; y++) {
      int outputOffset = y * width;
      for (int x = 0; x < width; x++) {
        int grey = yuv[inputOffset + x] & 0xff;
        pixels[outputOffset + x] = 0xFF000000 | (grey * 0x00010101);
      }
      inputOffset += dataWidth;
    }
    Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
    bitmap.setPixels(pixels, 0, width, 0, 0, width, height);
    return bitmap;
  }
}

轉換數據代碼如下:

  BinaryBitmap createBinaryBitmap(byte[] data, int width, int height) {
    byte[] rotatedData = new byte[data.length];
    for (int y = 0; y < height; y++) {
      for (int x = 0; x < width; x++) {
        rotatedData[x * height + height - y - 1] = data[x + y * width];
      }
    }
    int tmp = width; // Here we are swapping, that's the difference to #11
    width = height;
    height = tmp;
    PlanarYUVLuminanceSource source = CameraManager.get().buildLuminanceSource(rotatedData, width, height);
    BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
    return bitmap;
}

識別代碼如下:

 MultiFormatReader multiFormatReader = new MultiFormatReader();
 multiFormatReader.setHints(hints);
 Result rawResult;
 try {
    rawResult = multiFormatReader.decodeWithState(bitmap);
    Log.d(TAG, "Found barcode:\n" + rawResult.toString());
  } catch (ReaderException re) {
    // continue
  } finally {
    multiFormatReader.reset();
  }

5.快速集成二維碼掃描功能方法

您還可以直接集成前人寫好的Demo中的代碼,引入二維碼掃描和生成的全部功能,最常用的Demo為QrCodeScan-master,下面介紹集成方法。

QrCodeScan-master下載地址

(1) 下載demo,拷貝demo中的com.google.zxing下的5個包和com.utils包到自己的項目中。

qrcodescan-zxing-pkg.png

(2) 拷貝項目資源中的activity_scanner.xml和toolbar_scanner.xml。
(3) 拷貝項目資源中目錄中的raw文件夾到本項目中,raw文件夾下的beep.ogg是掃描成功時的提示音。
(4) 拷貝或合并資源中的attrs.xml、colors.xml和ids.xml這三個文件。

qrcodescan-zxing-res.png

(5) app build.gradle中加入依賴

dependencies {
 ...
 implementation 'com.google.zxing:core:3.3.0'
}

(6) 修改R文件引入路徑,為本項目的R文件引用地址,需要修改的文件有以下4個文件
com.google.zxing.activity.CaptureActivity
com.google.zxing.decoding.CaptureActivityHandler
com.google.zxing.decoding.DecodeHandler
com.google.zxing.view.ViewfinderView

(7) 配置權限
在 AndroidManifest.xml里增加權限申請代碼:

 <uses-permission android:name="android.permission.INTERNET" /> <!-- 網絡權限 -->
 <uses-permission android:name="android.permission.VIBRATE" /> <!-- 震動權限 -->
 <uses-permission android:name="android.permission.CAMERA" /> <!-- 攝像頭權限 -->
 <uses-feature android:name="android.hardware.camera.autofocus" /> <!-- 自動聚焦權限 -->

(8) 在AndroidManifest.xml里增加攝像頭掃描Activity的配置代碼

 <activity android:name="com.google.zxing.activity.CaptureActivity"
 android:theme="@style/Theme.AppCompat.DayNight.NoActionBar"/>

(9) 完成以上步驟后,通過調用CaptureActivity就可以實現掃碼功能,可參考MainActivity中的代碼
打開二維碼掃描界面代碼:

 if(CommonUtil.isCameraCanUse()){
   Intent intent = new Intent(MainActivity.this, CaptureActivity.class);
   startActivityForResult(intent, REQUEST_CODE);
 }else{
   Toast.makeText(this,"請打開此應用的攝像頭權限!",Toast.LENGTH_SHORT).show();
 }

掃描結果回調代碼:

 @Override
 protected void onActivityResult(int requestCode, int resultCode, Intent data) {
   super.onActivityResult(requestCode, resultCode, data);
   //掃描結果回調
   if (resultCode == RESULT_OK) { //RESULT_OK = -1
     Bundle bundle = data.getExtras();
     String scanResult = bundle.getString("qr_scan_result");
     //將掃描出的信息顯示出來
     qrCodeText.setText(scanResult);
  }
}

二維碼生成代碼:

 try {
   //獲取輸入的文本信息
   String str = text.getText().toString().trim();
   if(str != null && !"".equals(str.trim())) {
     //根據輸入的文本生成對應的二維碼并且顯示出來
     Bitmap mBitmap = EncodingHandler.createQRCode(text.getText().toString(), 500);
     if(mBitmap != null){
       Toast.makeText(this,"二維碼生成成功!",Toast.LENGTH_SHORT).show();
       qrCode.setImageBitmap(mBitmap);
     }
   }else{
   Toast.makeText(this,"文本信息不能為空!",Toast.LENGTH_SHORT).show();
   }
 } catch (WriterException e) {
   e.printStackTrace();
 }
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容