Android平臺下利用zxing實現二維碼開發

http://www.cnblogs.com/dolphin0520/p/3355728.htmlhtml


如今走在大街小巷都能看到二維碼,並且最近因爲項目須要,因此研究了下二維碼開發的東西,開源的二維碼掃描庫主要有zxing和zbar,zbar在iPos平臺上應用比較成熟,而在Android平臺上主流仍是用zxing庫,所以這裏主要講述如何利用zxing進行二維碼開發。java

1.如何將zxing的Android源碼導入工程。android

  在導入zxing的android源碼以前,先去官方下載zxing的源碼:http://code.google.com/p/zxing/downloads/listcanvas

  我這裏下載的是1.6版本的,我試驗了幾個版本,發現2.0之後的版本實際用起來沒有1.6和1.7版本的好用,最後選擇了1.6版本。app

  zxing 1.6源碼結構以下:ide

 

  其中android文件夾就是android平臺下的官方例子。佈局

  在導入以前先要對core文件下的源碼進行編譯,獲得核心包core.jar。字體

  編譯方法請參照:http://blog.163.com/yimigao@126/blog/static/671560502011611111116747/ui

  而後就能夠導入android平臺下的例子了,導入方法以下:this

  1)打開Eclipse,新建android項目:(注意不要直接把android文件夾拷到workspace下導入,那樣會沒法導入)

  

  2)導入核心包core.jar。

  3)修改strings.xml文件。在導入core.jar以後工程仍是會有錯誤:

  

  出現這種錯誤多是因爲字符錯誤致使的,只須要把全部的%s 和%f改爲 %1 s f   便可。

  修改完以後從新清理項目,此時已經沒有錯誤了:

  

   4)運行效果:

  

2.代碼簡化

  上面代碼中,不少功能咱們在本身的項目中都用不到,所以須要對其進行簡化,至於如何簡化這裏就不贅述了,網上有不少教程。下面只給出簡化後的結果:

  

  若是隻進行二維碼識別和二維碼生成的話,只須要上面包中的文件。其中CaptureActivity.java是拍照取景的類,camera包下面的類主要與照相機相關的類,decoding和encoding是解碼和編碼相關的類,view是取景框相關的類。

3.將簡化的zxing代碼嵌入本身的工程。

  在本身的工程中嵌入簡化的zxing代碼便可實現二維碼生成和識別功能。

  嵌入方法:

  1)將上述簡化的代碼拖到本身工程目錄下;

  2)將values文件夾和raw文件夾複製本身工程目錄下;

  3)創建CaptureActivity.java的佈局文件capture.xml。

複製代碼
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    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"
        android:layout_gravity="center" />

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

    <RelativeLayout
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:layout_gravity="center"
        android:orientation="vertical" >

        <TextView
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_alignParentTop="true"
            android:layout_centerInParent="true"
            android:gravity="center"
            android:paddingBottom="10dp"
            android:paddingTop="10dp"
            android:text="Scan Barcode"
            android:textColor="@android:color/white"
            android:textSize="18sp"
            android:textStyle="bold" />

        <Button
            android:id="@+id/btn_cancel_scan"
            android:layout_width="230dp"
            android:layout_height="40dp"
            android:layout_alignParentBottom="true"
            android:layout_centerInParent="true"
            android:layout_marginBottom="75dp"
            android:text="Cancel"
            android:textSize="15sp"
            android:textStyle="bold" />
        
    </RelativeLayout>

</FrameLayout>
複製代碼

  3)導入core.jar包

  4)修改AndrodMainfest.xml

複製代碼
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="com.qrcode"
      android:versionCode="1"
      android:versionName="1.0">
    <uses-sdk android:minSdkVersion="7" />
    
    <uses-permission android:name="android.permission.VIBRATE" />  <!-- 震動權限 -->
    <uses-permission android:name="android.permission.CAMERA" />
    <uses-feature android:name="android.hardware.camera" />        <!-- 使用照相機權限 -->
    <uses-feature android:name="android.hardware.camera.autofocus" />   <!-- 自動聚焦權限 -->

    <application android:icon="@drawable/icon" android:label="@string/app_name">
        <activity android:name=".MainActivity"
                  android:label="@string/app_name">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        
        <!-- 隱藏鍵盤 --><!-- 全屏 -->
        <activity
            android:configChanges="orientation|keyboardHidden"    
            android:name="com.zxing.activity.CaptureActivity"
            android:screenOrientation="portrait"
            android:theme="@android:style/Theme.NoTitleBar.Fullscreen"    
            android:windowSoftInputMode="stateAlwaysHidden" >
        </activity>

    </application>
