前言
现在的应用中二维码扫描已经成为一个应用必不可少的功能,现在大部分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() { @Override public 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/*"); //封装intent Intent 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启动相册。
@Override protected 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() { @Override public 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); //返回启动扫码界面的Activity setResult(300, data); finish(); } } }).start(); } }
这个方法主要是处理上个方法打开相册选取图片后结果返回的处理。可以看到,我们拿到结果将扫码扫码界面finish()掉,并通过setResult()方法将数据交给跳转我们的扫码界面的活动去处理。 下面我们再来看下生成二维码
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()方法
@Override public 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();
@Override public 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) darkened paint.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 rectangle paint.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的调用: