蛰伏半月有余,一直在准备期末考试,期间抽空研究了一些Android的源代码,现在我就把在这其中的一些收获分享给大家。
今天想分享给大家的是二维码扫描。说起二维码,大家一定不会陌生,尤其是微信火了以后,在我们的生活中几乎随处都可以看到二维码的影
子。相关科技媒体甚至把二维码当成是未来移动互联网的入口,因此研究二维码的相关技术就显得意义非凡。目前在移动开发领域,使用最为广泛的二
维码库有两个,分别是ZXing和ZBar,其中ZXing在Android开发中较为常见,而ZBar则在IOS开发中较为常见,更重要的一点是,这两个库都是开源
的,因此我们可以从源代码中获得很多有用的东西。关于ZXing,网上有很多相关的博文,我今天不想多说,我今天想说的是ZBar,你可能会说,ZBar
不是用在IOS中,怎么今天要说ZBar呢?其实我是从这两个库使用的难易程度来选择的,ZXing功能强大,但是使用起来比较繁琐,网上有很多简化的
教程,大家可以自行前去研究。相比较而言,ZBar则比较简单,使用起来容易上手,因此我们今天选择了ZBar作为我们的库来使用。
一、准备工作
下载ZBar的SDK:由于ZBar的项目托管在sourceforge,所以在这里给出下载地址:http://download.csdn.net/detail/qinyuanpei/6794713
二、导入项目
下载完成后,我们直接解压,可以看到下面的目录结构
打开android文件夹,我们可以找到一个Example的文件夹,这是官方给出的示例代码,我们下面的所有工作都是基于这个示例程序而来。我们
自行创建一个Android项目,并将这两个文件拷贝到我们的项目中,同时引入ZBar相关的库文件。
三、建立布局
首先建立主界面,即扫描二维码的界面,界面布局代码如下:
实现的布局效果如下图所示:
接下里,我们在来设计一个用于显示结果的界面,界面布局代码如下:
实现的界面效果如图所示:
四、编写代码
首先我们来写一个用于扫描的相机预览视图CameraPreview,此文件由ZBar的SDK提供,这里我做了下简单的修改
package com.Android.ZBar4Android;
import java.io.IOException;
import android.util.Log;
import android.view.SurfaceView;
import android.view.SurfaceHolder;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Canvas;
import android.hardware.Camera;
import android.hardware.Camera.Parameters;
import android.hardware.Camera.PreviewCallback;
import android.hardware.Camera.AutoFocusCallback;
//此类由ZBar项目的SDK提供,我做了下修改
@SuppressLint("ViewConstructor")
public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback
{
private SurfaceHolder mHolder;
private Camera mCamera;
private PreviewCallback mPreviewCallBack;
private AutoFocusCallback mAutoFocusCallBack;
public CameraPreview(Context context, Camera camera,
PreviewCallback previewCb,
AutoFocusCallback autoFocusCb) {
super(context);
mCamera = camera;
mPreviewCallBack = previewCb;
mAutoFocusCallBack = autoFocusCb;
/*
* 自动聚焦
* 要求API版本>9
*/
Camera.Parameters parameters = camera.getParameters();
for (String f : parameters.getSupportedFocusModes()) {
if (f == Parameters.FOCUS_MODE_CONTINUOUS_PICTURE) {
parameters.setFocusMode(Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
mAutoFocusCallBack = null;
break;
}
}
// Install a SurfaceHolder.Callback so we get notified when the
// underlying surface is created and destroyed.
mHolder = getHolder();
mHolder.addCallback(this);
// deprecated setting, but required on Android versions prior to 3.0
mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
}
public void surfaceCreated(SurfaceHolder holder) {
// The Surface has been created, now tell the camera where to draw the preview.
try {
mCamera.setPreviewDisplay(holder);
} catch (IOException e) {
Log.d("DBG", "Error setting camera preview: " + e.getMessage());
}
}
public void surfaceDestroyed(SurfaceHolder holder) {
// Camera preview released in activity
}
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
/*
* If your preview can change or rotate, take care of those events here.
* Make sure to stop the preview before resizing or reformatting it.
*/
if (mHolder.getSurface() == null){
// preview surface does not exist
return;
}
// stop preview before making changes
try {
mCamera.stopPreview();
} catch (Exception e){
// ignore: tried to stop a non-existent preview
}
try {
// Hard code camera surface rotation 90 degs to match Activity view in portrait
mCamera.setDisplayOrientation(90);
mCamera.setPreviewDisplay(mHolder);
mCamera.setPreviewCallback(mPreviewCallBack);
mCamera.startPreview();
mCamera.autoFocus(mAutoFocusCallBack);
} catch (Exception e){
Log.d("DBG", "Error starting camera preview: " + e.getMessage());
}
}
/*
* 绘制校准框
* 修改:秦元培
* 时间:2013年11月22日
*
*/
@Override
protected void onDraw(Canvas mCanvas)
{
//这里不会写了?
}
}
1、相机的获取及相机的交互处理
2、二维码图片的获取
3、二维码图片的解析
对于第一个问题,需要我们深入地了解相机的工作原理,即我们需要了解Camera类。
获取相机的代码如下:
//获取照相机的方法
public static Camera getCameraInstance()
{
Camera mCamera = null;
try
{
mCamera = Camera.open();
//没有后置摄像头,尝试打开前置摄像头*******************
if (mCamera == null)
{
Camera.CameraInfo mCameraInfo = new Camera.CameraInfo();
int cameraCount = Camera.getNumberOfCameras();
for (int camIdx = 0; camIdx < cameraCount; camIdx++)
{
Camera.getCameraInfo(camIdx, mCameraInfo);
if (mCameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT)
{
mCamera = Camera.open(camIdx);
}
}
}
}
catch (Exception e)
{
e.printStackTrace();
}
return mCamera;
}
释放照相机的方法
//释放照相机
private void releaseCamera()
{
if (mCamera != null)
{
IsPreview = false;
mCamera.setPreviewCallback(null);
mCamera.release();
mCamera = null;
}
}
PreviewCallback previewCb = new PreviewCallback()
{
public void onPreviewFrame(byte[] data, Camera camera)
{
Camera.Parameters parameters = camera.getParameters();
//获取扫描图片的大小
Size mSize = parameters.getPreviewSize();
//构造存储图片的Image
Image mResult = new Image(mSize.width, mSize.height, "Y800");//第三个参数不知道是干嘛的
//设置Image的数据资源
mResult.setData(data);
//获取扫描结果的代码
int mResultCode = mScanner.scanImage(mResult);
//如果代码不为0,表示扫描成功
if (mResultCode != 0)
{
//停止扫描
IsPreview = false;
mCamera.setPreviewCallback(null);
mCamera.stopPreview();
//开始解析扫描图片
SymbolSet Syms = mScanner.getResults();
for (Symbol mSym : Syms)
{
//mSym.getType()方法可以获取扫描的类型,ZBar支持多种扫描类型,这里实现了条形码、二维码、ISBN码的识别
if (mSym.getType() == Symbol.CODE128 || mSym.getType() == Symbol.QRCODE ||
mSym.getType() == Symbol.CODABAR || mSym.getType() == Symbol.ISBN10 ||
mSym.getType() == Symbol.ISBN13|| mSym.getType()==Symbol.DATABAR ||
mSym.getType()==Symbol.DATABAR_EXP || mSym.getType()==Symbol.I25)
{
//添加震动效果,提示用户扫描完成
Vibrator mVibrator=(Vibrator)getSystemService(VIBRATOR_SERVICE);
mVibrator.vibrate(400);
Intent intent=new Intent(MainActivity.this,ResultActivity.class);
intent.putExtra("ScanResult", "扫描类型:"+GetResultByCode(mSym.getType())+"\n"+ mSym.getData());
//这里需要注意的是,getData方法才是最终返回识别结果的方法
//但是这个方法是返回一个标识型的字符串,换言之,返回的值中包含每个字符串的含义
//例如N代表姓名,URL代表一个Web地址等等,其它的暂时不清楚,如果可以对这个进行一个较好的分割
//效果会更好,如果需要返回扫描的图片,可以对Image做一个合适的处理
startActivity(intent);
IsScanned = true;
}
else
{
//否则继续扫描
IsScanned = false;
mCamera.setPreviewCallback(previewCb);
mCamera.startPreview();
IsPreview = true;
mCamera.autoFocus(autoFocusCB);
}
}
}
}
};
Symbol中去实现的,由此,第三个问题得以解答。下面给出完整代码:
/*
* ZBar4Android
* 作者:秦元培
* 时间:2013年12月21日
* 需要解决的问题有:
* 1、返回内容的正则解析
* 2、如果锁屏后打开程序会报错
* 3、没有校正框,画不出来啊,郁闷
* 4、可能会与其它相机应用冲突,如微信
* 5、条形码还是读不出来
*/
package com.Android.ZBar4Android;
import com.Android.ZBar4Android.CameraPreview;
import com.Android.ZBar4Android.R;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.os.Bundle;
import android.os.Handler;
import android.os.Vibrator;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup.LayoutParams;
import android.view.Window;
import android.view.View.OnClickListener;
import android.widget.FrameLayout;
import android.widget.Button;
import android.widget.PopupWindow;
import android.hardware.Camera;
import android.hardware.Camera.PreviewCallback;
import android.hardware.Camera.AutoFocusCallback;
import android.hardware.Camera.Size;
import net.sourceforge.zbar.ImageScanner;
import net.sourceforge.zbar.Image;
import net.sourceforge.zbar.Symbol;
import net.sourceforge.zbar.SymbolSet;
import net.sourceforge.zbar.Config;
public class MainActivity extends Activity
{
//关于按钮
private Button BtnAbout;
//相机
private Camera mCamera;
//预览视图
private CameraPreview mPreview;
//自动聚焦
private Handler mAutoFocusHandler;
//图片扫描器
private ImageScanner mScanner;
//弹出窗口
private PopupWindow mPopupWindow;
//是否扫描完毕
private boolean IsScanned = false;
//是否处于预览状态
private boolean IsPreview = true;
//是否显示弹出层
private boolean IsShowPopup=false;
//加载iconvlib
static
{
System.loadLibrary("iconv");
}
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
//去除标题栏
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.layout_main);
//设置屏幕方向为竖屏
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
//自动聚焦线程
mAutoFocusHandler = new Handler();
//获取相机实例
mCamera = getCameraInstance();
if(mCamera == null)
{
//在这里写下获取相机失败的代码
AlertDialog.Builder mBuilder=new AlertDialog.Builder(this);
mBuilder.setTitle("ZBar4Android");
mBuilder.setMessage("ZBar4Android获取相机失败,请重试!");
mBuilder.setPositiveButton("确定", new DialogInterface.OnClickListener()
{
@Override
public void onClick(DialogInterface mDialogInterface, int mIndex)
{
MainActivity.this.finish();
}
});
AlertDialog mDialog=mBuilder.create();
mDialog.show();
}
//实例化Scanner
mScanner = new ImageScanner();
mScanner.setConfig(0, Config.X_DENSITY, 3);
mScanner.setConfig(0, Config.Y_DENSITY, 3);
//设置相机预览视图
mPreview = new CameraPreview(this, mCamera, previewCb, autoFocusCB);
FrameLayout preview = (FrameLayout)findViewById(R.id.cameraPreview);
preview.addView(mPreview);
if (IsScanned)
{
IsScanned = false;
mCamera.setPreviewCallback(previewCb);
mCamera.startPreview();
IsPreview = true;
mCamera.autoFocus(autoFocusCB);
}
//获取BtnAbout,显示程序信息
BtnAbout=(Button)findViewById(R.id.BtnAbout);
BtnAbout.setOnClickListener(new OnClickListener()
{
@Override
public void onClick(View v)
{
//如果弹出层已打开,销毁弹出层
if(IsShowPopup)
{
mPopupWindow.dismiss();
IsShowPopup=false;
}
else
{
//否则显示弹出层
mPopupWindow=new PopupWindow();
LayoutInflater mInflater=LayoutInflater.from(getApplicationContext());
View view=mInflater.inflate(R.layout.layout_about, null);
mPopupWindow.setContentView(view);
mPopupWindow.setWidth(LayoutParams.WRAP_CONTENT);
mPopupWindow.setHeight(LayoutParams.WRAP_CONTENT);
mPopupWindow.showAtLocation(mPreview, 0, 100, 100);
IsShowPopup=true;
}
}
});
}
//实现Pause方法
public void onPause()
{
super.onPause();
releaseCamera();
}
//获取照相机的方法
public static Camera getCameraInstance()
{
Camera mCamera = null;
try
{
mCamera = Camera.open();
//没有后置摄像头,尝试打开前置摄像头*******************
if (mCamera == null)
{
Camera.CameraInfo mCameraInfo = new Camera.CameraInfo();
int cameraCount = Camera.getNumberOfCameras();
for (int camIdx = 0; camIdx < cameraCount; camIdx++)
{
Camera.getCameraInfo(camIdx, mCameraInfo);
if (mCameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT)
{
mCamera = Camera.open(camIdx);
}
}
}
}
catch (Exception e)
{
e.printStackTrace();
}
return mCamera;
}
//释放照相机
private void releaseCamera()
{
if (mCamera != null)
{
IsPreview = false;
mCamera.setPreviewCallback(null);
mCamera.release();
mCamera = null;
}
}
private Runnable doAutoFocus = new Runnable()
{
public void run()
{
if (IsPreview)
mCamera.autoFocus(autoFocusCB);
}
};
PreviewCallback previewCb = new PreviewCallback()
{
public void onPreviewFrame(byte[] data, Camera camera)
{
Camera.Parameters parameters = camera.getParameters();
//获取扫描图片的大小
Size mSize = parameters.getPreviewSize();
//构造存储图片的Image
Image mResult = new Image(mSize.width, mSize.height, "Y800");//第三个参数不知道是干嘛的
//设置Image的数据资源
mResult.setData(data);
//获取扫描结果的代码
int mResultCode = mScanner.scanImage(mResult);
//如果代码不为0,表示扫描成功
if (mResultCode != 0)
{
//停止扫描
IsPreview = false;
mCamera.setPreviewCallback(null);
mCamera.stopPreview();
//开始解析扫描图片
SymbolSet Syms = mScanner.getResults();
for (Symbol mSym : Syms)
{
//mSym.getType()方法可以获取扫描的类型,ZBar支持多种扫描类型,这里实现了条形码、二维码、ISBN码的识别
if (mSym.getType() == Symbol.CODE128 || mSym.getType() == Symbol.QRCODE ||
mSym.getType() == Symbol.CODABAR || mSym.getType() == Symbol.ISBN10 ||
mSym.getType() == Symbol.ISBN13|| mSym.getType()==Symbol.DATABAR ||
mSym.getType()==Symbol.DATABAR_EXP || mSym.getType()==Symbol.I25)
{
//添加震动效果,提示用户扫描完成
Vibrator mVibrator=(Vibrator)getSystemService(VIBRATOR_SERVICE);
mVibrator.vibrate(400);
Intent intent=new Intent(MainActivity.this,ResultActivity.class);
intent.putExtra("ScanResult", "扫描类型:"+GetResultByCode(mSym.getType())+"\n"+ mSym.getData());
//这里需要注意的是,getData方法才是最终返回识别结果的方法
//但是这个方法是返回一个标识型的字符串,换言之,返回的值中包含每个字符串的含义
//例如N代表姓名,URL代表一个Web地址等等,其它的暂时不清楚,如果可以对这个进行一个较好的分割
//效果会更好,如果需要返回扫描的图片,可以对Image做一个合适的处理
startActivity(intent);
IsScanned = true;
}
else
{
//否则继续扫描
IsScanned = false;
mCamera.setPreviewCallback(previewCb);
mCamera.startPreview();
IsPreview = true;
mCamera.autoFocus(autoFocusCB);
}
}
}
}
};
//用于刷新自动聚焦的方法
AutoFocusCallback autoFocusCB = new AutoFocusCallback()
{
public void onAutoFocus(boolean success, Camera camera)
{
mAutoFocusHandler.postDelayed(doAutoFocus, 1000);
}
};
//根据返回的代码值来返回相应的格式化数据
public String GetResultByCode(int CodeType)
{
String mResult="";
switch(CodeType)
{
//条形码
case Symbol.CODABAR:
mResult="条形码";
break;
//128编码格式二维码)
case Symbol.CODE128:
mResult="二维码";
break;
//QR码二维码
case Symbol.QRCODE:
mResult="二维码";
break;
//ISBN10图书查询
case Symbol.ISBN10:
mResult="图书ISBN号";
break;
//ISBN13图书查询
case Symbol.ISBN13:
mResult="图书ISBN号";
break;
}
return mResult;
}
}
对于显示扫描结果的界面,代码比较简单
/*
* 返回扫描结果
* 作者:秦元培
* 时间:2013年12月21日
* 总结:这里有一个问题,就是在这个界面上按下返回键的时候程序会立即报错,试着重写过相关的方法都解决不了问题
* 谁要是知道这个问题怎么解决,记得给我说一声啊
*/
package com.Android.ZBar4Android;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.Window;
import android.widget.Button;
import android.widget.TextView;
public class ResultActivity extends Activity
{
private TextView tv;
private Button BtnBack;
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.layout_result);
//获取扫描结果
Intent intent=getIntent();
Bundle mData=intent.getExtras();
CharSequence mResult=mData.getCharSequence("ScanResult");
StringHelper mHelper=new StringHelper(mResult.toString());
mResult=mHelper.SplitFormDict();
tv=(TextView)findViewById(R.id.TextResult);
tv.setText(mResult);
//返回扫描界面
BtnBack=(Button)findViewById(R.id.BtnBack);
BtnBack.setOnClickListener(new OnClickListener()
{
@Override
public void onClick(View arg0)
{
Intent intent=new Intent(ResultActivity.this,MainActivity.class);
startActivity(intent);
}
});
}
@Override
protected void onPause()
{
super.onPause();
}
}
经过测试,可以快速地对二维码进行识别,并显示扫描结果。目前尚存在的问题有:
1、官方的文档说它是支持条形码、ISBN、二维码等多种形式的编码的,并且在程序代码中亦有所体现,但是实际测试中,发现二维码可以扫,其余的无法扫描
2、锁屏后再次打开程序会报错
3、与微信等类似的需要相机功能的软件冲突
4、校准框死活画不出来
5、在扫描结果界面下按下返回键,程序报错,无法拦截
欢迎大家积极寻找解决问题的方法,再次谢谢大家!
源代码下载