</manifest>
複製代碼

  5)運行效果:

   

  在這裏識別二維碼以後是把結果返回到上一個activity顯示,因此在MainActivity中調用方法是:

Intent openCameraIntent = new Intent(MainActivity.this,CaptureActivity.class);
startActivityForResult(openCameraIntent, 0);

  而後獲取結果在onActivityResult中進行:

複製代碼
@Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
    
        if (resultCode == RESULT_OK) {
            Bundle bundle = data.getExtras();
            String scanResult = bundle.getString("result");
            resultTextView.setText(scanResult);
        }
    }
複製代碼

  若是要直接在CaptureActivity中彈出識別結果,能夠更改CaptureActivity.java的handleDecode方法:

複製代碼
public void handleDecode(Result result, Bitmap barcode) {
        inactivityTimer.onActivity();
        playBeepSoundAndVibrate();
        String resultString = result.getText();
        //FIXME
        if (resultString.equals("")) {
            Toast.makeText(CaptureActivity.this, "Scan failed!", Toast.LENGTH_SHORT).show();
        }else {
//            System.out.println("Result:"+resultString);
            Intent resultIntent = new Intent();
            Bundle bundle = new Bundle();
            bundle.putString("result", resultString);
            resultIntent.putExtras(bundle);
            this.setResult(RESULT_OK, resultIntent);
        }
        CaptureActivity.this.finish();
    }
複製代碼

  上述代碼是獲得結果以後傳給以前調用CaptureActivity的activity,因此若是要改的話只須要把else語句塊下面的語句和最後一句finish註釋掉,補上本身的代碼就能夠了。好比能夠改爲:

複製代碼
public void handleDecode(Result result, Bitmap barcode) {
        inactivityTimer.onActivity();
        playBeepSoundAndVibrate();
        final String resultString = result.getText();
        //FIXME
        if (resultString.equals("")) {
            Toast.makeText(CaptureActivity.this, "Scan failed!", Toast.LENGTH_SHORT).show();
        }else {
//            System.out.println("Result:"+resultString);
            /*Intent resultIntent = new Intent();
            Bundle bundle = new Bundle();
            bundle.putString("result", resultString);
            resultIntent.putExtras(bundle);
            this.setResult(RESULT_OK, resultIntent);*/
            
            AlertDialog resutlDialog = new AlertDialog.Builder(CaptureActivity.this).create();
            resutlDialog.setTitle("掃描結果");
            resutlDialog.setMessage(resultString);
            resutlDialog.setButton(AlertDialog.BUTTON_NEGATIVE, "取消", new DialogInterface.OnClickListener() 
            {
                @Override
                public void onClick(DialogInterface dialog, int which) 
                {
                    dialog.dismiss();
                }
            });    
            resutlDialog.show();
        }
        //CaptureActivity.this.finish();
    }
複製代碼

  個人工程源碼下載:http://files.cnblogs.com/dolphin0520/QRCode.rar

3.關於zxing源碼的若干問題。

  在實際使用過程當中發現zxing源碼存在一些問題,下面逐一來講一下:

  1)豎屏問題。

  zxing給出的官方例子是橫屏的,可是對於手機實際上豎屏操做更加方便一點,若是要改爲豎屏,須要修改如下幾個文件:

  首先將AndrodMainfest.xml下CaptureActivity的配置改成豎屏:

android:screenOrientation="portrait"

  而後須要修改CameraConfigurationManager.java下的setDesiredCameraParameters方法,該方法直接使用

camera.setDisplayOrientation(90);

  來讓屏幕旋轉90度,這個辦法在android 2.2版本之後是可行的,可是2.2以前的版本並無提供這個接口,所以須要對sdk版本進行判斷:

