之前给公司做了一个摄影相关的应用,现在要添加二维码扫描的功能,网上找资料后,虽然已经成功集成到app里面,但是总感觉心里没底儿。所以趁这段时间不是很忙,总结一下。
首先是启动扫描的UI类:
1,Activity启动,当然是onCreate方法
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
230
231
232
233
234
235
236
237
238
239
|
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) {
Scaner. 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 @param result * @param @param barcode * @author Administrator * @return void */
public
void handleDecode(Result result, Bitmap barcode) {
inactivityTimer.onActivity();
playBeepSoundAndVibrate();
String
resultString = result.getText();
if
(resultString.equals( "" )) {
Toast.makeText(Scaner. this , "Scan failed!" , 3000 ).show();
} else
{
//查询keycode 本地数据库 1,优先查询本地库,2,没有本地库,直接跳到知道链接
//分析出keyCode
Log.i( "testMain" , "scan_result=====>" +resultString);
String
keyCode= "" ;
String [] split1;
if (resultString.lastIndexOf( "?" )< 0 ){
Intent intent = new
Intent( this , InnerBrowser. class );
Bundle bundle = new
Bundle();
bundle.putString( "result" , resultString);
//bundle.putParcelable("bitmap", barcode);
intent.putExtras(bundle);
startActivity(intent);Scaner. this .finish(); return ;
}
String [] attr = resultString.substring(resultString.lastIndexOf( "?" )- 1 , resultString.length()).split( "&" );
for
( String string : attr) {
split1 = string.split( "=" );
if (split1[ 0 ].equalsIgnoreCase( "keycode" )){
//找到
if (split1.length== 2 ){
keyCode=split1[ 1 ];
}
}
}
Log.i( "testMain" , "keyCode=====>" +keyCode);
if (!StringUtils.isBlank(keyCode)){
AttractionDAO dao= new
AttractionDAO(Scaner. this );
Attraction a=dao.findAttrByKeyCode(keyCode);
Log.i( "testMain" , "a=====>" +a);
if (a!= null ){
Intent it= new
Intent();
it.setClass(Scaner. this , UIAttractionDetail. class );
it.putExtra( "a" , a);
startActivity(it);
} else {
Intent intent = new
Intent( this , InnerBrowser. class );
Bundle bundle = new
Bundle();
bundle.putString( "result" , resultString);
//bundle.putParcelable("bitmap", barcode);
intent.putExtras(bundle);
startActivity(intent);
//this.setResult(RESULT_OK, resultIntent);
//使用内置浏览器打开网站内容
}
} else {
Intent intent = new
Intent( this , InnerBrowser. class );
Bundle bundle = new
Bundle();
bundle.putString( "result" , resultString);
//bundle.putParcelable("bitmap", barcode);
intent.putExtras(bundle);
startActivity(intent);
//this.setResult(RESULT_OK, resultIntent);
//使用内置浏览器打开网站内容
}
}
Scaner. 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 );
}
}; |
从上面代码可以看出,做了三件事儿:加载布局文件;初始化了一个相机管理器;设置按钮监听,初始化了一个InactivityTimer实例;
然后,最重要的是他实现了一个CallBack函数:具体参见:
SurfaceHolder.Callback 译文
此时,
1
|
surfaceCreated |
这个方法会调用然后就初始化相机的一些参数:
前两个我们好理解,第三个是干嘛的?
我们先看布局文件:
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
|
<?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"
>
<RelativeLayout
android:layout_width= "fill_parent"
android:layout_height= "fill_parent"
>
<SurfaceView
android:id= "@+id/preview_view"
android:layout_width= "fill_parent"
android:layout_height= "fill_parent"
android:layout_gravity= "center"
/>
<com.euc.app.scan.view.ViewfinderView
android:id= "@+id/viewfinder_view"
android:layout_width= "wrap_content"
android:layout_height= "wrap_content"
/>
< include
android:id= "@+id/include1"
android:layout_width= "fill_parent"
android:layout_height= "wrap_content"
android:layout_alignParentTop= "true"
layout= "@layout/activity_title"
/>
</RelativeLayout>
</FrameLayout> |
可以看到里面有一个自定义的View及surfaceView,
对于我这样的初学者来说,surfaceView 是什么东西?
csdn上看到这个文章
Android中SurfaceView的使用详解
虽然不是很明白,但是大致明白这是个什么东西了。
了解了生命周期之后,我们来看他执行的方法:
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
|
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 ;
}
|
这个surfaceView 创建出来之后,其实也把摄像头的配置信息以及硬件信息初始化好了。
OK,经过上面一个oncreate以及布局文件的加载,我们已经知道,摄像头预览成功,
这个自定义的View又是干嘛的?我们继续看源码:
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
|
private final int maskColor;
private final int resultColor;
private final int resultPointColor;
private Collection<ResultPoint> possibleResultPoints;
private Collection<ResultPoint> lastPossibleResultPoints;
boolean isFirst; public ViewfinderView(Context context, AttributeSet attrs) {
super (context, attrs);
density = context.getResources().getDisplayMetrics().density;
//将像素转换成dp
ScreenRate = ( int )( 20
* density);
paint = new
Paint();
Resources resources = getResources();
maskColor = resources.getColor(R.color.viewfinder_mask);
resultColor = resources.getColor(R.color.result_view);
resultPointColor = resources.getColor(R.color.possible_result_points);
possibleResultPoints = new
HashSet<ResultPoint>( 5 );
} @Override public
void onDraw(Canvas canvas) {
//中间的扫描框,你要修改扫描框的大小,去CameraManager里面修改
Rect frame = CameraManager. get ().getFramingRect();
if
(frame == null ) {
return ;
}
//初始化中间线滑动的最上边和最下边
if (!isFirst){
isFirst = true ;
slideTop = frame.top;
slideBottom = frame.bottom;
}
//获取屏幕的宽和高
int
width = canvas.getWidth();
int
height = canvas.getHeight();
paint.setColor(resultBitmap != null
? resultColor : maskColor);
//画出扫描框外面的阴影部分,共四个部分,扫描框的上面到屏幕上面,扫描框的下面到屏幕下面
//扫描框的左边面到屏幕左边,扫描框的右边到屏幕右边
canvas.drawRect( 0 , 0 , width, frame.top, paint);
canvas.drawRect( 0 , frame.top, frame.left, frame.bottom + 1 , paint);
canvas.drawRect(frame.right + 1 , frame.top, width, frame.bottom + 1 ,
paint);
canvas.drawRect( 0 , frame.bottom + 1 , width, height, paint);
if
(resultBitmap != null ) {
// Draw the opaque result bitmap over the scanning rectangle
paint.setAlpha(OPAQUE);
canvas.drawBitmap(resultBitmap, frame.left, frame.top, paint);
} else
{
//画扫描框边上的角,总共8个部分
paint.setColor(Color.GREEN);
canvas.drawRect(frame.left, frame.top, frame.left + ScreenRate,
frame.top + CORNER_WIDTH, paint);
canvas.drawRect(frame.left, frame.top, frame.left + CORNER_WIDTH, frame.top
+ ScreenRate, paint);
canvas.drawRect(frame.right - ScreenRate, frame.top, frame.right,
frame.top + CORNER_WIDTH, paint);
canvas.drawRect(frame.right - CORNER_WIDTH, frame.top, frame.right, frame.top
+ ScreenRate, paint);
canvas.drawRect(frame.left, frame.bottom - CORNER_WIDTH, frame.left
+ ScreenRate, frame.bottom, paint);
canvas.drawRect(frame.left, frame.bottom - ScreenRate,
frame.left + CORNER_WIDTH, frame.bottom, paint);
canvas.drawRect(frame.right - ScreenRate, frame.bottom - CORNER_WIDTH,
frame.right, frame.bottom, paint);
canvas.drawRect(frame.right - CORNER_WIDTH, frame.bottom - ScreenRate,
frame.right, frame.bottom, paint);
//绘制中间的线,每次刷新界面,中间的线往下移动SPEEN_DISTANCE
slideTop += SPEEN_DISTANCE;
if (slideTop >= frame.bottom){
slideTop = frame.top;
}
canvas.drawRect(frame.left + MIDDLE_LINE_PADDING, slideTop - MIDDLE_LINE_WIDTH/ 2 , frame.right - MIDDLE_LINE_PADDING,slideTop + MIDDLE_LINE_WIDTH/ 2 , paint);
//画扫描框下面的字
paint.setColor(Color.WHITE);
paint.setTextSize(TEXT_SIZE * density);
paint.setAlpha( 0x40 );
paint.setTypeface(Typeface.create( "System" , Typeface.BOLD));
canvas.drawText(getResources().getString(R.string.scan_text), frame.left, (float) (frame.bottom + (float)TEXT_PADDING_TOP *density), paint);
Collection<ResultPoint> currentPossible = possibleResultPoints;
Collection<ResultPoint> currentLast = lastPossibleResultPoints;
if
(currentPossible.isEmpty()) {
lastPossibleResultPoints = null ;
} else
{
possibleResultPoints = new
HashSet<ResultPoint>( 5 );
lastPossibleResultPoints = currentPossible;
paint.setAlpha(OPAQUE);
paint.setColor(resultPointColor);
for
(ResultPoint point : currentPossible) {
canvas.drawCircle(frame.left + point.getX(), frame.top
+ point.getY(), 6 .0f, paint);
}
}
if
(currentLast != null ) {
paint.setAlpha(OPAQUE / 2 );
paint.setColor(resultPointColor);
for
(ResultPoint point : currentLast) {
canvas.drawCircle(frame.left + point.getX(), frame.top
+ point.getY(), 3 .0f, paint);
}
}
//只刷新扫描框的内容,其他地方不刷新
postInvalidateDelayed(ANIMATION_DELAY, frame.left, frame.top,
frame.right, frame.bottom);
}
} public
void drawViewfinder() {
resultBitmap = null ;
invalidate();
} /** * Draw a bitmap with the result points highlighted instead of the live
* scanning display.
*
* @param barcode
* An image of the decoded barcode.
*/
public
void drawResultBitmap(Bitmap barcode) {
resultBitmap = barcode;
invalidate();
} public
void addPossibleResultPoint(ResultPoint point) {
possibleResultPoints.add(point);
} |
哦,这个就是定义了一个有动态效果的扫描界面
上面的虽然代码不多,当时我们现在回忆一下步骤:
1,启动activity,加载布局文件,初始化surfaceView,初始化自定义的View(动态界面),
2,在初始化surfaceView的时候,同时初始化了摄像头的参数,初始化的handler处理器,启动了摄像头预览。
问题:那什么时候开始监听扫描二维码的呢?
初始化handler 的时候就开始监听了,看一下其构造函数:
1
2
3
4
5
6
7
8
9
10
11
|
public CaptureActivityHandler(Scaner activity, Vector<BarcodeFormat> decodeFormats,
String
characterSet) {
this .activity = activity;
decodeThread = new
DecodeThread(activity, decodeFormats, characterSet,
new
ViewfinderResultPointCallback(activity.getViewfinderView()));
decodeThread.start();
state = State.SUCCESS;
// Start ourselves capturing previews and decoding.
CameraManager. get ().startPreview();
restartPreviewAndDecode();
}
|
再来一个:上面构造函数new了一个对象,这个对象就是用来监听获取扫描的图像的。
直到获取了二维码图像,调用回调函数就结束。
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
|
final class DecodeThread extends
Thread {
public
static final String BARCODE_BITMAP = "barcode_bitmap" ;
private
final Scaner activity;
private
final Hashtable<DecodeHintType, Object > hints;
private
Handler handler;
private
final CountDownLatch handlerInitLatch;
DecodeThread(Scaner activity,
Vector<BarcodeFormat> decodeFormats,
String
characterSet,
ResultPointCallback resultPointCallback) {
this .activity = activity;
handlerInitLatch = new
CountDownLatch( 1 );
hints = new
Hashtable<DecodeHintType, Object >( 3 );
if
(decodeFormats == null
|| decodeFormats.isEmpty()) {
decodeFormats = new
Vector<BarcodeFormat>();
decodeFormats.addAll(DecodeFormatManager.ONE_D_FORMATS);
decodeFormats.addAll(DecodeFormatManager.QR_CODE_FORMATS);
decodeFormats.addAll(DecodeFormatManager.DATA_MATRIX_FORMATS);
}
hints.put(DecodeHintType.POSSIBLE_FORMATS, decodeFormats);
if
(characterSet != null ) {
hints.put(DecodeHintType.CHARACTER_SET, characterSet);
}
hints.put(DecodeHintType.NEED_RESULT_POINT_CALLBACK, resultPointCallback);
}
Handler getHandler() {
try
{
handlerInitLatch.await();
} catch
(InterruptedException ie) {
// continue?
}
return
handler;
}
@Override
public
void run() {
Looper.prepare();
handler = new
DecodeHandler(activity, hints);
handlerInitLatch.countDown();
Looper.loop();
}
} |
回调函数:
1
2
3
4
5
6
7
8
9
10
11
|
public void handleDecode(Result result, Bitmap barcode) {
inactivityTimer.onActivity();
playBeepSoundAndVibrate();
String
resultString = result.getText();
if
(resultString.equals( "" )) {
Toast.makeText(Scaner. this , "Scan failed!" , 3000 ).show();
} else
{
//扫描结果的处理。
}
Scaner. this .finish();
} |