天天看點

幾行代碼快速內建二維碼掃描庫

轉至:一片楓葉的專欄

前言

本文将講解一下我最近寫的一個快速內建二維碼掃描庫,這裡需要說明的是其核心的實作掃描的功能,是通過調用ZXing庫實作的。内部App中使用到了二維碼掃描功能,但是網上找了一些關于二維碼掃描的例子,隻是我在內建的時候發現通過Android studio內建zxing二維碼庫不是特别友善,由于我就有了将其制作成标準庫的想法,也就有了本文即快速內建二維碼掃描庫。

本文的項目位址是在:android-zxingLibrary

使用說明

  • 可打開預設二維碼掃描頁面
  • 支援對圖檔Bitmap的掃描功能
  • 支援對UI的定制化操作
  • 支援對條形碼的掃描功能
  • 支援生成二維碼操作
  • 支援控制閃光燈開關

使用方式:

1.內建預設的二維碼掃描頁面

在具體介紹該掃描庫之前我們先看一下其具體的使用方式,看看是不是幾行代碼就可以內建二維碼掃描的功能。

  • 在module的build.gradle中執行compile操作
compile 'cn.yipianfengye.android:zxing-library:'
           
  • 在Application中執行初始化操作
@Override
    public void onCreate() {
        super.onCreate();

        ZXingLibrary.initDisplayOpinion(this);
    }
           
  • 在代碼中執行打開掃描二維碼界面操作
/**
         * 打開預設二維碼掃描界面
         */
        button1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(MainActivity.this, CaptureActivity.class);
                startActivityForResult(intent, REQUEST_CODE);
            }
        });
           

這裡的REQUEST_CODE是我們定義的int型常量。

  • 在Activity的onActivityResult方法中接收掃描結果
/**
         * 處理二維碼掃描結果
         */
        if (requestCode == REQUEST_CODE) {
            //處理掃描結果(在界面上顯示)
            if (null != data) {
                Bundle bundle = data.getExtras();
                if (bundle == null) {
                    return;
                }
                if (bundle.getInt(CodeUtils.RESULT_TYPE) == CodeUtils.RESULT_SUCCESS) {
                    String result = bundle.getString(CodeUtils.RESULT_STRING);
                    Toast.makeText(this, "解析結果:" + result, Toast.LENGTH_LONG).show();
                } else if (bundle.getInt(CodeUtils.RESULT_TYPE) == CodeUtils.RESULT_FAILED) {
                    Toast.makeText(MainActivity.this, "解析二維碼失敗", Toast.LENGTH_LONG).show();
                }
            }
        }
           

怎麼樣是不是很簡單?下面我們可以來看一下具體的執行效果:

幾行代碼快速內建二維碼掃描庫

但是這樣的話是不是太簡單了,如果我想選擇圖檔解析呢?别急,對二維碼圖檔的解析也是支援的

  • 內建對二維碼圖檔的解析功能
  • 調用系統API打開圖庫
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("image/*");
startActivityForResult(intent, REQUEST_IMAGE);
           
  • 在Activity的onActivityResult方法中擷取使用者選中的圖檔并調用二維碼圖檔解析API
