Google ZXing系列講解(四)——ZXing 解決豎屏掃描問題

前言

本篇文章帶你了解

  • ZXing Android Demo豎屏后無法實(shí)現(xiàn)掃描原因
  • ZXing 橫豎屏下掃描條碼/二維碼

ZXing橫豎屏掃描

默認(rèn)的ZXing Demo提供的是橫屏掃描,講真,確實(shí)不符合使用習(xí)慣。然,僅僅修改AndroidManifest.xml中的 android:screenOrientationportrait 無法掃描成功。

    <activity android:name=".CaptureActivity"
-              android:screenOrientation="sensorLandscape"
+              android:screenOrientation="portrait"
               android:clearTaskOnLaunch="true"

要想在豎屏下, 掃描成功,手機(jī)還得橫屏掃碼。(無語!''')

問題分析

有關(guān)ZXing增加豎屏掃描問題,在ZXing issue中有提到,作者跟網(wǎng)友討論了不少, 也有人給出其他的解決方案。但是作者標(biāo)記為了 enhancement , 到現(xiàn)在3.3.0穩(wěn)定版本 還是沒有實(shí)現(xiàn)改善。
https://github.com/zxing/zxing/issues/111

仔細(xì)查看其中的討論,會(huì)有不少收獲:

  • 有人給出了其他方法,基于ZXing 網(wǎng)友封裝的jar
  • 給出特定的解決方法

這篇文章是基于其中名叫 @GuoJinyu 的方法實(shí)現(xiàn)。該同學(xué)深入研究了ZXing,并說明橫屏無法掃描的原因。
首先給出了Android設(shè)備上, 存在以下幾個(gè)概念:

屏幕方向:在Android系統(tǒng)中,屏幕的左上角是坐標(biāo)系統(tǒng)的原點(diǎn)(0,0)坐標(biāo)。原點(diǎn)向右延伸是X軸正方向,原點(diǎn)向下延伸是Y軸正方向。
相機(jī)傳感器方向:手機(jī)相機(jī)的圖像數(shù)據(jù)都是來自于攝像頭硬件的圖像傳感器,這個(gè)傳感器在被固定到手機(jī)上后有一個(gè)默認(rèn)的取景方向,坐標(biāo)原點(diǎn)位于手機(jī)橫放時(shí)的左上角,即與橫屏應(yīng)用的屏幕X方向一致。換句話說,與豎屏應(yīng)用的屏幕X方向呈90度角。
相機(jī)的預(yù)覽方向:由于手機(jī)屏幕可以360度旋轉(zhuǎn),為了保證用戶無論怎么旋轉(zhuǎn)手機(jī)都能看到“正確”的預(yù)覽畫面(這個(gè)“正確”是指顯示在UI預(yù)覽界面的畫面與人眼看到的眼前的畫面是一致的),Android系統(tǒng)底層根據(jù)當(dāng)前手機(jī)屏幕的方向?qū)D像傳感器采集到的數(shù)據(jù)進(jìn)行了旋轉(zhuǎn)處理,然后才送給顯示系統(tǒng),因此可以保證預(yù)覽畫面始終“正確”。在相機(jī)API中可以通過setDisplayOrientation()設(shè)置相機(jī)預(yù)覽方向。在默認(rèn)情況下,這個(gè)值為0,與圖像傳感器一致。因此對(duì)于橫屏應(yīng)用來說,由于屏幕方向和預(yù)覽方向一致,預(yù)覽圖像不會(huì)顛倒90度。但是對(duì)于豎屏應(yīng)用,屏幕方向和預(yù)覽方向垂直,所以會(huì)出現(xiàn)顛倒90度現(xiàn)象。為了得到正確的預(yù)覽畫面,必須通過API將相機(jī)的預(yù)覽方向旋轉(zhuǎn)90,保持與屏幕方向一致

也就是說,相機(jī)得到的圖像數(shù)據(jù)始終是一個(gè)橫屏的姿態(tài),當(dāng)手機(jī)處于豎屏?xí)r,即使我們通過設(shè)置在屏幕上看到的拍攝畫面是準(zhǔn)確的,沒有90度翻轉(zhuǎn)的,我們通過API得到的圖像數(shù)據(jù)仍然是基于橫屏的,因此在判斷到width < height即屏幕處于豎屏狀態(tài)時(shí),我們首先對(duì)byte數(shù)組進(jìn)行一個(gè)手動(dòng)90度旋轉(zhuǎn),然后將結(jié)果構(gòu)造為一個(gè)PlanarYUVLuminanceSource對(duì)象,進(jìn)行真正的解析處理去了,這里我們就不管了。

詳細(xì)參看 Android二維碼掃描的簡(jiǎn)單實(shí)現(xiàn)及源碼分析

ZXing 橫豎屏下掃描條碼/二維碼

本篇基于ZXing3.3.0分析,參考了 https://github.com/zxing/zxing/issues/111 中 @GuoJinyu的方法

1. 刪除 android:screenOrientation="sensorLandscape"

AndroidManifest.xml

2. CaptureActivity內(nèi)部類 MyOrientationDetector使用

  • 2.1 定義內(nèi)部類
private class MyOrientationDetector extends OrientationEventListener {

      private int lastOrientation = -1;

      MyOrientationDetector(Context context) {
          super(context);
      }

      void setLastOrientation(int rotation) {
          switch (rotation) {
              case Surface.ROTATION_90:
                  lastOrientation = 270;
                  break;
              case Surface.ROTATION_270:
                  lastOrientation = 90;
                  break;
              default:
                  lastOrientation = -1;
          }
      }

      @Override
      public void onOrientationChanged(int orientation) {
          Log.d(TAG, "orientation:" + orientation);
          if (orientation > 45 && orientation < 135) {
              orientation = 90;
          } else if (orientation > 225 && orientation < 315) {
              orientation = 270;
          } else {
              orientation = -1;
          }
          if ((orientation == 90  && lastOrientation == 270) || (orientation == 270  && lastOrientation == 90)) {
              Log.i(TAG, "orientation:" + orientation + "lastOrientation:" + lastOrientation);
              Intent intent = getIntent();
              finish();
              startActivity(intent);
              lastOrientation = orientation;
              Log.i(TAG, "SUCCESS");
          }
      }
  }
  • 2.2 聲明MyOrientationDetector變量
MyOrientationDetector myOrientationDetector;
  • 2.3 onCreate中使用
    //add by tancolo
    myOrientationDetector = new MyOrientationDetector(this);
    myOrientationDetector.setLastOrientation(getWindowManager().getDefaultDisplay().getRotation());
    //end add
  • 2.4 onResume中使用
if (prefs.getBoolean(PreferencesActivity.KEY_DISABLE_AUTO_ORIENTATION, true)) {
      setRequestedOrientation(getCurrentOrientation());
    } else {
      setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR); // 旋轉(zhuǎn)
      myOrientationDetector.enable(); //啟用監(jiān)聽
    }
  • 2.5 onPause中使用
  //add by tancolo
    myOrientationDetector.disable();
    //end add

    super.onPause();

3. 獲得準(zhǔn)確的相機(jī)預(yù)覽框 (CameraManager.java)

getFramingRectInPreview()

if (cameraResolution == null || screenResolution == null) {
        // Called early, before init even finished
        return null;
      }

      //add by tancolo
      if(screenResolution.x < screenResolution.y){
        // portrait
        rect.left = rect.left * cameraResolution.y / screenResolution.x;
        rect.right = rect.right * cameraResolution.y / screenResolution.x;
        rect.top = rect.top * cameraResolution.x / screenResolution.y;
        rect.bottom = rect.bottom * cameraResolution.x / screenResolution.y;
      } else {
        // landscape
        rect.left = rect.left * cameraResolution.x / screenResolution.x;
        rect.right = rect.right * cameraResolution.x / screenResolution.x;
        rect.top = rect.top * cameraResolution.y / screenResolution.y;
        rect.bottom = rect.bottom * cameraResolution.y / screenResolution.y;
      }
      // end add

4. 修改PreviewCallback.java

onPreviewFrame()

    Handler thePreviewHandler = previewHandler;
    if (cameraResolution != null && thePreviewHandler != null) {
        //add by tancolo
      Point screenResolution = configManager.getScreenResolution();
      Message message;
      if (screenResolution.x < screenResolution.y){
        // portrait
        message = thePreviewHandler.obtainMessage(previewMessage, cameraResolution.y,
                cameraResolution.x, data);
      } else {
        // landscape
        message = thePreviewHandler.obtainMessage(previewMessage, cameraResolution.x,
                cameraResolution.y, data);
      }
//      Message message = thePreviewHandler.obtainMessage(previewMessage, cameraResolution.x,
//          cameraResolution.y, data);
        //end add

5. DecodeHandler.java

decode()

long start = System.currentTimeMillis();

    //add by tancolo
    if (width < height) {
      // portrait
      byte[] rotatedData = new byte[data.length];
      for (int x = 0; x < width; x++) {
        for (int y = 0; y < height; y++)
          rotatedData[y * width + width - x - 1] = data[y + x * height];
      }
      data = rotatedData;
    }
    //end add

6. 驗(yàn)證成果

MIUI8.1,CM12.1版本 掃描條碼/二維碼, 通過!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 二維碼掃描最近兩年簡(jiǎn)直是風(fēng)靡移動(dòng)互聯(lián)網(wǎng)時(shí)代,尤其在國(guó)內(nèi)發(fā)展神速。圍繞條碼掃碼功能,首先說說通過本文你可以知道啥。一...
    55book閱讀 4,334評(píng)論 0 1
  • 二維碼掃描最近兩年簡(jiǎn)直是風(fēng)靡移動(dòng)互聯(lián)網(wǎng)時(shí)代,尤其在國(guó)內(nèi)發(fā)展神速。圍繞條碼掃碼功能,首先說說通過本文你可以知道啥。一...
    北五環(huán)外閱讀 40,996評(píng)論 13 33
  • 以下是復(fù)制別人的文章過來的,目的是保留下這個(gè)方法,便于今后查閱使用,該文章不會(huì)用于任何商業(yè)用途,該文章的出處:ht...
    鴻蒙一笑萬物開閱讀 8,744評(píng)論 0 3
  • 標(biāo)簽(空格分隔):Android Camera 相機(jī) 圖像方向 圖像大小 【注】本文所提到的 Camera 均為 ...
    koguma閱讀 18,416評(píng)論 5 47
  • 我沒有任何權(quán)利 我聽從男人的命令 他身如死尸 橫在門邊 我 為了重新活下去 拿鉛筆畫大地飛沙 畫拿著鋤...
    鋤風(fēng)少年閱讀 259評(píng)論 2 2

友情鏈接更多精彩內(nèi)容