扫描二维码研究总结(高仿微信扫一扫,轻松实现定制扫描界面)

本文主要是介绍扫描二维码研究总结(高仿微信扫一扫,轻松实现定制扫描界面),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

在正文之前说点题外话,加上这篇我已经写了3篇博客了,其实我写博客的初衷不是想证明自己有多牛,并且我也只是从事安卓开发只有半年时间的小渣,但是不想成为大牛的渣不是好渣,所以我想通过博客把工作学习中遇到的问题进行研究总结,从而提高自己,与此同时如果能给广大从事安卓开发的朋友们提供帮助或者是提供一点点思路我也是很心满意足了!~~好了,废话不多少进入正题吧!

先上界面图。由于目前不会录屏,所以直接上截图吧!~另外扫描框内本身有一个从上之下循环的绿线,但是不知道为毛手机截屏没显示。。。汗。。
这里写图片描述

最近公司需要一个二维码扫描的需求,于是我便在网上下载了一个应用谷歌zxing的开源项目,但是这个项目界面不符合公司需求,并且不灵活,我便研究了下这个项目,在这个项目的基础上进行了修改,编写了ZxingScannerViewNew这个类。先看下目录结构。
这里写图片描述
整个项目是在Android studio上进行开发的,zxinglib是一个Android Lib modul,libs文件夹中便是谷歌zxing的jar包,主要解码等方法都是这个包提供的。

BarcodeScannerView,CameraPreview,CameraUtils,DisplayUtils,ViewFinderView,这几个文件都是第三方项目提供的。我结合了BarcodeScannerView和ViewFinderView主要功能,并且进行了修改,使ZxingScannerViewNew更方便自定义扫描界面。CameraPreview是Camera的预览界面。下面先看下ZxingScannerViewNew这个类。

ZxingScannerViewNew继承FrameLayout并且实现Camera.PreviewCallback接口(后面有介绍)。看下成员变量。

    private Camera mCamera;private CameraPreview mPreview;//相机预览界面private View showPanel;//扫描界面private QrSize qrSize;//自己定义的接口

其中QrSize是自己定义的接口,代码如下。

    public interface QrSize {public Rect getDetectRect();}

