前言
本篇文章帶你了解
- ZXing Android Demo豎屏后無法實(shí)現(xiàn)掃描原因
- ZXing 橫豎屏下掃描條碼/二維碼
ZXing橫豎屏掃描
默認(rèn)的ZXing Demo提供的是橫屏掃描,講真,確實(shí)不符合使用習(xí)慣。然,僅僅修改AndroidManifest.xml中的 android:screenOrientation 為 portrait 無法掃描成功。
<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版本 掃描條碼/二維碼, 通過!
- 開啟手機(jī)旋轉(zhuǎn)模式
- 豎屏開啟 ZXing app -> 掃描OK
- 橫屏開啟ZXing app -> 掃描OK
修改后ZXing apk 下載地址