if (requestCode == REQUEST_IMAGE) {
            if (data != null) {
                Uri uri = data.getData();
                ContentResolver cr = getContentResolver();
                try {
                    Bitmap mBitmap = MediaStore.Images.Media.getBitmap(cr, uri);//顯得到bitmap圖檔

                    CodeUtils.analyzeBitmap(mBitmap, new CodeUtils.AnalyzeCallback() {
                        @Override
                        public void onAnalyzeSuccess(Bitmap mBitmap, String result) {
                            Toast.makeText(MainActivity.this, "解析結果:" + result, Toast.LENGTH_LONG).show();
                        }

                        @Override
                        public void onAnalyzeFailed() {
                            Toast.makeText(MainActivity.this, "解析二維碼失敗", Toast.LENGTH_LONG).show();
                        }
                    });

                    if (mBitmap != null) {
                        mBitmap.recycle();
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
           

執行效果

幾行代碼快速內建二維碼掃描庫

有了預設的二維碼掃描界面,也有了對二維碼圖檔的解析,可能有的同學會說如果我想定制化顯示UI怎麼辦呢?沒關系也支援滴。

  • 定制化顯示掃描UI

由于我們的掃描元件是通過Fragment實作的,是以能夠很輕松的實作掃描UI的定制化。

  • 在新的Activity中定義Layout布局檔案
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/activity_second"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <Button
        android:id="@+id/second_button1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="取消"
        android:layout_marginTop="20dp"
        android:layout_marginLeft="20dp"
        android:layout_marginRight="20dp"
        android:layout_marginBottom="10dp"
        android:layout_gravity="bottom|center_horizontal"
        />

    <FrameLayout
        android:id="@+id/fl_my_container"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        ></FrameLayout>

</FrameLayout>
           

啟動id為fl_my_container的FrameLayout就是我們需要替換的掃描元件,也就是說我們會将我們定義的掃描Fragment替換到id為fl_my_container的FrameLayout的位置。而上面的button是我們添加的一個額外的控件,在這裡你可以添加任意的控件,各種UI效果等。具體可以看下面在Activity的初始化過程。

  • 在Activity中執行Fragment的初始化操作
@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_second);
        /**
         * 執行掃面Fragment的初始化操作
         */
        CaptureFragment captureFragment = new CaptureFragment();
        // 為二維碼掃描界面設定定制化界面
        CodeUtils.setFragmentArgs(captureFragment, R.layout.my_camera);

        captureFragment.setAnalyzeCallback(analyzeCallback);
        /**
         * 替換我們的掃描控件
         */ getSupportFragmentManager().beginTransaction().replace(R.id.fl_my_container, captureFragment).commit();
    }
           

其中analyzeCallback是我們定義的掃描回調函數,其具體的定義:

/**
     * 二維碼解析回調函數
     */
    CodeUtils.AnalyzeCallback analyzeCallback = new CodeUtils.AnalyzeCallback() {
        @Override
        public void onAnalyzeSuccess(Bitmap mBitmap, String result) {
            Intent resultIntent = new Intent();
            Bundle bundle = new Bundle();
            bundle.putInt(CodeUtils.RESULT_TYPE, CodeUtils.RESULT_SUCCESS);
            bundle.putString(CodeUtils.RESULT_STRING, result);
            resultIntent.putExtras(bundle);
            SecondActivity.this.setResult(RESULT_OK, resultIntent);
            SecondActivity.this.finish();
        }

        @Override
        public void onAnalyzeFailed() {
            Intent resultIntent = new Intent();
            Bundle bundle = new Bundle();
            bundle.putInt(CodeUtils.RESULT_TYPE, CodeUtils.RESULT_FAILED);
            bundle.putString(CodeUtils.RESULT_STRING, "");
            resultIntent.putExtras(bundle);
            SecondActivity.this.setResult(RESULT_OK, resultIntent);
            SecondActivity.this.finish();
        }
    };
           

仔細看的話,你會發現我們調用了CondeUtils.setFragmentArgs方法,該方法主要用于修改掃描界面掃描框與透明框相對位置的,與若不調用的話,其會顯示預設的元件效果,而如果調用該方法的話,可以修改掃描框與透明框的相對位置等UI效果,我們可以看一下my_camera布局檔案的實作。

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent" >

    <SurfaceView
        android:id="@+id/preview_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        />

    <com.uuzuche.lib_zxing.view.ViewfinderView
        android:id="@+id/viewfinder_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:inner_width="200dp"
        app:inner_height="200dp"
        app:inner_margintop="150dp"
        app:inner_corner_color="@color/scan_corner_color"
        app:inner_corner_length="30dp"
        app:inner_corner_width="5dp"
        app:inner_scan_bitmap="@drawable/scan_image"
        app:inner_scan_speed="10"
        app:inner_scan_iscircle="false"
        />

</FrameLayout>
           

上面我們自定義的掃描控件的布局檔案,下面我們看一下預設的掃描控件的布局檔案:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent" >

    <SurfaceView
        android:id="@+id/preview_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        />

    <com.uuzuche.lib_zxing.view.ViewfinderView
        android:id="@+id/viewfinder_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        />

</FrameLayout>
           

可以發現其主要的差別就是在自定義的掃描控件中多了幾個自定義的掃描框屬性:

<declare-styleable name="innerrect">
        <attr name="inner_width" format="dimension"/><!-- 控制掃描框的寬度 -->
        <attr name="inner_height" format="dimension"/><!-- 控制掃描框的高度 -->
        <attr name="inner_margintop" format="dimension" /><!-- 控制掃描框距離頂部的距離 -->
        <attr name="inner_corner_color" format="color" /><!-- 控制掃描框四角的顔色 -->
        <attr name="inner_corner_length" format="dimension" /><!-- 控制掃描框四角的長度 -->
        <attr name="inner_corner_width" format="dimension" /><!-- 控制掃描框四角的寬度 -->
        <attr name="inner_scan_bitmap" format="reference" /><!-- 控制掃描圖 -->
        <attr name="inner_scan_speed" format="integer" /><!-- 控制掃描速度 -->
        <attr name="inner_scan_iscircle format="false""><!-- 控制小圓點是否展示 -->
    </declare-styleable>
           

通過以上幾個屬性我們就可以定制化的顯示我們的掃描UI了,比如定制化微信掃描UI:

幾行代碼快速內建二維碼掃描庫

當然了如果以上的以上,你還是對定制化UI方面不太滿意,可以直接下載下傳我的項目,然後引入lib-zxing module作為你的module,直接修改其代碼。

  • 生成二維碼圖檔
  • 生成帶Logo的二維碼圖檔:
/**
         * 生成二維碼圖檔
         */
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                String textContent = editText.getText().toString();
                if (TextUtils.isEmpty(textContent)) {
                    Toast.makeText(ThreeActivity.this, "您的輸入為空!", Toast.LENGTH_SHORT).show();
                    return;
                }
                editText.setText("");
                mBitmap = CodeUtils.createImage(textContent, , , BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher));
                imageView.setImageBitmap(mBitmap);
            }
        });
           
  • 生成不帶logo的二維碼圖檔