这个接口主要是获取扫描区域的Rect矩形坐标等信息。接下来看下几个重要的方法。

 private void init() {//初始化方法addView(mPreview = new CameraPreview(getContext()));//加载默认扫描界面showPanel = View.inflate(getContext(), R.layout.default_scan, null);addView(showPanel);initMultiFormatReader();//初始化解码的类}public void setContentView(int res) {try {View panelView = View.inflate(getContext(), res, null);removeView(showPanel);showPanel = panelView;addView(showPanel);} catch (Exception e) {return;}}

在init方法中会初始化相机预览界面CameraPreview并且加入到ZxingScannerViewNew中作为底层。接着初始化默认扫描界面,并且加入到ZxingScannerViewNew中。因为ZxingScannerViewNew是framelayout所以showPanel 会覆盖到相机预览界面之上。如果调用setContentView方法,会移除默认的扫描界面,并且把传递进来的layout覆盖到相机预览界面之上,所以通过这个方法可以轻松实现自定义扫面界面。

接下来介绍下Camera.PreviewCallback接口。

很多时候,android摄像头模块不仅预览拍照这么简单,而是需要在预览的同时,能够做出一些检测,比如最常见的人脸检测。在未按下拍照按钮前,就检测出人脸然后显示矩形提示框,再按拍照。那么如何获得预览帧视频呢?只需要继承PreviewCallback这个接口就行了。继承这个方法后,会自动重载这个函数:
public void onPreviewFrame(byte[] data, Camera camera) {}。
这个函数里的data就是实时预览帧视频数据。一旦程序调用PreviewCallback接口,就会自动调用onPreviewFrame这个函数。调用PreviewCallback的方法有三种
1,setPreviewCallback。
2,setOneShotPreviewCallback。
3, setPreviewCallbackWithBuffer,。
一般是使用第二种方式。

什么时候触发onPreviewFrame()这个函数呢?可以是按一个按键触发一次,就在按键的监听里执行setOneShotPreviewCallback,便会自动触发一次。但大多数希望程序自动每隔多长时间,自动进行一次检测预览帧。所以我会在程序中设置一个timer每隔1.5秒便执行一次。

private void startFreshThread(final Camera camera, final Camera.PreviewCallback callback) {//初始化每timer每隔1.5秒设置一次 setOneShotPreviewCallbacktimer = new Timer();timer.scheduleAtFixedRate(new TimerTask() {@Overridepublic void run() {Log.e("Run", "Running");if (camera == null || !cameraAvailable()) {cancelTask();return;}camera.setOneShotPreviewCallback(callback);}}, 0, 1500);}private byte[] rotatedData;private boolean onlyOnce;@Overridepublic void onPreviewFrame(byte[] data, Camera camera) {if (onlyOnce) {//只开启timer一次startFreshThread(camera, this);onlyOnce = false;}Camera.Parameters parameters = camera.getParameters();//获取帧视频sizeCamera.Size size = parameters.getPreviewSize();int width = size.width;//获取帧视频宽度int height = size.height;//获取帧视频高度if (DisplayUtils.getScreenOrientation(getContext()) == Configuration.ORIENTATION_PORTRAIT) {//判断是否是竖屏if (rotatedData == null) {rotatedData = new byte[data.length];}for (int y = 0; y < height; y++) {for (int x = 0; x < width; x++)rotatedData[x * height + height - y - 1] = data[x + y * width];}//交换高和宽的值int tmp = width;width = height;height = tmp;data = rotatedData;}Result rawResult = null;//调用zxing jar包中的方法 生成解码所需的YUV类型数据
PlanarYUVLuminanceSource source = buildLuminanceSource(data, width, height);if (source != null) {//调用zxing jar包中的方法将source转为bitmapBinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
try {//调用zxing jar包中的方法进行解码rawResult = mMultiFormatReader.decodeWithState(bitmap);} catch (NullPointerException npe) {} catch (ArrayIndexOutOfBoundsException aoe) {} finally {mMultiFormatReader.reset();}}if (rawResult != null) {if (mResultHandler != null) {//将结果返回给回调方法handleResultmResultHandler.handleResult(rawResult);}}}

需要说明的是默认情况下获取的帧视频是倒着的,所以如果手机是竖屏我们需要交换宽和高的值。上面代码中通过buildLuminanceSource方法形成解码所需数据,所以我们看下这个方法。

 public PlanarYUVLuminanceSource buildLuminanceSource(byte[] data, int width, int height) {Rect rect = getFramingRectInPreview(width, height);if (rect == null) {return null;}// Go ahead and assume it's YUV rather than die.PlanarYUVLuminanceSource source = null;try {//新建PlanarYUVLuminanceSource对象source = new PlanarYUVLuminanceSource(data, width, height, rect.left, rect.top,rect.width(), rect.height(), false);} catch (Exception e) {}return source;}

这个方法有3个参数,第一个是帧视频数据,第二个第三个为帧视频的宽和高,通过getFramingRectInPreview获得扫描区的rect ,接着把rect的左边界坐标,上边界坐标、高度、宽度还有帧视频的高宽交给zxing jar包提供的方法来new一个PlanarYUVLuminanceSource对象并把这个对象返回。

接着看下getFramingRectInPreview方法的代码。

public synchronized Rect getFramingRectInPreview(int previewWidth, int previewHeight) {if (qrSize == null || qrSize.getDetectRect() == null) {return null;}//通过回调方法获取扫描区的rectRect rect = qrSize.getDetectRect();int width=showPanel.getWidth();int height=showPanel.getHeight();if ((rect.right - rect.left) != 0 && (rect.top - rect.bottom) != 0) {rect.left = rect.left * previewWidth / width;rect.right = rect.right * previewWidth / width;rect.top = rect.top * previewHeight / height;rect.bottom = rect.bottom * previewHeight / height;return rect;} else {return null;}}

代码中主要通过回调方法getDetectRect获取扫描区的rect,另外有一个计算公式。

         rect.left = rect.left * previewWidth / width;rect.right = rect.right * previewWidth / width;rect.top = rect.top * previewHeight / height;rect.bottom = rect.bottom * previewHeight /height; 

这部分代码根据视频帧的高度、宽度和扫描界面高宽对扫描区进行缩放,主要是为了防止相机预览界面不是全屏导致rect数据错误问题。

好了ZxingScannerViewNew这个类我们已经介绍的差不多了。接下来我们只需在自己的activity中通过setContentView方法把自己定制的扫描layout传递给ZxingScannerViewNew,然后把扫描区域的rect计算好,通过QrSize接口传递给ZxingScannerViewNew,最后在回调方法handleResult中接收扫描结果即可~~~

微信扫一扫中会与一根绿线自上而下循环显示,我的解决方案是在扫描区域中设置一根绿线imageview起始状态设置为gone,界面初始化后让它visiable,同时添加自上而下的循环移动动画。

最后贴上activity代码。

package com.afun.zxingcore;import android.app.Activity;
import android.graphics.Rect;
import android.os.Bundle;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.TranslateAnimation;
import android.widget.ImageView;
import android.widget.TextView;import com.afun.zxinglib.ScanView.ZXingScannerViewNew;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.Result;import java.util.ArrayList;
import java.util.List;public class QrScanActivity extends Activity implements ZXingScannerViewNew.ResultHandler, ZXingScannerViewNew.QrSize, View.OnClickListener {ZXingScannerViewNew scanView;private TextView result;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);//初始化自己定制的扫描界面scanView = new ZXingScannerViewNew(this);scanView.setContentView(R.layout.logistics_scan_qr);scanView.setQrSize(this);setContentView(scanView);setupFormats();initUI();}private void initUI() {findViewById(R.id.confirm).setOnClickListener(this);result= (TextView) findViewById(R.id.editText);}@Overrideprotected void onResume() {super.onResume();//设置处理扫描结果的回调方法scanView.setResultHandler(this);//设置camerascanView.startCamera(-1);scanView.setFlash(false);scanView.setAutoFocus(true);}@Overridepublic void handleResult(Result rawResult) {//回调方法中接收扫描结果并且设置到textview中result.setText(rawResult.toString());}@Overrideprotected void onPause() {super.onPause();scanView.stopCamera();}//zxing项目可以解析二维码和条形码等,我们这里只添加二维码格式,让他只处理二维码public void setupFormats() {List<BarcodeFormat> formats = new ArrayList<BarcodeFormat>();formats.add(BarcodeFormat.QR_CODE);if (scanView != null) {scanView.setFormats(formats);}}//计算扫描区域的rect,算法比较简单就不解释了@Overridepublic Rect getDetectRect() {View view = findViewById(R.id.scan_window);int top = ((View) view.getParent()).getTop() + view.getTop();int left = view.getLeft();int width = view.getWidth();int height = view.getHeight();Rect rect = null;if (width != 0 && height != 0) {rect = new Rect(left, top, left + width, top + height);addLineAnim(rect);}return rect;}//给扫描框内的绿线设置移动动画,让它在扫描区域内循环移动private void addLineAnim(Rect rect) {ImageView imageView = (ImageView) findViewById(R.id.scanner_line);imageView.setVisibility(View.VISIBLE);if (imageView.getAnimation() == null) {TranslateAnimation anim = new TranslateAnimation(0, 0, 0, rect.height());anim.setDuration(1500);anim.setRepeatCount(Animation.INFINITE);imageView.startAnimation(anim);}}@Overridepublic void onClick(View v) {int id = v.getId();if (id == R.id.confirm){//TODO something}}
}

我自己定制的扫描界面如下图
这里写图片描述
其实layout布局也很简单,下面我贴出下载地址,感兴趣的话大家可以下载下来研究研究!~~~

好了,写到这了,希望能给大家带来思路上的提示!~欢迎大家留言一起交流!~

下载链接
高仿微信扫一扫,轻松实现定制扫描界面

这篇关于扫描二维码研究总结(高仿微信扫一扫,轻松实现定制扫描界面)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/988649

相关文章

Python实现对阿里云OSS对象存储的操作详解

《Python实现对阿里云OSS对象存储的操作详解》这篇文章主要为大家详细介绍了Python实现对阿里云OSS对象存储的操作相关知识,包括连接,上传,下载,列举等功能,感兴趣的小伙伴可以了解下... 目录一、直接使用代码二、详细使用1. 环境准备2. 初始化配置3. bucket配置创建4. 文件上传到os

关于集合与数组转换实现方法

《关于集合与数组转换实现方法》:本文主要介绍关于集合与数组转换实现方法,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1、Arrays.asList()1.1、方法作用1.2、内部实现1.3、修改元素的影响1.4、注意事项2、list.toArray()2.1、方

使用Python实现可恢复式多线程下载器

《使用Python实现可恢复式多线程下载器》在数字时代,大文件下载已成为日常操作,本文将手把手教你用Python打造专业级下载器,实现断点续传,多线程加速,速度限制等功能,感兴趣的小伙伴可以了解下... 目录一、智能续传:从崩溃边缘抢救进度二、多线程加速:榨干网络带宽三、速度控制:做网络的好邻居四、终端交互

JavaSE正则表达式用法总结大全

《JavaSE正则表达式用法总结大全》正则表达式就是由一些特定的字符组成,代表的是一个规则,:本文主要介绍JavaSE正则表达式用法的相关资料,文中通过代码介绍的非常详细,需要的朋友可以参考下... 目录常用的正则表达式匹配符正则表China编程达式常用的类Pattern类Matcher类PatternSynta

java实现docker镜像上传到harbor仓库的方式

《java实现docker镜像上传到harbor仓库的方式》:本文主要介绍java实现docker镜像上传到harbor仓库的方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地... 目录1. 前 言2. 编写工具类2.1 引入依赖包2.2 使用当前服务器的docker环境推送镜像2.2

C++20管道运算符的实现示例

《C++20管道运算符的实现示例》本文简要介绍C++20管道运算符的使用与实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录标准库的管道运算符使用自己实现类似的管道运算符我们不打算介绍太多,因为它实际属于c++20最为重要的

Java easyExcel实现导入多sheet的Excel

《JavaeasyExcel实现导入多sheet的Excel》这篇文章主要为大家详细介绍了如何使用JavaeasyExcel实现导入多sheet的Excel,文中的示例代码讲解详细,感兴趣的小伙伴可... 目录1.官网2.Excel样式3.代码1.官网easyExcel官网2.Excel样式3.代码

python实现对数据公钥加密与私钥解密

《python实现对数据公钥加密与私钥解密》这篇文章主要为大家详细介绍了如何使用python实现对数据公钥加密与私钥解密,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录公钥私钥的生成使用公钥加密使用私钥解密公钥私钥的生成这一部分,使用python生成公钥与私钥,然后保存在两个文

浏览器插件cursor实现自动注册、续杯的详细过程

《浏览器插件cursor实现自动注册、续杯的详细过程》Cursor简易注册助手脚本通过自动化邮箱填写和验证码获取流程,大大简化了Cursor的注册过程,它不仅提高了注册效率,还通过友好的用户界面和详细... 目录前言功能概述使用方法安装脚本使用流程邮箱输入页面验证码页面实战演示技术实现核心功能实现1. 随机

Golang如何对cron进行二次封装实现指定时间执行定时任务

《Golang如何对cron进行二次封装实现指定时间执行定时任务》:本文主要介绍Golang如何对cron进行二次封装实现指定时间执行定时任务问题,具有很好的参考价值,希望对大家有所帮助,如有错误... 目录背景cron库下载代码示例【1】结构体定义【2】定时任务开启【3】使用示例【4】控制台输出总结背景