行业知识
Android之 Zxing二维码详解
一 简介
1.1 ZXing
目前Android扫描二维码,条形码主要用google官方的工具Zxing,支持扫码,相册解码,生成带logo的二维码等功能
Zxing github 示例地址:https://github.com/zxing/zxing
1.2 ZBar
由于zxing是基于java编写的,扫码速度和解析上可能没那么快,但大部分场合足够用。也有基于c/c++的库zbar,需要编译通过才能用,下面是官网,有兴趣的可以编译试试:
ZBar官网:http://zbar.sourceforge.net/
ZBar GitHub地址:https://github.com/ZBar/ZBar
1.3 华为ScanKit
目前体验最好的华为统一扫码SDK,基本可以做到秒扫和快速识别,支持多码识别和二维码生成。但该服务必须在华为开发者联盟平台注册应用,配置包名和服务json
https://gitee.com/hms-core/hms-scan-demo华为官方demo示例 gitee地址:https://gitee.com/hms-core/hms-scan-demo
二 Zxing使用
2.1 依赖远程zxing库
dependencies { //zxing的core库 implementation "com.google.zxing:core:3.5.1" //zxing implementation "com.google.zxing:zxing-parent:3.5.1" //zxing implementation 'com.journeyapps:zxing-android-embedded:4.1.0'} 或直接使用下面库,目前识别比较快的Zxing库
implementation 'com.journeyapps:zxing-android-embedded:4.3.0'
2.2 添加权限
2.3 调用SDK的扫码页面并返回结果
/** * 跳转到扫码界面扫码 */private void goScan(){ Intent intent = new Intent(MainActivity.this, CaptureActivity.class); startActivityForResult(intent, REQUEST_CODE_SCAN);} @Overrideprotected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); // 扫描二维码/条码回传 if (requestCode == REQUEST_CODE_SCAN && resultCode == RESULT_OK) { if (data != null) { //返回的文本内容 String content = data.getStringExtra(DECODED_CONTENT_KEY); //返回的BitMap图像 Bitmap bitmap = data.getParcelableExtra(DECODED_BITMAP_KEY); } }} 三 自定义扫码页面
3.1 效果图
3.2 activity_scan.xml
3.3 ScanActivity.java
public class ScanActivity extends Activity { public static final int REQUEST_CODE_PHOTO = 0X1113; private static final String TAG = "ScanActivity"; private Camera camera = null; private Camera.Parameters parameters = null; private boolean isPreview = false; private FrameCallback frameCallback = new FrameCallback(); private int width = 1920; private int height = 1080; private double defaultZoom = 1.0; private SurfaceHolder surfaceHolder; private SurfaceCallBack surfaceCallBack; private CommonHandler handler; private boolean isShow; private FrameLayout rim; private SurfaceView surfaceView; private ImageView backImg; private TextView tvDecode; private ImageView imgBtn; private int shortSize; private int longSize; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Window window = getWindow(); window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); requestWindowFeature(Window.FEATURE_NO_TITLE); setContentView(R.layout.activity_common); rim = (FrameLayout) findViewById(R.id.rim); surfaceView = (SurfaceView) findViewById(R.id.surfaceView); backImg = (ImageView) findViewById(R.id.back_img); tvDecode = (TextView) findViewById(R.id.tv_decode); imgBtn = (ImageView) findViewById(R.id.img_btn); checkCameraPermission(); } //权限请求 public final int REQUEST_CAMERA_PERMISSION = 1; private String cameraPermission = Manifest.permission.CAMERA; private void checkCameraPermission() { //检查是否有相机权限 if (ContextCompat.checkSelfPermission(this, cameraPermission) != PackageManager.PERMISSION_GRANTED) { //没权限,请求权限 ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA}, REQUEST_CAMERA_PERMISSION); } else { //有权限 createSurfaceView(); } } //权限请求回调 @Override public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) { switch (requestCode) { case REQUEST_CAMERA_PERMISSION: if (grantResults != null && grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { //用户同意权限 createSurfaceView(); } else { // 权限被用户拒绝了,可以提示用户,关闭界面等等。 Toast.makeText(this, "拒绝权限,请去设置里面手动开启权限", Toast.LENGTH_SHORT).show(); } break; } } /** * 创建预览 */ private void createSurfaceView() { surfaceCallBack = new SurfaceCallBack(); adjustSurface(surfaceView); surfaceHolder = surfaceView.getHolder(); isShow = false; setBackOperation(); setPictureScanOperation(); setDecodeSelectOperation(); } private void adjustSurface(SurfaceView cameraPreview) { FrameLayout.LayoutParams paramSurface = (FrameLayout.LayoutParams) cameraPreview.getLayoutParams(); if (getSystemService(Context.WINDOW_SERVICE) != null) { WindowManager windowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE); Display defaultDisplay = windowManager.getDefaultDisplay(); Point outPoint = new Point(); defaultDisplay.getRealSize(outPoint); int sceenWidth = outPoint.x; int sceenHeight = outPoint.y; shortSize = Math.min(sceenWidth, sceenHeight); longSize = shortSize; //横屏 paramSurface.width = shortSize; paramSurface.height = shortSize;// float rate;// if (sceenWidth / (float) 1080 > sceenHeight / (float) 1920) {// rate = sceenWidth / (float) 1080;// int targetHeight = (int) (1920 * rate);// paramSurface.width = FrameLayout.LayoutParams.MATCH_PARENT;// paramSurface.height = targetHeight;// int topMargin = (int) (-(targetHeight - sceenHeight) / 2);// if (topMargin = maxZoom) { return allZoomRatios.size() - 1; } for (int i = 1; i = (zoomValue * 100) && allZoomRatios.get(i - 1) 200) { preTime = currentTime; beepManager.playBeepSoundAndVibrate(); restart(DEFAULT_ZOOM); stopPreview(); Intent intent = new Intent(ScanActivity.this, ResultActivity.class); intent.putExtra("result", result); intent.putExtra("time", message.arg1); startActivity(intent); } } } public void quit() { try { stopPreview(); decodeHandle.getLooper().quit(); decodeThread.join(500); } catch (InterruptedException e) { Log.w(TAG, e); } } public void restart(double zoomValue) { callbackFrame(decodeHandle, zoomValue); } /** * Call the MultiProcessor API in synchronous mode. */ private String decodeSyn(int width, int height, byte[] data) { String result=""; switch (decodeType){ case 1://Zxing result = ZxingUtils.decode(width, height, data); break; case 2://商米 result = SunmiUtils.decode(width, height, data); break; case 3://华为 result = HuaweiHmsUtils.decode(width, height, data, ScanActivity.this); break; } return result; } }} 3.4 解析工具类
public class ZxingUtils { public static String decode(int width, int height, byte[] data) { Map hints = new EnumMap(DecodeHintType.class); hints.put(DecodeHintType.CHARACTER_SET, "utf-8"); Result rawResult = null; PlanarYUVLuminanceSource source = buildLuminanceSource(data, width, height); MultiFormatReader multiFormatReader = null; if (source != null) { BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source)); try { multiFormatReader = new MultiFormatReader(); multiFormatReader.setHints(hints); rawResult = multiFormatReader.decodeWithState(bitmap); } catch (ReaderException re) { // continue } finally { multiFormatReader.reset(); } } if(rawResult==null){ return null; }else { return rawResult.getText(); } } /** * A factory method to build the appropriate LuminanceSource object based on the format * of the preview buffers, as described by Camera.Parameters. * * @param data A preview frame. * @param width The width of the image. * @param height The height of the image. * @return A PlanarYUVLuminanceSource instance. */ public static PlanarYUVLuminanceSource buildLuminanceSource(byte[] data, int width, int height) { Rect rect = new Rect(0,0,width,height); if (rect == null) { return null; } // Go ahead and assume it's YUV rather than die. return new PlanarYUVLuminanceSource(data, width, height, rect.left, rect.top, rect.width(), rect.height(), false); }} 四 相册选区图片解析二维码
4.1 选择相册图片
private void setPictureScanOperation() { imgBtn = findViewById(R.id.img_btn); imgBtn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent pickIntent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI); pickIntent.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "image/*"); ZxingActivity.this.startActivityForResult(pickIntent, REQUEST_CODE_PHOTO); } }); } 4.2 本地图片文件转换成可解码二维码的 Bitmap
/** * 将本地图片文件转换成可解码二维码的 Bitmap。为了避免图片太大,这里对图片进行了压缩。 * * @param picturePath 本地图片文件路径 */ public static Bitmap getDecodeAbleBitmap(String picturePath) { try { BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeFile(picturePath, options); int sampleSize = options.outHeight / 400; if (sampleSize 0) { bitmap = showContent(bitmap, content, textSize); } return bitmap; } catch (Exception e) { e.printStackTrace(); } return null; } /** * 显示条形的内容 * * @param barcodeBitmap 已生成的条形码的位图 * @param content 条形码包含的内容 * @param textSize 字体大小,单位px * @return 返回生成的新条形码位图 */ private static Bitmap showContent(Bitmap barcodeBitmap, String content, int textSize) { if (TextUtils.isEmpty(content) || null == barcodeBitmap) { return null; } Paint paint = new Paint(); paint.setColor(Color.BLACK); paint.setAntiAlias(true); paint.setStyle(Paint.Style.FILL); paint.setTextSize(textSize); paint.setTextAlign(Paint.Align.CENTER); int textWidth = (int) paint.measureText(content); Paint.FontMetrics fm = paint.getFontMetrics(); int textHeight = (int) (fm.bottom - fm.top); float scaleRateX = barcodeBitmap.getWidth() * 1.0f / textWidth; if (scaleRateX 5.4 异步调用QRCodeEncoder生成二维码
private void updateQrcode(){ new GenerateQrcodeTask().execute(content); } public class GenerateQrcodeTask extends AsyncTask { @Override protected Bitmap doInBackground(String... strings) { Bitmap logoBitmap=null; if(logo!=0){ logoBitmap = BitmapFactory.decodeResource(getResources(), logo); } int borderColor22=TextUtils.isEmpty(borderColor)?0:Color.parseColor(borderColor); return QRCodeEncoder.syncEncodeQRCode(strings[0], (int) StatuesBarUtils.dp2px(mContext, 300f), Color.parseColor(qianColor),Color.parseColor(beinColor),logoBitmap, border , borderColor22); } @Override protected void onPostExecute(Bitmap bitmap) { super.onPostExecute(bitmap); qrBitmap=bitmap; if (bitmap != null) { mDataBinding.vImage.setImageBitmap(qrBitmap); } else { Toast.makeText(mContext, "生成維碼失敗", Toast.LENGTH_SHORT).show(); } } } 5.5 保存二维码,FileUtil.java工具类
public class FileUtil { public static String saveToImage(Bitmap bitmap) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { return MediaStore.Images.Media.insertImage(MyApp.getInstance().getContentResolver(), bitmap, "", ""); } else { try { File dir = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "qrcode"); if (!dir.exists()) { dir.mkdirs(); } File picFile = new File(dir, System.currentTimeMillis() + ".png"); FileOutputStream fos = new FileOutputStream(picFile); bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos); fos.flush(); fos.close(); return picFile.getAbsolutePath(); } catch (Exception e) { e.printStackTrace(); } } return null; } 异步调用FileUtil,保存二维码
public class SaveBitmapTask extends AsyncTask { @Override protected String doInBackground(Bitmap... bitmaps) { return FileUtil.saveToImage(bitmaps[0]); } @Override protected void onPostExecute(String result) { super.onPostExecute(result); if (result != null) { Intent intent = new Intent(mContext, QrBarcodeEditResultActivity.class); intent.putExtra("filePath", result); startActivity(intent); } else { Toast.makeText(mContext, "保存二維碼失敗", Toast.LENGTH_SHORT).show(); } } } 5.6 核心源码解析,我们知道bitmap是一个保存所有像素内容的容器,那二维码原理即是把内容解析到每一个像素里面。如下
/** * * @param content 要生成的二维码图片内容 * @param size 图片宽高,单位为px * @param foregroundColor 二维码图片的前景色 * @param backgroundColor 二维码图片的背景色 * @param logo 二维码图片的logo * @param borderColor 边框颜色 */public static Bitmap syncEncodeQRCode(String content, int size, int foregroundColor, int backgroundColor, Bitmap logo ,int border , int borderColor) {int borderWidth= (int) dp2px(MyApp.getInstance(),4);size-=borderWidth*2;try {//内容解析到字节矩阵BitMatrix matrix = new MultiFormatWriter().encode(content, BarcodeFormat.QR_CODE, size, size, HINTS);//设置宽高像素数组int[] pixels = new int[size * size];//遍历记录图片每个像素信息for (int y = 0; y
上一篇:宝塔面板怎么安装gd?