最近项目中又用到了扫一扫功能,仅以此文记录一下,扫一扫的集成与自定义扫一扫页面以及对于扫描出来的结果根据项目需求进行处理,有需要的小伙伴儿 可以参考一下。
首先,为了方便二次开发,所以集成源码,此处集成的是一个二次封装过的zxing 库,该库对扫一扫页面已经进行了处理,UI的展示比较美观,第二就是加上了 闪光灯与从相册扫描。
具体步骤如下:
此处贴上 二次封装的 库的地址:https://github.com/yuzhiqiang1993/zxing
大家可以根据需要,自行下载导入。
此处为了开发方便,我直接把源码拷贝进 app 目录下面。
截图如下:
这样做的原因是 考虑到 我们的项目中需要根据扫码类型的不同 跳转不同的页面,还有扫码之后 ,只添加商品不跳转页面的需求,所以,都在同一module 下,方便处理扫码结果。
第二步,将所需要用到的权限,还有activity 拷贝到 配置文件中。
如图所示:
此处权限截图 不放出来,可以参考zxing源码拷贝相关文章,不是什么问题。
第三步,就到了自定义 扫一扫页面。
实现效果如下:
这个效果,实现出来,有一个难点就是 提示文字的位置与文字的换行,此处的效果,利用的drawText 下面是实现代码。
以下所有代码 均在 扫一扫自定义ViewfinderView中
//提示文字的画笔
private TextPaint textPaint;
//扫一扫提示文字 此处显示为 我所需的默认提示语 ,可以根据项目需要自定义
private String reminderText = Constants.ScanCodeReminder.DEFAULT_REMINDER;
/*提示文字的画笔*/
textPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
textPaint.setStyle(Paint.Style.FILL);
textPaint.setColor(getResources().getColor(R.color.white));
textPaint.setTextSize(dp2px(14));
接下来就是文字的换行问题,昨天搜索好久,终于发现一个神器 StaticLayout ,此处不做过多的解释。
StaticLayout staticLayout1 = new StaticLayout(reminderText, textPaint, dp2px(300),
Layout.Alignment.ALIGN_CENTER, 2, 0, true);
canvas.save();
canvas.translate(frame.left - dp2px(40), frame.bottom + dp2px(40));
staticLayout1.draw(canvas);
canvas.restore();
此处根据项目需要 new StaticLayout(reminderText, textPaint, dp2px(300), Layout.Alignment.ALIGN_CENTER, 2, 0, true);
这个方法中四个参数分别为:
1提示文字 2画笔 3 文字如果过多的话展示的宽度,多余此换行显示 4 文字的显示类型,居中或者左对齐或者右对齐
5.行间距 此处的2 意思就是文字高度的二倍 6 此处数字代表的意思就是在行间距已经设置的基础上 要再加多少 7 boolean includepad 没有过多研究 所以此处不做解释 感兴趣的小伙伴儿自己去研究一下
至此,已经很完美的解决了 提示文字的问题。
接下来就是因为提示文字要根据 功能的不同,提示文字不同,所以需要通过zxingconfig类来进行设置。
很简单,就是给zxingconfig 加上一个参数,set,get 该参数 就可以了。
接下来 ,贴出完整的代码。
package com.yzq.zxinglibrary.bean;
import android.support.annotation.ColorRes;
import com.haigoubeibei.app.R;
import com.yzq.zxinglibrary.callback.ScanSuccessCallback;
import java.io.Serializable;
/**
* @author: yzq
* @date: 2017/10/27 14:48
* @declare :zxing配置类
*/
public class ZxingConfig implements Serializable {
/*是否播放声音*/
private boolean isPlayBeep = true;
/*是否震动*/
private boolean isShake = true;
/*是否显示下方的其他功能布局*/
private boolean isShowbottomLayout = true;
/*是否显示闪光灯按钮*/
private boolean isShowFlashLight = true;
/*是否显示相册按钮*/
private boolean isShowAlbum = true;
/*是否解析条形码*/
private boolean isDecodeBarCode = true;
/*是否全屏扫描*/
private boolean isFullScreenScan = true;
/*提示文字*/
private String textReminder;
/*四个角的颜色*/
@ColorRes
private int reactColor=R.color.react;
/*扫描框颜色*/
@ColorRes
private int frameLineColor = -1;
/*扫描线颜色*/
@ColorRes
private int scanLineColor=R.color.scanLineColor;
public void setTextReminder(String textReminder) {
this.textReminder = textReminder;
}
public String getTextReminder() {
return textReminder;
}
public int getFrameLineColor() {
return frameLineColor;
}
public void setFrameLineColor(@ColorRes int frameLineColor) {
this.frameLineColor = frameLineColor;
}
public int getScanLineColor() {
return scanLineColor;
}
public void setScanLineColor(@ColorRes int scanLineColor) {
this.scanLineColor = scanLineColor;
}
public int getReactColor() {
return reactColor;
}
public void setReactColor(@ColorRes int reactColor) {
this.reactColor = reactColor;
}
public boolean isFullScreenScan() {
return isFullScreenScan;
}
public void setFullScreenScan(boolean fullScreenScan) {
isFullScreenScan = fullScreenScan;
}
public boolean isDecodeBarCode() {
return isDecodeBarCode;
}
public void setDecodeBarCode(boolean decodeBarCode) {
isDecodeBarCode = decodeBarCode;
}
public boolean isPlayBeep() {
return isPlayBeep;
}
public void setPlayBeep(boolean playBeep) {
isPlayBeep = playBeep;
}
public boolean isShake() {
return isShake;
}
public void setShake(boolean shake) {
isShake = shake;
}
public boolean isShowbottomLayout() {
return isShowbottomLayout;
}
public void setShowbottomLayout(boolean showbottomLayout) {
isShowbottomLayout = showbottomLayout;
}
public boolean isShowFlashLight() {
return isShowFlashLight;
}
public void setShowFlashLight(boolean showFlashLight) {
isShowFlashLight = showFlashLight;
}
public boolean isShowAlbum() {
return isShowAlbum;
}
public void setShowAlbum(boolean showAlbum) {
isShowAlbum = showAlbum;
}
}
接下来是自定义view中的完整代码
package com.yzq.zxinglibrary.view;
import android.animation.ValueAnimator;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.support.annotation.Nullable;
import android.support.v4.content.ContextCompat;
import android.text.Layout;
import android.text.StaticLayout;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;
import android.view.animation.DecelerateInterpolator;
import com.google.zxing.ResultPoint;
import com.haigoubeibei.app.R;
import com.haigoubeibei.app.app.Constants;
import com.haigoubeibei.app.tool.util.StringUtil;
import com.yzq.zxinglibrary.bean.ZxingConfig;
import com.yzq.zxinglibrary.camera.CameraManager;
import java.util.ArrayList;
import java.util.List;
public final class ViewfinderView extends View {
/*界面刷新间隔时间*/
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 Paint paint, scanLinePaint, reactPaint, frameLinePaint;
private Bitmap resultBitmap;
private int maskColor; // 取景框外的背景颜色
private int resultColor;// result Bitmap的颜色
private int resultPointColor; // 特征点的颜色
private int reactColor;//四个角的颜色
private int scanLineColor;//扫描线的颜色
private int frameLineColor = -1;//边框线的颜色
private List possibleResultPoints;
private List lastPossibleResultPoints;
// 扫描线移动的y
private int scanLineTop;
private ZxingConfig config;
private ValueAnimator valueAnimator;
//取景框
private Rect frame;
//提示文字的画笔
private TextPaint textPaint;
//扫一扫提示文字
private String reminderText = Constants.ScanCodeReminder.DEFAULT_REMINDER;
public ViewfinderView(Context context) {
this(context, null);
}
public ViewfinderView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public void setZxingConfig(ZxingConfig config) {
this.config = config;
reactColor = ContextCompat.getColor(getContext(), config.getReactColor());
if (config.getFrameLineColor() != -1) {
frameLineColor = ContextCompat.getColor(getContext(), config.getFrameLineColor());
}
if (!StringUtil.isNull(config.getTextReminder())) {
reminderText = config.getTextReminder();
}
scanLineColor = ContextCompat.getColor(getContext(), config.getScanLineColor());
initPaint();
}
public ViewfinderView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
maskColor = ContextCompat.getColor(getContext(), R.color.viewfinder_mask);
resultColor = ContextCompat.getColor(getContext(), R.color.result_view);
resultPointColor = ContextCompat.getColor(getContext(), R.color.possible_result_points);
possibleResultPoints = new ArrayList(10);
lastPossibleResultPoints = null;
}
private void initPaint() {
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
/*四个角的画笔*/
reactPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
reactPaint.setColor(reactColor);
reactPaint.setStyle(Paint.Style.FILL);
reactPaint.setStrokeWidth(dp2px(1));
/*边框线画笔*/
if (frameLineColor != -1) {
frameLinePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
frameLinePaint.setColor(ContextCompat.getColor(getContext(), config.getFrameLineColor()));
frameLinePaint.setStrokeWidth(dp2px(1));
frameLinePaint.setStyle(Paint.Style.STROKE);
}
/*扫描线画笔*/
scanLinePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
scanLinePaint.setStrokeWidth(dp2px(2));
scanLinePaint.setStyle(Paint.Style.FILL);
scanLinePaint.setDither(true);
scanLinePaint.setColor(scanLineColor);
/*提示文字的画笔*/
textPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
textPaint.setStyle(Paint.Style.FILL);
textPaint.setColor(getResources().getColor(R.color.white));
textPaint.setTextSize(dp2px(14));
}
private void initAnimator() {
if (valueAnimator == null) {
valueAnimator = ValueAnimator.ofInt(frame.top, frame.bottom);
valueAnimator.setDuration(3000);
valueAnimator.setInterpolator(new DecelerateInterpolator());
valueAnimator.setRepeatMode(ValueAnimator.RESTART);
valueAnimator.setRepeatCount(ValueAnimator.INFINITE);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
scanLineTop = (int) animation.getAnimatedValue();
invalidate();
}
});
valueAnimator.start();
}
}
public void setCameraManager(CameraManager cameraManager) {
this.cameraManager = cameraManager;
}
public void stopAnimator() {
if (valueAnimator != null) {
valueAnimator.end();
valueAnimator.cancel();
valueAnimator = null;
}
}
@SuppressLint("DrawAllocation")
@Override
public void onDraw(Canvas canvas) {
if (cameraManager == null) {
return;
}
// frame为取景框
frame = cameraManager.getFramingRect();
Rect previewFrame = cameraManager.getFramingRectInPreview();
if (frame == null || previewFrame == null) {
return;
}
initAnimator();
int width = canvas.getWidth();
int height = canvas.getHeight();
/*绘制遮罩*/
drawMaskView(canvas, frame, width, height);
/*绘制取景框边框*/
drawFrameBounds(canvas, frame);
if (resultBitmap != null) {
// Draw the opaque result bitmap over the scanning rectangle
// 如果有二维码结果的Bitmap,在扫取景框内绘制不透明的result Bitmap
paint.setAlpha(CURRENT_POINT_OPACITY);
canvas.drawBitmap(resultBitmap, null, frame, paint);
} else {
/*绘制扫描线*/
drawScanLight(canvas, frame);
/*绘制闪动的点*/
// drawPoint(canvas, frame, previewFrame);
}
StaticLayout staticLayout1 = new StaticLayout(reminderText, textPaint, dp2px(300),
Layout.Alignment.ALIGN_CENTER, 2, 0, true);
canvas.save();
canvas.translate(frame.left - dp2px(40), frame.bottom + dp2px(40));
staticLayout1.draw(canvas);
canvas.restore();
}
private void drawPoint(Canvas canvas, Rect frame, Rect previewFrame) {
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);
}
}
}
// 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);
}
private void drawMaskView(Canvas canvas, Rect frame, int width, int height) {
// 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 drawFrameBounds(Canvas canvas, Rect frame) {
/*扫描框的边框线*/
if (frameLineColor != -1) {
canvas.drawRect(frame, frameLinePaint);
}
/*四个角的长度和宽度*/
int width = frame.width();
int corLength = (int) (width * 0.07);
int corWidth = (int) (corLength * 0.2);
corWidth = corWidth > 15 ? 15 : corWidth;
/*角在线外*/
// 左上角
canvas.drawRect(frame.left - corWidth, frame.top, frame.left, frame.top
+ corLength, reactPaint);
canvas.drawRect(frame.left - corWidth, frame.top - corWidth, frame.left
+ corLength, frame.top, reactPaint);
// 右上角
canvas.drawRect(frame.right, frame.top, frame.right + corWidth,
frame.top + corLength, reactPaint);
canvas.drawRect(frame.right - corLength, frame.top - corWidth,
frame.right + corWidth, frame.top, reactPaint);
// 左下角
canvas.drawRect(frame.left - corWidth, frame.bottom - corLength,
frame.left, frame.bottom, reactPaint);
canvas.drawRect(frame.left - corWidth, frame.bottom, frame.left
+ corLength, frame.bottom + corWidth, reactPaint);
// 右下角
canvas.drawRect(frame.right, frame.bottom - corLength, frame.right
+ corWidth, frame.bottom, reactPaint);
canvas.drawRect(frame.right - corLength, frame.bottom, frame.right
+ corWidth, frame.bottom + corWidth, reactPaint);
}
/**
* 绘制移动扫描线
*
* @param canvas
* @param frame
*/
private void drawScanLight(Canvas canvas, Rect frame) {
canvas.drawLine(frame.left, scanLineTop, frame.right, scanLineTop, scanLinePaint);
}
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 int dp2px(int dp) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, getResources().getDisplayMetrics());
}
}
接下来 给出一个调用的例子:
/*传递的zxingconfing*/
public static final String INTENT_ZXING_CONFIG = "zxingConfig";
这个是拷贝的库里面自带的,防止有的小伙伴儿找不到,此处我贴出来。
还有一点儿需要注意的是,点击扫一扫跳转页面的时候,不要忘记加上权限的判断,拍照读写读写权限的判断。
如果有的小伙伴儿在权限的申请有困难,可以去翻看我之前的博客。
接下来是 ,对于扫描到的结果进行 灵活处理,不需要跳转页面等相关处理。
如图所示,获取扫描结果的方法中,进行相应业务的操作就可以了。
到此为止,自定义扫一扫就已经结束了。
如果解决了小伙伴儿的燃眉之急,坐等打赏,十块八块都是爱,么么哒。
参考文章:https://blog.csdn.net/yuzhiqiang_1993/article/details/78292004