ZXing實現二維碼的生成與解析

雨幕青山發表於2017-05-09

大概一年前的掃碼風潮徹底把二維碼推向大眾,現在什麼下載,加好友,關注,都要掃一掃,這裡道長絮叨一下google旗下的ZXing,用ZXing實現二維碼的生成和解析。

一、二維碼的生成

這裡道長用的是3.1.0版本的ZXing,如果要使用可以自己下載,也可以使用道長Demo中的。和以前一樣,Demo道長放在文章最後。如果想看一下ZXing的原始碼可以點選傳送門

1.帶邊界的二維碼

  • 傳遞要生成二維碼的資料
    private void initData() {

        String filePath = Environment.getDataDirectory().getPath() + File.separator + "data" + File.separator;
        String fileName = "zxing.png";
        json = new JSONObject();
        try {
            json.put("url", "https://github.com/zxing/zxing");
            json.put("author", "yushan");
            json.put("filepath", filePath);
            json.put("filename", fileName);
        } catch (JSONException e) {
            e.printStackTrace();
        }
    }
  • 建立工具類,並傳遞引數
  EncodingHelper encodingHelper = new EncodingHelper(HomeActivity.this, json, 350);
  Bitmap bitmap = encodingHelper.getBitmap(true);
  tv_show.setVisibility(View.GONE);
  iv_client_ewm.setVisibility(View.VISIBLE);
  iv_client_ewm.setBackgroundDrawable(new BitmapDrawable(bitmap));
  • 生成二維碼的程式碼
    /**
     * 獲取帶邊界的二維碼
     *
     * @param hasBK
     * @return
     */
    public Bitmap getBitmap(Boolean hasBK) {
        BitMatrix bitMatrix = null;
        try {
            bitMatrix = dealData();
        } catch (WriterException e) {
            e.printStackTrace();
        }

        int width = bitMatrix.getWidth();
        int height = bitMatrix.getHeight();
        int[] pixels = new int[width * height];
        for (int y = 0; y < height; y++) {
            for (int x = 0; x < width; x++) {
                if (bitMatrix.get(x, y)) {
                    pixels[y * width + x] = BLACK;
                } else {
                    pixels[y * width + x] = WHITE;
                }
            }
        }
        Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_4444);
        bitmap.setPixels(pixels, 0, width, 0, 0, width, height);

        // 畫邊框
        if (hasBK != null && hasBK == true) {
            Paint paint = new Paint();
            Canvas canvas = new Canvas(bitmap);

            drawBK(paint, canvas);
        }

        return bitmap;
    }

    /**
     * 生成二維碼矩陣
     *
     * @return
     * @throws WriterException
     */
    private BitMatrix dealData() throws WriterException {

        String content = json.toString();

        Map<EncodeHintType, Object> hints = new HashMap<EncodeHintType, Object>();
        hints.put(EncodeHintType.CHARACTER_SET, "utf-8"); // 字元編碼
        hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.M); // 糾錯等級
        hints.put(EncodeHintType.MARGIN, 1);
        BitMatrix bitMatrix = new MultiFormatWriter().encode(content,
                BarcodeFormat.QR_CODE, width, height, hints); // 生成矩陣

        return bitMatrix;
    }
  • 效果圖
    這裡寫圖片描述

