Android 二维码生成框架Zxing
一:定义
ZXing是一个开源的,用Java实现的多种格式的1D/2D条码图像处理库,它包含了联系到其他语言的端口。zxing可以实现使用手机的内置的摄像头完成条形码的扫描及解码。
Android Studio下添加依赖
implementation 'com.google.zxing:core:3.3.2'
添加权限
因为扫描二维码需要摄像头权限,把图片保存到本地需要sdcard权限,所以需要在AndroidManifest.xml中加入相应的权限
这里我们看到
声明一项应用程序需要用到的软、硬件特性。
声明一项
假设应用程序需要使用蓝牙和摄像头设备,则应声明两个元素:
通常,应该确保为应用程序需要的所有特性均声明了
有些设备特性可能会存在一些特殊的属性,用于定义该特性的版本,比如 Open GL 版本(用 glEsVersion声明)。 其他的一些与硬件是否就绪有关的特性,比如摄像头,则通过 name 属性进行声明。
二:生成二维码图片
生成二维码图片调用CreateQRBitmp.createQRCodeBitmap方法生成,这个方法是我们自己封装的,需要传入两个参数,参数1:图片内容、参数2:二维码图片最中间显示的logo(Bitmap对象)。
public class EightTeenActivity extends AppCompatActivity implements View.OnClickListener {
private EditText etInput;
private Bitmap qrCodeBitmap;
private ImageView ivQrImage;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_eight_teen);
etInput = findViewById(R.id.et_input);
ivQrImage = findViewById(R.id.iv_qr_image);
findViewById(R.id.btn_scanning).setOnClickListener(this::onClick);//扫描点击事件
findViewById(R.id.btn_select).setOnClickListener(this::onClick);//选择图库点击事件
findViewById(R.id.generate_qr_code).setOnClickListener(this::onClick);//生成二维码点击事件
findViewById(R.id.btn_long_press).setOnClickListener(this::onClick);//
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_scanning://扫描
break;
case R.id.btn_select:
//激活系统库选择一张图片
break;
case R.id.generate_qr_code:
//生成二维码
String contentString =etInput.getText().toString().trim();//获取输入框中内容
if (TextUtils.isEmpty(contentString)){
showToast("请输入二维码内容");
return;
}
Log.i("rocky","输入的内容:"+contentString);
//加载资源文件的图片生成Bitmap
//高版本,这里会出现一个问题:找不到资源图片为null
Bitmap portrait= BitmapFactory.decodeResource(getResources(),R.mipmap.login_icon);
//生成二维码工具类CreateQRBitmp,两个方法,一个不传大小,使用默认
qrCodeBitmap=CreateQRBitmp.createQRCodeBitmap(contentString,portrait);//生成BitMap图片
//imageView上设置图片
ivQrImage.setImageBitmap(qrCodeBitmap);
break;
case R.id.btn_long_press:
break;
}
}
private void showToast(String str){
Toast.makeText(EightTeenActivity.this,str,Toast.LENGTH_LONG).show();
}
}
高版本Sdk,加载资源图片logo时候,获取不到图片问题
Bitmap portrait= BitmapFactory.decodeResource(getResources(),R.mipmap.ic_launcher);
修改一下图片命名可以解决
CreateQRBitmp创建一个二维码图片
public class CreateQRBitmp {
/**
* 生成二维码图片大小
*/
private static int QRCODE_SIZE = 300;
/**
* 头像图片大小
*/
private static int PORTRAIT_SIZE = 55;
/**
* 头像图片
*/
private Bitmap portrait;
/**
* 功能:创建QR二维码图片
* 可设置图片大小和头像图片大小
*
* @param portrait 头像bitmap
* @param content 生成二维码内容数据
*/
public static Bitmap createQRCodeBitmap(String content, Bitmap portrait, int widthAndHeight, int portraitSize) {
QRCODE_SIZE = widthAndHeight;
PORTRAIT_SIZE = portraitSize;
// 用于设置QR二维码参数
Hashtable qrParam = new Hashtable();
// 设置QR二维码的纠错级别——这里选择最高H级别
qrParam.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);
// 设置编码方式
qrParam.put(EncodeHintType.CHARACTER_SET, "UTF-8");
// 生成QR二维码数据——这里只是得到一个由true和false组成的数组
// 参数顺序分别为:编码内容,编码类型,生成图片宽度,生成图片高度,设置参数
try {
BitMatrix bitMatrix = new MultiFormatWriter().encode(content,
BarcodeFormat.QR_CODE, QRCODE_SIZE, QRCODE_SIZE, qrParam);
// 开始利用二维码数据创建Bitmap图片,分别设为黑白两色
int w = bitMatrix.getWidth();
int h = bitMatrix.getHeight();
int[] data = new int[w * h];
for (int y = 0; y < h; y++) {
for (int x = 0; x < w; x++) {
if (bitMatrix.get(x, y))
data[y * w + x] = 0xff000000;// 黑色
else
data[y * w + x] = 0x00ffffff;// -1 相当于0xffffffff 白色
}
}
// 创建一张bitmap图片,采用最高的图片效果ARGB_8888
Bitmap bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
// 将上面的二维码颜色数组传入,生成图片颜色
bitmap.setPixels(data, 0, w, 0, 0, w, h);
if (portrait != null) {
createQRCodeBitmapWithPortrait(bitmap, initProtrait(portrait));
}
return bitmap;
} catch (WriterException e) {
e.printStackTrace();
}
return null;
}
/**
* 功能:创建QR二维码图片
* 头像图片大小默认
*
* @param portrait 头像bitmap
* @param content 生成二维码内容数据
*/
public static Bitmap createQRCodeBitmap(String content, Bitmap portrait) {
//用于设置QR二维码参数
Hashtable qrParam = new Hashtable<>();//使用HashTable保存数据
// 设置QR二维码的纠错级别——这里选择最高H级别
qrParam.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);
// 设置编码方式
qrParam.put(EncodeHintType.CHARACTER_SET, "UTF-8");
// 生成QR二维码数据——这里只是得到一个由true和false组成的数组
// 参数顺序分别为:编码内容,编码类型,生成图片宽度,生成图片高度,设置参数
try {
BitMatrix bitMatrix = new MultiFormatWriter().encode(content, BarcodeFormat.QR_CODE, QRCODE_SIZE, QRCODE_SIZE, qrParam);
//开始利用二维码数据创建Bitmap图片,分别设为黑白两色
int w = bitMatrix.getWidth();
int h = bitMatrix.getHeight();
int[] data=new int[w*h];
for (int y = 0; y < h; y++) {
for (int x = 0; x < w; x++) {
if (bitMatrix.get(x, y))
data[y * w + x] = 0xff000000;// 黑色
else
data[y * w + x] = 0x00ffffff;// -1 相当于0xffffffff 白色
}
}
//创建一张bitmap图片,采用最高的图片效果ARGB_8888
Bitmap bitmap=Bitmap.createBitmap(w,h,Bitmap.Config.ARGB_8888);
// 将上面的二维码颜色数组传入,生成图片颜色
bitmap.setPixels(data, 0, w, 0, 0, w, h);
if(portrait!=null){//添加最中间的logo
createQRCodeBitmapWithPortrait(bitmap,initProtrait(portrait));
}
return bitmap;
} catch (WriterException e) {
e.printStackTrace();
}
return null;
}
/**
* 初始化头像图片
*/
public static Bitmap initProtrait(Bitmap portrait) {
// 对原有图片压缩显示大小
Matrix mMatrix = new Matrix();
float width = portrait.getWidth();
float height = portrait.getHeight();
mMatrix.setScale(PORTRAIT_SIZE / width, PORTRAIT_SIZE / height);
return Bitmap.createBitmap(portrait, 0, 0, (int) width,
(int) height, mMatrix, true);
}
/**
* 在二维码上绘制头像
*/
public static void createQRCodeBitmapWithPortrait(Bitmap qr, Bitmap portrait) {
// 头像图片的大小
int portrait_W = portrait.getWidth();
int portrait_H = portrait.getHeight();
// 设置头像要显示的位置,即居中显示
int left = (QRCODE_SIZE - portrait_W) / 2;
int top = (QRCODE_SIZE - portrait_H) / 2;
int right = left + portrait_W;
int bottom = top + portrait_H;
Rect rect1 = new Rect(left, top, right, bottom);
// 取得qr二维码图片上的画笔,即要在二维码图片上绘制我们的头像
Canvas canvas = new Canvas(qr);
// 设置我们要绘制的范围大小,也就是头像的大小范围
Rect rect2 = new Rect(0, 0, portrait_W, portrait_H);
// 开始绘制
canvas.drawBitmap(portrait, rect2, rect1, null);
}
}
三:长按识别二维码以及保存图片
识别二维码跟从相册中选择图片进行识别功能上很相似,所以就不在做重复介绍了,就介绍一下保存图片功能。
private void longPress() {
if (qrCodeBitmap==null){
showToast("请先生成二维码图片");
return ;
}
//这个一个自定义Dialog弹窗
ImageOptDialog imageOptDialog=new ImageOptDialog(EightTeenActivity.this);
imageOptDialog.setCallback(new ImageOptDialog.ImageOptCallback() {
//识别二维码
@Override
public void onIdentifyQrClick() {
View view = getWindow().getDecorView().getRootView();//找到当前页面的根布局
view.setDrawingCacheEnabled(true);//禁用绘图缓存
view.buildDrawingCache();
Bitmap temBitmap = view.getDrawingCache();
//String result=BitmapUtil.parseQRcode(temBitmap);
// showToast("长按识别二维码结果:"+result);
//禁用DrawingCahce否则会影响性能 ,而且不禁止会导致每次截图到保存的是缓存的位图
view.setDrawingCacheEnabled(false);//识别完成之后开启绘图缓存
}
//保存图片到本地
@Override
public void onSaveImageClick() {
View view = getWindow().getDecorView().getRootView();//找到当前页面的根布局
view.setDrawingCacheEnabled(true);//禁用绘图缓存
view.buildDrawingCache();
Bitmap temBitmap = view.getDrawingCache();
ImageUtil.savePicToLocal(temBitmap,EightTeenActivity.this);
//禁用DrawingCahce否则会影响性能 ,而且不禁止会导致每次截图到保存的是缓存的位图
view.setDrawingCacheEnabled(false);//识别完成之后开启绘图缓存
showToast("保存图片到本地成功");
}
});
imageOptDialog.show();
}
图片保存成功后路径:rocky: filePath:/storage/emulated/0/screen/1627982791618.png
保存图片的方法:
public class ImageUtil {
public static void savePicToLocal(Bitmap bitmap, Context context) {
//路径是sdcard 获取sd卡目录即:sdcard/screen/时间.png
String filePath= Environment.getExternalStorageDirectory().getAbsolutePath()+"/screen"+ File.separator+System.currentTimeMillis()+".png";
if (bitmap!=null){
try {
// 图片文件路径
Log.i("rocky", "filePath:" + filePath);
File file = new File(filePath);
if (!file.getParentFile().exists()) {
file.getParentFile().mkdirs();
}
FileOutputStream os = new FileOutputStream(file);
bitmap.compress(Bitmap.CompressFormat.PNG, 100, os);
Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
Uri uri = Uri.fromFile(new File(filePath));
intent.setData(uri);
context.sendBroadcast(intent);
os.flush();
os.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
存储需要动态添加权限:
//6.0版本或以上需请求权限
String[] permissions=new String[]{Manifest.permission.
WRITE_EXTERNAL_STORAGE, Manifest.permission.CAMERA};
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP_MR1) {
requestPermissions(permissions,PERMS_REQUEST_CODE);
}
四:从相册选择二维码图片进行识别
首先启动系统相册,从相册中选择一张图片。
//激活系统图库,选择一张图片
Intent innerIntent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
Intent wrapperIntent = Intent.createChooser(innerIntent, "选择二维码图片");
startActivityForResult(wrapperIntent, SELECT_IMAGE_REQUEST_CODE);
然后在onActivityResult中获取选择图片路径,调用BitmapUtil.parseQRcode方法解析二维码图片。
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent intent) {
super.onActivityResult(requestCode, resultCode, intent);
if(requestCode==SELECT_IMAGE_REQUEST_CODE){//从图库选择图片
String[] proj = {MediaStore.Images.Media.DATA};
// 获取选中图片的路径
Cursor cursor = this.getContentResolver().query(intent.getData(),proj, null, null, null);
if (cursor.moveToFirst()) {
int columnIndex = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
String photoPath = cursor.getString(columnIndex);
String result= BitmapUtil.parseQRcode(photoPath);
if (!TextUtils.isEmpty(result)) {
showToast("从图库选择的图片识别结果:"+result);
} else {
showToast("从图库选择的图片不是二维码图片");
}
}
cursor.close();
}/*else if (requestCode == SCAN_REQUEST_CODE && resultCode == RESULT_OK) {
String input = intent.getStringExtra(ScanActivity.INTENT_EXTRA_RESULT);
showToast("扫描结果:"+input);
}*/
}
BitmapUtil解析二维码图片
public class BitmapUtil {
/**
* 解析二维码图片
* @param bitmapPath 文件路径
* @return
*/
public static String parseQRcode(String bitmapPath){
Bitmap bitmap = BitmapFactory.decodeFile(bitmapPath, null);
String result=parseQRcode(bitmap);
return result;
}
public static String parseQRcode(Bitmap bmp){
bmp=comp(bmp);//bitmap压缩 如果不压缩的话在低配置的手机上解码很慢
int width = bmp.getWidth();//图片宽度
int height = bmp.getHeight();//图片高度
int[] pixels = new int[width * height];
bmp.getPixels(pixels, 0, width, 0, 0, width, height);
QRCodeReader reader = new QRCodeReader();
Map hints = new EnumMap<>(DecodeHintType.class);
hints.put(DecodeHintType.TRY_HARDER, Boolean.TRUE);//优化精度
hints.put(DecodeHintType.CHARACTER_SET,"utf-8");//解码设置编码方式为:utf-8
try {
Result result = reader.decode(new BinaryBitmap(new HybridBinarizer(new RGBLuminanceSource(width, height, pixels))), hints);
return result.getText();
} catch (NotFoundException e) {
Log.i("ansen",""+e.toString());
e.printStackTrace();
} catch (ChecksumException e) {
e.printStackTrace();
} catch (FormatException e) {
e.printStackTrace();
}
return null;
}
//图片按比例大小压缩方法(根据Bitmap图片压缩)
private static Bitmap comp(Bitmap image) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
image.compress(Bitmap.CompressFormat.JPEG, 100, baos);
if( baos.toByteArray().length / 1024>1024) {//判断如果图片大于1M,进行压缩避免在生成图片(BitmapFactory.decodeStream)时溢出
baos.reset();//重置baos即清空baos
image.compress(Bitmap.CompressFormat.JPEG, 50, baos);//这里压缩50%,把压缩后的数据存放到baos中
}
ByteArrayInputStream isBm = new ByteArrayInputStream(baos.toByteArray());
BitmapFactory.Options newOpts = new BitmapFactory.Options();
//开始读入图片,此时把options.inJustDecodeBounds 设回true了
newOpts.inJustDecodeBounds = true;
Bitmap bitmap = BitmapFactory.decodeStream(isBm, null, newOpts);
newOpts.inJustDecodeBounds = false;
int w = newOpts.outWidth;
int h = newOpts.outHeight;
//现在主流手机比较多是800*480分辨率,所以高和宽我们设置为
float hh = 400f;//这里设置高度为800f
float ww = 400f;//这里设置宽度为480f
//缩放比。由于是固定比例缩放,只用高或者宽其中一个数据进行计算即可
int be = 1;//be=1表示不缩放
if (w > h && w > ww) {//如果宽度大的话根据宽度固定大小缩放
be = (int) (newOpts.outWidth / ww);
} else if (w < h && h > hh) {//如果高度高的话根据宽度固定大小缩放
be = (int) (newOpts.outHeight / hh);
}
if (be <= 0)
be = 1;
newOpts.inSampleSize = be;//设置缩放比例
//重新读入图片,注意此时已经把options.inJustDecodeBounds 设回false了
isBm = new ByteArrayInputStream(baos.toByteArray());
bitmap = BitmapFactory.decodeStream(isBm, null, newOpts);
return compressImage(bitmap);//压缩好比例大小后再进行质量压缩
}
//质量压缩方法
private static Bitmap compressImage(Bitmap image) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
image.compress(Bitmap.CompressFormat.JPEG, 100, baos);//质量压缩方法,这里100表示不压缩,把压缩后的数据存放到baos中
int options = 100;
while (baos.toByteArray().length/1024>100) { //循环判断如果压缩后图片是否大于100kb,大于继续压缩
baos.reset();//重置baos即清空baos
image.compress(Bitmap.CompressFormat.JPEG, options, baos);//这里压缩options%,把压缩后的数据存放到baos中
options -= 10;//每次都减少10
}
ByteArrayInputStream isBm = new ByteArrayInputStream(baos.toByteArray());//把压缩后的数据baos存放到ByteArrayInputStream中
Bitmap bitmap = BitmapFactory.decodeStream(isBm, null, null);//把ByteArrayInputStream数据生成图片
return bitmap;
}
}
五:扫描二维码
Intent intent = new Intent(EightTeenActivity.this,ScanActivity.class);
startActivityForResult(intent,SCAN_REQUEST_CODE);
重写onActivityResult方法,监听扫描结果。
if (requestCode == SCAN_REQUEST_CODE && resultCode == RESULT_OK) {
String input = intent.getStringExtra(ScanActivity.INTENT_EXTRA_RESULT);
showToast("扫描结果:"+input);
}
ScanActivity类代码比较多
具体可以参考:https://github.com/ansen666/Z...
END:藏好自己,做好清理