1. 程式人生 > 程式設計 >Android整合zxing掃碼框架功能

Android整合zxing掃碼框架功能

我們知道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" />

匯入相關程式碼和資原始檔

匯入的程式碼檔案如下(原始碼在末尾):

Android整合zxing掃碼框架功能

相關的資原始檔:

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,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,3));
  lineMoveDistance = ta.getDimension(R.styleable.ViewfinderView_line_move_distance,2));
  frameWidth = ta.getDimension(R.styleable.ViewfinderView_frame_width,220));
  frameHeight = ta.getDimension(R.styleable.ViewfinderView_frame_height,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,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,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,paint);
  canvas.drawRect(0,frame.bottom,paint);
  canvas.drawRect(frame.right,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.left + frameStrokeWidth,paint);
    //上邊
    canvas.drawRect(frame.left,frame.top + frameStrokeWidth,paint);
    //右邊
    canvas.drawRect(frame.right - frameStrokeWidth,paint);
    //下邊
    canvas.drawRect(frame.left,frame.bottom - frameStrokeWidth,paint);
   } else { //邊角在掃描區外
    //左邊
    canvas.drawRect(frame.left - frameStrokeWidth,frame.top - frameStrokeWidth,frame.bottom + frameStrokeWidth,paint);
    //上邊
    canvas.drawRect(frame.left - frameStrokeWidth,frame.right + frameStrokeWidth,paint);
    //右邊
    canvas.drawRect(frame.right,paint);
    //下邊
    canvas.drawRect(frame.left - 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.left + cornerSize,frame.top + cornerStrokeWidth,paint);
    canvas.drawRect(frame.left,frame.left + cornerStrokeWidth,frame.top + cornerSize,paint);
    //右上
    canvas.drawRect(frame.right - cornerSize,paint);
    canvas.drawRect(frame.right - cornerStrokeWidth,paint);
    //左下
    canvas.drawRect(frame.left,frame.bottom - cornerSize,frame.bottom - cornerStrokeWidth,paint);
    //右下
    canvas.drawRect(frame.right - cornerSize,paint);
   } else { //繪製在掃描區域外區
    //左上
    canvas.drawRect(frame.left - cornerStrokeWidth,frame.top - cornerStrokeWidth,frame.left - cornerStrokeWidth + cornerSize,paint);
    canvas.drawRect(frame.left - cornerStrokeWidth,frame.top - cornerStrokeWidth + cornerSize,paint);
    //右上
    canvas.drawRect(frame.right + cornerStrokeWidth - cornerSize,frame.right + cornerStrokeWidth,paint);
    canvas.drawRect(frame.right,paint);
    //左下
    canvas.drawRect(frame.left - cornerStrokeWidth,frame.bottom + cornerStrokeWidth,frame.bottom + cornerStrokeWidth - cornerSize,paint);
    //右下
    canvas.drawRect(frame.right + cornerStrokeWidth - cornerSize,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(),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,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 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);
     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,"未找到圖片");
      }
     }
    }
   }
  }
 }
}

看一下使用的例子

Android整合zxing掃碼框架功能 Android整合zxing掃碼框架功能

最後,附上整個專案的github地址,注:專案使用了檢視繫結ViewBinding,所以需要使用AndroidStudio 3.6.x版本。

到此這篇關於Android整合zxing掃碼框架功能的文章就介紹到這了,更多相關android zxing掃碼內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!