Android Zxing 二維碼與相冊選取圖片掃描

Zxing 是谷歌的一個開源庫满败,網(wǎng)上大多數(shù)二維碼掃描功能都是基于該庫實現(xiàn)功能的楔壤,現(xiàn)在來動手簡單實現(xiàn)二維碼掃描與相冊圖片讀取二維碼拷况。
在寫這篇文章時,參考了幾位大佬的博客相關(guān)知識:https://blog.csdn.net/qq_34902522/article/details/78384661 梗顺,他已經(jīng)實現(xiàn)了封裝二維碼相冊讀取圖片的功能的庫YZxing
還有本次所依賴的大佬的所封裝的掃描庫
https://blog.csdn.net/qq_23547831/article/details/52037710
老規(guī)矩先上圖:

二維碼掃描錄制.gif

由于申請權(quán)限代碼過于繁瑣泡孩,除了依賴Zxing ,這里還引入了郭霖大神的PermissionX

    implementation 'cn.yipianfengye.android:zxing-library:2.1'
    implementation 'com.permissionx.guolindev:permissionx:1.2.2'

在Mainifest中聲明我們需要的的攝像頭權(quán)限和Zxing需要的權(quán)限:振動器(掃描后解析結(jié)果會震動)

 <uses-permission android:name="android.permission.CAMERA"/>
  <uses-permission android:name="android.permission.VIBRATE" />

點擊二維碼掃描按鈕后調(diào)用方法checkCamera()
代碼如下:

  private void checkCamera() {
        PermissionX.init(this).permissions(CAMERA).onExplainRequestReason(new ExplainReasonCallback() {
            @Override
            public void onExplainReason(ExplainScope scope, List<String> deniedList) {
                scope.showRequestReasonDialog(deniedList, "掃描二維碼需要開啟攝像頭", "允許");
            }
        }).onForwardToSettings(new ForwardToSettingsCallback() {
            @Override
            public void onForwardToSettings(ForwardScope scope, List<String> deniedList) {
                scope.showForwardToSettingsDialog(deniedList, "需要在應(yīng)用程序設(shè)置中手動開啟", "OK");
            }
        }).request(new RequestCallback() {
            @Override
            public void onResult(boolean allGranted, List<String> grantedList, List<String> deniedList) {
                if (allGranted) {
                    startActivityForResult(
                            new Intent(MainActivity.this, ScanQRCodeActivity.class)
                            , SCAN_RESULT);
                } else {
                    Toast.makeText(MainActivity.this, "權(quán)限已拒絕", Toast.LENGTH_SHORT).show();
                }
            }
        });
    }

在權(quán)限檢查通過后(PermissionX具體用法參考上面貼出的鏈接)寺谤,啟動意圖跳轉(zhuǎn)到ScanQRCodeActivity二維碼掃描界面:

public class ScanQRCodeActivity extends AppCompatActivity {
    private FrameLayout fl_my_container;
    public static final int SCAN_SUCCESS=1111;//掃描成功
    public static final int SCAN_FAIL=1112;//掃描失敗

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_scan_q_r_code);
        fl_my_container=findViewById(R.id.fl_my_container);
        initScan();
    }

    private void initScan() {
        try {
            /**
             * 二維碼解析回調(diào)函數(shù)
             */
            CodeUtils.AnalyzeCallback analyzeCallback = new CodeUtils.AnalyzeCallback() {
                @Override
                public void onAnalyzeSuccess(Bitmap mBitmap, String result) {
                    if (!TextUtils.isEmpty(result)) {
                        setResult(SCAN_SUCCESS,getIntent().putExtra("success_result",result));
                        finish();
                    }
                }

                @Override
                public void onAnalyzeFailed() {
                    setResult(SCAN_FAIL,getIntent().putExtra("fail_result","掃描失敗"));
                    finish();
                }
            };

            /**
             * 執(zhí)行掃面Fragment的初始化操作
             */
            CaptureFragment captureFragment = new CaptureFragment();
            // 為二維碼掃描界面設(shè)置定制化界面
            CodeUtils.setFragmentArgs(captureFragment, R.layout.my_camera);
            captureFragment.setAnalyzeCallback(analyzeCallback);
            /**
             * 替換我們的掃描控件
             */
            getSupportFragmentManager().beginTransaction().replace(R.id.fl_my_container, captureFragment).commit();
        } catch (Exception e) {

        }
    }
}

CodeUtils 是二維碼掃描工具類Zxing下的包仑鸥,根據(jù)回調(diào)結(jié)果響應(yīng)返回數(shù)據(jù)給上一個Activity
其中布局activity_scan_q_r_code,my_camera 截圖如下:


activity_scan_q_r_code.png
my_camera .png

掃描結(jié)果返回到MainActivity 中的 onActivityResult方法变屁,將結(jié)果顯示在控件TextView 上

  @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        switch (resultCode){
            case ScanQRCodeActivity.SCAN_SUCCESS:
                tv_content.setText(data.getStringExtra("success_result"));
                break;

            case ScanQRCodeActivity.SCAN_FAIL:
                tv_content.setText(data.getStringExtra("fail_result"));
                break;
        }
    }

實現(xiàn)了掃描二維碼后眼俊,接著來實現(xiàn)通過相冊識別二維碼
截圖如下:


相冊識別二維碼.gif

由于相冊圖片是存儲在SD卡上的,首先添加WRITE_EXTERNAL_STORAGE粟关,授予APP對系統(tǒng)的讀寫權(quán)限

 <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

checkStorage()點擊調(diào)用系統(tǒng)相冊,動態(tài)申請權(quán)限

 private void checkStorage() {
        PermissionX.init(this).permissions(WRITE_STORAGE).onExplainRequestReason(new ExplainReasonCallback() {
            @Override
            public void onExplainReason(ExplainScope scope, List<String> deniedList) {
                scope.showRequestReasonDialog(deniedList, "讀取相冊需要該權(quán)限", "允許");
            }
        })
                .onForwardToSettings(new ForwardToSettingsCallback() {
                    @Override
                    public void onForwardToSettings(ForwardScope scope, List<String> deniedList) {
                        scope.showForwardToSettingsDialog(deniedList, "需要在應(yīng)用程序設(shè)置中手動開啟", "OK");
                    }
                })
                .request(new RequestCallback() {
                    @Override
                    public void onResult(boolean allGranted, List<String> grantedList, List<String> deniedList) {
                        if (allGranted) {
                            Intent intent = new Intent(Intent.ACTION_PICK,
                                    android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
                            startActivityForResult(intent, DEVICE_PHOTO_REQUEST);
                        } else {
                            Toast.makeText(MainActivity.this, "權(quán)限已拒絕", Toast.LENGTH_SHORT).show();
                        }
                    }
                });
    }

相關(guān)的請求碼和常量如下:

public static final String WRITE_STORAGE = Manifest.permission.WRITE_EXTERNAL_STORAGE;
public final static int DEVICE_PHOTO_REQUEST = 1234;

在onActivityResult 方法中對回調(diào)的結(jié)果進行操作

@Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        //系統(tǒng)相冊返回請求
        switch (requestCode) {
            case DEVICE_PHOTO_REQUEST:
                if (data != null) {
                    Uri uri = data.getData();
                    String imagePath = BitMapUtil.getPicturePathFromUri(MainActivity.this, uri);
                    //對獲取到的二維碼照片進行壓縮
                    Bitmap bitmap = BitMapUtil.compressPicture(imagePath);
                    Result result = setZxingResult(bitmap);
                    if (result == null) {
                        tv_content.setText("識別失敗疮胖,請試試其它二維碼");
                    } else {
                        tv_content.setText(result.getText());
                    }
                }
                break;
        }
    }

通過返回的Uri 獲取其圖片,優(yōu)化的話可以對其壓縮裁剪,使得Zxing識別更加快速,BitMapUtil已經(jīng)分開處理判斷7.0 uri的異常問題和Bitmap的壓縮
工具類代碼如下

public class BitMapUtil {
    public static String getPicturePathFromUri(Context context, Uri uri) {
        int sdkVersion = Build.VERSION.SDK_INT;
        if (sdkVersion >= 19) {
            return getPicturePathFromUriAboveApi19(context, uri);
        } else {
            return getPicturePathFromUriBelowAPI19(context, uri);
        }
    }


    @RequiresApi(api = Build.VERSION_CODES.KITKAT)
    private static String getPicturePathFromUriAboveApi19(Context context, Uri uri) {
        String filePath = null;
        if (DocumentsContract.isDocumentUri(context, uri)) {
            // 如果是document類型的 uri, 則通過document id來進行處理
            String documentId = DocumentsContract.getDocumentId(uri);
            if (isMediaDocument(uri)) { // MediaProvider
                // 使用':'分割
                String id = documentId.split(":")[1];

                String selection = MediaStore.Images.Media._ID + "=?";
                String[] selectionArgs = {id};
                filePath = getDataColumn(context, MediaStore.Images.Media.EXTERNAL_CONTENT_URI, selection, selectionArgs);
            } else if (isDownloadsDocument(uri)) { // DownloadsProvider
                Uri contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), Long.valueOf(documentId));
                filePath = getDataColumn(context, contentUri, null, null);
            }
        } else if ("content".equalsIgnoreCase(uri.getScheme())) {
            // 如果是 content 類型的 Uri
            filePath = getDataColumn(context, uri, null, null);
        } else if ("file".equals(uri.getScheme())) {
            // 如果是 file 類型的 Uri,直接獲取圖片對應(yīng)的路徑
            filePath = uri.getPath();
        }
        return filePath;
    }

    private static String getPicturePathFromUriBelowAPI19(Context context, Uri uri) {
        return getDataColumn(context, uri, null, null);
    }


    private static String getDataColumn(Context context, Uri uri, String selection, String[] selectionArgs) {
        String path = null;

        String[] projection = new String[]{MediaStore.Images.Media.DATA};
        Cursor cursor = null;
        try {
            cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null);
            if (cursor != null && cursor.moveToFirst()) {
                int columnIndex = cursor.getColumnIndexOrThrow(projection[0]);
                path = cursor.getString(columnIndex);
            }
        } catch (Exception e) {
            if (cursor != null) {
                cursor.close();
            }
        }
        return path;
    }


    private static boolean isMediaDocument(Uri uri) {
        return "com.android.providers.media.documents".equals(uri.getAuthority());
    }

    private static boolean isDownloadsDocument(Uri uri) {
        return "com.android.providers.downloads.documents".equals(uri.getAuthority());
    }


    /**
     *
     * 對圖片壓縮
     *
     * */

    public static Bitmap compressPicture(String imgPath) {
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeFile(imgPath, options);
        Log.e( "log","未壓縮之前圖片的寬:" + options.outWidth + "--未壓縮之前圖片的高:"
                + options.outHeight + "--未壓縮之前圖片大小:" + options.outWidth * options.outHeight * 4 / 1024 / 1024 + "M");

        options.inSampleSize = calculateInSampleSize(options, 100, 100);
        Log.e( "log" ," inSampleSize:" + options.inSampleSize);
        options.inJustDecodeBounds = false;
        Bitmap afterCompressBm = BitmapFactory.decodeFile(imgPath, options);
//      //默認(rèn)的圖片格式是Bitmap.Config.ARGB_8888
        Log.e("log" ," 圖片的寬:" + afterCompressBm.getWidth() + "--圖片的高:"
                + afterCompressBm.getHeight() + "--圖片大小:" + afterCompressBm.getWidth() * afterCompressBm.getHeight() * 4 / 1024 / 1024 + "M");
        return afterCompressBm;
    }


    private static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
        final int height = options.outHeight;
        final int width = options.outWidth;
        int inSampleSize = 1;

        if (height > reqHeight || width > reqWidth) {

            final int halfHeight = height / 2;
            final int halfWidth = width / 2;

            while ((halfHeight / inSampleSize) >= reqHeight
                    && (halfWidth / inSampleSize) >= reqWidth) {
                inSampleSize *= 2;
            }
        }

        return inSampleSize;
    }

}

這個方法就是我們這次最主要的功能通過 setZxingResult()方法對優(yōu)化完后的圖片調(diào)用二維碼庫里的類來解析讀取澎灸,并且返回結(jié)果展示

 private static Result setZxingResult(Bitmap bitmap) {
        if (bitmap == null) return null;
        int picWidth = bitmap.getWidth();
        int picHeight = bitmap.getHeight();
        int[] pix = new int[picWidth * picHeight];
        //Log.e(TAG, "decodeFromPicture:圖片大性喝: " + bitmap.getByteCount() / 1024 / 1024 + "M");
        bitmap.getPixels(pix, 0, picWidth, 0, 0, picWidth, picHeight);
        //構(gòu)造LuminanceSource對象
        RGBLuminanceSource rgbLuminanceSource = new RGBLuminanceSource(picWidth
                , picHeight, pix);
        BinaryBitmap bb = new BinaryBitmap(new HybridBinarizer(rgbLuminanceSource));
        //因為解析的條碼類型是二維碼,所以這邊用QRCodeReader最合適性昭。
        QRCodeReader qrCodeReader = new QRCodeReader();
        Map<DecodeHintType, Object> hints = new EnumMap<>(DecodeHintType.class);
        hints.put(DecodeHintType.CHARACTER_SET, "utf-8");
        hints.put(DecodeHintType.TRY_HARDER, true);
        Result result;
        try {
            result = qrCodeReader.decode(bb, hints);
            return result;
        } catch (NotFoundException | ChecksumException | FormatException e) {
            e.printStackTrace();
            return null;
        }
    }

至此拦止,功能已經(jīng)實現(xiàn)完畢,希望能對各位起到一點作用
在最后附上本項目的GitHub鏈接

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末巩梢,一起剝皮案震驚了整個濱河市创泄,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌括蝠,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件饭聚,死亡現(xiàn)場離奇詭異忌警,居然都是意外死亡,警方通過查閱死者的電腦和手機秒梳,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進店門法绵,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人酪碘,你說我怎么就攤上這事朋譬。” “怎么了兴垦?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵徙赢,是天一觀的道長。 經(jīng)常有香客問我探越,道長狡赐,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任钦幔,我火速辦了婚禮枕屉,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘鲤氢。我一直安慰自己搀擂,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布卷玉。 她就那樣靜靜地躺著哨颂,像睡著了一般。 火紅的嫁衣襯著肌膚如雪揍庄。 梳的紋絲不亂的頭發(fā)上咆蒿,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天,我揣著相機與錄音,去河邊找鬼沃测。 笑死缭黔,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的蒂破。 我是一名探鬼主播馏谨,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼附迷!你這毒婦竟也來了惧互?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤喇伯,失蹤者是張志新(化名)和其女友劉穎喊儡,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體稻据,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡艾猜,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了捻悯。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片匆赃。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖今缚,靈堂內(nèi)的尸體忽然破棺而出算柳,到底是詐尸還是另有隱情,我是刑警寧澤姓言,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布瞬项,位于F島的核電站,受9級特大地震影響事期,放射性物質(zhì)發(fā)生泄漏滥壕。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一兽泣、第九天 我趴在偏房一處隱蔽的房頂上張望绎橘。 院中可真熱鬧,春花似錦唠倦、人聲如沸称鳞。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽冈止。三九已至,卻和暖如春候齿,著一層夾襖步出監(jiān)牢的瞬間熙暴,已是汗流浹背闺属。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留周霉,地道東北人掂器。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像俱箱,于是被迫代替她去往敵國和親国瓮。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,786評論 2 345