Android项目开发中经常会用到二维码扫描,例如登陆、支付等谷歌方面已经有了一个开源库(地址: https://github.com/zxing/zxing),里面的内容还是比较多的,如果想深入学习这方面的内容,还是不错的。当然了还是要根据实际需求去选择,所以我们就选择类库中的核心Jar包就行了(地址:https://github.com/ASCN-BJ/ZhaoYun/blob/master/qrcodelibrary/libs/core-3.3.1.jar)。
GoogleZxing中的原装的类库有一个问题就是竖屏的时候会导致无法识别,原因是竖屏的时候没有对相机的预览效果进行处理,所以在项目中,进行了处理,使得竖屏的时候的识别能够成功。所以正好看了一下,就当学习了。
效果图
- 二维码的扫描
zxing中的二维码识别界面主要是在CaptureActivity中,基本原理通过获取相机的预览界面然后对获取到的数据进行处理,预览效果是在PreviewCallback中获取数据,然后通过DecodeHandler发送数据,具体识别是在DecodeHandler类中处理。
@Override
public void onPreviewFrame(byte[] data, Camera camera) {
Point cameraResolution = configManager.getCameraResolution();
Handler thePreviewHandler = previewHandler;
// if (cameraResolution != null && thePreviewHandler != null) {
// Message message = thePreviewHandler.obtainMessage(previewMessage, cameraResolution.x,
// cameraResolution.y, data);
// message.sendToTarget();
// previewHandler = null;
// } else {
// Log.d(TAG, "Got preview callback, but no handler or resolution available");
// }
// Log.d(TAG, "onPreviewFrame: " + "onRunning");
if (cameraResolution != null && thePreviewHandler != null) {
//add by tancolo
Point screenResolution = configManager.getScreenResolution();
Message message;
if (screenResolution.x < screenResolution.y) {
// portrait
message = thePreviewHandler.obtainMessage(previewMessage, cameraResolution.y,
cameraResolution.x, data);
} else {
// landscape
message = thePreviewHandler.obtainMessage(previewMessage, cameraResolution.x,
cameraResolution.y, data);
}
message.sendToTarget();
previewHandler = null;
}
}
可以看到在onPreviewFrame中将具体byte[] data数据发送出去,同时将预览效果的宽高发送出去,用于后续的处理。
接下来看具体二维码识别的代码
@Override
public void handleMessage(Message message) {
if (message == null || !running) {
return;
}
if (message.what == R.id.decode) {
decode((byte[]) message.obj, message.arg1, message.arg2);
} else if (message.what == R.id.quit) {
running = false;
Looper.myLooper().quit();
}
}
private void decode(byte[] data, int width, int height) {
long start = System.currentTimeMillis();
//add by tancolo
if (width < height) {
// portrait
byte[] rotatedData = new byte[data.length];
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++)
rotatedData[y * width + width - x - 1] = data[y + x * height];
}
data = rotatedData;
}
//end add
Result rawResult = null;
PlanarYUVLuminanceSource source = activity.getCameraManager().buildLuminanceSource(data, width, height);
if (source != null) {
BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
try {
rawResult = multiFormatReader.decodeWithState(bitmap);//具体识别方法,获取到数据
} catch (ReaderException re) {
// continue
} finally {
multiFormatReader.reset();
}
}
Handler handler = activity.getHandler();
if (rawResult != null) {
// Don't log the barcode contents for security.
long end = System.currentTimeMillis();
Log.d(TAG, "Found barcode in " + (end - start) + " ms");
if (handler != null) {
Message message = Message.obtain(handler, R.id.decode_succeeded, rawResult);
Bundle bundle = new Bundle();
bundleThumbnail(source, bundle);
message.setData(bundle);
message.sendToTarget();
}
} else {
if (handler != null) {
Message message = Message.obtain(handler, R.id.decode_failed);
message.sendToTarget();
}
}
}
这里写代码片
try {
mQRCodeEncoder2 = new QRCodeEncoder2(smallerDimension);
//生成二维码
Bitmap bitmap = mQRCodeEncoder2.encodeAsBitmap(text, BarcodeFormat.QR_CODE);
if (bitmap == null) {
qrCodeEncoder = null;
return true;
}
iv_image_view.setImageBitmap(bitmap);
} catch (WriterException e) {
qrCodeEncoder = null;
}
具体识别
public Bitmap encodeAsBitmap(String contentsToEncode, BarcodeFormat format) throws WriterException {
if (contentsToEncode == null) {
return null;
}
Map hints = null;
String encoding = guessAppropriateEncoding(contentsToEncode);
if (encoding != null) {
hints = new EnumMap<>(EncodeHintType.class);
hints.put(EncodeHintType.CHARACTER_SET, encoding);
}
BitMatrix result;
try {
format = BarcodeFormat.valueOf(format.toString());
result = new MultiFormatWriter().encode(contentsToEncode, format, dimension, dimension, hints);
} catch (IllegalArgumentException iae) {
// Unsupported format
return null;
}
int width = result.getWidth();
int height = result.getHeight();
int[] pixels = new int[width * height];
for (int y = 0; y < height; y++) {
int offset = y * width;
for (int x = 0; x < width; x++) {
pixels[offset + x] = result.get(x, y) ? BLACK : WHITE;//生成二维码数据
}
}
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
bitmap.setPixels(pixels, 0, width, 0, 0, width, height);
return bitmap;
}
public static String getResult(Bitmap bitmap) {
//Zxing自带的解析类
byte data[];
int[] datas = new int[bitmap.getWidth() * bitmap.getHeight()];
data = new byte[bitmap.getWidth() * bitmap.getHeight()];
bitmap.getPixels(datas, 0, bitmap.getWidth(), 0, 0, bitmap.getWidth(), bitmap.getHeight());
for (int i = 0; i < datas.length; i++) {
data[i] = (byte) datas[i];
}
Set decodeFormats = new HashSet<>();
decodeFormats.addAll(DecodeFormatManager.PRODUCT_FORMATS);
decodeFormats.addAll(DecodeFormatManager.INDUSTRIAL_FORMATS);
decodeFormats.addAll(DecodeFormatManager.QR_CODE_FORMATS);
decodeFormats.addAll(DecodeFormatManager.DATA_MATRIX_FORMATS);
decodeFormats.addAll(DecodeFormatManager.AZTEC_FORMATS);
decodeFormats.addAll(DecodeFormatManager.PDF417_FORMATS);
Map hints = new EnumMap<>(DecodeHintType.class);
hints.put(DecodeHintType.POSSIBLE_FORMATS, decodeFormats);
MultiFormatReader multiFormatReader = new MultiFormatReader();
multiFormatReader.setHints(hints);
Result rawResult = null;
PlanarYUVLuminanceSource source = buildLuminanceSource(data, bitmap.getWidth(), bitmap.getHeight());
if (source != null) {
BinaryBitmap bitmap1 = new BinaryBitmap(new HybridBinarizer(source));
try {
rawResult = multiFormatReader.decodeWithState(bitmap1);
} catch (ReaderException re) {
// continue
} finally {
multiFormatReader.reset();
}
}
return rawResult != null ? rawResult.getText() : "";
}
public final class ViewfinderView extends View implements ScaleGestureDetector.OnScaleGestureListener,
View.OnTouchListener{
private static final int[] SCANNER_ALPHA = {0, 64, 128, 192, 255, 192, 128, 64};
private static final long ANIMATION_DELAY = 80L;
private static final int CURRENT_POINT_OPACITY = 0xA0;
private static final int MAX_RESULT_POINTS = 20;
private static final int POINT_SIZE = 6;
private CameraManager cameraManager;
private final Paint paint;
private Bitmap resultBitmap;
private final int maskColor;
private final int resultColor;
private final int laserColor;
private final int resultPointColor;
private int scannerAlpha;
private List possibleResultPoints;
private List lastPossibleResultPoints;
private Rect tmpRect;
private Bitmap tmpBitmap;
private float cornerWidth;//方角宽度
private float cornerLength;//方角长度
private int mStrokeWidth;//内圈寬度
private int corner_rect_length;//内圈颜色
private int cornerColor;//角度颜色
private String description;//描述文字
private int textColor;
private boolean textVisible;
private float textMargin;//描述文本距离上面的距离
private float text_size;//描述文本字体大小
private ScaleGestureDetector scaleGestureDetector;
private GestureDetector gestureDetector;
private boolean zoomMaxFlag = true;
// This constructor is used when the class is built from an XML resource.
public ViewfinderView(Context context, AttributeSet attrs) {
super(context, attrs);
// Initialize these once for performance rather than calling them every time in onDraw().
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
Resources resources = getResources();
resultColor = resources.getColor(R.color.result_view);
laserColor = resources.getColor(R.color.viewfinder_laser);
resultPointColor = resources.getColor(R.color.possible_result_points);
scannerAlpha = 0;
possibleResultPoints = new ArrayList<>(5);
lastPossibleResultPoints = null;
tmpRect = new Rect();
tmpBitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.scan);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ViewfinderView);
cornerWidth = a.getDimensionPixelSize(R.styleable.ViewfinderView_corner_width, dip2px(context, 4));
cornerLength = a.getDimensionPixelSize(R.styleable.ViewfinderView_corner_length, dip2px(context, 20));
mStrokeWidth = a.getDimensionPixelSize(R.styleable.ViewfinderView_corner_rect_length, dip2px(context, 1));
cornerColor = a.getColor(R.styleable.ViewfinderView_corner_color, Color.parseColor("#62e203"));
corner_rect_length = a.getColor(R.styleable.ViewfinderView_corner_rect_color, Color.parseColor("#66ffffff"));
maskColor = a.getColor(R.styleable.ViewfinderView_mask_color, resources.getColor(R.color.viewfinder_mask));
description = a.getString(R.styleable.ViewfinderView_text_description);
if (TextUtils.isEmpty(description)) {
description = "将二维码/条码放入框内,即可自动扫描";
}
textColor = a.getColor(R.styleable.ViewfinderView_text_color, Color.parseColor("#66ffffff"));
textVisible = a.getBoolean(R.styleable.ViewfinderView_text_visible, true);
textMargin = a.getDimensionPixelSize(R.styleable.ViewfinderView_text_margin, dip2px(context, 20));
text_size = a.getDimensionPixelSize(R.styleable.ViewfinderView_text_size, sp2px(context, 14));
a.recycle();
setOnTouchListener(this);
scaleGestureDetector = new ScaleGestureDetector(context, this);
gestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() {
@Override
public boolean onDoubleTap(MotionEvent e) {
if (zoomMaxFlag) {
if (mCameraZoomListener != null) {
mCameraZoomListener.onZooming(true, false, true, 1);
}
zoomMaxFlag = false;
} else {
if (mCameraZoomListener != null) {
mCameraZoomListener.onZooming(true, false, false, 1);
}
zoomMaxFlag = true;
}
return true;
}
@Override
public boolean onSingleTapUp(MotionEvent e) {
// if (mCameraZoomListener != null) {
// //判断点击事件是否在
// mCameraZoomListener.onZooming(false, true, false, 1);
// }
return false;
}
@Override
public void onLongPress(MotionEvent e) {
super.onLongPress(e);
// if (mCameraZoomListener != null) {
// //判断点击事件是否在
// mCameraZoomListener.onZooming(false, true, false, 1);
// }
}
});
}
public void setCameraManager(CameraManager cameraManager) {
this.cameraManager = cameraManager;
}
@SuppressLint("DrawAllocation")
@Override
public void onDraw(Canvas canvas) {
if (cameraManager == null) {
return; // not ready yet, early draw before done configuring
}
Rect frame = cameraManager.getFramingRect();
Rect previewFrame = cameraManager.getFramingRectInPreview();
if (frame == null || previewFrame == null) {
return;
}
int width = canvas.getWidth();
int height = canvas.getHeight();
// Draw the exterior (i.e. outside the framing rect) darkened
paint.setColor(resultBitmap != null ? resultColor : maskColor);
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(CURRENT_POINT_OPACITY);
canvas.drawBitmap(resultBitmap, null, frame, paint);
} else {
// Draw a red "laser scanner" line through the middle to show decoding is active
paint.setColor(laserColor);
// paint.setAlpha(SCANNER_ALPHA[scannerAlpha]);
scannerAlpha = (scannerAlpha + 1) % SCANNER_ALPHA.length;
// int middle = frame.height() / 2 + frame.top;
// canvas.drawRect(frame.left + 2, middle - 1, frame.right - 1, middle + 2, paint);//移除红线
float scaleX = frame.width() / (float) previewFrame.width();
float scaleY = frame.height() / (float) previewFrame.height();
List currentPossible = possibleResultPoints;
List currentLast = lastPossibleResultPoints;
int frameLeft = frame.left;
int frameTop = frame.top;
if (currentPossible.isEmpty()) {
lastPossibleResultPoints = null;
} else {
possibleResultPoints = new ArrayList<>(5);
lastPossibleResultPoints = currentPossible;
paint.setAlpha(CURRENT_POINT_OPACITY);
paint.setColor(resultPointColor);
synchronized (currentPossible) {
for (ResultPoint point : currentPossible) {
canvas.drawCircle(frameLeft + (int) (point.getX() * scaleX),
frameTop + (int) (point.getY() * scaleY),
POINT_SIZE, paint);
}
}
}
if (currentLast != null) {
paint.setAlpha(CURRENT_POINT_OPACITY / 2);
paint.setColor(resultPointColor);
synchronized (currentLast) {
float radius = POINT_SIZE / 2.0f;
for (ResultPoint point : currentLast) {
canvas.drawCircle(frameLeft + (int) (point.getX() * scaleX),
frameTop + (int) (point.getY() * scaleY),
radius, paint);
}
}
}
drawRectCorner(frame, canvas);
drawBitmap(frame, canvas);
drawBottomText(frame, canvas);
// Request another update at the animation interval, but only repaint the laser line,
// not the entire viewfinder mask.
// postInvalidateDelayed(ANIMATION_DELAY,
// frame.left - POINT_SIZE,
// frame.top - POINT_SIZE,
// frame.right + POINT_SIZE,
// frame.bottom + POINT_SIZE);
}
}
public void drawViewfinder() {
Bitmap resultBitmap = this.resultBitmap;
this.resultBitmap = null;
if (resultBitmap != null) {
resultBitmap.recycle();
}
invalidate();
}
/**
* Draw a bitmap with the result points highlighted instead of the live scanning display.
*
* @param barcode An image of the decoded barcode.
*/
public void drawResultBitmap(Bitmap barcode) {
resultBitmap = barcode;
invalidate();
}
public void addPossibleResultPoint(ResultPoint point) {
List points = possibleResultPoints;
synchronized (points) {
points.add(point);
int size = points.size();
if (size > MAX_RESULT_POINTS) {
// trim it
points.subList(0, size - MAX_RESULT_POINTS / 2).clear();
}
}
}
private ValueAnimator animator;
private float x;
private boolean flag = true;
/*画四个角,如果通过绘制path的方式会卡*/
private void drawRectCorner(Rect frame, Canvas canvas) {
//画内部rect
paint.setStrokeWidth(mStrokeWidth);
paint.setColor(corner_rect_length);
paint.setStyle(Paint.Style.STROKE);
canvas.drawRect(frame, paint);
paint.reset();
//画四角
paint.setStyle(Paint.Style.FILL);
paint.setColor(cornerColor);
//左上角
canvas.drawRect(frame.left, frame.top, frame.left + cornerLength, frame.top + cornerWidth, paint);
canvas.drawRect(frame.left, frame.top, frame.left + cornerWidth, frame.top + cornerLength, paint);
//右上角
canvas.drawRect(frame.right - cornerLength, frame.top, frame.right, frame.top + cornerWidth, paint);
canvas.drawRect(frame.right - cornerWidth, frame.top, frame.right, frame.top + cornerLength, paint);
//左下角
canvas.drawRect(frame.left, frame.bottom - cornerLength, frame.left + cornerWidth, frame.bottom, paint);
canvas.drawRect(frame.left, frame.bottom - cornerWidth, frame.left + cornerLength, frame.bottom, paint);
//右下角
canvas.drawRect(frame.right - cornerLength, frame.bottom - cornerWidth, frame.right, frame.bottom, paint);
canvas.drawRect(frame.right - cornerWidth, frame.bottom - cornerLength, frame.right, frame.bottom, paint);
paint.reset();
}
/*画扫描线*/
private void drawBitmap(final Rect frame, Canvas canvas) {
if (animator == null) {
animator = ValueAnimator.ofFloat(frame.top, frame.bottom);
animator.setDuration(3000);
animator.setRepeatCount(ValueAnimator.INFINITE);
animator.setRepeatMode(ValueAnimator.RESTART);
animator.setInterpolator(new LinearInterpolator());
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
x = (Float) animation.getAnimatedValue();
postInvalidate(
frame.left - POINT_SIZE,
frame.top - POINT_SIZE,
frame.right + POINT_SIZE,
frame.bottom + POINT_SIZE);
}
});
}
if (flag) {
animator.start();
flag = false;
}
tmpRect.set(frame.left, (int) x, frame.right, (int) x + 40);
canvas.drawBitmap(tmpBitmap, null, tmpRect, paint);
}
private Rect tmpRect1 = new Rect();
private void drawBottomText(Rect frame, Canvas canvas) {
if (textVisible) {
paint.setTextSize(text_size);
paint.setColor(textColor);
paint.setTextAlign(Paint.Align.CENTER);
paint.getTextBounds(description, 0, description.length(), tmpRect1);
canvas.drawText(description, 0, description.length(), frame.centerX(),
frame.centerY() + frame.height() / 2 + textMargin + tmpRect1.height(), paint);
paint.reset();
}
}
/**
* 将dip或dp值转换为px值,保证尺寸大小不变
*/
public static int dip2px(Context context, float dipValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dipValue * scale + 0.5f);
}
/**
* 将sp值转换为px值,保证文字大小不变
*/
public static int sp2px(Context context, float spValue) {
final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
return (int) (spValue * fontScale + 0.5f);
}
@Override
public boolean onScale(ScaleGestureDetector detector) {
if (mCameraZoomListener != null) {
mCameraZoomListener.onZooming(false, false, false, detector.getScaleFactor());
}
return true;
}
@Override
public boolean onScaleBegin(ScaleGestureDetector detector) {
return true;
}
@Override
public void onScaleEnd(ScaleGestureDetector detector) {
}
@Override
public boolean onTouch(View v, MotionEvent event) {
scaleGestureDetector.onTouchEvent(event);
gestureDetector.onTouchEvent(event);
return true;
}
public interface CameraZoomListener {
/**
* @param isDouble 是否双击
* @param isMax //是否移动到最大
* @param scaleValue //缩小放大的值
* @param isSingle //是否单击
*/
void onZooming(boolean isDouble, boolean isSingle, boolean isMax, float scaleValue);
}
private CameraZoomListener mCameraZoomListener;
public void setCameraZoomListener(CameraZoomListener mCameraZoomListener) {
this.mCameraZoomListener = mCameraZoomListener;
}
}
如果生产环境的话,可以根据具体的产品修改界面
项目地址:https://github.com/ASCN-BJ/ZXingLibrary