需求说明
前面几篇文章讲述了Zxing自定义扫描界面,但是没有说明如何扫描二维码和识别二维码图片,通过这篇文章希望给大家一些思路。
修改过程
1.识别二维码图片
首先导入知乎的图片选择器
//图片选择
implementation 'com.zhihu.android:matisse:0.5.1'
在CaptureActivitty中初始化matisse
Matisse.from(CaptureActivity.this)
.choose(MimeType.ofAll())//图片类型
.countable(true)//是否显示选择图片的数字
.maxSelectable(1)//最大图片选择数
//gridExpectedSize(120)getResources().getDimensionPixelSize(R.dimen.grid_expected_size) 显示图片大小
.restrictOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED)//图像选择和预览活动所需的方向
.thumbnailScale(0.85f)//清晰度
.theme(R.style.Matisse_Zhihu)//主题样式
.imageEngine(new MyGlideEngine())//图片加载引擎
.forResult(REQUEST_CODE);
注意matisse使用Glide的是3.7.X的版本如果你的Glide版本为4.X.X,请修改图片加载引擎,按照matisse的GlideEngine重写出一个Glide 4版本的图片加载引擎,代码如下:
public class MyGlideEngine implements ImageEngine {
@Override
public void loadThumbnail(Context context, int resize, Drawable placeholder, ImageView imageView, Uri uri) {
Glide.with(context)
.asBitmap() // some .jpeg files are actually gif
.load(uri)
.override(resize, resize)
.centerCrop()
.into(imageView);
}
@Override
public void loadGifThumbnail(Context context, int resize, Drawable placeholder, ImageView imageView, Uri uri) {
Glide.with(context)
.asBitmap()
.load(uri)
.placeholder(placeholder)
.override(resize, resize)
.centerCrop()
.into(imageView);
}
@Override
public void loadImage(Context context, int resizeX, int resizeY, ImageView imageView, Uri uri) {
Glide.with(context)
.load(uri)
.override(resizeX, resizeY)
.priority(Priority.HIGH)
.into(imageView);
}
@Override
public void loadGifImage(Context context, int resizeX, int resizeY, ImageView imageView, Uri uri) {
Glide.with(context)
.load(uri)
.override(resizeX, resizeY)
.priority(Priority.HIGH)
.into(imageView);
}
@Override
public boolean supportAnimatedGif() {
return true;
}
}
读取图片需要Android权限,不要忘记在6.0以上动态获取
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
tv_picture.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
rxPermissions.request(Manifest.permission.WRITE_EXTERNAL_STORAGE)
.subscribe(granted->{
if (granted){
Matisse.from(CaptureActivity.this)
.choose(MimeType.ofAll())//图片类型
.countable(true)//是否显示选择图片的数字
.maxSelectable(1)//最大图片选择数
//gridExpectedSize(120)getResources().getDimensionPixelSize(R.dimen.grid_expected_size) 显示图片大小
.restrictOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED)//图像选择和预览活动所需的方向
.thumbnailScale(0.85f)//清晰度
.theme(R.style.Matisse_Zhihu)
.imageEngine(new MyGlideEngine())
.forResult(REQUEST_CODE);//请求码
}else{
Toast.makeText(CaptureActivity.this,"未获取相关权限,请稍后再试",Toast.LENGTH_SHORT).show();
}
});
}
});
在onActivityResult获取选中图片的uri,这里使用了Glide的SimpleTarget,作用是选中图片的bitmap对象
@Override
public void onActivityResult(int requestCode, int resultCode, Intent intent) {
if (resultCode == RESULT_OK && requestCode == HISTORY_REQUEST_CODE && historyManager != null) {
int itemNumber = intent.getIntExtra(Intents.History.ITEM_NUMBER, -1);
if (itemNumber >= 0) {
HistoryItem historyItem = historyManager.buildHistoryItem(itemNumber);
decodeOrStoreSavedBitmap(null, historyItem.getResult());
}
}
//获取选中的照片
if (requestCode == REQUEST_CODE && resultCode == RESULT_OK) {
List<Uri> list = Matisse.obtainResult(intent);
Glide.with(this).asBitmap().load(list.get(0)).into(target);
}
}
private SimpleTarget target = new SimpleTarget<Bitmap>() {
@Override
public void onResourceReady(@NonNull Bitmap bitmap, @Nullable Transition<? super Bitmap> transition) {
String s = decodeQRCode(bitmap);
if (s!=null){
runOnUiThread(()-> {
Toast.makeText(CaptureActivity.this,s,Toast.LENGTH_SHORT).show();
});
}
}
};
decodeQRcode(Bitmap bitmap)是解析图片二维码的核心方法,通过调用zxing的BinaryBitmap将图片转换成二进制图片对象,再由QRCodeReader解析出信息
/**
* 解析图片
*
* @param srcBitmap
* @return
*/
public static String decodeQRCode(Bitmap srcBitmap) {
// 解码的参数
Hashtable<DecodeHintType, Object> hints = new Hashtable<>(2);
// 可以解析的编码类型
Vector<BarcodeFormat> decodeFormats = new Vector<>();
if (decodeFormats.isEmpty()) {
decodeFormats.addAll(DecodeFormatManager.QR_CODE_FORMATS);
}
hints.put(DecodeHintType.POSSIBLE_FORMATS, decodeFormats);
hints.put(DecodeHintType.CHARACTER_SET, "UTF-8");
Result result = null;
int width = srcBitmap.getWidth();
int height = srcBitmap.getHeight();
int[] pixels = new int[width * height];
srcBitmap.getPixels(pixels, 0, width, 0, 0, width, height);
//新建一个RGBLuminanceSource对象
RGBLuminanceSource source = new RGBLuminanceSource(width, height, pixels);
//将图片转换成二进制图片
BinaryBitmap binaryBitmap = new BinaryBitmap(new GlobalHistogramBinarizer(source));
QRCodeReader reader = new QRCodeReader();//初始化解析对象
try {
result = reader.decode(binaryBitmap, hints);//开始解析
} catch (NotFoundException | ChecksumException | FormatException e) {
e.printStackTrace();
}
if (result != null) {
return result.getText();
}
return null;
}
最终效果如图
可以看到读取除了图片中的链接
2扫描二维码
当开启CaptureActivity时设置的action为com.google.zxing.client.android.SCAN时,扫描后会将结果返回
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == 0 && resultCode == RESULT_OK) {
if (data != null) {
String content = data.getStringExtra("SCAN_RESULT");
Log.e("result",content);
}
}
}
可能你会问我怎么知道是key是SCAN_RESULT?你可以看一下CaptureActivityHandler中的handlerMessage
@Override
public void handleMessage(Message message) {
if(message.what==R.id.restart_preview){
restartPreviewAndDecode();
}else if (message.what==R.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);
}
activity.handleDecode((Result) message.obj, barcode, scaleFactor);
}else if (message.what==R.id.decode_failed){
// We're decoding as fast as possible, so when one decode fails, start another.
state = State.PREVIEW;
cameraManager.requestPreviewFrame(decodeThread.getHandler(), R.id.decode);
}else if (message.what==R.id.return_scan_result){
activity.setResult(Activity.RESULT_OK, (Intent) message.obj);
activity.finish();
}else if (message.what==R.id.launch_product_query){
String url = (String) message.obj;
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.addFlags(Intents.FLAG_NEW_DOC);
intent.setData(Uri.parse(url));
ResolveInfo resolveInfo =
activity.getPackageManager().resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY);
String browserPackageName = null;
if (resolveInfo != null && resolveInfo.activityInfo != null) {
browserPackageName = resolveInfo.activityInfo.packageName;
Log.d(TAG, "Using browser in package " + browserPackageName);
}
// Needed for default Android browser / Chrome only apparently
if (browserPackageName != null) {
switch (browserPackageName) {
case "com.android.browser":
case "com.android.chrome":
intent.setPackage(browserPackageName);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.putExtra(Browser.EXTRA_APPLICATION_ID, browserPackageName);
break;
}
}
try {
activity.startActivity(intent);
} catch (ActivityNotFoundException ignored) {
Log.w(TAG, "Can't find anything to handle VIEW of URI");
}
}
}
其中message.what==R.id.return_scan_result,在CaptureActivity中查找该id可以找到如下信息:
case NATIVE_APP_INTENT:
// Hand back whatever action they requested - this can be changed to Intents.Scan.ACTION when
// the deprecated intent is retired.
Intent intent = new Intent(getIntent().getAction());
intent.addFlags(Intents.FLAG_NEW_DOC);
intent.putExtra(Intents.Scan.RESULT, rawResult.toString());
intent.putExtra(Intents.Scan.RESULT_FORMAT, rawResult.getBarcodeFormat().toString());
byte[] rawBytes = rawResult.getRawBytes();
if (rawBytes != null && rawBytes.length > 0) {
intent.putExtra(Intents.Scan.RESULT_BYTES, rawBytes);
}
Map<ResultMetadataType, ?> metadata = rawResult.getResultMetadata();
if (metadata != null) {
if (metadata.containsKey(ResultMetadataType.UPC_EAN_EXTENSION)) {
intent.putExtra(Intents.Scan.RESULT_UPC_EAN_EXTENSION,
metadata.get(ResultMetadataType.UPC_EAN_EXTENSION).toString());
}
Number orientation = (Number) metadata.get(ResultMetadataType.ORIENTATION);
if (orientation != null) {
intent.putExtra(Intents.Scan.RESULT_ORIENTATION, orientation.intValue());
}
String ecLevel = (String) metadata.get(ResultMetadataType.ERROR_CORRECTION_LEVEL);
if (ecLevel != null) {
intent.putExtra(Intents.Scan.RESULT_ERROR_CORRECTION_LEVEL, ecLevel);
}
@SuppressWarnings("unchecked")
Iterable<byte[]> byteSegments = (Iterable<byte[]>) metadata.get(ResultMetadataType.BYTE_SEGMENTS);
if (byteSegments != null) {
int i = 0;
for (byte[] byteSegment : byteSegments) {
intent.putExtra(Intents.Scan.RESULT_BYTE_SEGMENTS_PREFIX + i, byteSegment);
i++;
}
}
}
sendReplyMessage(R.id.return_scan_result, intent, resultDurationMS);
break;
注释中说 action 为Intents.Scan.ACTION 时 会发送sendReplyMessage(R.id.return_scan_result, intent, resultDurationMS),返回的结果会封装在key 为SCAN_RESULT;Intents对RESULT 有相关的解释
/**
* If a barcode is found, Barcodes returns {@link android.app.Activity#RESULT_OK} to
* {@link android.app.Activity#onActivityResult(int, int, android.content.Intent)}
* of the app which requested the scan via
* {@link android.app.Activity#startActivityForResult(android.content.Intent, int)}
* The barcodes contents can be retrieved with
* {@link android.content.Intent#getStringExtra(String)}.
* If the user presses Back, the result code will be {@link android.app.Activity#RESULT_CANCELED}.
*/
public static final String RESULT = "SCAN_RESULT";
在handleDecodeExternally中注释掉中的相关代码,该方法的功能是如果获取到二维码则会在viewFinderView中绘制获取到的图片bitmap对象。
private void handleDecodeExternally(Result rawResult, ResultHandler resultHandler, Bitmap barcode) {
if (barcode != null) {
//注释掉
//绘制结果图片
//viewfinderView.drawResultBitmap(barcode);
}
long resultDurationMS;
if (getIntent() == null) {
resultDurationMS = DEFAULT_INTENT_RESULT_DURATION_MS;
} else {
resultDurationMS = getIntent().getLongExtra(Intents.Scan.RESULT_DISPLAY_DURATION_MS,
DEFAULT_INTENT_RESULT_DURATION_MS);
}
if (resultDurationMS > 0) {
String rawResultString = String.valueOf(rawResult);
if (rawResultString.length() > 32) {
rawResultString = rawResultString.substring(0, 32) + " ...";
}
}
maybeSetClipboard(resultHandler);
switch (source) {
case NATIVE_APP_INTENT:
// Hand back whatever action they requested - this can be changed to Intents.Scan.ACTION when
// the deprecated intent is retired.
Intent intent = new Intent(getIntent().getAction());
intent.addFlags(Intents.FLAG_NEW_DOC);
intent.putExtra(Intents.Scan.RESULT, rawResult.toString());
intent.putExtra(Intents.Scan.RESULT_FORMAT, rawResult.getBarcodeFormat().toString());
byte[] rawBytes = rawResult.getRawBytes();
if (rawBytes != null && rawBytes.length > 0) {
intent.putExtra(Intents.Scan.RESULT_BYTES, rawBytes);
}
Map<ResultMetadataType, ?> metadata = rawResult.getResultMetadata();
if (metadata != null) {
if (metadata.containsKey(ResultMetadataType.UPC_EAN_EXTENSION)) {
intent.putExtra(Intents.Scan.RESULT_UPC_EAN_EXTENSION,
metadata.get(ResultMetadataType.UPC_EAN_EXTENSION).toString());
}
Number orientation = (Number) metadata.get(ResultMetadataType.ORIENTATION);
if (orientation != null) {
intent.putExtra(Intents.Scan.RESULT_ORIENTATION, orientation.intValue());
}
String ecLevel = (String) metadata.get(ResultMetadataType.ERROR_CORRECTION_LEVEL);
if (ecLevel != null) {
intent.putExtra(Intents.Scan.RESULT_ERROR_CORRECTION_LEVEL, ecLevel);
}
@SuppressWarnings("unchecked")
Iterable<byte[]> byteSegments = (Iterable<byte[]>) metadata.get(ResultMetadataType.BYTE_SEGMENTS);
if (byteSegments != null) {
int i = 0;
for (byte[] byteSegment : byteSegments) {
intent.putExtra(Intents.Scan.RESULT_BYTE_SEGMENTS_PREFIX + i, byteSegment);
i++;
}
}
}
sendReplyMessage(R.id.return_scan_result, intent, resultDurationMS);
break;
case PRODUCT_SEARCH_LINK:
// Reformulate the URL which triggered us into a query, so that the request goes to the same
// TLD as the scan URL.
int end = sourceUrl.lastIndexOf("/scan");
String productReplyURL = sourceUrl.substring(0, end) + "?q=" +
resultHandler.getDisplayContents() + "&source=zxing";
sendReplyMessage(R.id.launch_product_query, productReplyURL, resultDurationMS);
break;
case ZXING_LINK:
if (scanFromWebPageManager != null && scanFromWebPageManager.isScanFromWebPage()) {
String linkReplyURL = scanFromWebPageManager.buildReplyURL(rawResult, resultHandler);
scanFromWebPageManager = null;
sendReplyMessage(R.id.launch_product_query, linkReplyURL, resultDurationMS);
}
break;
}
}
MainActivity代码如下:
package com.zhqy.zxingdemo;
import android.content.Intent;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.widget.Toast;
import com.google.zxing.client.android.CaptureActivity;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Intent intent=new Intent(this, CaptureActivity.class);
intent.setAction("com.google.zxing.client.android.SCAN");
startActivityForResult(intent,0);
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == 0 && resultCode == RESULT_OK) {
if (data != null) {
String content = data.getStringExtra("SCAN_RESULT");
Toast.makeText(this,content,Toast.LENGTH_SHORT).show();
}
}
}
}
最终结果如下: