Android:实际运用Zxing集成二维码扫描 及 自定义扫码界面(demo源码)
Posted 鸽一门
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android:实际运用Zxing集成二维码扫描 及 自定义扫码界面(demo源码)相关的知识,希望对你有一定的参考价值。
二维码扫描,各大主流App必不可少的功能,而且google已将轮子替我们造好,直接拿来使用即可。以下是教学如何将Zxing开源库集成到自己项目中,并且自定义扫码界面,后期可根据自己的业务需求进行修改,最后补充了一点由此延伸的学习技能点。
一. 集成Zing开源库到应用中
如上图所示,我使用的集成方法是拷入jar包,然后拷贝相关类即可,具体的资源都已经在Demo中做好了,具体内容都可以直接参考Demo。(看到网上还有其它集成的方法,虽没试过,各位也可以考虑)
- app 扫描Activity
- camera 摄像头相关
- decode 解析二维码相关
- encode 生成二维码相关
- util 工具类
- view 扫描预览视图ViewfinderView
二. 自定义扫码界面
<?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" />
<cn.edu.wsyu.hgh.zhiliao.component.zxing.view.ViewfinderView
android:id="@+id/viewfinder_view"
android:layout_width="fill_parent"
android:layout_height="fill_parent" />
</RelativeLayout>
</FrameLayout>
如果集成Zxing成功后打开扫描功能,你会发现扫描页面如上图所示,这是CaptureActivity的布局,CaptureActivity主要来处理扫描界面的逻辑。布局仅有一个SurfaceView,中间一个供扫描的矩形框ViewfinderView。就目前而言,扫描的功能已经可以使用了,但是这种UI效果是市场上的app不符,所以需要自定义扫码页面,而理想实现效果如下图所示:
1. 需要实现的需求
对比以上两张图,总结出我们需要实现的需求:
- (1) 增加一个toolbar,内容填充为返回键和界面提示。
- (2) 中间的扫描区域—–即Zxing为我们绘制的矩形的四个角上分别画了8个绿色小矩形和一条不停上下移动的扫描线。
- (3) 在最下方加了一个footer,增加了相册、开灯两个功能使得这个扫描功能更加完善。
经过以上分析可得,完成上述3个需求,就可以达到理想的UI效果了,接下来依次实现:
2. 实现头部效果
这个头部实现实在很简单,这里不过多赘述,提供2种实现思路:
- 1 使用Android提供控件Toolbar,在控件中再加一个TextView用来显示“扫一扫”即可。
- 2 直接加一个布局View,布局中一个ImageView、TextView完成
布局完成后,在CaptureActivity中设置对应的点击事件,处理返回逻辑即可。
3. 实现扫描框效果
这个扫描框是Zxing自带的一个自定义View,名为ViewfinderView,而目前需要做的需求:
- 1 扫码框的四个角上,每个角有两条边,总共八条,绘制8条绿色矩形,完成边框。
- 2 在扫码框中绘制一条不停上下移动的线。
- 3 在扫描框下方添加一条提示语句
3.1 绘制扫描边框
熟悉自定义View的人首先想到在ViewfinderView中的onDraw方法中完成绘制,首先来看它默认绘制的onDraw方法:
@Override
public void onDraw(Canvas canvas)
//获得扫描框的矩形
Rect frame = CameraManager.get().getFramingRect();
if (frame == null)
return;
int width = canvas.getWidth();
int height = canvas.getHeight();
//绘制扫描框矩形
// Draw the exterior (i.e. outside the framing rect) darkened
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);
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);
初步了解后,需要使用canvas来绘制8个小矩形,代码不难如下
paint.setColor(Color.BLUE);
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);
这值得注意的是ScreenRate,即小矩形的长度,这里并没有把它固定化,而是将理想值乘以屏幕密度,来更好地适配不同大小屏幕:
density = context.getResources().getDisplayMetrics().density;
ScreenRate = (int) (15 * density);
3.2 绘制移动的线
//定义好扫描线每秒移动端的像素
slideTop += SPEEN_DISTANCE;
//判断扫描线的长度是否大于扫描框底部,若大于则重新回到扫描框头部再进行扫描
if (slideTop >= frame.bottom)
slideTop = frame.top;
//动态定义出扫描线矩形
Rect lineRect = new Rect();
lineRect.left = frame.left;
lineRect.right = frame.right;
lineRect.top = slideTop;
lineRect.bottom = slideTop + 18;
//绘制上去
canvas.drawBitmap(((BitmapDrawable) (getResources()
.getDrawable(R.drawable.fle))).getBitmap(), null, lineRect,
paint);
3.3 绘制文字
paint.setColor(Color.WHITE);
paint.setTextSize(TEXT_SIZE * density);
paint.setAlpha(0x40);
paint.setTypeface(Typeface.create("System", Typeface.BOLD));
String text = getResources().getString(R.string.scan_text);
float textWidth = paint.measureText(text);
canvas.drawText(
text,
(width - textWidth) / 2,
(float) (frame.bottom + (float) TEXT_PADDING_TOP * density),
paint);
三. 扫码功能完善
1. 扫码界面功能完善
以上界面完成后,需要在CaptureActivity中稍加完善几个按钮(开闪光灯、相册)的功能:
之所以选择Zxing开源库的一大原因是因为它将所有的方法都封装完善,只需我们调用即可,例如开启或关闭闪光灯。
@Override
public void onClick(View v)
switch (v.getId())
case R.id.flash_btn:// 闪光灯开关
if (isTorchOn)
isTorchOn = false;
flash_btn.setText("关灯");
CameraManager.start();
else
isTorchOn = true;
flash_btn.setText("开灯");
CameraManager.stop();
break;
case R.id.photo_btn: // 选择相册
selectPhoto();
break;
case R.id.button_back: // 返回
finish();
break;
default:
break;
2 主页跳转到扫码界面
MainActivity
private static final int REQUEST_QRCODE = 0x01;
Intent intent = new Intent(mContext, CaptureActivity.class);
startActivityForResult(intent, REQUEST_QRCODE);
以上代码非常简单,假设是在MainActivity中进行操作,点击对应按钮进入到CaptureActivity中,唯一需要注意的是跳转页面使用的方法是startActivityForResult,因为我们需要获取到扫描后的数据,然后做下一步的处理。
3二维码生成
生成二维码的方式:
/**
* 二维码生成
*/
private void qrCodeGenerated()
String str = "点个赞噻";
DisplayMetrics metric = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(metric);
int width = metric.widthPixels;
try
Bitmap qrCode = Utils.createQRCode(str, width / 2);
qrcodeImg.setImageBitmap(qrCode);
catch (WriterException e)
// TODO Auto-generated catch block
e.printStackTrace();
str代表二维码中的数据,主要依赖的是Util工具类的方法,其中还有二维码中含Logo的方式,自己可修改,两种方法如下:
/**
* 生成一个二维码图像
*
* @param url
* 传入的字符串,通常是一个URL
* @param widthAndHeight
* 图像的宽高
* @return
*/
private static final int BLACK = 0xff000000;
public static Bitmap createQRCode(String str, int widthAndHeight)
/**
* 生成一个有logo的二维码图像
*
* @param url
* 传入的字符串,通常是一个URL
* @param widthAndHeight
* 图像的宽高
* @return
*/
public static Bitmap createQRCodeLogo(String str, int widthAndHeight,
Bitmap bit)
4. 主页获取扫码数据并处理
要想在MainActivity 中的 onActivityResult方法获取到扫描的数据,必须要知道什么时候将数据setResult进来的,获取数据的Key值是什么?
解析二维码是个耗时的操作,必然在子线程中执行,所以我们直接查看CaptureActivity对应的Handle——CaptureActivityHandle,通过类注释可知,这个类负责处理所有扫码相关的业务,既然是个Handle,那我们直接来看handleMessage方法:
@Override
public void handleMessage(Message message)
......
case R.id.decode_succeeded:// 解码成功
Log.d(TAG, "Got decode succeeded message");
state = State.SUCCESS;
Bundle bundle = message.getData();
/****************************************************/
Bitmap barcode = bundle == null ? null : (Bitmap) bundle
.getParcelable(DecodeThread.BARCODE_BITMAP);
//将扫码后得的数据返回给CaptureActivity去处理
activity.handleDecode((Result) message.obj, barcode);
break;
......
handleMessage方法中考虑了好几种情况,我们只需要了解这一种即可,将状态标记为“成功”,将扫码后得的数据返回给CaptureActivity去处理,所以最后还是回到CaptureActivity的handleDecode方法:
/**
* 处理扫描结果
*
* @param result
* @param barcode
*/
public void handleDecode(Result result, Bitmap barcode)
inactivityTimer.onActivity();
playBeepSoundAndVibrate();
String resultString = result.getText();
if (resultString.equals(""))
Toast.makeText(this, "扫描失败!", Toast.LENGTH_SHORT)
.show();
else
Intent data = new Intent();
data.putExtra("SCAN_RESULT", resultString);
setResult(RESULT_OK, data);
finish();
主要逻辑为将获取到扫码后的数据放入到Intent中,结束当前扫码页面。重要的是:我们获取到了Key值:SCAN_RESULT。此时,我们可以在MainActivity中获取到扫码后的数据并处理它。以上只是举一个实例,不仅这个Key值,还有其余需要使用的都可在CaptureActivity找到,如下表格:
含义 | Key | resultCode |
---|---|---|
直接扫码后获得的数据 | SCAN_RESULT | Activity.RESULT_OK |
相册图片扫码后获得的数据 | LOCAL_PHOTO_RESULT | 300 |
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data)
switch (requestCode)
case REQUEST_QRCODE:
if (resultCode == Activity.RESULT_OK)
//扫描后的业务逻辑
String code = data.getStringExtra("SCAN_RESULT");
if (code.contains("http") || code.contains("https"))
//打开链接
/*Intent intent = new Intent(this, AdBrowserActivity.class);
intent.putExtra(AdBrowserActivity.KEY_URL, code);
startActivity(intent);*/
Toast.makeText(this, code, Toast.LENGTH_SHORT).show();
else
Toast.makeText(this, code, Toast.LENGTH_SHORT).show();
else if(resultCode == 300)
//从本地相册扫描后的业务逻辑
String code = data.getStringExtra("LOCAL_PHOTO_RESULT");
Toast.makeText(this, code, Toast.LENGTH_SHORT).show();
break;
以上代码通过Key值获取到扫码后的数据data.getStringExtra("SCAN_RESULT")
。这里多做了一个判断,如果数据包含http或者https时,可以用浏览器打开,这用用户体验会更好,当然,你也可以按照自己的想法去处理。
四. 拓展学习——闪光灯使用
在集成使用一个开源库时,可是适当学习它的源码设计、封装风格等等,例如Zxing中已实现的使用闪光灯,单独将此代码拿出来,平常在开发中也会有使用到闪光灯的场景,大可以将此方法放到自己的工具库中,使用的时候调用即可,此刻可以很好的参考参考大神封装的方法:
CameraManager.java
private Camera camera;
public static void init(Context context)
if (cameraManager == null)
cameraManager = new CameraManager(context);
/**
* 通过设置Camera打开闪光灯
*/
public void turnLightOn()
if (camera == null)
return;
Parameters parameters = camera.getParameters();
if (parameters == null)
return;
List<String> flashModes = parameters.getSupportedFlashModes();
if (flashModes == null)
return;
String flashMode = parameters.getFlashMode();
Log.i(TAG, "Flash mode: " + flashMode);
Log.i(TAG, "Flash modes: " + flashModes);
// 闪光灯关闭状态
if (!Parameters.FLASH_MODE_TORCH.equals(flashMode))
// Turn on the flash
if (flashModes.contains(Parameters.FLASH_MODE_TORCH))
parameters.setFlashMode(Parameters.FLASH_MODE_TORCH);
camera.setParameters(parameters);
camera.startPreview();
else
/**
* 通过设置Camera关闭闪光灯
*
* @param mCamera
*/
public void turnLightOff()
if (camera == null)
return;
Parameters parameters = camera.getParameters();
if (parameters == null)
return;
List<String> flashModes = parameters.getSupportedFlashModes();
String flashMode = parameters.getFlashMode();
// Check if camera flash exists
if (flashModes == null)
return;
// 闪光灯打开状态
if (!Parameters.FLASH_MODE_OFF.equals(flashMode))
// Turn off the flash
if (flashModes.contains(Parameters.FLASH_MODE_OFF))
parameters.setFlashMode(Parameters.FLASH_MODE_OFF);
camera.setParameters(parameters);
else
Log.e(TAG, "FLASH_MODE_OFF not supported");
最后附加一点自己的最近的想法:
最近这几个月写博客写的不像初学Android时那么勤了,与同伴在做一个App,项目中用了不少开源库,这个Zxing也是其中一个。写完这篇博客不由得扪心自问,这些东西仅仅停留在会使用的阶段,不算是真正的学习,就像我这篇博客,是一篇参考型文章,并非学习类文章,通篇看下来也不会有恍然大悟的感觉,作为笔者的我感觉也没有深层次的学到什么,正如很多大牛说过的:许多开源库不是会用就够了,我们还需要学习它的实现封装等等,这才能使我们进步(鸡汤奉上)……
所以接下来打算学习一些偏向底层的干货,缓解近期的迷茫。将自己的想法分享给各位志同道合的friends,在未来杠 Android的路上继续努力,哈哈哈~
希望对你们有帮助 :)
源码奉上~~
ZxingDemo源码
以上是关于Android:实际运用Zxing集成二维码扫描 及 自定义扫码界面(demo源码)的主要内容,如果未能解决你的问题,请参考以下文章