QRCode是被广泛应用的一种二维码,解码速度快。二维码相对于条形码来说,二维码的存储数据量更大,空间利用率高,有一定的容错性。
二维码原理介绍:
二维码是用某种特定的几何图形按一定的规律在平面上分布的黑白相间的图形记录数据符号信息的;
在代码编制上巧妙的利用构成计算机内部逻辑基础的0/1比特流的概念,使用若干个与二进制相对应的几何形体来表示文字数值信息,通过图像输入设备或光电扫描设备自动识读以实现信息自动处理;
二维码能够在横向和纵向两个方位同时表达信息,因此能在很小的面积内表达大量的信息;
二维码相对于条形码的优势就是省空间;
上图是一个QRCode的基本结构:
位置探测图形、位置探测图形分隔符、定位图形:用于二维码的定位,对每个QR来说,位置都是固定存在的,只是大小规格有所差异;
校正图形:确定规格,校正图形的数量和位置也就确定了;
格式信息:表示二维码的纠错级别,分为L、M、Q、H;
版本信息:即二维码的规格,QR码符号共有40种规格的矩阵;
数据和纠错码字:实际保存的二维码信息,和纠错码字(用于修正二维码损坏带来的错误)。
条形码原理介绍:
条形码扫描器结构
由于不同颜色的物体,其反射的可见光的波长不同,所以当条形码扫描器光源发出的光经光阑及凸透镜1后,照射到黑白相间的条形码上时,反射光经凸透镜2聚焦后,照射到光电转换器上,于是光电转换器接受到与白条和黑条相对应的强弱不同的反射光信号,并转换成相应的电信号输出到放大整形电路,整形电路把模拟信号转换成数字电信号,再经译码接口电路译成数字字符信息。
整形电路的脉冲数字信号经译码器译成数字、字符信息.它通过识别起始、终止字符来判别出条形码符号的码制及扫描方向;通过测量脉冲数字电信号0、1的数目来判别出条和空的数目.通过测量0、1信号持续的时间来判别条和空的宽度.这样便得到了被辩读的条形码符号的条和空的数目及相应的宽度和所用码制,根据码制所对应的编码规则,便可将条形符号换成相应的数字、字符信息,通过接口电路送给计算机系统进行数据处理与管理,便完成了条形码辨读的全过程。
Zxing是一个开源的,用于Java实现的多种格式的1D/2D条码图像处理库,它包含了联系到其他语言的接口。
Zxing可以实现使用手机的内置摄像头完成条形码和二维码的扫描与解码。
Zxing可以实现条形码和二维码的编码与解码。
Zxing目前支持的格式如下:UPC-A、UPC-E、EAN-8、EAN-13、39码、93码、代码128、QR码。
二、Zxing使用原理介绍
Zxing是一个开源的,用于Java实现的多种格式的1D/2D条码图像处理库,它包含了联系到其他语言的接口。
Zxing可以实现使用手机的内置摄像头完成条形码和二维码的扫描与解码。
Zxing可以实现条形码和二维码的编码与解码。
Zxing目前支持的格式如下:UPC-A、UPC-E、EAN-8、EAN-13、39码、93码、代码128、QR码。
三、实现步骤
1.导入Zxing.jar包,直接将源码内的com.mining.app.zxing.camera,com.mining.app.zxing.decoding, com.mining.app.zxing.view, com.mining.app.zxing.encoding四个文件夹,zxing package直接复制到你的工程目录下。
源码下载地址:http://code.google.com/p/zxing/
GitHub下载地址:https://github.com/zxing/zxing
2.将raw文件(用于),values,权限以及资源文件,全部准备完全,详情可参见源码。
3. activity.xml主菜单布局文件
1
2
3
4
5
6
7
8
9
10
11
|
<linearlayout android:background=
"#ffe1e0de"
android:layout_height=
"match_parent"
android:layout_width=
"match_parent"
android:orientation=
"vertical"
xmlns:android=
"http://schemas.android.com/apk/res/android"
xmlns:tools=
"http://schemas.android.com/tools"
><button android:id=
"@+id/button1"
android:layout_alignparenttop=
"true"
android:layout_height=
"wrap_content"
android:layout_width=
"fill_parent"
android:text=
"扫描二维码/条形码"
>
<edittext android:id=
"@+id/et_qr_string"
android:layout_height=
"wrap_content"
android:layout_width=
"match_parent"
>
<linearlayout android:layout_height=
"wrap_content"
android:layout_width=
"match_parent"
>
</linearlayout></edittext></button><button android:id=
"@+id/button2"
android:layout_alignparenttop=
"true"
android:layout_height=
"wrap_content"
android:layout_width=
"wrap_content"
android:text=
"生成二维码"
></button><button android:id=
"@+id/button3"
android:layout_alignparenttop=
"true"
android:layout_height=
"wrap_content"
android:layout_width=
"wrap_content"
android:text=
"生成条形码"
>
<textview android:gravity=
"center_horizontal"
android:id=
"@+id/result"
android:layout_below=
"@+id/button1"
android:layout_height=
"wrap_content"
android:layout_width=
"fill_parent"
android:lines=
"2"
android:textcolor=
"@android:color/black"
android:textsize=
"16sp"
>
<imageview android:id=
"@+id/qrcode_bitmap"
android:layout_alignparentleft=
"true"
android:layout_below=
"@+id/result"
android:layout_height=
"fill_parent"
android:layout_width=
"fill_parent"
>
</imageview></textview></button></linearlayout>
|
4.activity_capture.xml扫描布局文件
1
2
3
4
5
6
7
8
9
10
11
12
13
|
<!--?xml version=
"1.0"
encoding=
"utf-8"
?-->
<framelayout android:layout_height=
"fill_parent"
android:layout_width=
"fill_parent"
xmlns:android=
"http://schemas.android.com/apk/res/android"
>
<relativelayout android:layout_height=
"fill_parent"
android:layout_width=
"fill_parent"
>
<surfaceview android:id=
"@+id/preview_view"
android:layout_gravity=
"center"
android:layout_height=
"fill_parent"
android:layout_width=
"fill_parent"
>
<zxing.view.viewfinderview android:id=
"@+id/viewfinder_view"
android:layout_height=
"wrap_content"
android:layout_width=
"wrap_content"
>
<include android:id=
"@+id/include1"
android:layout_alignparenttop=
"true"
android:layout_height=
"wrap_content"
android:layout_width=
"fill_parent"
layout=
"@layout/activity_title"
>
</include></zxing.view.viewfinderview></surfaceview></relativelayout>
</framelayout>
|
5.activity_title.xml Title布局文件
1
2
3
4
5
6
|
<!--?xml version=
"1.0"
encoding=
"utf-8"
?-->
<relativelayout android:background=
"@drawable/mmtitle_bg_alpha"
android:layout_height=
"wrap_content"
android:layout_width=
"fill_parent"
xmlns:android=
"http://schemas.android.com/apk/res/android"
><button android:background=
"@drawable/mm_title_back_btn"
android:id=
"@+id/button_back"
android:layout_centervertical=
"true"
android:layout_height=
"wrap_content"
android:layout_marginleft=
"2dip"
android:layout_width=
"75.0dip"
android:text=
"返回"
android:textcolor=
"@android:color/white"
>
<textview android:gravity=
"center_vertical"
android:id=
"@+id/textview_title"
android:layout_alignbaseline=
"@+id/button_back"
android:layout_alignbottom=
"@+id/button_back"
android:layout_centerhorizontal=
"true"
android:layout_height=
"wrap_content"
android:layout_width=
"wrap_content"
android:text=
"二维码扫描"
android:textcolor=
"@android:color/white"
android:textsize=
"18sp"
>
</textview></button></relativelayout>
|
上述文件用于标签,便于修改以及占少量内存
6.MainActivity.java.主界面用于事件的处理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
|
package
example.com.zxingdemo;
import
android.app.Activity;
import
android.content.Intent;
import
android.graphics.Bitmap;
import
android.os.Bundle;
import
android.view.View;
import
android.view.View.OnClickListener;
import
android.widget.Button;
import
android.widget.EditText;
import
android.widget.ImageView;
import
android.widget.TextView;
import
android.widget.Toast;
import
com.google.zxing.WriterException;
import
zxing.encoding.EncodingHandler;
public
class
MainActivity
extends
Activity {
private
final
static
int
SCANNIN_GREQUEST_CODE =
1
;
/**
* 显示扫描结果
*/
private
TextView mTextView ;
/**
* 显示扫描拍的图片
*/
private
ImageView mImageView;
/**
* 输入框产生二维码
* @param savedInstanceState
*/
private
EditText qrStrEditText;
@Override
protected
void
onCreate(Bundle savedInstanceState) {
super
.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTextView = (TextView) findViewById(R.id.result);
mImageView = (ImageView) findViewById(R.id.qrcode_bitmap);
qrStrEditText = (EditText) findViewById(R.id.et_qr_string);
//点击按钮跳转到二维码扫描界面,这里用的是startActivityForResult跳转
//扫描完了之后调到该界面
Button mButtonScan = (Button) findViewById(R.id.button1);
Button mBtnTwoCode = (Button) findViewById(R.id.button2);
Button mBtnOneCode = (Button) findViewById(R.id.button3);
mButtonScan.setOnClickListener(
new
OnClickListener() {
@Override
public
void
onClick(View v) {
Intent intent =
new
Intent();
intent.setClass(MainActivity.
this
, MipcaActivityCapture.
class
);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivityForResult(intent, SCANNIN_GREQUEST_CODE);
}
});
//产生二维码
mBtnTwoCode.setOnClickListener(
new
OnClickListener() {
@Override
public
void
onClick(View v) {
String contentString = qrStrEditText.getText().toString();
try
{
if
(!contentString.equals(
""
)) {
Bitmap qrCodeBitmap = EncodingHandler.createQRCode(contentString,
350
);
mImageView.setImageBitmap(qrCodeBitmap);
qrStrEditText.setText(
""
);
mTextView.setText(contentString);
}
else
{
Toast.makeText(getApplicationContext(),
"Text can be not empty"
, Toast.LENGTH_SHORT).show();
}
}
catch
(WriterException e) {
e.printStackTrace();
}
}
});
//产生条形码
mBtnOneCode.setOnClickListener(
new
OnClickListener() {
@Override
public
void
onClick(View v) {
String contentString = qrStrEditText.getText().toString();
int
size = contentString.length();
for
(
int
i =
0
; i < size; i++) {
int
c = contentString.charAt(i);
if
((
19968
<= c && c <
40623
)) {
Toast.makeText(getApplicationContext(),
"text not be chinese"
, Toast.LENGTH_SHORT).show();
return
;
}
}
Bitmap mBmpOneCode =
null
;
try
{
if
(contentString !=
null
&& !
""
.equals(contentString)) {
mBmpOneCode = EncodingHandler.CreateOneDCode(contentString);
qrStrEditText.setText(
""
);
mTextView.setText(contentString);
}
}
catch
(WriterException e) {
e.printStackTrace();
}
if
(mBmpOneCode !=
null
) {
mImageView.setImageBitmap(mBmpOneCode);
}
}
});
}
@Override
protected
void
onActivityResult(
int
requestCode,
int
resultCode, Intent data) {
super
.onActivityResult(requestCode, resultCode, data);
switch
(requestCode) {
case
SCANNIN_GREQUEST_CODE:
if
(resultCode == RESULT_OK){
Bundle bundle = data.getExtras();
//显示扫描到的内容
mTextView.setText(bundle.getString(
"result"
));
//显示
mImageView.setImageBitmap((Bitmap) data.getParcelableExtra(
"bitmap"
));
}
break
;
}
}
}
|
7.MipcaActivityCapture.java.主要是调用起Camera功能进行扫描
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
|
package
example.com.zxingdemo;
import
android.app.Activity;
import
android.content.Intent;
import
android.content.res.AssetFileDescriptor;
import
android.graphics.Bitmap;
import
android.media.AudioManager;
import
android.media.MediaPlayer;
import
android.media.MediaPlayer.OnCompletionListener;
import
android.os.Bundle;
import
android.os.Handler;
import
android.os.Vibrator;
import
android.view.SurfaceHolder;
import
android.view.SurfaceHolder.Callback;
import
android.view.SurfaceView;
import
android.view.View;
import
android.view.View.OnClickListener;
import
android.widget.Button;
import
android.widget.Toast;
import
com.google.zxing.BarcodeFormat;
import
com.google.zxing.Result;
import
java.io.IOException;
import
java.util.Vector;
import
zxing.camera.CameraManager;
import
zxing.decoding.CaptureActivityHandler;
import
zxing.decoding.InactivityTimer;
import
zxing.view.ViewfinderView;
/**
* Initial the camera
* @author Ryan.Tang
*/
public
class
MipcaActivityCapture
extends
Activity
implements
Callback {
private
CaptureActivityHandler handler;
private
ViewfinderView viewfinderView;
private
boolean
hasSurface;
private
Vector<barcodeformat> decodeFormats;
private
String characterSet;
private
InactivityTimer inactivityTimer;
private
MediaPlayer mediaPlayer;
private
boolean
playBeep;
private
static
final
float
BEEP_VOLUME =
0
.10f;
private
boolean
vibrate;
/**
* Called when the activity is first created.
*/
@Override
public
void
onCreate(Bundle savedInstanceState) {
super
.onCreate(savedInstanceState);
setContentView(R.layout.activity_capture);
//ViewUtil.addTopView(getApplicationContext(), this, R.string.scan_card);
CameraManager.init(getApplication());
viewfinderView = (ViewfinderView) findViewById(R.id.viewfinder_view);
Button mButtonBack = (Button) findViewById(R.id.button_back);
mButtonBack.setOnClickListener(
new
OnClickListener() {
@Override
public
void
onClick(View v) {
MipcaActivityCapture.
this
.finish();
}
});
hasSurface =
false
;
inactivityTimer =
new
InactivityTimer(
this
);
}
@Override
protected
void
onResume() {
super
.onResume();
SurfaceView surfaceView = (SurfaceView) findViewById(R.id.preview_view);
SurfaceHolder surfaceHolder = surfaceView.getHolder();
if
(hasSurface) {
initCamera(surfaceHolder);
}
else
{
surfaceHolder.addCallback(
this
);
surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
}
decodeFormats =
null
;
characterSet =
null
;
playBeep =
true
;
AudioManager audioService = (AudioManager) getSystemService(AUDIO_SERVICE);
if
(audioService.getRingerMode() != AudioManager.RINGER_MODE_NORMAL) {
playBeep =
false
;
}
initBeepSound();
vibrate =
true
;
}
@Override
protected
void
onPause() {
super
.onPause();
if
(handler !=
null
) {
handler.quitSynchronously();
handler =
null
;
}
CameraManager.get().closeDriver();
}
@Override
protected
void
onDestroy() {
inactivityTimer.shutdown();
super
.onDestroy();
}
/**
* 处理扫描结果
*
* @param result
* @param barcode
*/
public
void
handleDecode(Result result, Bitmap barcode) {
inactivityTimer.onActivity();
playBeepSoundAndVibrate();
String resultString = result.getText();
if
(resultString.equals(
""
)) {
Toast.makeText(MipcaActivityCapture.
this
,
"Scan failed!"
, Toast.LENGTH_SHORT).show();
}
else
{
Intent resultIntent =
new
Intent();
Bundle bundle =
new
Bundle();
bundle.putString(
"result"
, resultString);
bundle.putParcelable(
"bitmap"
, barcode);
resultIntent.putExtras(bundle);
this
.setResult(RESULT_OK, resultIntent);
}
MipcaActivityCapture.
this
.finish();
}
private
void
initCamera(SurfaceHolder surfaceHolder) {
try
{
CameraManager.get().openDriver(surfaceHolder);
}
catch
(IOException ioe) {
return
;
}
catch
(RuntimeException e) {
return
;
}
if
(handler ==
null
) {
handler =
new
CaptureActivityHandler(
this
, decodeFormats,
characterSet);
}
}
@Override
public
void
surfaceChanged(SurfaceHolder holder,
int
format,
int
width,
int
height) {
}
@Override
public
void
surfaceCreated(SurfaceHolder holder) {
if
(!hasSurface) {
hasSurface =
true
;
initCamera(holder);
}
}
@Override
public
void
surfaceDestroyed(SurfaceHolder holder) {
hasSurface =
false
;
}
public
ViewfinderView getViewfinderView() {
return
viewfinderView;
}
public
Handler getHandler() {
return
handler;
}
public
void
drawViewfinder() {
viewfinderView.drawViewfinder();
}
private
void
initBeepSound() {
if
(playBeep && mediaPlayer ==
null
) {
// The volume on STREAM_SYSTEM is not adjustable, and users found it
// too loud,
// so we now play on the music stream.
setVolumeControlStream(AudioManager.STREAM_MUSIC);
mediaPlayer =
new
MediaPlayer();
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mediaPlayer.setOnCompletionListener(beepListener);
AssetFileDescriptor file = getResources().openRawResourceFd(
R.raw.beep);
try
{
mediaPlayer.setDataSource(file.getFileDescriptor(),
file.getStartOffset(), file.getLength());
file.close();
mediaPlayer.setVolume(BEEP_VOLUME, BEEP_VOLUME);
mediaPlayer.prepare();
}
catch
(IOException e) {
mediaPlayer =
null
;
}
}
}
private
static
final
long
VIBRATE_DURATION = 200L;
private
void
playBeepSoundAndVibrate() {
if
(playBeep && mediaPlayer !=
null
) {
mediaPlayer.start();
}
if
(vibrate) {
Vibrator vibrator = (Vibrator) getSystemService(VIBRATOR_SERVICE);
vibrator.vibrate(VIBRATE_DURATION);
}
}
/**
* When the beep has finished playing, rewind to queue up another one.
*/
private
final
OnCompletionListener beepListener =
new
OnCompletionListener() {
public
void
onCompletion(MediaPlayer mediaPlayer) {
mediaPlayer.seekTo(
0
);
}
};
}</barcodeformat>
|
四、代码分析
1.生成二维码
调用方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
//产生二维码
mBtnTwoCode.setOnClickListener(
new
OnClickListener() {
@Override
public
void
onClick(View v) {
String contentString = qrStrEditText.getText().toString();
try
{
if
(!contentString.equals(
""
)) {
Bitmap qrCodeBitmap = EncodingHandler.createQRCode(contentString,
350
);
mImageView.setImageBitmap(qrCodeBitmap);
qrStrEditText.setText(
""
);
mTextView.setText(contentString);
}
else
{
Toast.makeText(getApplicationContext(),
"Text can be not empty"
, Toast.LENGTH_SHORT).show();
}
}
catch
(WriterException e) {
e.printStackTrace();
}
}
});
|
实现方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
private
static
final
int
BLACK =
0xff000000
;
//生成二维码
public
static
Bitmap createQRCode(String str,
int
widthAndHeight)
throws
WriterException {
Hashtable<encodehinttype, string=
""
> hints =
new
Hashtable<encodehinttype, string=
""
>();
hints.put(EncodeHintType.CHARACTER_SET,
"utf-8"
);
BitMatrix matrix =
new
MultiFormatWriter().encode(str,
BarcodeFormat.QR_CODE, widthAndHeight, widthAndHeight);
int
width = matrix.getWidth();
int
height = matrix.getHeight();
int
[] pixels =
new
int
[width * height];
for
(
int
y =
0
; y < height; y++) {
for
(
int
x =
0
; x < width; x++) {
if
(matrix.get(x, y)) {
pixels[y * width + x] = BLACK;
}
}
}
Bitmap bitmap = Bitmap.createBitmap(width, height,
Bitmap.Config.ARGB_8888);
bitmap.setPixels(pixels,
0
, width,
0
,
0
, width, height);
return
bitmap;
}</encodehinttype,></encodehinttype,>
|
2.生成条形码
调用方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
//产生条形码
mBtnOneCode.setOnClickListener(
new
OnClickListener() {
@Override
public
void
onClick(View v) {
String contentString = qrStrEditText.getText().toString();
int
size = contentString.length();
for
(
int
i =
0
; i < size; i++) {
int
c = contentString.charAt(i);
if
((
19968
<= c && c <
40623
)) {
Toast.makeText(getApplicationContext(),
"text not be chinese"
, Toast.LENGTH_SHORT).show();
return
;
}
}
Bitmap mBmpOneCode =
null
;
try
{
if
(contentString !=
null
&& !
""
.equals(contentString)) {
mBmpOneCode = EncodingHandler.CreateOneDCode(contentString);
qrStrEditText.setText(
""
);
mTextView.setText(contentString);
}
}
catch
(WriterException e) {
e.printStackTrace();
}
if
(mBmpOneCode !=
null
) {
mImageView.setImageBitmap(mBmpOneCode);
}
}
});
|
实现方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
//生成条形码
public
static
Bitmap CreateOneDCode(String content)
throws
WriterException {
// 生成一维条码,编码时指定大小,不要生成了图片以后再进行缩放,这样会模糊导致识别失败
BitMatrix matrix =
new
MultiFormatWriter().encode(content,
BarcodeFormat.CODE_128,
500
,
200
);
int
width = matrix.getWidth();
int
height = matrix.getHeight();
int
[] pixels =
new
int
[width * height];
for
(
int
y =
0
; y < height; y++) {
for
(
int
x =
0
; x < width; x++) {
if
(matrix.get(x, y)) {
pixels[y * width + x] = BLACK;
}
}
}
|
3.扫描二维码、条形码
1
2
3
4
5
6
7
8
9
10
|
mButtonScan.setOnClickListener(
new
OnClickListener() {
@Override
public
void
onClick(View v) {
Intent intent =
new
Intent();
intent.setClass(MainActivity.
this
, MipcaActivityCapture.
class
);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivityForResult(intent, SCANNIN_GREQUEST_CODE);
}
});
|
五、实现效果图