夢幻时空 发表于 2013-12-13 23:27:25

Android平台下利用zxing实现二维码开发

现在走在大街小巷都能看到二维码,而且最近由于项目需要,所以研究了下二维码开发的东西,开源的二维码扫描库主要有zxing和zbar,zbar在iPos平台上应用比较成熟,而在Android平台上主流还是用zxing库,因此这里主要讲述如何利用zxing进行二维码开发。1.如何将zxing的Android源码导入工程。  在导入zxing的android源码之前,先去官方下载zxing的源码:http://code.google.com/p/zxing/downloads/list。  我这里下载的是1.6版本的,我试验了几个版本,发现2.0以后的版本实际用起来没有1.6和1.7版本的好用,最后选择了1.6版本。  zxing 1.6源码结构如下: http://images.cnitblog.com/blog/288799/201310/10090129-97a8d556b6ac4309acd34eabe2a58afc.jpg  其中android文件夹就是android平台下的官方例子。  在导入之前先要对core文件下的源码进行编译,得到核心包core.jar。  编译方法请参照:http://blog.163.com/yimigao@126/blog/static/671560502011611111116747/  然后就可以导入android平台下的例子了,导入方法如下:  1)打开Eclipse,新建android项目:(注意不要直接把android文件夹拷到workspace下导入,那样会无法导入)  http://images.cnitblog.com/blog/288799/201310/10090625-b956e77d3f0e43029b886b4c27712742.jpg  2)导入核心包core.jar。  3)修改strings.xml文件。在导入core.jar之后工程还是会有错误:  http://images.cnitblog.com/blog/288799/201310/10092834-df91bc56666c4541b1641cccd464eeaa.jpg  出现这种错误可能是由于字符错误导致的,只需要把所有的%s 和%f改成 %1$s和%1$f   即可。  修改完之后重新清理项目,此时已经没有错误了:  http://images.cnitblog.com/blog/288799/201310/10093711-815df1b44a8847bba20d2fe859c8ec38.jpg   4)运行效果:  http://images.cnitblog.com/blog/288799/201310/10095418-f0e3413a822d4788aaae06c1b5ba4adb.jpg2.代码简化  上面代码中,很多功能我们在自己的项目中都用不到,因此需要对其进行简化,至于如何简化这里就不赘述了,网上有很多教程。下面只给出简化后的结果:  http://images.cnitblog.com/blog/288799/201310/10100219-a1fe214d25634128974028e3e6e9dd07.jpg  如果只进行二维码识别和二维码生成的话,只需要上面包中的文件。其中CaptureActivity.java是拍照取景的类,camera包下面的类主要与照相机相关的类,decoding和encoding是解码和编码相关的类,view是取景框相关的类。3.将简化的zxing代码嵌入自己的工程。  在自己的工程中嵌入简化的zxing代码即可实现二维码生成和识别功能。  嵌入方法:  1)将上述简化的代码拖到自己工程目录下;  2)将values文件夹和raw文件夹复制自己工程目录下;  3)建立CaptureActivity.java的布局文件capture.xml。http://common.cnblogs.com/images/copycode.gif
<?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>http://common.cnblogs.com/images/copycode.gif

  3)导入core.jar包  4)修改AndrodMainfest.xmlhttp://common.cnblogs.com/images/copycode.gif
<?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>http://common.cnblogs.com/images/copycode.gif

  5)运行效果:  http://images.cnitblog.com/blog/288799/201310/10102930-3851c91ee22d4f04917e6f0f1368c13f.jpg http://images.cnitblog.com/blog/288799/201310/10103013-839dc880007a47b3b03eddbcfab707dd.jpg  在这里识别二维码之后是把结果返回到上一个activity显示,所以在MainActivity中调用方法是:Intent openCameraIntent = new Intent(MainActivity.this,CaptureActivity.class);startActivityForResult(openCameraIntent, 0);
  然后获取结果在onActivityResult中进行:http://common.cnblogs.com/images/copycode.gif
