歡迎您光臨本站 註冊首頁

Android集成zxing掃碼框架功能

←手機掃碼閱讀     niceskyabc @ 2020-04-30 , reply:0

我們知道zxing是一個強大的處理二維碼和條形碼等的開源庫,本篇文章記錄一下自己在項目中集成zxing開源庫的過程。

導入依賴

implementation 'com.google.zxing:core:3.3.3'

申請權限

在AndroidManifest中申請相應權限:



導入相關代碼和資源文件

導入的代碼文件如下(源碼在末尾):

相關的資源文件:

1、在res/values下新建ids.xml文件,引入下面id:



2、在res/values下新建attrs.xml文件,加入掃碼框的屬性,主要是ViewfinderView在使用:



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 CollectionpossibleResultPoints; private CollectionlastPossibleResultPoints; // 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(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) { CollectioncurrentPossible = possibleResultPoints; CollectioncurrentLast = lastPossibleResultPoints; if (currentPossible.isEmpty()) { lastPossibleResultPoints = null; } else { possibleResultPoints = new HashSet(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 VectormDecodeFormats; 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版本。


[niceskyabc ] Android集成zxing掃碼框架功能已經有374次圍觀

http://coctec.com/docs/android/show-post-232229.html