/**
         * 生成不帶logo的二維碼圖檔
         */
        button1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                String textContent = editText.getText().toString();
                if (TextUtils.isEmpty(textContent)) {
                    Toast.makeText(ThreeActivity.this, "您的輸入為空!", Toast.LENGTH_SHORT).show();
                    return;
                }
                editText.setText("");
                mBitmap = CodeUtils.createImage(textContent, , , null);
                imageView.setImageBitmap(mBitmap);
            }
        });
           

執行效果

幾行代碼快速內建二維碼掃描庫
  • 支援控制閃光燈
/**
 * 打開閃光燈
 */
CodeUtils.isLightEnable(true);

/**
 * 關閉閃光燈
 */
 CodeUtils.isLightEnable(false);
           

建立二維碼庫的流程:

  • 建立一個android studio項目,并建立zxingLibrary庫;
幾行代碼快速內建二維碼掃描庫
  • 在zxingLibrary庫中實作二維碼的掃描操作;

在實作過程中有以下幾個注意點:

(1)由于二維碼掃描界面是一個Activity,是以需要在AndroidManifest.xml中定義,而我為了減少對項目的依賴,選擇了在zxingLibrary中的AndroidManifest.xml中定義該Activity。

<application android:allowBackup="true"
        >

        <activity
            android:configChanges="orientation|keyboardHidden"
            android:name="com.uuzuche.lib_zxing.activity.CaptureActivity"
            android:screenOrientation="portrait"
            android:windowSoftInputMode="stateAlwaysHidden"
            android:label="掃描二維碼"
           android:theme="@style/Theme.AppCompat.NoActionBar"
            ></activity>
    </application>
           

(2)由于掃描操作需要使用攝像頭等權限,也在zxingLibrary中的AndroidManifest.xml中申請了部分權限:

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

    <uses-feature android:name="android.hardware.camera" />
    <uses-feature android:name="android.hardware.camera.autofocus" />

    <uses-permission android:name="android.permission.VIBRATE" />
    <uses-permission android:name="android.permission.WAKE_LOCK" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.INTERNET" />
           

(3)原來的項目中二維碼掃面的部分是作為主項目實作的,部分代碼使用了switch(id)的操作,而這樣的代碼在library中有問題,具體可參考:在Android library中不能使用switch-case語句通路資源ID的原因分析及解決方案,是以我做了修改:

修改之前:

@Override
  public void handleMessage(Message message) {
    switch (message.what) {
      case R.id.decode:
        decode((byte[]) message.obj, message.arg1, message.arg2);
        break;
      case R.id.quit:
        Looper.myLooper().quit();
        break;
    }
  }
           

修改之後:

@Override
  public void handleMessage(Message message) {
    if (message.what == R.id.decode) {
      decode((byte[]) message.obj, message.arg1, message.arg2);
    } else if (message.what == R.id.quit) {
      Looper.myLooper().quit();
    }
  }
           
  • 将zxingLibrary庫上傳至Jcenter中

為了使用compile,是以講zxingLibrary庫上傳至了jCenter中,具體的上傳過程可參考我的:Github項目解析(二)–>将Android項目釋出至JCenter代碼庫

有同學反應掃描過程中,二維碼圖檔有拉伸的現象,最新的compile已經改正,具體原因可參考:Zxing圖檔拉伸解決 Android 二維碼掃描