之前在开发一个项目的时候,项目需求有二维码扫描识别,扫描界面类似微信扫描,但是扫描框要偏上,还要有闪光灯功能。
我找了很多的资料,都没有一步到位的,后来也是一点点修改BUG,慢慢的找代码,还要跳出各种坑,所以首先写这个博客为了以后自己开发方便,同时也方便大家使用。技术水平有限,各位大牛大神请轻喷,谢谢。
最开始我找了很多精简的zxing,里面代码都差不多,选了其中一个,在此基础上修改的,这是我修改后的:
点击打开链接
1.修改预览变形问题,com.zxing.camera.CameraConfigurationManager这个类里修改一行代码,在第146行改为:
float newDiff = Math.abs(screenResolution.x * 1.0f / newY - screenResolution.y * 1.0f / newX);
//扫描线的颜色渐变
public int[] colors = new int[]{0x3303d5fb, 0xff03d5fb, 0x3303d5fb};
public float[] position = new float[]{0f, 0.5f, 1f};
//四个角的颜色
public int conerColor = 0xff03d5fb;
@Override
public void onDraw(Canvas canvas) {
Rect frame = CameraManager.get().getFramingRect();
if (frame == null) {
return;
}
int width = canvas.getWidth();
int height = canvas.getHeight();
//描绘四周半透明
paint.setColor(resultBitmap != null ? resultColor : maskColor);
canvas.drawRect(0, 0, width, frame.top, paint);
canvas.drawRect(0, frame.top, frame.left, frame.bottom, paint);
canvas.drawRect(frame.right, frame.top, width, frame.bottom, paint);
canvas.drawRect(0, frame.bottom, width, height, paint);
if (resultBitmap != null) {
// Draw the opaque result bitmap over the scanning rectangle
paint.setAlpha(OPAQUE);
canvas.drawBitmap(resultBitmap, frame.left, frame.top, paint);
} else {
//绘制4个角
paint.setColor(conerColor);//定义画笔的颜色
///////////////////////////////为了适配屏幕修改的/////////////////////////
int lineLong = 70 * widthPixels/1080;
int lineWidth = 10 * widthPixels/1080;
canvas.drawRect(frame.left, frame.top, frame.left + lineLong, frame.top + lineWidth, paint);
canvas.drawRect(frame.left, frame.top, frame.left + lineWidth, frame.top + lineLong, paint);
canvas.drawRect(frame.right - lineLong, frame.top, frame.right, frame.top + lineWidth, paint);
canvas.drawRect(frame.right - lineWidth, frame.top, frame.right, frame.top + lineLong, paint);
canvas.drawRect(frame.left, frame.bottom - lineWidth, frame.left + lineLong, frame.bottom, paint);
canvas.drawRect(frame.left, frame.bottom - lineLong, frame.left + lineWidth, frame.bottom, paint);
canvas.drawRect(frame.right - lineLong, frame.bottom - lineWidth, frame.right, frame.bottom, paint);
canvas.drawRect(frame.right - lineWidth, frame.bottom - lineLong, frame.right, frame.bottom, 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 {
// paint.setAlpha(SCANNER_ALPHA[scannerAlpha]);
// scannerAlpha = (scannerAlpha + 1) % SCANNER_ALPHA.length;
int middle = frame.height() / 2 + frame.top;
laserLinePosition = laserLinePosition + 5*widthPixels/1080;
if (laserLinePosition > frame.height()) {
laserLinePosition = 0;
}
linearGradient = new LinearGradient(frame.left + 1, frame.top + laserLinePosition, frame.right - 1, frame.top + 10 + laserLinePosition, colors, position, Shader.TileMode.CLAMP);
// Draw a red "laser scanner" line through the middle to show decoding is active
//paint.setColor(laserColor);
paint.setShader(linearGradient);
//绘制扫描线
canvas.drawRect(frame.left + 1, frame.top + laserLinePosition, frame.right - 1, frame.top + lineWidth + laserLinePosition, paint);
paint.setShader(null);
float scaleX = frame.width() / 50.0f;
float scaleY = frame.height() / 50.0f;
Collection currentPossible = possibleResultPoints;
Collection 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);
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);
float radius = POINT_SIZE / 2.0f;
for (ResultPoint point : currentLast) {
canvas.drawCircle(frameLeft + (int) (point.getX() * scaleX),
frameTop + (int) (point.getY() * scaleY),
radius, paint);
}
}
}
//刷新控制
postInvalidateDelayed(10, frame.left, frame.top, frame.right, frame.bottom);
}
}
控制识别框大小和位置的关键代码,适配系数还有Rect的位置根据自己的需求修改:
public Rect getFramingRect() {
Point screenResolution = configManager.getScreenResolution();
if (framingRect == null) {
if (camera == null) {
return null;
}
//屏幕适配控制系数screenResolution.x/800
int width = screenResolution.x * 8 / 13;
int leftOffset = (screenResolution.x - width) / 2;
int topOffset = screenResolution.y * 5 / 13 - width / 2;
framingRect = new Rect(leftOffset, topOffset, leftOffset + width, topOffset + width);
Log.d(TAG, "Calculated framing rect: " + framingRect);
}
return framingRect;
}
package com.zxing.camera;
import android.content.Context;
import android.graphics.PixelFormat;
import android.graphics.Point;
import android.graphics.Rect;
import android.hardware.Camera;
import android.os.Build;
import android.os.Handler;
import android.util.Log;
import android.view.SurfaceHolder;
import java.io.IOException;
public final class CameraManager {
private static final String TAG = CameraManager.class.getSimpleName();
private static CameraManager cameraManager;
static final int SDK_INT; // Later we can use Build.VERSION.SDK_INT
static {
int sdkInt;
try {
sdkInt = Integer.parseInt(Build.VERSION.SDK);
} catch (NumberFormatException nfe) {
// Just to be safe
sdkInt = 10000;
}
SDK_INT = sdkInt;
}
private final Context context;
private final CameraConfigurationManager configManager;
private static Camera camera;
private Rect framingRect;
private Rect framingRectInPreview;
private boolean initialized;
private static boolean previewing;
private static boolean useOneShotPreviewCallback;
private static PreviewCallback previewCallback;
private static AutoFocusCallback autoFocusCallback;
public static void init(Context context) {
if (cameraManager == null) {
cameraManager = new CameraManager(context);
}
}
/**
* 获取相机,用于打开闪光灯
* @return
*/
public static Camera getCamera(){
return camera;
}
public static CameraManager get() {
return cameraManager;
}
private CameraManager(Context context) {
this.context = context;
this.configManager = new CameraConfigurationManager(context);
useOneShotPreviewCallback = Integer.parseInt(Build.VERSION.SDK) > 3; // 3 = Cupcake
previewCallback = new PreviewCallback(configManager, useOneShotPreviewCallback);
autoFocusCallback = new AutoFocusCallback();
}
public void openDriver(SurfaceHolder holder) throws IOException {
if (camera == null) {
camera = Camera.open();
if (camera == null) {
throw new IOException();
}
camera.setPreviewDisplay(holder);
if (!initialized) {
initialized = true;
configManager.initFromCameraParameters(camera);
}
configManager.setDesiredCameraParameters(camera);
FlashlightManager.enableFlashlight();
} else {
camera.setPreviewDisplay(holder);
}
}
public void closeDriver() {
if (camera != null) {
FlashlightManager.disableFlashlight();
if (previewing){
camera.release();
}
camera.release();
camera = null;
previewing = false;
}
}
public void startPreview() {
if (camera != null && !previewing) {
camera.startPreview();
previewing = true;
}
}
public static void stopPreview() {
if (camera != null && previewing) {
if (!useOneShotPreviewCallback) {
camera.setPreviewCallback(null);
}
camera.stopPreview();
previewCallback.setHandler(null, 0);
autoFocusCallback.setHandler(null, 0);
previewing = false;
}
}
public void requestPreviewFrame(Handler handler, int message) {
if (camera != null && previewing) {
previewCallback.setHandler(handler, message);
if (useOneShotPreviewCallback) {
camera.setOneShotPreviewCallback(previewCallback);
} else {
camera.setPreviewCallback(previewCallback);
}
}
}
public void requestAutoFocus(Handler handler, int message) {
if (camera != null && previewing) {
autoFocusCallback.setHandler(handler, message);
//Log.d(TAG, "Requesting auto-focus callback");
camera.autoFocus(autoFocusCallback);
}
}
public Rect getFramingRect() {
Point screenResolution = configManager.getScreenResolution();
if (framingRect == null) {
if (camera == null) {
return null;
}
//屏幕适配控制系数screenResolution.x/800
int width = screenResolution.x * 8 / 13;
int leftOffset = (screenResolution.x - width) / 2;
int topOffset = screenResolution.y * 5 / 13 - width / 2;
framingRect = new Rect(leftOffset, topOffset, leftOffset + width, topOffset + width);
Log.d(TAG, "Calculated framing rect: " + framingRect);
}
return framingRect;
}
public Rect getFramingRectInPreview() {
if (framingRectInPreview == null) {
Rect rect = new Rect(getFramingRect());
Point cameraResolution = configManager.getCameraResolution();
Point screenResolution = configManager.getScreenResolution();
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;
framingRectInPreview = rect;
}
return framingRectInPreview;
}
public PlanarYUVLuminanceSource buildLuminanceSource(byte[] data, int width, int height) {
Rect rect = getFramingRectInPreview();
int previewFormat = configManager.getPreviewFormat();
String previewFormatString = configManager.getPreviewFormatString();
switch (previewFormat) {
case PixelFormat.YCbCr_420_SP:
case PixelFormat.YCbCr_422_SP:
return new PlanarYUVLuminanceSource(data, width, height, rect.left, rect.top,
rect.width(), rect.height());
default:
if ("yuv420p".equals(previewFormatString)) {
return new PlanarYUVLuminanceSource(data, width, height, rect.left, rect.top,
rect.width(), rect.height());
}
}
throw new IllegalArgumentException("Unsupported picture format: " +
previewFormat + '/' + previewFormatString);
}
public Context getContext() {
return context;
}
}
但是,这个类里代码又太多,又不想改动多了会乱,我的方法是自己写个类来继承CaptureActivity,在这个自定义类里自己写都不会怕改错CaptureActivity类,所以,首先对CaptureActivity稍做修改:
布局:
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.capture);
//ViewUtil.addTopView(getApplicationContext(), this, R.string.scan_card);
CameraManager.init(getApplication());
viewfinderView = (ViewfinderView) findViewById(R.id.viewfinder_view);
hasSurface = false;
inactivityTimer = new InactivityTimer(this);
childConrainer = (LinearLayout)findViewById(R.id.child_container_ll);
camera = CameraManager.getCamera();
}
public class MyZxingActivity extends CaptureActivity {
private CheckBox flash;
private Camera camera;
private Camera.Parameters parameter;
private RadioGroup radioGroup;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
View view = View.inflate(this, R.layout.activity_my_zxing, null);
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
childContainer.addView(view, 0, layoutParams);
initView();
}
private void initView() {
//控制闪光灯
flash = (CheckBox) findViewById(R.id.cb_cancel_scan);
flash.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
camera = CameraManager.getCamera();
parameter = camera.getParameters();
// TODO 开灯
if (b) {
parameter.setFlashMode(Camera.Parameters.FLASH_MODE_TORCH);
camera.setParameters(parameter);
} else { // 关灯
parameter.setFlashMode(Camera.Parameters.FLASH_MODE_OFF);
camera.setParameters(parameter);
}
}
});
}
}
自定义类的布局:
这是我修改的zxing,是一个module,import进去:
再在“app”这个module依赖这个zxing即可。
链接在这里→→修改好的zxing
本人水平有限,有不足之处欢迎批评指正,谢谢。