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

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

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

接入步驟:

1.引入jar包

app build.gradle中加入依賴

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

2.如何生成二維碼

直接上代碼

/**
*
* @param content 字符串內(nèi)容
* @param width 二維碼寬度
* @param height 二維碼高度
* @param character_set  編碼方式(一般使用UTF-8)
* @param error_correction_level    容錯率 L:7% M:15% Q:25% H:35%
* @param margin 空白邊距(二維碼與邊框的空白區(qū)域)
* @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) {
  // 字符串內(nèi)容判空
  if (TextUtils.isEmpty(content)) {
      return null;
  }
  // 寬和高>=0
  if (width < 0 || height < 0) {
      return null;
  }

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

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

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

    /** 2.將配置參數(shù)傳入到QRCodeWriter的encode方法生成BitMatrix(位矩陣)對象 */
    BitMatrix bitMatrix = new QRCodeWriter().encode(content, BarcodeFormat.QR_CODE, width, height, hints);
    /** 3.創(chuàng)建像素數(shù)組,并根據(jù)BitMatrix(位矩陣)對象為數(shù)組元素賦顏色值 */
    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;//黑色色塊像素設(shè)置
        } else {
          pixels[y * width + x] = color_white;// 白色色塊像素設(shè)置
        }
      }
    }

     /** 4.創(chuàng)建Bitmap對象,根據(jù)像素數(shù)組設(shè)置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;
  }
}
主要步驟:

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

主要參數(shù)介紹:

character_set
字符集/字符轉(zhuǎn)碼格式,通常使用UTF-8,格式不對可能導(dǎo)致亂碼。傳null時,默認(rèn)使用 “ISO-8859-1”

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

margin
二維碼和邊框的空白區(qū)域?qū)挾?/p>

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"); //設(shè)置二維碼內(nèi)容的編碼
  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.如何識別攝像頭掃描的二維碼

實現(xiàn)原理,在攝像頭的PreviewCallback的回調(diào)函數(shù)中,可以直接獲取攝像頭捕獲二進(jìn)制YUV數(shù)據(jù),將數(shù)據(jù)轉(zhuǎn)換為BinaryBitmap對象。

將YUV數(shù)據(jù)轉(zhuǎn)換為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;
  }
}

轉(zhuǎn)換數(shù)據(jù)代碼如下:

  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) 配置權(quán)限
在 AndroidManifest.xml里增加權(quán)限申請代碼:

 <uses-permission android:name="android.permission.INTERNET" /> <!-- 網(wǎng)絡(luò)權(quán)限 -->
 <uses-permission android:name="android.permission.VIBRATE" /> <!-- 震動權(quán)限 -->
 <uses-permission android:name="android.permission.CAMERA" /> <!-- 攝像頭權(quán)限 -->
 <uses-feature android:name="android.hardware.camera.autofocus" /> <!-- 自動聚焦權(quán)限 -->

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

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

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

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

掃描結(jié)果回調(diào)代碼:

 @Override
 protected void onActivityResult(int requestCode, int resultCode, Intent data) {
   super.onActivityResult(requestCode, resultCode, data);
   //掃描結(jié)果回調(diào)
   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())) {
     //根據(jù)輸入的文本生成對應(yīng)的二維碼并且顯示出來
     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();
 }
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,119評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,382評論 3 415
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,038評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,853評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 71,616評論 6 408
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,112評論 1 323
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,192評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,355評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,869評論 1 334
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,727評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,928評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,467評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,165評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,570評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,813評論 1 282
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,585評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 47,892評論 2 372

推薦閱讀更多精彩內(nèi)容