複製代碼
if (Integer.parseInt(Build.VERSION.SDK) >=8) 
        setDisplayOrientation(camera, 90);    
    else 
    {     
        if (context.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) 
        {      
           parameters.set("orientation", "portrait");      
           parameters.set("rotation", 90);     
        }     
        if (context.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) 
        {      
           parameters.set("orientation", "landscape");      
           parameters.set("rotation", 90);     
        }  
    }   
複製代碼

  在修改成豎屏以後發現取景框會發生拉伸,須要進行如下幾點修改:

  在CameraManager.java中將:

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;

  修改成:

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;

  而後將DecodeHandler類中的方法decode改成:

複製代碼
private void decode(byte[] data, int width, int height) {
    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.d(TAG, "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();
    }
  }
複製代碼

  再將CameraConfigurationManager類中的方法initFromCameraParameters修改成:

複製代碼
void initFromCameraParameters(Camera camera) {
    Camera.Parameters parameters = camera.getParameters();
    previewFormat = parameters.getPreviewFormat();
    previewFormatString = parameters.get("preview-format");
    Log.d(TAG, "Default preview format: " + previewFormat + '/' + previewFormatString);
    WindowManager manager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
    Display display = manager.getDefaultDisplay();
    screenResolution = new Point(display.getWidth(), display.getHeight());
    Log.d(TAG, "Screen resolution: " + screenResolution);
    
    Point screenResolutionForCamera = new Point();
    screenResolutionForCamera.x = screenResolution.x;
    screenResolutionForCamera.y = screenResolution.y;
    // preview size is always something like 480*320, other 320*480
    if (screenResolution.x < screenResolution.y) {
    screenResolutionForCamera.x = screenResolution.y;
    screenResolutionForCamera.y = screenResolution.x;
    }

    cameraResolution = getCameraResolution(parameters, screenResolutionForCamera);


    //cameraResolution = getCameraResolution(parameters, screenResolution);
    Log.d(TAG, "Camera resolution: " + screenResolution);
  }
複製代碼

  最後將PlanarYUVLuminanceSource.java中的renderCroppedGreyscaleBitmap方法改成:

複製代碼
public Bitmap renderCroppedGreyscaleBitmap() {
        int width = getWidth();
        int height = getHeight();
        int[] pixels = new int[width * height];
        byte[] yuv = yuvData;
        int inputOffset = top * dataHeight + left;

        for (int y = 0; y < height; y++) {
          int outputOffset = y * width;
          for (int x = 0; x < width; x++) {
            int grey = yuv[inputOffset + x] & 0xff;
            pixels[outputOffset + x] = 0xFF000000 | (grey * 0x00010101);
          }
          inputOffset += dataHeight;
        }

        Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
        bitmap.setPixels(pixels, 0, width, 0, 0, width, height);
        return bitmap;
      }
複製代碼

  3)取景框的繪製

  取景框的繪製能夠參照這篇博文:http://www.cnblogs.com/forrestsun/archive/2012/11/06/2757005.html

  4)閃光燈的開啓和關閉

  若是須要開啓和關閉閃光燈,在CameraManager.java中添加2個方法:

複製代碼
  public void openLight()   //打開閃光燈
    {
        if(camera!=null)
        {
             Parameters parameter=camera.getParameters();  
             parameter.setFlashMode(Parameters.FLASH_MODE_TORCH); 
             camera.setParameters(parameter);
        }
    }
    
    public void closeLight()  //關閉閃光燈
    {
        if(camera!=null)
        {
             Parameters parameter=camera.getParameters();  
             parameter.setFlashMode(Parameters.FLASH_MODE_OFF); 
             camera.setParameters(parameter);
        }
    }
複製代碼

  而後在CaptureActivity中的initCamera方法中打開閃光燈:

CameraManager.get().openDriver(surfaceHolder);
CameraManager.get().openLight();   //開閃光燈

  在須要的地方關閉閃光燈便可。

  5)連續掃描問題

  若是在識別二維碼成功以後,須要連續屢次掃描二維碼,只需在掃描完成以後添加代碼(在CaptureActivity的dandleDecode方法中添加):

if(handler!=null)     //實現連續掃描
   handler.restartPreviewAndDecode();

  好比:

