我们知道zxing是一个强大的处理二维码和条形码等的开源库,本篇文章记录一下自己在项目中集成zxing开源库的过程。
导入依赖
implementation 'com.google.zxing:core:3.3.3'
申请权限
在AndroidManifest中申请相应权限:
<!--相机--> <uses-permission android:name="android.permission.CAMERA" /> <!--震动--> <uses-permission android:name="android.permission.VIBRATE" /> <!--存储--> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
导入相关代码和资源文件
导入的代码文件如下(源码在末尾):
相关的资源文件:
1、在res/values下新建ids.xml文件,引入下面id:
<!--二维码/条形码扫描相关--> <item name="auto_focus" type="id" /> <item name="decode" type="id" /> <item name="decode_failed" type="id" /> <item name="decode_succeeded" type="id" /> <item name="encode_failed" type="id" /> <item name="encode_succeeded" type="id" /> <item name="launch_product_query" type="id" /> <item name="quit" type="id" /> <item name="restart_preview" type="id" /> <item name="return_scan_result" type="id" /> <item name="search_book_contents_failed" type="id" /> <item name="search_book_contents_succeeded" type="id" />
2、在res/values下新建attrs.xml文件,加入扫码框的属性,主要是ViewfinderView在使用:
<!--扫码框属性--> <declare-styleable name="ViewfinderView"> <attr name="corner_color" format="color" /> <attr name="corner_size" format="dimension" /> <attr name="corner_stroke_width" format="dimension" /> <attr name="corner_position" format="enum"> <enum name="inside" value="1" /> <enum name="outside" value="2" /> </attr> <attr name="line_color" format="color" /> <attr name="line_height" format="dimension" /> <attr name="line_move_distance" format="dimension" /> <attr name="frame_width" format="dimension" /> <attr name="frame_height" format="dimension" /> <attr name="frame_centerX" format="dimension" /> <attr name="frame_centerY" format="dimension" /> <attr name="frame_color" format="color" /> <attr name="frame_stroke_width" format="dimension" /> <attr name="mask_color" format="color" /> <attr name="result_point_color" format="color" /> <attr name="label_text" format="string" /> <attr name="label_text_color" format="color" /> <attr name="label_text_size" format="dimension" /> <attr name="label_text_margin" format="dimension" /> </declare-styleable>
3、在res下新建raw目录,导入beep.mp3,实现扫码成功的滴滴音效,BeepManager在使用
上面是一些比较重要的资源。
然后介绍一下几个主要的类:
1、ViewfinderView:自定义扫描框,代码如下,因为有注释,就不多说明了。
public final class ViewfinderView extends View { private static final long ANIMATION_DELAY = 10L; private static final int OPAQUE = 1; private static final int CORNER_INSIDE = 1; //四个边角在扫描区内 private static final int CORNER_OUTSIDE = 2; //四个边角在扫描区外 private Paint paint; //扫描区四个边角的颜色 private int cornerColor; //扫描区边角的大小 private float cornerSize; //扫描区边角的宽度 private float cornerStrokeWidth; //边角的方向,在扫描区域内还是扫描区域外 private int cornerPosition; //扫描线颜色 private int lineColor; //扫描线高度 private float lineHeight; //扫描线移动距离 private float lineMoveDistance; //扫描区域宽度度 private float frameWidth; //扫描区域高度 private float frameHeight; //扫描区域中心位置的X坐标,默认正中间,在onLayout中设置 private float frameCenterX; //扫描区域中心位置的Y坐标,默认正中间,在onLayout中设置 private float frameCenterY; //扫描区域边框颜色 private int frameColor; //扫描区域边框宽度 private float frameStrokeWidth; //模糊区域颜色 private int maskColor; //扫描点的颜色 private int resultPointColor; //扫描区域提示文本 private String labelText; //扫描区域提示文本颜色 private int labelTextColor; //扫描区域提示文本字体大小 private float labelTextSize; //扫描区域提示文本的边距 private float labelTextMargin; public static int scannerStart = 0; public static int scannerEnd = 0; private Collection<ResultPoint> possibleResultPoints; private Collection<ResultPoint> lastPossibleResultPoints; // This constructor is used when the class is built from an XML resource. public ViewfinderView(Context context, AttributeSet attrs) { super(context, attrs); //初始化自定义属性信息 TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.ViewfinderView); cornerColor = ta.getColor(R.styleable.ViewfinderView_corner_color, getResources().getColor(R.color.colorPrimary)); cornerSize = ta.getDimension(R.styleable.ViewfinderView_corner_size, dp2px(context, 28)); cornerStrokeWidth = ta.getDimension(R.styleable.ViewfinderView_corner_stroke_width, dp2px(context, 4)); cornerPosition = ta.getInt(R.styleable.ViewfinderView_corner_position, CORNER_INSIDE); lineColor = ta.getColor(R.styleable.ViewfinderView_line_color, getResources().getColor(R.color.colorPrimary)); lineHeight = ta.getDimension(R.styleable.ViewfinderView_line_height, dp2px(context, 3)); lineMoveDistance = ta.getDimension(R.styleable.ViewfinderView_line_move_distance, dp2px(context, 2)); frameWidth = ta.getDimension(R.styleable.ViewfinderView_frame_width, dp2px(context, 220)); frameHeight = ta.getDimension(R.styleable.ViewfinderView_frame_height, dp2px(context, 220)); frameCenterX = ta.getDimension(R.styleable.ViewfinderView_frame_centerX, -1); frameCenterY = ta.getDimension(R.styleable.ViewfinderView_frame_centerY, -1); frameColor = ta.getColor(R.styleable.ViewfinderView_frame_color, Color.parseColor("#90FFFFFF")); frameStrokeWidth = ta.getDimension(R.styleable.ViewfinderView_frame_stroke_width, dp2px(context, 0.2f)); maskColor = ta.getColor(R.styleable.ViewfinderView_mask_color, Color.parseColor("#60000000")); resultPointColor = ta.getColor(R.styleable.ViewfinderView_result_point_color, Color.TRANSPARENT); labelText = ta.getString(R.styleable.ViewfinderView_label_text); labelTextColor = ta.getColor(R.styleable.ViewfinderView_label_text_color, Color.WHITE); labelTextSize = ta.getDimension(R.styleable.ViewfinderView_label_text_size, sp2px(context, 15)); labelTextMargin = ta.getDimension(R.styleable.ViewfinderView_label_text_margin, dp2px(context, 18)); ta.recycle(); paint = new Paint(); paint.setAntiAlias(true); possibleResultPoints = new HashSet<ResultPoint>(5); } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); //如果没有设置frameCenterX和frameCenterY默认布局正中间的X、Y坐标 frameCenterX = (frameCenterX == -1) ? getWidth() / 2f : frameCenterX; frameCenterY = (frameCenterY == -1) ? getHeight() / 2f : frameCenterY; //设置扫描区域位置 int leftOffset = (int) (frameCenterX - frameWidth / 2f); int topOffset = (int) (frameCenterY - frameHeight / 2f); //设置扫描区不超过屏幕 leftOffset = leftOffset > 0 ? leftOffset : 0; topOffset = topOffset > 0 ? topOffset : 0; Rect rect = new Rect(); rect.left = leftOffset; rect.top = topOffset; rect.right = (int) (leftOffset + frameWidth); rect.bottom = (int) (topOffset + frameHeight); CameraManager.get().setFramingRect(rect); } @Override public void onDraw(Canvas canvas) { Rect frame = CameraManager.get().getFramingRect(); if (frame == null) { return; } if (scannerStart == 0 || scannerEnd == 0) { scannerStart = frame.top; scannerEnd = frame.bottom; } int width = canvas.getWidth(); int height = canvas.getHeight(); //绘制模糊区域 drawExterior(canvas, frame, width, height); //绘制扫描区边框 drawFrame(canvas, frame); //绘制边角 drawCorner(canvas, frame); //绘制提示信息 drawTextInfo(canvas, frame); //绘制扫描线 drawScanLine(canvas, frame); //绘制闪烁点 drawResultPoint(canvas, frame); // Request another update at the animation interval, but only repaint the laser line, // not the entire viewfinder mask. //指定重绘区域,该方法会在子线程中执行 postInvalidateDelayed(ANIMATION_DELAY, frame.left, frame.top, frame.right, frame.bottom); } // 绘制模糊区域 Draw the exterior (i.e. outside the framing rect) darkened private void drawExterior(Canvas canvas, Rect frame, int width, int height) { paint.setColor(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); } // 绘制扫描区边框 Draw a two pixel solid black border inside the framing rect private void drawFrame(Canvas canvas, Rect frame) { if (frameStrokeWidth > 0) { paint.setColor(frameColor); if (cornerPosition == CORNER_INSIDE) { //边角在扫描区内 //左边 canvas.drawRect(frame.left, frame.top, frame.left + frameStrokeWidth, frame.bottom, paint); //上边 canvas.drawRect(frame.left, frame.top, frame.right, frame.top + frameStrokeWidth, paint); //右边 canvas.drawRect(frame.right - frameStrokeWidth, frame.top, frame.right, frame.bottom, paint); //下边 canvas.drawRect(frame.left, frame.bottom - frameStrokeWidth, frame.right, frame.bottom, paint); } else { //边角在扫描区外 //左边 canvas.drawRect(frame.left - frameStrokeWidth, frame.top - frameStrokeWidth, frame.left, frame.bottom + frameStrokeWidth, paint); //上边 canvas.drawRect(frame.left - frameStrokeWidth, frame.top - frameStrokeWidth, frame.right + frameStrokeWidth, frame.top, paint); //右边 canvas.drawRect(frame.right, frame.top - frameStrokeWidth, frame.right + frameStrokeWidth, frame.bottom + frameStrokeWidth, paint); //下边 canvas.drawRect(frame.left - frameStrokeWidth, frame.bottom, frame.right + frameStrokeWidth, frame.bottom + frameStrokeWidth, paint); } } } //绘制边角 private void drawCorner(Canvas canvas, Rect frame) { if (cornerSize > 0 && cornerStrokeWidth > 0) { paint.setColor(cornerColor); if (cornerPosition == CORNER_INSIDE) { //绘制在扫描区域内区 //左上 canvas.drawRect(frame.left, frame.top, frame.left + cornerSize, frame.top + cornerStrokeWidth, paint); canvas.drawRect(frame.left, frame.top, frame.left + cornerStrokeWidth, frame.top + cornerSize, paint); //右上 canvas.drawRect(frame.right - cornerSize, frame.top, frame.right, frame.top + cornerStrokeWidth, paint); canvas.drawRect(frame.right - cornerStrokeWidth, frame.top, frame.right, frame.top + cornerSize, paint); //左下 canvas.drawRect(frame.left, frame.bottom - cornerSize, frame.left + cornerStrokeWidth, frame.bottom, paint); canvas.drawRect(frame.left, frame.bottom - cornerStrokeWidth, frame.left + cornerSize, frame.bottom, paint); //右下 canvas.drawRect(frame.right - cornerSize, frame.bottom - cornerStrokeWidth, frame.right, frame.bottom, paint); canvas.drawRect(frame.right - cornerStrokeWidth, frame.bottom - cornerSize, frame.right, frame.bottom, paint); } else { //绘制在扫描区域外区 //左上 canvas.drawRect(frame.left - cornerStrokeWidth, frame.top - cornerStrokeWidth, frame.left - cornerStrokeWidth + cornerSize, frame.top, paint); canvas.drawRect(frame.left - cornerStrokeWidth, frame.top - cornerStrokeWidth, frame.left, frame.top - cornerStrokeWidth + cornerSize, paint); //右上 canvas.drawRect(frame.right + cornerStrokeWidth - cornerSize, frame.top - cornerStrokeWidth, frame.right + cornerStrokeWidth, frame.top, paint); canvas.drawRect(frame.right, frame.top - cornerStrokeWidth, frame.right + cornerStrokeWidth, frame.top - cornerStrokeWidth + cornerSize, paint); //左下 canvas.drawRect(frame.left - cornerStrokeWidth, frame.bottom, frame.left - cornerStrokeWidth + cornerSize, frame.bottom + cornerStrokeWidth, paint); canvas.drawRect(frame.left - cornerStrokeWidth, frame.bottom + cornerStrokeWidth - cornerSize, frame.left, frame.bottom + cornerStrokeWidth, paint); //右下 canvas.drawRect(frame.right + cornerStrokeWidth - cornerSize, frame.bottom, frame.right + cornerStrokeWidth, frame.bottom + cornerStrokeWidth, paint); canvas.drawRect(frame.right, frame.bottom + cornerStrokeWidth - cornerSize, frame.right + cornerStrokeWidth, frame.bottom + cornerStrokeWidth, paint); } } } //绘制文本 private void drawTextInfo(Canvas canvas, Rect frame) { if (!TextUtils.isEmpty(labelText)) { paint.setColor(labelTextColor); paint.setTextSize(labelTextSize); paint.setTextAlign(Paint.Align.CENTER); Paint.FontMetrics fm = paint.getFontMetrics(); float baseY = frame.bottom + labelTextMargin - fm.ascent; canvas.drawText(labelText, frame.left + frame.width() / 2, baseY, paint); } } //绘制扫描线 private void drawScanLine(Canvas canvas, Rect frame) { if (lineHeight > 0) { paint.setColor(lineColor); RadialGradient radialGradient = new RadialGradient( (float) (frame.left + frame.width() / 2), (float) (scannerStart + lineHeight / 2), 360f, lineColor, shadeColor(lineColor), Shader.TileMode.MIRROR); paint.setShader(radialGradient); if (scannerStart <= scannerEnd) { //椭圆 RectF rectF = new RectF(frame.left + 2 * lineHeight, scannerStart, frame.right - 2 * lineHeight, scannerStart + lineHeight); canvas.drawOval(rectF, paint); scannerStart += lineMoveDistance; } else { scannerStart = frame.top; } paint.setShader(null); } } private void drawResultPoint(Canvas canvas, Rect frame) { if (resultPointColor != Color.TRANSPARENT) { Collection<ResultPoint> currentPossible = possibleResultPoints; Collection<ResultPoint> currentLast = lastPossibleResultPoints; if (currentPossible.isEmpty()) { lastPossibleResultPoints = null; } else { possibleResultPoints = new HashSet<ResultPoint>(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); } } } } //处理颜色模糊 public int shadeColor(int color) { String hax = Integer.toHexString(color); String result = "20" + hax.substring(2); return Integer.valueOf(result, 16); } public void drawViewfinder() { invalidate(); } public void addPossibleResultPoint(ResultPoint point) { possibleResultPoints.add(point); } private int dp2px(Context context, float dpValue) { float density = context.getApplicationContext().getResources().getDisplayMetrics().density; return (int) (dpValue * density + 0.5f); } private int sp2px(Context context, float spValue) { float scaleDensity = context.getApplicationContext().getResources().getDisplayMetrics().scaledDensity; return (int) (spValue * scaleDensity + 0.5f); } }
2、CaptureActivity:扫码的Activity基类,代码如下;
/** * Created by xuzhb on 2019/11/16 * Desc:扫码的Activity类 * 整个Activity最重要的两个控件是一个SurfaceView(摄像头)和一个ViewfinderView(扫描区) * 对于继承CaptureActivity的Activity子类来说, * 可以选择在自己的布局中定义和CaptureActivity的布局文件id相同的控件, * 这样即使它们在两个布局中表现不同也能执行相同的逻辑,包括其他控件 * 或者选择重写getSurfaceView()和getViewfinderView()返回对应的两个控件, * 扫码最终是在handleDecode(Result result, Bitmap bitmap)处理扫描后的结果 */ public class CaptureActivity extends AppCompatActivity implements SurfaceHolder.Callback { private static final String TAG = "CaptureActivity"; private static final int IMAGE_PICKER = 1999; private BeepManager mBeepManager; private CaptureActivityHandler mHandler; private Vector<BarcodeFormat> mDecodeFormats; private String mCharacterSet; private InactivityTimer mInactivityTimer; private boolean hasSurface = false; private boolean isLightOn = false; //是否打开闪光灯 private boolean isPlayBeep = true; //是否开启扫描后的滴滴声 private boolean isVibrate = true; //是否震动 private String mPhotoPath; //选中的图片路径 private TitleBar mTitleBar; private SurfaceView mSurfaceView; private ViewfinderView mViewfinderView; private LinearLayout mLightLl; private ImageView mLightIv; private TextView mLightTv; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(getLayoutId()); CameraManager.init(getApplicationContext()); mBeepManager = new BeepManager(this); hasSurface = false; mInactivityTimer = new InactivityTimer(this); handleView(savedInstanceState); initView(); initListener(); } protected int getLayoutId() { return R.layout.activity_capture; } protected void handleView(@Nullable Bundle savedInstanceState) { } private void initView() { mTitleBar = findViewById(R.id.title_bar); mSurfaceView = findViewById(R.id.surfaceView); mViewfinderView = findViewById(R.id.viewfinderView); mLightLl = findViewById(R.id.light_ll); mLightIv = findViewById(R.id.light_iv); mLightTv = findViewById(R.id.light_tv); } protected void initListener() { //因为继承CaptureActivity的Activity子类的布局不一定包含id为title_bar和light_ll的控件, //没有的话如果子类通过super.initListener()覆写时会因为找不到而报异常,所以这里加了一个判空; //如果子类的布局中包含id相同的控件,则不需要在子类中再重写相同的逻辑 if (mTitleBar != null) { StatusBarUtil.INSTANCE.darkModeAndPadding(this, mTitleBar, Color.BLACK, 0, false); mTitleBar.setOnLeftClickListener(v -> { finish(); return null; }); mTitleBar.setOnRightClickListener(v -> { openAlbum(); //打开相册选取图片扫描 return null; }); } if (mLightLl != null) { mLightLl.setOnClickListener(v -> switchLight()); //打开或关闭闪光灯 } } //打开相册 protected void openAlbum() { Intent intent = new Intent(Intent.ACTION_PICK, null); intent.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "image/*"); startActivityForResult(intent, IMAGE_PICKER); } //开启/关闭闪光灯 private void switchLight() { if (CameraManager.get() != null) { if (isLightOn) { mLightTv.setText("轻触点亮"); CameraManager.get().turnLightOffFlashLight(); } else { mLightTv.setText("轻触关闭"); CameraManager.get().turnOnFlashLight(); } isLightOn = !isLightOn; mLightIv.setSelected(isLightOn); } } @Override protected void onResume() { super.onResume(); SurfaceHolder holder = getSurfaceView().getHolder(); if (hasSurface) { initCamera(holder); } else { holder.addCallback(this); holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); } mDecodeFormats = null; mCharacterSet = null; } @Override protected void onPause() { super.onPause(); if (mHandler != null) { mHandler.quitSynchronously(); mHandler = null; } CameraManager.get().closeDriver(); } @Override protected void onDestroy() { mInactivityTimer.shutdown(); mBeepManager.releaseRing(); super.onDestroy(); } @Override public void surfaceCreated(SurfaceHolder holder) { } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { if (!hasSurface) { hasSurface = true; initCamera(holder); } } @Override public void surfaceDestroyed(SurfaceHolder holder) { hasSurface = false; } private void initCamera(SurfaceHolder holder) { try { CameraManager.get().openDriver(holder); } catch (Exception e) { e.printStackTrace(); } if (mHandler == null) { mHandler = new CaptureActivityHandler(this, mDecodeFormats, mCharacterSet); } } //继承CaptureActivity的Activity类,如果SurfaceView的id和CaptureActivity布局中SurfaceView的id不同 //需要重写这个方法,返回自己布局中的SurfaceView public SurfaceView getSurfaceView() { return mSurfaceView; } //继承CaptureActivity的Activity类,如果ViewfinderView的id和CaptureActivity布局中ViewfinderView的id不同 //需要重写这个方法,返回自己布局中的ViewfinderView public ViewfinderView getViewfinderView() { return mViewfinderView; } public Handler getHandler() { return mHandler; } public void drawViewfinder() { getViewfinderView().drawViewfinder(); } //处理扫描后的结果 public void handleDecode(Result result, Bitmap bitmap) { mInactivityTimer.onActivity(); if (result != null) { String text = result.getText(); Log.i(TAG, "识别的结果:" + text); if (!TextUtils.isEmpty(text)) { //识别成功 playBeepSoundAndVibrate(); returnQRCodeResult(text); } else { showToast("很抱歉,识别二维码失败!"); } } else { showToast("未发现二维码!"); } } private void playBeepSoundAndVibrate() { if (isPlayBeep) { mBeepManager.startRing(); //播放扫码的滴滴声 } if (isVibrate) { Vibrator vibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE); if (vibrator != null) { vibrator.vibrate(200); //震动200毫秒 } } } //返回扫描结果 private void returnQRCodeResult(String result) { Intent intent = new Intent(); intent.putExtra(QRConstant.SCAN_QRCODE_RESULT, result); setResult(Activity.RESULT_OK, intent); finish(); } private void showToast(CharSequence text) { runOnUiThread(() -> { ToastUtil.INSTANCE.showToast(text, true, false, getApplicationContext()); }); } @Override protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == IMAGE_PICKER && resultCode == Activity.RESULT_OK) { if (data != null) { Uri uri = data.getData(); if (uri != null) { Cursor cursor = getContentResolver().query(uri, null, null, null, null); if (cursor != null) { if (cursor.moveToFirst()) { mPhotoPath = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA)); } cursor.close(); if (!TextUtils.isEmpty(mPhotoPath)) { //可以加个提示正在扫描的加载框,如showLoadingDialog("正在扫描...") new Thread(() -> { handleDecode(QRCodeUtil.decodeImage(mPhotoPath), null); //取消加载框,dismissLoadingDialog() }).start(); } else { Log.e(TAG, "未找到图片"); } } } } } } }
看一下使用的例子
最后,附上整个项目的github地址,注:项目使用了视图绑定ViewBinding,所以需要使用AndroidStudio 3.6.x版本。
到此这篇关于Android集成zxing扫码框架功能的文章就介绍到这了,更多相关android zxing扫码内容请搜索自学编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持自学编程网!
- 本文固定链接: https://zxbcw.cn/post/185582/
- 转载请注明:必须在正文中标注并保留原文链接
- QQ群: PHP高手阵营官方总群(344148542)
- QQ群: Yii2.0开发(304864863)