二维码识别(Zxing)

时间:2022-11-16 23:21:00
自己总结研究
参考做法
一、 简介
二维码因其可以在一个几何图上可以包含不同编码的大信息量数据, 低成本, 易制作等优点,
导致现在二维码的使用可谓是遍布大街小巷; 而手机上的使用也是非常的广泛, 电商类、 金融类等各种app都能看到二维码的使用, 而在
Android平台上主流还是用zxing库, 因此这里主要讲述如何利用zxing进行二维码开发。
二、 Zxing的使用
首先从Github上下载Zxing, 因Zxing官方的项目设计功能方面太多, 并且没有精简, 所以本次我们从Github上下载精简过的项目;
 1) 下载地址: https://github.com/yipianfengye/android-zxingLibrary
 2) 下载完ZxingLibrary之后, 将项目中的lib-zxing作为module导入
compile project(':lib-zxing')
 3) 将lib-zxing中的Mainifest里面的权限和注册的activity拷贝到app下的Mainifest中, 讲ZApplication中的初始化代码拷贝
到自己的Application中;
 4) , 使用二维码有两种, 一种是跳转到lib-zxingCaptureActivity, 另外一种是
加载自己的Activity中加载CaptureFragment;
第一种:
Intent intent = newIntent(MainActivity.this, CaptureActivity.class);
startActivityForResult(intent, REQUEST_CODE);
onActivityResult接收扫码结果:
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
/**
* 处理二维码扫描结果
*/
if (requestCode == REQUEST_CODE) {
//处理扫描结果( 在界面上显示)
if (null != data) {
Bundle bundle = data.getExtras();
if (bundle == null) {
return;
}if
(bundle.getInt(CodeUtils.RESULT_TYPE) == CodeUtils.RESULT_SUCCESS) {
String result = bundle.getString(CodeUtils.RESULT_STRING);
Toast.makeText(this, "解析结果:" + result, Toast.LENGTH_LONG).show();
} else if (bundle.getInt(CodeUtils.RESULT_TYPE) == CodeUtils.RESULT_FAILED) {
Toast.makeText(MainActivity.this, "解析二维码失败", Toast.LENGTH_LONG).show();
}
}
}/*
*
* 选择系统图片并解析
*/
else if (requestCode == REQUEST_IMAGE) {
if (data != null) {
Uri uri = data.getData();
try {
CodeUtils.analyzeBitmap(uri.getPath(), newCodeUtils.AnalyzeCallback() {
@Override
public void onAnalyzeSuccess(Bitmap mBitmap, String result) {
Toast.makeText(MainActivity.this, "解析结果:" + result, Toast.LENGTH_LONG).show();
} @
Override
public void onAnalyzeFailed() {
Toast.makeText(MainActivity.this, "解析二维码失败", Toast.LENGTH_LONG).show();
}
});
} catch (Exception e) {
e.printStackTrace();}}}} 第二种:CaptureFragment captureFragment = newCaptureFragment();// 为二维码扫描界面设置定制化界面CodeUtils.setFragmentArgs(captureFragment, R.layout.my_camera);captureFragment.setAnalyzeCallback(analyzeCallback);getSupportFragmentManager().beginTransaction().replace(R.id.fl_my_container, captureFragment).commit();/*** 二维码解析回调函数*/CodeUtils.AnalyzeCallback analyzeCallback = newCodeUtils.AnalyzeCallback() {@Overridepublic void onAnalyzeSuccess(Bitmap mBitmap, String result) {//解析成功Intent resultIntent = newIntent();Bundle bundle = newBundle();bundle.putInt(CodeUtils.RESULT_TYPE, CodeUtils.RESULT_SUCCESS);bundle.putString(CodeUtils.RESULT_STRING, result);resultIntent.putExtras(bundle);SecondActivity.this.setResult(RESULT_OK, resultIntent);SecondActivity.this.finish();} @Overridepublic void onAnalyzeFailed() {//解析失败Intent resultIntent = newIntent();Bundle bundle = newBundle();bundle.putInt(CodeUtils.RESULT_TYPE, CodeUtils.RESULT_FAILED);bundle.putString(CodeUtils.RESULT_STRING, "");resultIntent.putExtras(bundle);SecondActivity.this.setResult(RESULT_OK, resultIntent);SecondActivity.this.finish();}};三、 Zxing其他的一些使用这里主要介绍下扫描UI绘制的类ViewfinderView和扫码处理的逻辑CaptureFragment的相关逻辑; ( 代码讲解)四、 关于zxing源码的若干问题在实际使用过程中发现zxing源码存在一些问题, 下面逐一来说一下:1) 竖屏问题。zxing给出的官方例子是横屏的, 但是对于手机实际上竖屏操作更加方便一点, 如果要改成竖屏, 需要修改以下几个文件:首先将AndrodMainfest.xml下CaptureActivity的配置改为竖屏:android:screenOrientation="portrait"然后需要修改CameraConfigurationManager.java下的setDesiredCameraParameters方法, 该方法直接使用camera.setDisplayOrientation(90);来让屏幕旋转90度, 这个办法在android 2.2版本以后是可行的, 但是2.2之前的版本并没有提供这个接口, 因此需要对sdk版本进行判断:if (Integer.parseInt(Build.VERSION.SDK) >=8)setDisplayOrientation(camera, 90);else{if (context.getResources().getConfiguration().orientation ==Configuration.ORIENTATION_PORTRAIT){parameters.set("orientation", "portrait");parameters.set("rotation", 90);} if (context.getResources().getConfiguration().orientation ==Configuration.ORIENTATION_LANDSCAPE){parameters.set("orientation", "landscape");parameters.set("rotation", 90);}} 在修改为竖屏之后发现取景框会发生拉伸, 需要进行以下几点修改:在CameraManager.java中将:rect.left = rect.left * cameraResolution.x / screenResolution.x;rect.right = rect.right * cameraResolution.x / screenResolution.x;rect.top = rect.top * cameraResolution.y / screenResolution.y;rect.bottom = rect.bottom * cameraResolution.y / screenResolution.y;修改为:rect.left = rect.left * cameraResolution.y / screenResolution.x;rect.right = rect.right * cameraResolution.y / screenResolution.x;rect.top = rect.top * cameraResolution.x / screenResolution.y;rect.bottom = rect.bottom * cameraResolution.x / screenResolution.y;然后将DecodeHandler类中的方法decode改为:private void decode(byte[] data, int width, int height) {long start = System.currentTimeMillis();Result rawResult = null;//modify herebyte[] 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 #11width = height;height = tmp;PlanarYUVLuminanceSource source =CameraManager.get().buildLuminanceSource(rotatedData, width, height);BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));try {rawResult = multiFormatReader.decodeWithState(bitmap);} catch (ReaderException re) {// continue} finally {multiFormatReader.reset();} if (rawResult != null) {long end = System.currentTimeMillis();Log.d(TAG, "Found barcode (" + (end - start) + " ms):\n" +rawResult.toString());Message message = Message.obtain(activity.getHandler(), R.id.decode_succeeded,rawResult);Bundle bundle = new Bundle();bundle.putParcelable(DecodeThread.BARCODE_BITMAP,source.renderCroppedGreyscaleBitmap());message.setData(bundle);//Log.d(TAG, "Sending decode succeeded message...");message.sendToTarget();} else {Message message = Message.obtain(activity.getHandler(), R.id.decode_failed);message.sendToTarget();}}再将CameraConfigurationManager类中的方法initFromCameraParameters修改为:void initFromCameraParameters(Camera camera) {Camera.Parameters parameters = camera.getParameters();previewFormat = parameters.getPreviewFormat();previewFormatString = parameters.get("preview-format");Log.d(TAG, "Default preview format: " + previewFormat + '/' +previewFormatString);WindowManager manager = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);Display display = manager.getDefaultDisplay();screenResolution = new Point(display.getWidth(), display.getHeight());Log.d(TAG, "Screen resolution: " + screenResolution);Point screenResolutionForCamera = new Point();screenResolutionForCamera.x = screenResolution.x;screenResolutionForCamera.y = screenResolution.y;// preview size is always something like 480*320, other 320*480if (screenResolution.x < screenResolution.y) {screenResolutionForCamera.x = screenResolution.y;screenResolutionForCamera.y = screenResolution.x;} cameraResolution = getCameraResolution(parameters, screenResolutionForCamera);//cameraResolution = getCameraResolution(parameters, screenResolution);Log.d(TAG, "Camera resolution: " + screenResolution);}最后将PlanarYUVLuminanceSource.java中的renderCroppedGreyscaleBitmap方法改为:public Bitmap renderCroppedGreyscaleBitmap() {int width = getWidth();int height = getHeight();int[] pixels = new int[width * height];byte[] yuv = yuvData;int inputOffset = top * dataHeight + 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 += dataHeight;} Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);bitmap.setPixels(pixels, 0, width, 0, 0, width, height);return bitmap;}3) 取景框的绘制取景框的绘制可以参照这篇博文: http://www.cnblogs.com/forrestsun/archive/2012/11/06/2757005.html4) 闪光灯的开启和关闭如果需要开启和关闭闪光灯, 在CameraManager.java中添加2个方法:public void openLight() //打开闪光灯{if(camera!=null){Parameters parameter=camera.getParameters();parameter.setFlashMode(Parameters.FLASH_MODE_TORCH);camera.setParameters(parameter);}} public void closeLight() //关闭闪光灯{if(camera!=null){Parameters parameter=camera.getParameters();parameter.setFlashMode(Parameters.FLASH_MODE_OFF);camera.setParameters(parameter);}} 然后在CaptureActivity中的initCamera方法中打开闪光灯:CameraManager.get().openDriver(surfaceHolder);CameraManager.get().openLight(); //开闪光灯在需要的地方关闭闪光灯即可。5) 连续扫描问题如果在识别二维码成功之后, 需要连续多次扫描二维码, 只需在扫描完成之后添加代码( 在CaptureActivity的dandleDecode方法中添加) :if(handler!=null) //实现连续扫描handler.restartPreviewAndDecode();比如:public void handleDecode(Result result, Bitmap barcode) {inactivityTimer.onActivity();playBeepSoundAndVibrate();final String resultString = result.getText();//FIXMEif (resultString.equals("")) {Toast.makeText(CaptureActivity.this, "Scan failed!",Toast.LENGTH_SHORT).show();}else {// System.out.println("Result:"+resultString);/*Intent resultIntent = new Intent();Bundle bundle = new Bundle();bundle.putString("result", resultString);resultIntent.putExtras(bundle);this.setResult(RESULT_OK, resultIntent);*/AlertDialog resutlDialog = newAlertDialog.Builder(CaptureActivity.this).create();resutlDialog.setTitle("扫描结果");resutlDialog.setMessage(resultString);resutlDialog.setButton(AlertDialog.BUTTON_POSITIVE, "打开链接", newDialogInterface.OnClickListener(){@Overridepublic void onClick(DialogInterface dialog, int which){dialog.dismiss();if(!isLegalUrl(resultString)) //如果url不合法{Toast.makeText(getApplicationContext(), "该链接不是合法的URL",Toast.LENGTH_SHORT).show();if(handler!=null) //实现连续扫描handler.restartPreviewAndDecode();return;}Intent intent = new Intent(); //打开链接intent.setAction("android.intent.action.VIEW");Uri content_url = Uri.parse(resultString);intent.setData(content_url);startActivity(intent);}});resutlDialog.setButton(AlertDialog.BUTTON_NEGATIVE, "取消", newDialogInterface.OnClickListener(){@Overridepublic void onClick(DialogInterface dialog, int which){dialog.dismiss();if(handler!=null) //实现连续扫描handler.restartPreviewAndDecode();}});resutlDialog.show();}//CaptureActivity.this.finish();}6) 识别完成之后的震动。如果需要取消二维码识别之后的震动, 只需要在CaptureActivity类的onResume方法中将vibrate 设置为false即可。7) 修改取景框距屏幕顶部位置如果需要修改取景框距屏幕顶部位置, 只需要修改CameraManager.java的getFramingRect方法, 在getFramingRect方法中,int topOffset = (screenResolution.y - height) / 2这句是控制取景框到屏幕顶部的距离, 若需要减小距屏幕顶部的距离, 只需要将分母变大即可。8) 取景框下方提示文字的绘制。在ViewfinderView.java的 Collection<ResultPoint> currentPossible = possibleResultPoints前面加入以下代码:TextPaint textPaint = new TextPaint();textPaint.setARGB(0xFF, 0xFF, 0xFF,0xFF); //字体颜色textPaint.setTextSize(TEXT_SIZE * density);textPaint.setAntiAlias(true); //设置抗锯齿, 否则字迹会很模糊StaticLayout layout = newStaticLayout(getResources().getString(R.string.scan_text),textPaint,frame.right-frame.left,Alignment.ALIGN_NORMAL,1.0F,0.0F,true);canvas.translate(frame.left, (float) (frame.bottom + (float)TEXT_PADDING_TOP*density)); //绘制起始位置layout.draw(canvas);这里解释一下, textPaint.setAntiAlias(true); 是设置为抗锯齿, 否则字体会很模糊。 StaticLayout的第一个参数就是要绘制的字符串, 第二个是画笔, 第三个参数是设置每一行的宽度, 即超过该宽度就换行, 第四个是对齐方式。canvas.translate(frame.left, (float) (frame.bottom + (float)TEXT_PADDING_TOP *density))的参数是绘制字符串的起始位置。