@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);      }    }http://common.cnblogs.com/images/copycode.gif

  如果要直接在CaptureActivity中弹出识别结果,可以更改CaptureActivity.java的handleDecode方法:http://common.cnblogs.com/images/copycode.gif
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();    }http://common.cnblogs.com/images/copycode.gif

  上述代码是得到结果之后传给之前调用CaptureActivity的activity,所以如果要改的话只需要把else语句块下面的语句和最后一句finish注释掉,补上自己的代码就可以了。比如可以改成:http://common.cnblogs.com/images/copycode.gif
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://common.cnblogs.com/images/copycode.gif

  我的工程源码下载:http://files.cnblogs.com/dolphin0520/QRCode.rar3.关于zxing源码的若干问题。  在实际使用过程中发现zxing源码存在一些问题,下面逐一来说一下:  1)竖屏问题。  zxing给出的官方例子是横屏的,但是对于手机实际上竖屏操作更加方便一点,如果要改成竖屏,需要修改以下几个文件:  首先将AndrodMainfest.xml下CaptureActivity的配置改为竖屏:android:screenOrientation="portrait"
  然后需要修改CameraConfigurationManager.java下的setDesiredCameraParameters方法,该方法直接使用camera.setDisplayOrientation(90);
  来让屏幕旋转90度,这个办法在android 2.2版本以后是可行的,但是2.2之前的版本并没有提供这个接口,因此需要对sdk版本进行判断:http://common.cnblogs.com/images/copycode.gif
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);             }      }   http://common.cnblogs.com/images/copycode.gif

  在修改为竖屏之后发现取景框会发生拉伸,需要进行以下几点修改:  在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改为:http://common.cnblogs.com/images/copycode.gif
private void decode(byte[] data, int width, int height) {    long start = System.currentTimeMillis();    Result rawResult = null;      //modify here    byte[] rotatedData = new byte;    for (int y = 0; y < height; y++) {      for (int x = 0; x < width; x++)            rotatedData* height + height - y - 1] = data 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();    }}http://common.cnblogs.com/images/copycode.gif

  再将CameraConfigurationManager类中的方法initFromCameraParameters修改为:http://common.cnblogs.com/images/copycode.gif
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);}http://common.cnblogs.com/images/copycode.gif

  最后将PlanarYUVLuminanceSource.java中的renderCroppedGreyscaleBitmap方法改为:http://common.cnblogs.com/images/copycode.gif
public Bitmap renderCroppedGreyscaleBitmap() {      int width = getWidth();      int height = getHeight();      int[] pixels = new int 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 & 0xff;            pixels+ 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;      }http://common.cnblogs.com/images/copycode.gif

  3)取景框的绘制  取景框的绘制可以参照这篇博文:http://www.cnblogs.com/forrestsun/archive/2012/11/06/2757005.html  4)闪光灯的开启和关闭  如果需要开启和关闭闪光灯,在CameraManager.java中添加2个方法:http://common.cnblogs.com/images/copycode.gif
  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);      }    }http://common.cnblogs.com/images/copycode.gif

  然后在CaptureActivity中的initCamera方法中打开闪光灯:CameraManager.get().openDriver(surfaceHolder);CameraManager.get().openLight();   //开闪光灯
  在需要的地方关闭闪光灯即可。  5)连续扫描问题  如果在识别二维码成功之后,需要连续多次扫描二维码,只需在扫描完成之后添加代码(在CaptureActivity的dandleDecode方法中添加):if(handler!=null)   //实现连续扫描   handler.restartPreviewAndDecode();
  比如:http://common.cnblogs.com/images/copycode.gif
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();    }http://common.cnblogs.com/images/copycode.gif

  6)识别完成之后的震动。  如果需要取消二维码识别之后的震动,只需要在CaptureActivity类的onResume方法中将vibrate 设置为false即可。  7)修改取景框距屏幕顶部位置  如果需要修改取景框距屏幕顶部位置,只需要修改CameraManager.java的getFramingRect方法,在getFramingRect方法中,int topOffset = (screenResolution.y - height) / 2
  这句是控制取景框到屏幕顶部的距离,若需要减小距屏幕顶部的距离,只需要将分母变大即可。  8)取景框下方提示文字的绘制。  在ViewfinderView.java的 Collection<ResultPoint> currentPossible = possibleResultPoints前面加入以下代码:
http://common.cnblogs.com/images/copycode.gif
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);http://common.cnblogs.com/images/copycode.gif

  这里解释一下,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 二维码识别的问题暂时就讨论这么多,有兴趣的朋友可以读源码深入研究一下。

botqwi50 发表于 2013-12-13 23:28:07

不错 支持下
页: [1]
查看完整版本: Android平台下利用zxing实现二维码开发