複製代碼
public void handleDecode(Result result, Bitmap barcode) {
        inactivityTimer.onActivity();
        playBeepSoundAndVibrate();
        final String resultString = result.getText();
        //FIXME
        if (resultString.equals("")) {
            Toast.makeText(CaptureActivity.this, "Scan failed!", Toast.LENGTH_SHORT).show();
        }else {
//            System.out.println("Result:"+resultString);
            /*Intent resultIntent = new Intent();
            Bundle bundle = new Bundle();
            bundle.putString("result", resultString);
            resultIntent.putExtras(bundle);
            this.setResult(RESULT_OK, resultIntent);*/
            
            AlertDialog resutlDialog = new AlertDialog.Builder(CaptureActivity.this).create();
            resutlDialog.setTitle("掃描結果");
            resutlDialog.setMessage(resultString);
            resutlDialog.setButton(AlertDialog.BUTTON_POSITIVE, "打開連接", new DialogInterface.OnClickListener() 
            {
                @Override
                public void onClick(DialogInterface dialog, int which) 
                {
                    dialog.dismiss();
                    if(!isLegalUrl(resultString))   //若是url不合法
                    {
                        Toast.makeText(getApplicationContext(), "該連接不是合法的URL", Toast.LENGTH_SHORT).show();
                        if(handler!=null)     //實現連續掃描
                            handler.restartPreviewAndDecode();
                        return;
                    }
                    Intent intent = new Intent();      //打開連接   
                    intent.setAction("android.intent.action.VIEW");    
                    Uri content_url = Uri.parse(resultString);   
                    intent.setData(content_url);  
                    startActivity(intent);
                }
            });
            
            resutlDialog.setButton(AlertDialog.BUTTON_NEGATIVE, "取消", new DialogInterface.OnClickListener() 
            {
                @Override
                public void onClick(DialogInterface dialog, int which) 
                {
                    dialog.dismiss();
                    if(handler!=null)     //實現連續掃描
                        handler.restartPreviewAndDecode();
                }
            });    
            resutlDialog.show();
        }
        //CaptureActivity.this.finish();
    }
複製代碼

  6)識別完成以後的震動。

  若是須要取消二維碼識別以後的震動,只須要在CaptureActivity類的onResume方法中將vibrate 設置爲false便可。

  7)修改取景框距屏幕頂部位置

  若是須要修改取景框距屏幕頂部位置,只須要修改CameraManager.java的getFramingRect方法,在getFramingRect方法中,

int topOffset = (screenResolution.y - height) / 2

  這句是控制取景框到屏幕頂部的距離,若須要減少距屏幕頂部的距離,只須要將分母變大便可。

  8)取景框下方提示文字的繪製。

  在ViewfinderView.java的 Collection<ResultPoint> currentPossible = possibleResultPoints前面加入如下代碼:

  

複製代碼
TextPaint textPaint = new TextPaint();           
textPaint.setARGB(0xFF, 0xFF, 0xFF,0xFF);   //字體顏色
textPaint.setTextSize(TEXT_SIZE * density);
textPaint.setAntiAlias(true);  //設置抗鋸齒,不然字跡會很模糊
StaticLayout layout = new StaticLayout(getResources().getString(R.string.scan_text),textPaint,
                    frame.right-frame.left,Alignment.ALIGN_NORMAL,1.0F,0.0F,true);
canvas.translate(frame.left, (float) (frame.bottom + (float)TEXT_PADDING_TOP *density)); //繪製起始位置
layout.draw(canvas);
複製代碼

  這裏解釋一下,textPaint.setAntiAlias(true); 是設置爲抗鋸齒,不然字體會很模糊。StaticLayout的第一個參數就是要繪製的字符串,第二個是畫筆,第三個參數是設置每一行的寬度,即超過該寬度就換行,第四個是對齊方式。

  canvas.translate(frame.left, (float) (frame.bottom + (float)TEXT_PADDING_TOP *density))的參數是繪製字符串的起始位置。

  9)亂碼問題

  在掃描一部分二維碼時會出現亂碼的狀況,是因爲編碼的問題,能夠參照這篇文章:

  http://www.2cto.com/kf/201209/152900.html

  若是不想編譯core包,個人工程源碼中的core包是已經改好了的,能夠識別GBK二維碼,須要的朋友能夠直接拿去用。

  關於zxing 二維碼識別的問題暫時就討論這麼多,有興趣的朋友能夠讀源碼深刻研究一下。

相關文章
相關標籤/搜索