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

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

在正文之前说点题外话,加上这篇我已经写了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实现AVIF图片与其他图片格式间的批量转换

《Python实现AVIF图片与其他图片格式间的批量转换》这篇文章主要为大家详细介绍了如何使用Pillow库实现AVIF与其他格式的相互转换,即将AVIF转换为常见的格式,比如JPG或PNG,需要的小... 目录环境配置1.将单个 AVIF 图片转换为 JPG 和 PNG2.批量转换目录下所有 AVIF 图

Pydantic中model_validator的实现

《Pydantic中model_validator的实现》本文主要介绍了Pydantic中model_validator的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价... 目录引言基础知识创建 Pydantic 模型使用 model_validator 装饰器高级用法mo

AJAX请求上传下载进度监控实现方式

《AJAX请求上传下载进度监控实现方式》在日常Web开发中,AJAX(AsynchronousJavaScriptandXML)被广泛用于异步请求数据,而无需刷新整个页面,:本文主要介绍AJAX请... 目录1. 前言2. 基于XMLHttpRequest的进度监控2.1 基础版文件上传监控2.2 增强版多

Redis分片集群的实现

《Redis分片集群的实现》Redis分片集群是一种将Redis数据库分散到多个节点上的方式,以提供更高的性能和可伸缩性,本文主要介绍了Redis分片集群的实现,具有一定的参考价值,感兴趣的可以了解一... 目录1. Redis Cluster的核心概念哈希槽(Hash Slots)主从复制与故障转移2.

springboot+dubbo实现时间轮算法

《springboot+dubbo实现时间轮算法》时间轮是一种高效利用线程资源进行批量化调度的算法,本文主要介绍了springboot+dubbo实现时间轮算法,文中通过示例代码介绍的非常详细,对大家... 目录前言一、参数说明二、具体实现1、HashedwheelTimer2、createWheel3、n

使用Python实现一键隐藏屏幕并锁定输入

《使用Python实现一键隐藏屏幕并锁定输入》本文主要介绍了使用Python编写一个一键隐藏屏幕并锁定输入的黑科技程序,能够在指定热键触发后立即遮挡屏幕,并禁止一切键盘鼠标输入,这样就再也不用担心自己... 目录1. 概述2. 功能亮点3.代码实现4.使用方法5. 展示效果6. 代码优化与拓展7. 总结1.

Mybatis 传参与排序模糊查询功能实现

《Mybatis传参与排序模糊查询功能实现》:本文主要介绍Mybatis传参与排序模糊查询功能实现,本文通过实例代码给大家介绍的非常详细,感兴趣的朋友跟随小编一起看看吧... 目录一、#{ }和${ }传参的区别二、排序三、like查询四、数据库连接池五、mysql 开发企业规范一、#{ }和${ }传参的

Docker镜像修改hosts及dockerfile修改hosts文件的实现方式

《Docker镜像修改hosts及dockerfile修改hosts文件的实现方式》:本文主要介绍Docker镜像修改hosts及dockerfile修改hosts文件的实现方式,具有很好的参考价... 目录docker镜像修改hosts及dockerfile修改hosts文件准备 dockerfile 文

基于SpringBoot+Mybatis实现Mysql分表

《基于SpringBoot+Mybatis实现Mysql分表》这篇文章主要为大家详细介绍了基于SpringBoot+Mybatis实现Mysql分表的相关知识,文中的示例代码讲解详细,感兴趣的小伙伴可... 目录基本思路定义注解创建ThreadLocal创建拦截器业务处理基本思路1.根据创建时间字段按年进

微信公众号脚本-获取热搜自动新建草稿并发布文章

《微信公众号脚本-获取热搜自动新建草稿并发布文章》本来想写一个自动化发布微信公众号的小绿书的脚本,但是微信公众号官网没有小绿书的接口,那就写一个获取热搜微信普通文章的脚本吧,:本文主要介绍微信公众... 目录介绍思路前期准备环境要求获取接口token获取热搜获取热搜数据下载热搜图片给图片加上标题文字上传图片