原文地址:/p/ee2ee7677831
前言
现在的应用中二维码扫描已经成为一个应用必不可少的功能,现在大部分Android二维码扫描都是基于zxing和Zbar,这文章就来介绍一下基于zxing的二维码扫描。先看下效果图
虽然现在的关于二维码的文章有很多,但是很多都是交我们怎么使用,在使用的时候我也遇到了很多问题,比如。UI界面太丑,没有用的文件太多,扫描太慢,版本太老。本Demo使用的是3.x的是比较新的版本。我用的小米2A(api=19)亲测在正常情况下扫描时间和QQ微信差不多,我的界面是类防QQ的也是比较美观的。那么我们看看如何使用和文件的作用。
zxing
zxing官网这是zxing官网。想了解更多的可以去官网,里面有文档不过在我看来。第三方库的使用我们没有必要完全了解,整体上我们了解所需要功能即可,这样减少了学习时间。
为什么选择zxing
google的开源项目,高可定制性可以识别多种码,不仅仅是二维码不依赖第三方库,使用起来简单
zxing的使用
zixng JAR
我们可以去官网,如果是Android Studio的话也可以在线搜索zxing jar,导入完成后别忘了ADD library。
res文件
drawable
文件中主要放的是一些我们显示界面的图片和一些点击按钮的background
layout
activity_qrcode_capture_layout.xml是zxing扫描的主界面,另外两个布局就是我们在开始图片中看到的,一个头部,一个脚部的布局
colors,dis,strings,styles,raw,xml
这些是zxing中一些类的资源文件和我们自定义布局的一些资源文件,不导入会报错,raw是我们扫描完成后的音效,我们也可以根据需求改成自己的音效(但是要注意的是文件格式和名字尽量要相同,避免出错和资源找不到),xml就是zxing用到的资源文件,我们直接复制过来就行。
PS:如果你是从官网拷贝,那么你自需要拷贝和我一样的就行。我这里只是多了一些drawwable中的布局图片和头部脚部2个布局
关键类
app
CaptureActivity 主要是我们的扫码界面,在这里我们引入我们自己的头部脚部布局,并给控件点击事件,在这个类中我们重点看这几个方法:
/*** 闪光灯点击事件*/private OnClickListener click = new OnClickListener() {@Overridepublic void onClick(View v) {int id = v.getId();if (id == R.id.button_back) {//返回按钮finish();} else if (id == R.id.flash_btn) {//打开关闭闪光灯if (!isFlash) {CameraManager.get().turnLightOn();} else {CameraManager.get().turnLightOff();}isFlash = !isFlash;} else if (id == R.id.photo_btn) {//扫描二维码图片// 打开手机中的相册Intent innerIntent = new Intent(Intent.ACTION_GET_CONTENT); // "android.intent.action.GET_CONTENT"innerIntent.setType("image/*");//封装intentIntent wrapperIntent = Intent.createChooser(innerIntent, "选择二维码图片");startActivityForResult(wrapperIntent, REQUEST_CODE);} else if (id == R.id.qrcode_btn) {// 跳转到生成二维码页面Bitmap b = createQRCode();Intent intent = getIntent();intent.putExtra("QR_CODE", b);setResult(200, intent);finish();}}};
所有扫码界面的点击事件都在这个Activity中,可以看到zxing给我做了比较好的封装,只需要2行代码我们就可以控制闪光灯的开关。扫描图片二维码的点击事件也比较简单我们自需要打开相册。并用startActivityForResult启动相册。
@Overrideprotected void onActivityResult(int requestCode, int resultCode, Intent data) {super.onActivityResult(requestCode, resultCode, data);if (resultCode == RESULT_OK) {//图片选择返回uri = data.getData();//获取图片Uri//启动线程完成图片扫码new Thread(new Runnable() {@Overridepublic void run() {Result result = scanningImage(uri);if (result == null) {Looper.prepare();Toast.makeText(getApplicationContext(), "图片格式有误", Toast.LENGTH_SHORT).show();Looper.loop();} else {// 数据返回,在这里去处理扫码结果String recode = (result.toString());Intent data = new Intent();data.putExtra("result", recode);//返回启动扫码界面的ActivitysetResult(300, data);finish();}}}).start();}}
这个方法主要是处理上个方法打开相册选取图片后结果返回的处理。可以看到,我们拿到结果将扫码扫码界面finish()掉,并通过setResult()方法将数据交给跳转我们的扫码界面的活动去处理。 下面我们再来看下生成二维码
生成二维码:可以看到生成二维码主要是调用了createQRCode();这个方法,那我来看下这个方法:
private Bitmap createQRCode() {int QR_WIDTH = 100;//生成二维码的宽int QR_HEIGHT = 100;//生成二维码的高try {// 需要引入core包QRCodeWriter writer = new QRCodeWriter();String text = Util.getIMEI(this);if (text == null || "".equals(text) || text.length() < 1) {return null;}// 把输入的文本转为二维码BitMatrix martix = writer.encode(text, BarcodeFormat.QR_CODE, QR_WIDTH, QR_HEIGHT);System.out.println("w:" + martix.getWidth() + "h:" + martix.getHeight());Hashtable<EncodeHintType, String> hints = new Hashtable<EncodeHintType, String>();hints.put(EncodeHintType.CHARACTER_SET, "utf-8");BitMatrix bitMatrix = new QRCodeWriter().encode(text, BarcodeFormat.QR_CODE, QR_WIDTH, QR_HEIGHT, hints);int[] pixels = new int[QR_WIDTH * QR_HEIGHT];for (int y = 0; y < QR_HEIGHT; y++) {for (int x = 0; x < QR_WIDTH; x++) {if (bitMatrix.get(x, y)) {pixels[y * QR_WIDTH + x] = 0xff000000;} else {pixels[y * QR_WIDTH + x] = 0xffffffff;}}}// 生成的二维码Bitmap bitmap = Bitmap.createBitmap(QR_WIDTH, QR_HEIGHT, Bitmap.Config.ARGB_8888);bitmap.setPixels(pixels, 0, QR_WIDTH, 0, 0, QR_WIDTH, QR_HEIGHT);return bitmap;} catch (WriterException e) {e.printStackTrace();}return null;}
它主要就是将一个文本生成了一个宽高为100*100的bitmap。那我们就把这个方法简单就修改下,宽高由我们传入。文本也是由我们自己来决定。因为生成二维码并不依赖扫描的Activity,所以不管在那里我们只要调用createQRcode,就能生成二维码。
camera 主要是一些相机的管理类,FlashlightManager闪光灯的管理类,CameraManager相机的管理类,比如刚才我们的开关闪光灯。decode 这里面主要是解码,因为解码也是比较耗时的炒作,所以我们是放在线程中去执行并通过handle来进行消息传递。这里重要的类是CaptureActivityHandler。处理的结果都是通过这个类传给我们的Activity的。既然是handle,那么我们就来看下handleMessage()方法
@Overridepublic void handleMessage(Message message) {if (message.what == R.id.auto_focus) {// Log.d(TAG, "Got auto-focus message");// When one auto focus pass finishes, start another. This is the// closest thing to// continuous AF. It does seem to hunt a bit, but I'm not sure what// else to do.if (state == State.PREVIEW) {CameraManager.get().requestAutoFocus(this, R.id.auto_focus);}} else if (message.what == R.id.restart_preview) {Log.d(TAG, "Got restart preview message");restartPreviewAndDecode();} else if (message.what == R.id.decode_succeeded) {Log.d(TAG, "Got decode succeeded message");state = State.SUCCESS;Bundle bundle = message.getData();Bitmap barcode = bundle == null ? null : (Bitmap) bundle.getParcelable(DecodeThread.BARCODE_BITMAP);activity.handleDecode((Result) message.obj, barcode);Result result = (Result) message.obj;Intent mIntent = new Intent();mIntent.putExtra("SCAN_RESULT", result.getText());activity.setResult(Activity.RESULT_OK, mIntent);activity.finish();} 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.get().requestPreviewFrame(decodeThread.getHandler(), R.id.decode);} else if (message.what == R.id.return_scan_result) {Log.d(TAG, "Got return scan result message");activity.setResult(Activity.RESULT_OK, (Intent) message.obj);activity.finish();} else if (message.what == R.id.launch_product_query) {Log.d(TAG, "Got product query message");String url = (String) message.obj;Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);activity.startActivity(intent);}}
可以看到处理的结果都在这里。有成功失败和其他一些情况的处理,这里我们主要看成功时会返回resultCode=RESULT_OK,扫描的结果放在inent中key=SCAN_RESULT,这样我们就可以根据intent携带的key和value去做们相应的处理
BeepManager是控制我们的消息音。
BeepManager manager = new BeepManager(activity);//开启提示音manager.playBeepSoundAndVibrate();
encode,util,就是编码和工具类,这里我们就不看了view中我们重点来看下ViewfinderView。这个就是我们扫码界面的主布局。他和我们自己添加的头部和脚部不同,它不是通过引入布局,而是自定义view画上去的。既然是画上去的那么就去看看onDraw()方法做了什么
@Overridepublic void onDraw(Canvas canvas) {/*** 想修改扫描框的位置修改CameraManager中的参数*/Rect frame = CameraManager.get().getFramingRect();if (frame == null) {return;}if (!isFirst) {isFirst = true;slideTop = frame.top;slideBottom = frame.bottom;}int width = canvas.getWidth();int height = canvas.getHeight();/*** 画白线矩形*/paint.setStyle(Paint.Style.STROKE);paint.setColor(Color.WHITE);path.moveTo(frame.left + CORNER_OFFEST, frame.top + CORNER_OFFEST);path.lineTo(frame.right - CORNER_OFFEST, frame.top + CORNER_OFFEST);path.lineTo(frame.right - CORNER_OFFEST, frame.bottom - CORNER_OFFEST);path.lineTo(frame.left + CORNER_OFFEST, frame.bottom - CORNER_OFFEST);path.close();canvas.drawPath(path, paint);// Draw the exterior (i.e. outside the framing rect) darkenedpaint.setColor(resultBitmap != null ? resultColor : maskColor);paint.setStyle(Paint.Style.FILL);/*** 画上下左右4个位置的半透明黑色布局*/canvas.drawRect(0, 0, width, frame.top, paint);canvas.drawRect(0, frame.top, frame.left, frame.bottom + 1, paint);canvas.drawRect(frame.right + 1, frame.top, width, frame.bottom + 1,paint);canvas.drawRect(0, frame.bottom + 1, width, height, paint);if (resultBitmap != null) {// Draw the opaque result bitmap over the scanning rectanglepaint.setAlpha(OPAQUE);canvas.drawBitmap(resultBitmap, null, frame, paint);} else {/*** 画4个角的小矩形,每个角2个矩形 共8个*/paint.setColor(getResources().getColor(R.color.view_rect));canvas.drawRect(frame.left, frame.top, frame.left + ScreenRate,frame.top + CORNER_WIDTH, paint);canvas.drawRect(frame.left, frame.top, frame.left + CORNER_WIDTH,frame.top + ScreenRate, paint);canvas.drawRect(frame.right - ScreenRate, frame.top, frame.right,frame.top + CORNER_WIDTH, paint);canvas.drawRect(frame.right - CORNER_WIDTH, frame.top, frame.right,frame.top + ScreenRate, paint);canvas.drawRect(frame.left, frame.bottom - CORNER_WIDTH, frame.left+ ScreenRate, frame.bottom, paint);canvas.drawRect(frame.left, frame.bottom - ScreenRate, frame.left+ CORNER_WIDTH, frame.bottom, paint);canvas.drawRect(frame.right - ScreenRate, frame.bottom- CORNER_WIDTH, frame.right, frame.bottom, paint);canvas.drawRect(frame.right - CORNER_WIDTH, frame.bottom- ScreenRate, frame.right, frame.bottom, paint);/*** 定义每秒的移动速度*/slideTop += SPEEN_DISTANCE;if (slideTop >= frame.bottom) {slideTop = frame.top;}lineRect.left = frame.left;lineRect.right = frame.right;lineRect.top = slideTop;lineRect.bottom = slideTop + 18;canvas.drawBitmap(((BitmapDrawable) (getResources().getDrawable(R.drawable.fle))).getBitmap(), null, lineRect,paint);paint.setColor(Color.WHITE);paint.setTextSize(TEXT_SIZE * density);paint.setAlpha(0x40);paint.setTypeface(Typeface.create("System", Typeface.BOLD));String text = getResources().getString(R.string.scan_text);float textWidth = paint.measureText(text);canvas.drawText(text,(width - textWidth) / 2,(float) (frame.bottom + (float) TEXT_PADDING_TOP * density),paint);Collection<ResultPoint> currentPossible = possibleResultPoints;Collection<ResultPoint> currentLast = lastPossibleResultPoints;if (currentPossible.isEmpty()) {lastPossibleResultPoints = null;} else {possibleResultPoints = new HashSet<ResultPoint>(5);lastPossibleResultPoints = currentPossible;paint.setAlpha(OPAQUE);paint.setColor(resultPointColor);for (ResultPoint point : currentPossible) {canvas.drawCircle(frame.left + point.getX(), frame.top+ point.getY(), 6.0f, paint);}}if (currentLast != null) {paint.setAlpha(OPAQUE / 2);paint.setColor(resultPointColor);for (ResultPoint point : currentLast) {canvas.drawCircle(frame.left + point.getX(), frame.top+ point.getY(), 3.0f, paint);}}//不断重绘 完成光标移动postInvalidateDelayed(ANIMATION_DELAY, frame.left, frame.top,frame.right, frame.bottom);}}
注释比较清晰,掌握基本的自定义View只是就可以画出来我们展示的效果。
最后我们在看下我们MainActivity的调用:
@Overrideprotected void onActivityResult(int requestCode, int resultCode, Intent data) {super.onActivityResult(requestCode, resultCode, data);if (requestCode == 200) {if (resultCode == Activity.RESULT_OK) {//还记得我们在讲CaptureActivityHandler时 成功就会发送SCAN_RESULT这个key值的intent嘛?String code = data.getStringExtra("SCAN_RESULT");Log.d(TAG, "onActivityResult:----> " + code);if (code.contains("http") || code.contains("https")) {//二维码mTextView.setText(code);} else if ((!code.contains("http") || !code.contains("https")) && code != null && TextUtils.isEmpty(code)) {//条形码数字//这里注意:如果你扫描的是商品条形码。返回的条形码数字//这里如果你使用webview是无法解析的,一般我们需要查询//的api接口或是数据库查找才能展示我们想要的结果 mTextView.setText(code);} else {Toast.makeText(this, "error", Toast.LENGTH_SHORT).show();}}if (resultCode == 300) {//扫描图片String code = data.getStringExtra("result");Log.d(TAG, "onActivityResult:---->result " + code);mTextView.setText(code);}if (resultCode == 200) {//生成二维码回调Bitmap bitmap = Util.createQRCode(dip2px(this, 200), dip2px(this, 200), "/");mImageView.setImageBitmap(bitmap);}}}
好了,zxing的使用和相关类的功能我们有了一个大致的了解。回头我们在看看zxing的优点:可以看到封装性比较好我们不需要多做什么处理,我们需要什么功能就在基础上加什么功能,源码都给我们了,我们也知道每个类是什么作用还不是想怎么改怎么改嘛(高可定制性),还有在使用第三方库的时候我们比较担心的就是我们需要一个库的功能,但是这个库却依赖很多其他的库,当其他库发生改动的时候我们需要的库也要改这就很烦。
结束
在这里我只是做一个抛砖引玉的作用,不管你想要什么样的UI还是什么样的布局,我在方法中加了注释不喜欢那里将代码删除写上自己想要的效果就好。毕竟我还是个小白菜鸟。如果你也在学习的路上,想在你自己应用加上扫码功能,希望这篇文章能给你一些帮助。如果像直接使用下面我给出了源码,可以直接放入项目中也可导入library。
写的不好大家多多谅解。如有错误真心希望大家提出来。最后希望大家一起进步。加油!!!
作者:g小志
链接:/p/ee2ee7677831
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。