前言
Android日常开发中,不可避免遇到二维码开发的需求,本文抛开扫描解析二维码,提供一下生成二维码的思路,和封装好的工具类,很简单实现二维码生成和展示。
注意:本文二维码生成基于著名的二维码库:Zxing v3.2.1
所以你需要先添加Zxing的Jar包 在Gradle中添加对Zxing的依赖
如果你觉得看一篇文章太麻烦,请直接跳到文章末尾,按说明,轻松实现二维码解析&生成
需求基本上有这几种:
- 生成二维码
- 生成带图标的二维码
- 生成二维码放入ImageView中展示(提供给其他设备扫描)
- 生成二维码保存到本地(以供文件导出,或者保存备用)
接口设计
根据需求设计两种方法:
- createQrCode() –> 生成二维码(可以带图标)
- createQrCode2ImageView() –> 生成二维码放入ImageView中展示(可以带图标)
简单设计2种接口,大部分都是重载方法:
interface IQrCodeEncoder {
//最基本的方法
Bitmap createQrCode(String content, int widthAndHeight);
void createQrCode2ImageView(String content, ImageView imageView);
//带图标(通过传入R资源/Drawable对象/Bitmap对象的图标)
Bitmap createQrCode(String content, int width, int iconRes);
Bitmap createQrCode(String content, int width, Drawable iconDrawable);
Bitmap createQrCode(String content, int width, Bitmap iconBitmap);
void createQrCode2ImageView(String content, ImageView imageView, int iconRes);
void createQrCode2ImageView(String content, ImageView imageView, Drawable iconDrawable);
void createQrCode2ImageView(String content, ImageView imageView, Bitmap iconBitmap);
//手动设定是否带图标
Bitmap createQrCode(String content, int width, int iconRes, boolean hasIcon);
Bitmap createQrCode(String content, int width, Drawable iconDrawable, boolean hasIcon);
Bitmap createQrCode(String content, int width, Bitmap iconBitmap, boolean hasIcon);
void createQrCode2ImageView(String content, ImageView imageView, int iconRes, boolean hasIcon);
void createQrCode2ImageView(String content, ImageView imageView, Drawable iconDrawable, boolean hasIcon);
void createQrCode2ImageView(String content, ImageView imageView, Bitmap iconBitmap, boolean hasIcon);
//将R资源转为Bitmap对象
Bitmap getBitmapByRes(int resId);
//将Drawable转为Bitmap对象
Bitmap getBitmapByDrawable(Drawable drawable);
//将Icon覆盖到二维码上并返回 带Icon的二维码
Bitmap addIcon2QrCode(Bitmap icon, Bitmap qrCode);
}
实现接口
实现思路很简单,基本上就是:
1.根据提供的内容生成对应的二维码
2.判断是否需要在二维码上添加Icon,若不需要,直接生成Bitmap
2.1 若需要添加Icon,先获取Icon,将不同的资源类型(R资源,Drawable)统统转换为Bitmap,然后通过
addIcon2QrCode(Bitmap icon, Bitmap qrCode)接口获取到带Icon的二维码Bitmap,
2.2 若不需要添加Icon,判断是否需要直接展示在ImageView上,若需要,展示在ImageView上,否则返回Bitmap3 获得返回的Bitmap后,判断是否需要直接展示在ImageView上,若需要,展示在ImageView上,否则返回Bitmap
看一下代码实现:
public class QRCodeEncoder implements IQrCodeEncoder {
private static final int NO_ICON_RES = 0;
private Activity activity;
private BitmapCompressor bmpConpressor;
public QRCodeEncoder(Activity activity) {
setActivity(activity);
bmpConpressor = new BitmapCompressor();
}
public void setActivity(Activity activity) {
this.activity = activity;
}
@Override
public Bitmap createQrCode(String content, int width) {
return createQrCode(content, width, NO_ICON_RES);
}
@Override
public Bitmap createQrCode(String content, int width, int iconRes) {
return createQrCode(content, width, iconRes, true);
}
@Override
public Bitmap createQrCode(String content, int width, Drawable iconDrawable) {
return createQrCode(content, width, iconDrawable, true);
}
@Override
public Bitmap createQrCode(String content, int width, Bitmap iconBitmap) {
return createQrCode(content, width, iconBitmap, true);
}
@Override
public void createQrCode2ImageView(String content, ImageView imageView) {
this.createQrCode2ImageView(content, imageView, NO_ICON_RES, false);
}
@Override
public void createQrCode2ImageView(String content, ImageView imageView, int iconRes) {
this.createQrCode2ImageView(content, imageView, iconRes, true);
}
@Override
public void createQrCode2ImageView(String content, ImageView imageView, Drawable iconDrawable) {
this.createQrCode2ImageView(content, imageView, iconDrawable, true);
}
@Override
public void createQrCode2ImageView(String content, ImageView imageView, Bitmap iconBitmap) {
this.createQrCode2ImageView(content, imageView, iconBitmap, true);
}
@Override
public void createQrCode2ImageView(String content, ImageView imageView, int iconRes, boolean hasIcon) {
Bitmap iconBitmap = null;
if (iconRes != NO_ICON_RES)
iconBitmap = getBitmapByRes(iconRes);
this.createQrCode2ImageView(content, imageView, iconBitmap, hasIcon);
}
@Override
public void createQrCode2ImageView(String content, ImageView imageView, Drawable iconDrawable, boolean hasIcon) {
Bitmap iconBitmap = null;
if (iconDrawable != null)
iconBitmap = getBitmapByDrawable(iconDrawable);
this.createQrCode2ImageView(content, imageView, iconBitmap, hasIcon);
}
@Override
public void createQrCode2ImageView(String content, ImageView imageView, Bitmap iconBitmap, boolean hasIcon) {
DisplayMetrics dm = new DisplayMetrics();
activity.getWindowManager().getDefaultDisplay().getMetrics(dm);
int mScreenWidth = dm.widthPixels;
Bitmap qrCode = createQrCode(content, mScreenWidth, iconBitmap, hasIcon);
if (qrCode != null) {
imageView.setImageBitmap(qrCode);
}
}
@Override
public Bitmap createQrCode(String content, int width, int iconRes, boolean hasIcon) {
Bitmap iconBitmap = null;
if (iconRes != NO_ICON_RES)
iconBitmap = getBitmapByRes(iconRes);
return createQrCode(content, width, iconBitmap, hasIcon);
}
@Override
public Bitmap createQrCode(String content, int width, Drawable iconDrawable, boolean hasIcon) {
Bitmap iconBitmap = null;
if (iconDrawable != null)
iconBitmap = getBitmapByDrawable(iconDrawable);
return createQrCode(content, width, iconBitmap, hasIcon);
}
/**
* Create QrCode with text content.
* We suggest that developer adjusts the content length, more content text means more difficult for scanning result on the device.
*
* @param content QrCode content
* @param width QrCode width&Height
* @param iconBitmap center icon if you want add icon in Bitmap
* @param hasIcon if false,iconBitmap will not show on QrCode bitmap
* @return QrCode bitmap
*/
@Override
public Bitmap createQrCode(String content, int width, Bitmap iconBitmap, boolean hasIcon) {
try {
//配置参数
Map<EncodeHintType, Object> hints = new HashMap<>();
hints.put(EncodeHintType.CHARACTER_SET, "utf-8");
//容错级别
hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);
// TODO set blank width, default is 4
// hints.put(EncodeHintType.MARGIN, 2);
BitMatrix bitMatrix = new QRCodeWriter().encode(content, BarcodeFormat.QR_CODE, width, width, hints);
int[] pixels = new int[width * width];
// 下面这里按照二维码的算法,逐个生成二维码的图片,
// 两个for循环是图片横列扫描的结果
for (int y = 0; y < width; y++) {
for (int x = 0; x < width; x++) {
if (bitMatrix.get(x, y)) {
pixels[y * width + x] = 0xff000000; //black
} else {
pixels[y * width + x] = 0xffffffff; //white
}
}
}
Bitmap bitmap = Bitmap.createBitmap(width, width, Bitmap.Config.ARGB_8888);
bitmap.setPixels(pixels, 0, width, 0, 0, width, width);
//Add the Icon to the QrCode bitmap center
if (iconBitmap != null && hasIcon) {
bitmap = addIcon2QrCode(iconBitmap, bitmap);
}
//compress bitmap
return bmpConpressor.compressBitmap(bitmap);
} catch (WriterException e) {
e.printStackTrace();
}
return null;
}
@Override
public Bitmap getBitmapByRes(int resId) {
return BitmapFactory.decodeResource(activity.getResources(), resId);
}
@Override
public Bitmap getBitmapByDrawable(Drawable drawable) {
return Bitmap.createBitmap(
drawable.getIntrinsicWidth(),
drawable.getIntrinsicHeight(),
drawable.getOpacity() != PixelFormat.OPAQUE ? Bitmap.Config.ARGB_8888
: Bitmap.Config.RGB_565);
}
/**
* Add the Icon to the QrCode bitmap center
*
* @param icon the icon
* @param qrCode the qrCode
* @return QrCode Bitmap with icon
*/
@Override
public Bitmap addIcon2QrCode(Bitmap icon, Bitmap qrCode) {
if (qrCode == null) {
return null;
}
if (icon == null) {
return qrCode;
}
int srcWidth = qrCode.getWidth();
int srcHeight = qrCode.getHeight();
int iconWidth = icon.getWidth();
int iconHeight = icon.getHeight();
if (srcWidth == 0 || srcHeight == 0) {
return null;
}
if (iconWidth == 0 || iconHeight == 0) {
return qrCode;
}
//Icon size/ QrCode size = 1:5
float scaleFactor = srcWidth * 1.0f / 5 / iconWidth;
Bitmap bitmap = Bitmap.createBitmap(srcWidth, srcHeight, Bitmap.Config.ARGB_8888);
try {
Canvas canvas = new Canvas(bitmap);
canvas.drawBitmap(qrCode, 0, 0, null);
canvas.scale(scaleFactor, scaleFactor, srcWidth / 2, srcHeight / 2);
canvas.drawBitmap(icon, (srcWidth - iconWidth) / 2, (srcHeight - iconHeight) / 2, null);
canvas.save(Canvas.ALL_SAVE_FLAG);
canvas.restore();
} catch (Exception e) {
bitmap = null;
e.getStackTrace();
}
return bitmap;
}
}
图片压缩
事实上,Bitmap是很吃资源的,所以我们在生成二维码的同时,简单对Bitmap进行压缩再返回:
接口
public interface IBitmapCompressor {
Bitmap compressBitmap(Bitmap bmp);
}
实现
public class BitmapCompressor implements IBitmapCompressor {
private int bmpQuality = 64 * 1024; //compress bitmap size -> 64k
/**
* set compress bitmap size,with unit KB,default value is 64KB
* @param bmpQuality
*/
public void setBmpQuality(int bmpQuality) {
this.bmpQuality = bmpQuality * 1024;
}
@Override
public Bitmap compressBitmap(Bitmap bmp) {
byte[] bytes = compressBitmap2ByteArray(bmp);
ByteArrayInputStream inputs = new ByteArrayInputStream(bytes);
return BitmapFactory.decodeStream(inputs, null, null);
}
private byte[] compressBitmap2ByteArray(Bitmap bmp) {
ByteArrayOutputStream output = new ByteArrayOutputStream();
bmp.compress(Bitmap.CompressFormat.JPEG, 100, output);
float zoom = (float) Math.sqrt(32 * 1024 / (float) output.toByteArray().length); //获取缩放比例
// set Rect datas
Matrix matrix = new Matrix();
matrix.setScale(zoom, zoom);
// create new bitmap with Rect datas
Bitmap resultBitmap = Bitmap.createBitmap(bmp, 0, 0, bmp.getWidth(), bmp.getHeight(), matrix, true);
output.reset();
resultBitmap.compress(Bitmap.CompressFormat.JPEG, 100, output);
// if result still > bmpQuality,compress bitmap continue.
while (output.toByteArray().length > bmpQuality) {
matrix.setScale(0.9f, 0.9f);//scale 0.1 every time
resultBitmap = Bitmap.createBitmap(
resultBitmap, 0, 0,
resultBitmap.getWidth(), resultBitmap.getHeight(), matrix, true);
output.reset();
resultBitmap.compress(Bitmap.CompressFormat.JPEG, 100, output);
}
return output.toByteArray();
}
}
这里的bmpQuality 就是要压缩到的大小,一般来说,64K的二维码都足以让绝大多数设备轻松扫描解析了。
当然我也提供了一个接口方便去设置bmpQuality的值:
/**
* set compress bitmap size,with unit KB,default value is 64KB
* @param bmpQuality
*/
public void setBmpQuality(int bmpQuality) {
this.bmpQuality = bmpQuality * 1024;
}
如何使用:
我们只需要在Activity中初始化:
//初始化
qrCodeEncoder = new QRCodeEncoder(this);
//根据内容生成二维码并展示在ImageView上
String textContent = "https://github.com/qingmei2";
qrCodeEncoder.createQrCode2ImageView(textContent, iamgeView);
//或者带Icon的二维码
qrCodeEncoder.createQrCode2ImageView(textContent, iamgeView, R.mipmap.ic_launcher);
结果如下:
内容:”https://github.com/qingmei2”
一般二维码:
带Icon的二维码:
更简单的方式
是不是觉得还是有点麻烦,没关系,笔者花了点时间将二维码的生成&解析封装成了library,只需要按照上面的使用文档使用就可以了:
如果只是需要用到二维码的生成,只需要添加对应的依赖,然后直接按照QrCodeScannerView-Android中文说明文档或者文章上方的「如何使用」方式直接使用即可。