最近我们公司项目需要实现二维码扫描功能,并且我们的产品需要我们实现如微信扫一扫的功能,也就是在扫一扫区域会有一个手电筒的图标,点击该区域可以打开闪光灯,当我们用手遮住摄像头这个图标也会显示,用会可以选择打开闪光灯,需求既然有了,下面就开始具体实现吧
一.首先集成Zxing到项目中 ,附上Zxing的项目地址
Zxing 打开链接 我们只需要将Android中的集成到我们的项目中
这个就是刚才所说的android的demo,新建一个android项目,将这个module导入工程并命名为zxinglib,在这个module里的gradle文件里添加依赖。
dependencies{
api 'com.google.zxing:android-core:3.3.0'
api 'com.google.zxing:core:3.3.2'
}
三 集成过程略过,现在我们主要看看怎么实现添加闪光灯
在CameraManager类中我们能看到下的代码,这个就是打开和关闭闪光灯的代码,网上有很多自己写的代码,远远不如这个好
/**
* Convenience method for {@link com.google.zxing.client.android.CaptureActivity}
*
* @param newSetting if {@code true}, light should be turned on if currently off. And vice versa.
*/
public synchronized void setTorch(boolean newSetting) {
OpenCamera theCamera = camera;
if (theCamera != null && newSetting != configManager.getTorchState(theCamera.getCamera())) {
boolean wasAutoFocusManager = autoFocusManager != null;
if (wasAutoFocusManager) {
autoFocusManager.stop();
autoFocusManager = null;
}
configManager.setTorch(theCamera.getCamera(), newSetting);
if (wasAutoFocusManager) {
autoFocusManager = new AutoFocusManager(context, theCamera.getCamera());
autoFocusManager.start();
}
}
}
知道开启和关闭闪光灯的方法后,下面就是什么时候开启闪光灯的图片出现呢, 我们需要找到ViewfinderView类 在onTouchEvent方法中根据当前的位置是否显示和隐藏闪光灯图片的位置来切换显示和隐藏图片
1.手动点击图片打开或者关闭闪光灯并切换图片 具体如下
public boolean onTouchEvent(MotionEvent event) {
// 获取点击屏幕时的点的坐标
float x = event.getX();
float y = event.getY();
if((x>drawableLeft&&x<drawableRight)
||(y>drawableTop&&y<drawableBottom)){
if(!isOpen){
cameraManager.setTorch(true);
}else{
cameraManager.setTorch(false);
}
// drawBitMap(isOpen);
isOpen=!isOpen;
}
return super.onTouchEvent(event);
}
2 .可以根据图片的光线的强弱是否显示打开闪光灯的图片
判断图片光线强弱的方法
第一步>定义一个接口监听图片光线强弱
public interface OnLightWeakListener {
void isLightWeak(boolean lightWeak);
}
第二步> 在PreviewCallback中可以获取来定义小于多少lux算暗 来自一位大神的代码如下
@SuppressWarnings("deprecation") // camera APIs
final class PreviewCallback implements Camera.PreviewCallback {
//小于多少Lux算是暗
private static final float TOO_DARK_LUX = 150.0f;
//在数据中采集的点数
private int sizePiece = 100;
private static final String TAG = PreviewCallback.class.getSimpleName();
private final CameraConfigurationManager configManager;
private Handler previewHandler;
private int previewMessage;
private OnLightWeakListener onLightWeakListenner;
PreviewCallback(CameraConfigurationManager configManager) {
this.configManager = configManager;
}
PreviewCallback(CameraConfigurationManager configManager, OnLightWeakListener onLightWeakListenner) {
this.configManager = configManager;
this.onLightWeakListenner = onLightWeakListenner;
}
void setHandler(Handler previewHandler, int previewMessage) {
this.previewHandler = previewHandler;
this.previewMessage = previewMessage;
}
@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");
}*/
setPreviewFrame(data, camera);
}
//监听光线的方法
private void setPreviewFrame(byte[] data, Camera camera) {
Point cameraResolution = configManager.getCameraResolution();
int size = cameraResolution.x * cameraResolution.y;
int y = 0;
for (int i = 0; i < size; i += size / sizePiece) {
y += data[i] & 0xff;
}
onLightWeakListenner.isLightWeak(y / sizePiece < TOO_DARK_LUX);
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, "...");
}
}
}
第三步> 我们发现在CameraManager类中的构造函数中初始化了PreviewCallback,我们知道我们在new PreviewCallback需要传入OnLightWeakListener 对象 因此
public CameraManager(Context context, OnLightWeakListener weakListener) {
this.context = context;
this.configManager = new CameraConfigurationManager(context);
previewCallback = new PreviewCallback(configManager,weakListener);
}
第四步>在我们的 CaptureActivity类中 初始化CameraManager时可监实现监听光线的方法
cameraManager = new CameraManager(getApplication(), new OnLightWeakListener() {
@Override
public void isLightWeak(boolean lightWeak) {//true 光线强 false 光线暗
//判断当前的闪光灯是否打开 如果已经打开显示图片 如果未打开而且光线暗则显示图片
boolean flashlightState = cameraManager.getLightOn();
Log.e(TAG, "isLightWeak: " + lightWeak + ",,,," + flashlightState);
if (flashlightState) {
viewfinderView.setShowLightBitmap(true);
} else {
viewfinderView.setShowLightBitmap(lightWeak);
}
Log.e(TAG, "isLightWeak: " + lightWeak);
}
});
到此我们的添加闪光灯功能就完成了 最后把ViewfinderView也贴出来
public final class ViewfinderView1 extends ViewfinderView
{
/**
* 字体大小
*/
private static final int TEXT_SIZE = 16;
/**
* 字体距离扫描框下面的距离
*/
private static final int TEXT_PADDING_TOP = 30;
/**
* 刷新界面的时间
*/
//private static final long ANIMATION_DELAY = 10L;
private static final long ANIMATION_DELAY = 10L;
private static final int OPAQUE = 0xFF;
/**
* 中间那条线每次刷新移动的距离
*/
private static final int SPEEN_DISTANCE = 10;
private static final int MAX_RESULT_POINTS = 20;
private static final String TAG = ViewfinderView1.class.getSimpleName();
/**
* 扫描框中的中间线的宽度
*/
private static int MIDDLE_LINE_WIDTH;
/**
* 扫描框中的中间线的与扫描框左右的间隙
*/
private static int MIDDLE_LINE_PADDING;
/**
* 遮掩层的颜色
*/
private final int maskColor;
private final int resultColor;
private final int resultPointColor;
private final int CORNER_PADDING;
/**
* 画笔对象的引用
*/
private final Paint paint;
/**
* 第一次绘制控件
*/
private boolean isFirst = true;
/**
* 中间滑动线的最顶端位置
*/
private int slideTop;
/**
* 中间滑动线的最底端位置
*/
private int slideBottom;
private Bitmap resultBitmap;
private List<ResultPoint> possibleResultPoints;
private List<ResultPoint> lastPossibleResultPoints;
private CameraManager cameraManager;
private boolean hasNotified = false;
private Context context;
//绘制显示的开灯关灯图片
private Resources mResources;
private Paint mBitPaint;
private Bitmap mBitmap;
private Rect mSrcRect, mDestRect;
private Rect frame;
private Canvas drawBitmapCanvas;//绘制图片
//绘制图片的上下左右坐标
private int drawableLeft;
private int drawableTop;
private int drawableBottom;
private int drawableRight;
//是否点击开灯图片
private boolean isOpen=false;
//是否绘画图片
private boolean isDraw = false;
private boolean showLightBitmap = false;//默认没有闪光灯图片 根据光感应判断
public void setOnDrawFinishListener(OnDrawFinishListener1 listener) {
this.listener = listener;
}
public void setShowLightBitmap(boolean showLightBitmap) {
this.showLightBitmap = showLightBitmap;
}
private OnDrawFinishListener1 listener;
// This constructor is used when the class is built from an XML resource.
public ViewfinderView1(Context context, AttributeSet attrs) {
super(context, attrs);
this.context = context;
CORNER_PADDING = dip2px(context, 0.0F);
MIDDLE_LINE_PADDING = dip2px(context, 20.0F);
MIDDLE_LINE_WIDTH = dip2px(context, 3.0F);
paint = new Paint(Paint.ANTI_ALIAS_FLAG); // 开启反锯齿
Resources resources = getResources();
maskColor = resources.getColor(R.color.viewfinder_mask); // 遮掩层颜色
resultColor = resources.getColor(R.color.result_view);
resultPointColor = resources.getColor(R.color.possible_result_points);
possibleResultPoints = new ArrayList<>(5);
lastPossibleResultPoints = null;
}
public void setCameraManager(CameraManager cameraManager) {
this.cameraManager = cameraManager;
}
@Override
public void onDraw(Canvas canvas) {
if (cameraManager == null) {
return; // not ready yet, early draw before done configuring
}
frame = cameraManager.getFramingRect();//获取二维码扫描识别区域,区域外遮罩层
if (frame == null) {
return;
}
drawBitmapCanvas = canvas;
//获取屏幕的宽和高
int width = this.getWidth();
int height = this.getHeight();
// 绘制遮掩层
drawCover(canvas, frame);
if (resultBitmap != null) { // 绘制扫描结果的图
// Draw the opaque result bitmap over the scanning rectangle
paint.setAlpha(0xA0);
canvas.drawBitmap(resultBitmap, null, frame, paint);
} else {
// 画扫描框边上的角
drawRectEdges(canvas, frame);
// 绘制扫描线
drawScanningLine(canvas, frame);
//画扫描框下面的字
paint.setColor(Color.parseColor("#99FFFFFF"));
paint.setTextSize(sp2px(context,12));
//paint.setAlpha(0x40);
paint.setTypeface(Typeface.DEFAULT_BOLD);
String text =context.getResources().getString(R.string.viewfinderView_hint);
float textWidth = paint.measureText(text);
canvas.drawText(text, (width - textWidth)/2, (float) (frame.bottom + dip2px(context,30)), paint);
List<ResultPoint> currentPossible = possibleResultPoints;
Collection<ResultPoint> currentLast = lastPossibleResultPoints;
if (currentPossible.isEmpty()) {
lastPossibleResultPoints = null;
} else {
possibleResultPoints = new ArrayList<>(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);
if (showLightBitmap) {
drawBitMap(isOpen);
}
if (listener != null && !hasNotified) {
listener.onDrawFinish(frame);
hasNotified = true;
}
}
}
/**
* 绘制扫描线
*
* @param frame 扫描框
*/
private void drawScanningLine(Canvas canvas, Rect frame) {
// 初始化中间线滑动的最上边和最下边
if (isFirst) {
isFirst = false;
slideTop = frame.top;
slideBottom = frame.bottom;
}
// 绘制中间的线,每次刷新界面,中间的线往下移动SPEEN_DISTANCE
slideTop += SPEEN_DISTANCE;
if (slideTop >= slideBottom) {
slideTop = frame.top;
}
// 从图片资源画扫描线
Rect lineRect = new Rect();
lineRect.left = frame.left + MIDDLE_LINE_PADDING;
lineRect.right = frame.right - MIDDLE_LINE_PADDING;
lineRect.top = slideTop;
lineRect.bottom = (slideTop + MIDDLE_LINE_WIDTH);
canvas.drawBitmap(BitmapFactory.decodeResource(getResources(), R.drawable.jdme_scan_laser), null,
lineRect, paint);
}
/**
* 绘制遮掩层
*/
private void drawCover(Canvas canvas, Rect frame) {
// 获取屏幕的宽和高
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);
}
/**
* 描绘方形的四个角
*
* @param canvas
* @param frame
*/
private void drawRectEdges(Canvas canvas, Rect frame) {
paint.setColor(Color.WHITE);
paint.setAlpha(OPAQUE);
Resources resources = getResources();
/**
* 这些资源可以用缓存进行管理,不需要每次刷新都新建
*/
Bitmap bitmapCornerTopleft = BitmapFactory.decodeResource(resources,
R.drawable.jdme_scan_corner_top_left);
Bitmap bitmapCornerTopright = BitmapFactory.decodeResource(resources,
R.drawable.jdme_scan_corner_top_right);
Bitmap bitmapCornerBottomLeft = BitmapFactory.decodeResource(resources,
R.drawable.jdme_scan_corner_bottom_left);
Bitmap bitmapCornerBottomRight = BitmapFactory.decodeResource(
resources, R.drawable.jdme_scan_corner_bottom_right);
canvas.drawBitmap(bitmapCornerTopleft, frame.left + CORNER_PADDING,
frame.top + CORNER_PADDING, paint);
canvas.drawBitmap(bitmapCornerTopright, frame.right - CORNER_PADDING
- bitmapCornerTopright.getWidth(), frame.top + CORNER_PADDING,
paint);
canvas.drawBitmap(bitmapCornerBottomLeft, frame.left + CORNER_PADDING,
2 + (frame.bottom - CORNER_PADDING - bitmapCornerBottomLeft
.getHeight()), paint);
canvas.drawBitmap(bitmapCornerBottomRight, frame.right - CORNER_PADDING
- bitmapCornerBottomRight.getWidth(), 2 + (frame.bottom
- CORNER_PADDING - bitmapCornerBottomRight.getHeight()), paint);
bitmapCornerTopleft.recycle();
bitmapCornerTopright.recycle();
bitmapCornerBottomLeft.recycle();
bitmapCornerBottomRight.recycle();
}
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<ResultPoint> 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();
}
}
}
//绘制图片,并且根据点击切换不同的图标,切换是否开灯图标
public void drawBitMap(boolean isOpen){
Log.e(TAG, "drawBitMap: ");
//绘制显示的图片
mResources = getResources();
if(mBitPaint==null){
mBitPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
}
mBitPaint.setFilterBitmap(true);
mBitPaint.setDither(true);
try {
if(isOpen){
mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.ic_light_on);;
}else{
mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.ic_light_off);
}
}catch (Exception e){
e.printStackTrace();
}
//要绘制的bitmap 区域
if(mSrcRect == null){
mSrcRect = new Rect(0, 0, mBitmap.getWidth(), mBitmap.getHeight());
}
//要将bitmap 绘制在屏幕的什么地方
// 计算左边位置
int left =frame.left+(frame.right-frame.left)/2- mBitmap.getWidth() / 2;
// 计算上边位置
int top = frame.bottom - dip2px (context,TEXT_PADDING_TOP)-mBitmap.getHeight();;
drawableLeft = left;
drawableRight = left+mBitmap.getWidth();
drawableTop = top;
drawableBottom = top+mBitmap.getHeight();
if(mDestRect == null){
mDestRect = new Rect(drawableLeft,drawableTop, drawableRight,drawableBottom);
}
invalidate(drawableLeft,drawableTop, drawableRight,drawableBottom);
drawBitmapCanvas.drawBitmap(mBitmap, mSrcRect, mDestRect, mBitPaint);
isDraw = true;
}
public boolean onTouchEvent(MotionEvent event) {
// 获取点击屏幕时的点的坐标
float x = event.getX();
float y = event.getY();
if((x>drawableLeft&&x<drawableRight)
||(y>drawableTop&&y<drawableBottom)){
if(!isOpen){
cameraManager.setTorch(true);
}else{
cameraManager.setTorch(false);
}
// drawBitMap(isOpen);
isOpen=!isOpen;
}
return super.onTouchEvent(event);
}
/**
* dp转px
*
* @param context
* @param dipValue
* @return
*/
private int dip2px(Context context, float dipValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dipValue * scale + 0.5f);
}
/**
* 将sp值转换为px值,保证文字大小不变
*
* @param spValue
* (DisplayMetrics类中属性scaledDensity)
* @return
*/
public static int sp2px(Context context, float spValue) {
final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
return (int) (spValue * fontScale + 0.5f);
}
public interface OnDrawFinishListener1 {
void onDrawFinish(Rect frame);
}
}