共计 12934 个字符,预计需要花费 33 分钟才能阅读完成。
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: 藏好本人,做好清理