SmartScan运用OpenGL从零打造一个依赖极低、高灵活、快速预览不变形ZXing扫码库,实现思路写在最后,喜欢可以看下去。
下面是演示效果,可直接下载试试 演示apk
依赖极低:
做一个老实的扫码库,权限判断应由应用自行判断;除去kotlin自身库仅依赖com.google.zxing:core:3.3.3,并且zxing:core用api方式依赖,方便引用。这里提醒一下3.4.0是有限制的,目前最好不要用,具体可查阅官网
在build.gradle加入依赖
implementation 'com.dming.glScan:smartscan:0.0.3'
若引用不上可在项目的build.gradle加上maven仓库地址,如下:
allprojects {
repositories {
google()
jcenter()
maven { url 'https://dl.bintray.com/dming/maven' }
}
}
扫描框、扫描线、背景等基础UI已实现,若没有需求,开箱即用;若有定制,按需设置参数即可
xml创建:
代码直接创建(Kotlin):
val smartScanView = SmartScanView(this)
val smartScanParameter = SmartScanParameter()
smartScanParameter.apply {
this.scanPercentWidth = 0.65f
this.scanPercentHeight = 0.65f
this.scanPercentTopOffset = 0.2f
this.enableFlashlightBtn = true
}
smartScanView.init(smartScanParameter)
代码直接创建(Java):
SmartScanView smartScanView = new SmartScanView(this);
SmartScanParameter smartScanParameter = new SmartScanParameter();
smartScanParameter.setScanPercentWidth(0.65f);
smartScanParameter.setScanPercentHeight(0.65f);
smartScanParameter.setScanPercentTopOffset(0.2f);
smartScanParameter.setEnableFlashlightBtn(true);
smartScanView.init(smartScanParameter);
动态改变配置(Kotlin):
val oneDP = TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, 1f,
this.resources.displayMetrics
)
val smartScanParameter = smartScanView.getSmartScanParameter()
smartScanParameter.scanTopOffset = 80 * oneDP
smartScanParameter.scanWidth = 300 * oneDP
smartScanParameter.scanHeight = 300 * oneDP
smartScanParameter.scanLineWidth = 6 * oneDP
smartScanParameter.onlyOneDCode = false
smartScanParameter.enableFlashlightBtn = true
smartScanView.updateConfigure(smartScanParameter)
动态改变配置(Java):
float oneDP = TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, 1f,
this.getResources().getDisplayMetrics()
);
SmartScanParameter smartScanParameter = smartScanView.getSmartScanParameter();
smartScanParameter.setScanTopOffset(80 * oneDP);
smartScanParameter.setScanWidth(300 * oneDP);
smartScanParameter.setScanHeight(300 * oneDP);
smartScanParameter.setScanLineWidth(6 * oneDP);
smartScanParameter.setOnlyOneDCode(false);
smartScanParameter.setEnableFlashlightBtn(true);
smartScanView.updateConfigure(smartScanParameter);
监听结果(Kotlin):
smartScanView.setOnResultOnceListener(object : OnResultListener {
override fun onResult(result: Result) {
Toast.makeText(this@JavaCreateActivity,"result: ${result.text}",Toast.LENGTH_SHORT).show()
}
})
监听结果(Java):
smartScanView.setOnResultOnceListener(new OnResultListener() {
@Override
public void onResult(Result result) {
Toast.makeText(JavaCreateActivity.this,"result: "+result.getText(),Toast.LENGTH_SHORT).show();
}
});
监听扫描框的位置变化,实现自定义UI重要监听器(Kotlin):
smartScanView.setScanViewChangeListener(object : OnScanViewListener {
// surface创建的时候回调
override fun onCreate() {
}
// 扫描窗口位置变化即回调
override fun onChange(rect: Rect) {
}
// surface销毁的时候回调
override fun onDestroy() {
}
})
监听扫描框的位置变化,实现自定义UI重要监听器(Java):
smartScanView.setScanViewChangeListener(new OnScanViewListener() {
// surface创建的时候回调
@Override
public void onCreate() {
}
// 扫描窗口位置变化即回调
@Override
public void onChange(Rect rect) {
}
// surface销毁的时候回调
@Override
public void onDestroy() {
}
});
class SmartScanParameter {
// 扫描框宽度,单位像素
var scanWidth: Float = 0f
// 扫描框高度,单位像素
var scanHeight: Float = 0f
// 扫描框宽度百分比 0 - 1.0f
var scanPercentWidth: Float = 0f
// 扫描框高度百分比 0 - 1.0f
var scanPercentHeight: Float = 0f
// 扫描框距离头部偏移,单位像素
var scanTopOffset: Float = 0f
// 扫描框距离头部偏移,相对高度百分比 0 - 1.0f
var scanPercentTopOffset: Float = 0f
// 约束扫描框为正方形,将使用最短的边为边长,可用在扫描二维码,默认开启
var scanMustSquare: Boolean? = null
// 扫描线drawable,当存在时,优先使用
var scanLine: Drawable? = null
// 扫描框drawable,当存在时,优先使用
var scanCorner: Drawable? = null
// 扫描背景颜色,一般黑色透明
var scanBackgroundColor: Int? = null
// 是否加入一维码解码,默认为QR二维码解码,按需加入
var addOneDCode: Boolean = false
// 仅使用一维码解码
var onlyOneDCode: Boolean = false
// 扫描角颜色
var scanCornerColor: Int? = null
// 自定义扫描角长度
var scanCornerSize: Float = 0f
// 自定义扫描角线宽度
var scanCornerThick: Float = 0f
// 扫描线颜色
var scanLineColor: Int? = null
// 扫描线宽度大小
var scanLineWidth: Float = 0f
// 扫描线时间,单位毫秒
var scanLineTime: Int? = null
// 扫描框的细线宽度,一般1px
var scanFrameLineWidth: Float? = null
// 扫描框的细线颜色
var scanFrameLineColor: Int? = null
// 禁止窗口双指放大,默认开启
var disableScale: Boolean = false
// 使能滴一声的扫描成功声音
var enableBeep: Boolean = false
// 使能震动扫描成功
var enableVibrate: Boolean = false
// 使能闪光灯按钮,由于按钮位置图像可变性太强,默认是不开启的,由用户定制
var enableFlashlightBtn: Boolean = false
}
XML参数也是同名,用法与上相同
// 设置获取亮度(灰度)图片监听
setGrayImgListener
// 从解码线程中监听解码结果,会不断的触发,可使用stopDecode()关闭
setOnResultInThreadListener
// 从解码线程中监听解码结果,成功后会停止,可使用startDecode()开启
setOnResultOnceInThreadListener
// 从UI线程中监听解码结果,会不断的触发,可使用stopDecode()关闭
setOnResultListener
// 从UI线程中监听解码结果,成功后会停止,可使用startDecode()开启
setOnResultOnceListener
// 监听smart的扫码窗口改变,自定义窗口极奇重要的监听
setScanViewChangeListener
// 手动开启解码
startDecode
// 手动关闭解码
stopDecode
// 闪光灯开启关闭
setFlashLight
// 扫码过程中改变配置,如窗口大小,扫码类型等
updateConfigure
Camera在GL线程开启,摄像头数据由该线程分别以亮度方式绘制到FBO和以原始图像方式绘制到预览界面,解码线程共享GL线程context,先绘制FBO中的亮度纹理数据,然后在绘制结果中读取像素,当然仅仅是读取设置的扫描窗口区域,然后进行解码。由于摄像头数据由自定义GL着色器绘制,可控性高,通过计算绘制出无变形的图像。ZXing解码过程需要转亮度,FBO中的纹理已经是亮度纹理,也就不用专门转亮度,算是提高了一丢丢的效率。考虑到activity旋转问题和surface的生命周期,surface在旋转、离开activity后会被销毁;既然surface销毁了,预览界面都没了,camera也就不必存在了,故把camera与surface捆绑起来,同生死。此外,剩下的问题就是摄像头旋转问题,这个需要拿getDefaultDisplay().getRotation()真实的旋转角度来计算出合适的旋转角度进行矫正。