二維碼又稱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包到自己的項目中。
(2) 拷貝項目資源中的activity_scanner.xml和toolbar_scanner.xml。
(3) 拷貝項目資源中目錄中的raw文件夾到本項目中,raw文件夾下的beep.ogg是掃描成功時的提示音。
(4) 拷貝或合并資源中的attrs.xml、colors.xml和ids.xml這三個文件。
(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();
}