2.帶圖示的二維碼

  • 呼叫方法
                EncodingHelper encodingHelper = new EncodingHelper(HomeActivity.this, json, 350);
                Bitmap bitmap = encodingHelper.getBitmapWithSingOrBK(R.drawable.ewm_hulu, true);
                tv_show.setVisibility(View.GONE);
                iv_client_ewm.setVisibility(View.VISIBLE);
                iv_client_ewm.setBackgroundDrawable(new BitmapDrawable(bitmap));
  • 生成二維碼的程式碼
    /**
     * 獲取帶圖示或邊界的二維碼
     *
     * @param drawableId
     * @param hasBK
     * @return
     */
    public Bitmap getBitmapWithSingOrBK(int drawableId, Boolean hasBK) {
        mBitmap = toRoundCorner(((BitmapDrawable) mContext.getResources().getDrawable(drawableId)).getBitmap(), 10);

        if (mBitmap != null) {
            Bitmap bitmapWithSign = bitmapWithSign(getBitmap(hasBK));

            return bitmapWithSign;
        } else {
            return getBitmap(hasBK);
        }
    }

    /**
     * 生成帶圖示的二維碼
     *
     * @param bitmap
     * @return
     */
    private Bitmap bitmapWithSign(Bitmap bitmap) {

        Paint paint = new Paint();
        Canvas canvas = new Canvas(bitmap);
        canvas.drawARGB(0, 0, 0, 0);// 透明色
        Rect outDst = new Rect();
        outDst.left = width * 2 / 5 - 6;
        outDst.top = height * 2 / 5 - 6;
        outDst.right = width * 3 / 5 + 6;
        outDst.bottom = height * 3 / 5 + 6;
        canvas.drawBitmap(((BitmapDrawable) mContext.getResources().getDrawable(
                R.drawable.ewm_bg)).getBitmap(), null, outDst, paint);
        Rect inDst = new Rect();

        inDst.left = width * 2 / 5;
        inDst.top = height * 2 / 5;
        inDst.right = width * 3 / 5;
        inDst.bottom = height * 3 / 5;

        canvas.drawBitmap(mBitmap, null, inDst, paint);

        inDst = null;
        outDst = null;
        paint = null;
        canvas = null;

        return bitmap;
    }
  • 效果圖

這裡寫圖片描述

到這裡道長已經把二維碼的生成已經說完了,至於傳參啊什麼的這裡道長就不說了,然後我們們說一下二維碼的解析。

二、二維碼的解析

這裡我們麼不止要初始化相機,還要自定義掃碼介面。
效果圖如下:
這裡寫圖片描述

