Android 二维码生成框架 Zxing
一:定义
ZXing 是一个开源的,用 Java 实现的多种格局的 1D/2D 条码图像处理库,它蕴含了分割到其余语言的端口。zxing 能够实现应用手机的内置的摄像头实现条形码的扫描及解码。
Android Studio 下增加依赖
implementation 'com.google.zxing:core:3.3.2'
增加权限
因为扫描二维码须要摄像头权限,把图片保留到本地须要 sdcard 权限,所以须要在 AndroidManifest.xml 中退出相应的权限
<!-- 相机权限 -->
<uses-permission android:name="android.permission.CAMERA" />
<!-- 触动权限 -->
<uses-permission android:name="android.permission.VIBRATE" />
<!--<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />-->
<!--<uses-feature 是为了把应用程序所依赖的软硬件个性告知应用程序之外的对象。-->
<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera.autofocus" />
这里咱们看到 <uses-feature/> 标签
<uses-feature
android:name="string"
android:required=["true" | "false"]
android:glEsVersion="integer" />
申明一项应用程序须要用到的软、硬件个性。
申明一项 <uses-feature> 的目标,是为了把应用程序所依赖的软硬件个性告知应用程序之外的对象。本元素给出了一个 required 属性,用于指定应用程序是否必须该项个性,也即不申明该项个性的话就无奈失常运行;或者最好是提供该项个性,但没有的话也能运行。因为每种 Android 设施提供的个性各不相同,<uses-feature> 元素施展着重要作用,应用程序能够用它来形容其用到的各种设施个性。
假如应用程序须要应用蓝牙和摄像头设施,则应申明两个元素:
<uses-feature android:name=”android.hardware.bluetooth” />
<uses-feature android:name=”android.hardware.camera” />
通常,应该确保为应用程序须要的所有个性均申明了 <uses-feature> 元素。
<uses-feature> 元素的申明仅仅是告知性质的,这意味着 Android 零碎自身不会在安装程序前查看设施是否反对这些个性。不过,其余服务(如 Google Play)或者其余应用程序能够查看 <uses-feature> 申明来进行相应解决或与本应用程序进行交互。因而,对须要用到的所有个性都进行申明(如下表所示)是十分重要的。
有些设施个性可能会存在一些非凡的属性,用于定义该个性的版本,比方 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<EncodeHintType, Object> qrParam = new Hashtable<EncodeHintType, Object>();
// 设置 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<EncodeHintType, Object> 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<DecodeHintType, Object> 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: 藏好本人,做好清理