這裡的自定義View是用畫筆和畫布實現的,這裡道長就不貼程式碼了。

  • 新增許可權
    記得在AndroidManifest.xml新增許可權:
    <uses-permission android:name="android.permission.CAMERA" />
  • 初始化相機
        viewfinderView.setShowInfoType(ViewfinderView.TEXTCONTENTTYPE_SCAN);
        CameraManager.frameSize = 2;
        CameraManager.init(getApplication());
  • 開啟掃描
    @Override
    protected void onResume() {
        super.onResume();
        SurfaceView surfaceView = (SurfaceView) findViewById(R.id.preview_view);
        SurfaceHolder surfaceHolder = surfaceView.getHolder();

        if (hasSurface) {
            initCamera(surfaceHolder);
        } else {
            surfaceHolder.addCallback(this);
            surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
        }

        decodeFormats = null;
        characterSet = null;
    }

    private void initCamera(SurfaceHolder surfaceHolder) {
        try {
            CameraManager.get().openDriver(surfaceHolder);
        } catch (IOException ioe) {
            Toast.makeText(this, "請授予相應許可權,再使用掃一掃!", Toast.LENGTH_SHORT).show();
            finish();// 獲取系統相機事件被拒絕後,直接finish()掉本activity
        } catch (RuntimeException e) {
            Toast.makeText(this, "請授予相應許可權,再使用掃一掃!", Toast.LENGTH_SHORT).show();
            finish();// 獲取系統相機事件被拒絕後,直接finish()掉本activity
        }

        if (handler == null) {
            handler = new CaptureActivityHandler(this, decodeFormats, characterSet);
        }
    }
  • 開啟一個執行緒decodeThread,用來掃描解析圖片
    public CaptureActivityHandler(ScanActivity activity, Vector<BarcodeFormat> decodeFormats,
                                  String characterSet) {
        this.activity = activity;
        decodeThread = new DecodeThread(activity, decodeFormats, characterSet,
                new ViewfinderResultPointCallback(activity.getViewfinderView()));
        decodeThread.start();
        state = State.SUCCESS;
        manager = CameraManager.get();
        // Start ourselves capturing previews and decoding.
        manager.startPreview();
        restartPreviewAndDecode();
    }
  • decodeThread執行緒在Handler中處理資料
    /**
     * Decode the data within the viewfinder rectangle, and time how long it took. For efficiency,
     * reuse the same reader objects from one decode to the next.
     *
     * @param data   The YUV preview frame.
     * @param width  The width of the preview frame.
     * @param height The height of the preview frame.
     */
    private void decode(byte[] data, int width, int height) {
        if ((data == null) || (data.length <= 0)) {
            return;
        } // 判斷以防null指標異常
        long start = System.currentTimeMillis();
        Result rawResult = null;

        // modify here
        byte[] rotatedData = new byte[data.length];
        for (int y = 0; y < height; y++) {
            for (int x = 0; x < width; x++)
                rotatedData[x * height + height - y - 1] = data[x + y * width];
        }
        int tmp = width; // Here we are swapping, that's the difference to #11
        width = height;
        height = tmp;

        PlanarYUVLuminanceSource source = CameraManager.get().buildLuminanceSource(rotatedData, width, height);
        BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
        try {
            rawResult = multiFormatReader.decodeWithState(bitmap);
        } catch (ReaderException re) {
            // continue
        } finally {
            multiFormatReader.reset();
        }

        if (rawResult != null) {
            long end = System.currentTimeMillis();
            Log.e("yushan", "Found barcode (" + (end - start) + " ms):\n" + rawResult.toString());
            Message message = Message.obtain(activity.getHandler(), R.id.decode_succeeded, rawResult);
            Bundle bundle = new Bundle();
            bundle.putParcelable(DecodeThread.BARCODE_BITMAP, source.renderCroppedGreyscaleBitmap());
            message.setData(bundle);
            // Log.d(TAG, "Sending decode succeeded message...");
            message.sendToTarget();
        } else {
            Message message = Message.obtain(activity.getHandler(), R.id.decode_failed);
            message.sendToTarget();
        }
    }
  • 處理完成的資料通過message傳送到另一個Handler
 @Override
  public void handleMessage(Message message) {
      switch (message.what) {
          case R.id.auto_focus:
              //Log.d(TAG, "Got auto-focus message");
              // When one auto focus pass finishes, start another. This is the closest thing to
              // continuous AF. It does seem to hunt a bit, but I'm not sure what else to do.
              if (state == State.PREVIEW) {
                  manager.requestAutoFocus(this, R.id.auto_focus);
              }
              break;
          case R.id.restart_preview:
              Log.d(TAG, "Got restart preview message");
              restartPreviewAndDecode();
              break;
          case R.id.decode_succeeded:
              Log.d(TAG, "Got decode succeeded message");
              state = State.SUCCESS;
              Bundle bundle = message.getData();

              /***********************************************************************/
//                Bitmap barcode = bundle == null ? null : (Bitmap) bundle.getParcelable(DecodeThread.BARCODE_BITMAP);
//                activity.handleDecode((Result) message.obj, barcode);
              String resultString = ((Result) message.obj).getText() + "";
              activity.handleDecode(resultString);// 返回資料
              /***********************************************************************/
              break;
          case R.id.decode_failed:
              // We're decoding as fast as possible, so when one decode fails, start another.
              state = State.PREVIEW;
              manager.requestPreviewFrame(decodeThread.getHandler(), R.id.decode);
              break;
          case R.id.return_scan_result:
              Log.d(TAG, "Got return scan result message");
              activity.setResult(Activity.RESULT_OK, (Intent) message.obj);
              activity.finish();
              break;
          case R.id.launch_product_query:
              Log.d(TAG, "Got product query message");
              String url = (String) message.obj;
              Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
              intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
              activity.startActivity(intent);
              break;
      }
  }
  • 把解析資料返回到主介面
    /**
     * 掃描返回資料
     *
     * @param resultString
     */
    public void handleDecode(String resultString) {
        inactivityTimer.onActivity();

        Log.e("yushan", resultString);

        Intent intent = this.getIntent();
        intent.putExtra("resultString", resultString);
        this.setResult(RESULT_OK, intent);
        this.finish();
    }
  • 展示解析結果
    這裡寫圖片描述

好了,到了這裡ZXing實現二維碼的生成與解析都已經實現了,希望這篇部落格能夠為你提供一些幫助。

原始碼下載

ZXingDemo


相關文章