1.生成二维码
简单介绍一下Zxing二维码库的使用方式,Zxing库很强大,可以生成各种格式的二维码(分析源码部分时再看其他的类型),最常用的就是QR格式。
1.1代码
如果没有Zxing库,可以到我的云盘下载。
把jar包下载,copy到工程的libs目录下,buildpath即可
1.1.1 获取编码后的数据Bitmatrix
BitMatrix是Zxing库定义的一个二维码的数据类。
这个方法主要是生成二维码的BitMatrix,实际上就是一个矩阵,二维数组–!
获取到Bitmap后,就可以随意展示了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
public
static
Bitmap generateQRCode(String content,
int
width,
int
height) {
try
{
HashMap hints =
new
HashMap();
// 设置编码方式utf-8
hints.put(EncodeHintType.CHARACTER_SET,
"utf-8"
);
//设置二维码的纠错级别为h
hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);
BitMatrix matrix =
new
MultiFormatWriter().encode(content,
BarcodeFormat.QR_CODE, width, height, hints);
return
bitMatrix2Bitmap(matrix);
}
catch
(WriterException e) {
e.printStackTrace();
}
return
null
;
}
|
1.1.2将数据Bitmatrix转换成Bitmap
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
private
static
Bitmap bitMatrix2Bitmap(BitMatrix matrix) {
matrix = updateBit(matrix,
0
);
int
w = matrix.getWidth();
int
h = matrix.getHeight();
int
[] rawData =
new
int
[w * h];
for
(
int
i =
0
; i < w; i++) {
for
(
int
j =
0
; j < h; j++) {
int
color = Color.WHITE;
if
(matrix.get(i, j)) {
// 有内容的部分,颜色设置为黑色,当然这里可以自己修改成喜欢的颜色
color = Color.BLACK;
}
rawData[i + (j * w)] = color;
}
}
Bitmap bitmap = Bitmap.createBitmap(w, h, Config.RGB_565);
bitmap.setPixels(rawData,
0
, w,
0
,
0
, w, h);
return
bitmap;
}
|
2.源码分析白边的生成过程
下面就分析Zxing的源码以及默认白边的形成
先看generateQRCode里的关键方法MultiFormatWriter类的encode方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
public
BitMatrix encode(String contents, BarcodeFormat format,
int
width,
int
height, Map hints)
throws
WriterException {
Writer writer;
switch
(format) {
case
EAN_8:
writer =
new
EAN8Writer();
break
;
case
EAN_13:
writer =
new
EAN13Writer();
break
;
case
UPC_A:
writer =
new
UPCAWriter();
break
;
case
QR_CODE:
writer =
new
QRCodeWriter();
break
;
case
CODE_39:
writer =
new
Code39Writer();
break
;
case
CODE_128:
writer =
new
Code128Writer();
break
;
case
ITF:
writer =
new
ITFWriter();
break
;
case
PDF_417:
writer =
new
PDF417Writer();
break
;
case
CODABAR:
writer =
new
CodaBarWriter();
break
;
default
:
throw
new
IllegalArgumentException(
"No encoder available for format "
+ format);
}
return
writer.encode(contents, format, width, height, hints);
}
}
|
实际上这个方法就是依据format来选择一种编码方式,最常用的就是QR_CODE的方式了,还有其他的方式,基本不用~~有兴趣的可以百度下
然后我们再看QRCodeWriter的encode方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
public
BitMatrix encode(String contents, BarcodeFormat format,
int
width,
int
height, Map hints)
throws
WriterException
{
if
(contents.length() ==
0
) {
throw
new
IllegalArgumentException(
"Found empty contents"
);
}
if
(format != BarcodeFormat.QR_CODE) {
throw
new
IllegalArgumentException(
"Can only encode QR_CODE, but got "
+ format);
}
if
((width <
0
) || (height <
0
)) {
throw
new
IllegalArgumentException(
"Requested dimensions are too small: "
+ width +
'x'
+ height);
}
ErrorCorrectionLevel errorCorrectionLevel = ErrorCorrectionLevel.L;
if
(hints !=
null
) {
ErrorCorrectionLevel requestedECLevel = (ErrorCorrectionLevel)hints.get(EncodeHintType.ERROR_CORRECTION);
if
(requestedECLevel !=
null
) {
errorCorrectionLevel = requestedECLevel;
}
}
// 前面的都是做编码前的准备,安全检验,纠错级别设置等
QRCode code =
new
QRCode();
// 这里才是真正的将contents转换成code
Encoder.encode(contents, errorCorrectionLevel, hints, code);
// return的时候将code转换成BitMatrix,并加入白边
return
renderResult(code, width, height);
}
|
下面再看将code转换成BitMatrix,并加入白边的方法renderResult
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
private
static
BitMatrix renderResult(QRCode code,
int
width,
int
height) {
ByteMatrix input = code.getMatrix();
if
(input ==
null
) {
throw
new
IllegalStateException();
}
int
inputWidth = input.getWidth();
int
inputHeight = input.getHeight();
// 这里qrWidth就是原始的二维码的宽度了,包含8单位宽度的白边
int
qrWidth = inputWidth +
8
;
int
qrHeight = inputHeight +
8
;
// 依据用户的输入宽高,计算最后的输出宽高
int
outputWidth = Math.max(width, qrWidth);
int
outputHeight = Math.max(height, qrHeight);
//计算缩放比例
int
multiple = Math.min(outputWidth / qrWidth, outputHeight / qrHeight);
// 计算白边的宽度
int
leftPadding = (outputWidth - inputWidth * multiple) /
2
;
int
topPadding = (outputHeight - inputHeight * multiple) /
2
;
BitMatrix output =
new
BitMatrix(outputWidth, outputHeight);
int
inputY =
0
;
// 嵌套循环,将ByteMatrix的内容计算padding后转换成BitMatrix
for
(
int
outputY = topPadding; inputY < inputHeight; outputY += multiple) {
int
inputX =
0
;
for
(
int
outputX = leftPadding; inputX < inputWidth; outputX += multiple) {
if
(input.get(inputX, inputY) ==
1
) {
output.setRegion(outputX, outputY, multiple, multiple);
}
inputX++;
}
inputY++;
}
return
output;
}
|
这个方法里的代码不难读懂,所以要去掉白边实际上就很简单了,自定义一个QRCodeWriter类,完全把Zxing包的QRCodeWriter类复制过来,然后将renderResult方法里的padding去掉就可以了(为什么不继承QRCodeWriter,因为它是final类~~)。
下面是去掉padding后的代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
private
static
BitMatrix renderResult(QRCode code,
int
width,
int
height) {
ByteMatrix input = code.getMatrix();
if
(input ==
null
) {
throw
new
IllegalStateException();
}
int
inputWidth = input.getWidth();
int
inputHeight = input.getHeight();
// 依据用户的输入宽高,计算最后的输出宽高
int
outputWidth = Math.max(width, inputWidth);
int
outputHeight = Math.max(height, inputHeight);
//计算缩放比例
int
multiple = Math.min(outputWidth / inputWidth, outputHeight / inputHeight);
BitMatrix output =
new
BitMatrix(outputWidth, outputHeight);
int
inputY =
0
;
// 嵌套循环,将ByteMatrix的内容计算padding后转换成BitMatrix
for
(
int
outputY =
0
; inputY < inputHeight; outputY += multiple) {
int
inputX =
0
;
for
(
int
outputX =
0
; inputX < inputWidth; outputX += multiple) {
if
(input.get(inputX, inputY) ==
1
) {
output.setRegion(outputX, outputY, multiple, multiple);
}
inputX++;
}
inputY++;
}
return
output;
}
|
效果图(为了区分白边,将整个背景色设置成的#0f0了)
去掉白边前:
去掉白边后:
去掉白边的其他方法
方法1:
这个方法就是将Zxing生成的BitMatrix更新一下去掉了周边,并重新设置白边的宽度,见margin
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
<code
class
=
"hljs cs"
>
private
static
BitMatrix updateBit(BitMatrix matrix,
int
margin) {
int
tempM = margin *
2
;
int
[] rec = matrix.getEnclosingRectangle();
// 获取二维码图案的属性
int
resWidth = rec[
2
] + tempM;
int
resHeight = rec[
3
] + tempM;
BitMatrix resMatrix =
new
BitMatrix(resWidth, resHeight);
// 按照自定义边框生成新的BitMatrix
resMatrix.clear();
for
(
int
i = margin; i < resWidth - margin; i++) {
// 循环,将二维码图案绘制到新的bitMatrix中
for
(
int
j = margin; j < resHeight - margin; j++) {
if
(matrix.get(i - margin + rec[
0
], j - margin + rec[
1
])) {
resMatrix.set(i, j);
}
}
}
return
resMatrix;
}</code>
|
方法2:
在比较新的Zxing包中EncodeHintType有另外一个属性就是Margin,可以设置这个属性来更新,有兴趣的可以去玩玩哈,这里就不贴代码了
3.自定义白边颜色
当然通过上面的阅读,可以自定义白边的宽度了,下面就介绍下自定义白边颜色的方法;
当然,最简单的自定义白边就是使用一个imageview展示无边的二维码,外层view设置一个白边背景,就可以了~
这里要介绍的方法是修改bitMatrix2Bitmap方法,通过上文知道,通过zxing包生成的只是BitMaxtrix,这是不能直接用在imageview上的。读者如果仔细阅读了上文的bitMatrix2Bitmap方法就应该可以猜到这里要介绍的方法了,不多说,代码如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
private
static
Bitmap bitMatrix2Bitmap(BitMatrix matrix, Bitmap logBitmap) {
int
w = matrix.getWidth();
int
h = matrix.getHeight();
int
[] rec = matrix.getEnclosingRectangle();
int
[] rawData =
new
int
[w * h];
for
(
int
i =
0
; i < w; i++) {
for
(
int
j =
0
; j < h; j++) {
int
color = Color.WHITE;
if
(matrix.get(i, j)) {
color = Color.BLACK;
}
// 设置白边的颜色
if
(i < rec[
0
] || j < rec[
1
] || i > (rec[
0
] + rec[
2
]) || j > (rec[
1
] + rec[
3
])){
color = Color.RED;
}
rawData[i + (j * w)] = color;
}
}
Bitmap bitmap = Bitmap.createBitmap(w, h, Config.RGB_565);
bitmap.setPixels(rawData,
0
, w,
0
,
0
, w, h);
return
addLogo(bitmap, logBitmap);
}
|
这个方法就是可以自定义白边颜色的方法,
效果图
这里需要解释下BitMatrix的getEnclosingRectangle()获取到的东西到底是什么,这个方法的源码我就不贴了,这个方法返回一个一维数组,长度为4<喎�"/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxwcmUgY2xhc3M9"brush:java;">return new int[] { left, top, width, height };
left就是二维码内容左上角的x坐标,top就是左上角的y坐标,with就是二维码内容的宽度,height就是二维码内容的高度。
所以当i和j超出二维码内容范围的时候就可以设置自己的边框颜色了~~
4.二维码添加LOGO(单个文字等)
逻辑比较简单,获取到二维码的Bitmap后,Logo就是在这个Bitmap中间再绘制一个Bitmap不就可以了么。
代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
private
static
Bitmap addLogo(Bitmap src, Bitmap logo) {
if
(logo ==
null
) {
return
src;
}
// 获取图片的宽高
int
srcWidth = src.getWidth();
int
srcHeight = src.getHeight();
int
logoWidth = logo.getWidth();
int
logoHeight = logo.getHeight();
if
(logoWidth ==
0
|| logoHeight ==
0
) {
return
src;
}
// logo大小为二维码整体大小的1/5
float
scaleFactor = srcWidth *
1
.0f /
5
/ logoWidth;
Bitmap bitmap = Bitmap.createBitmap(srcWidth, srcHeight, Bitmap.Config.ARGB_8888);
try
{
Canvas canvas =
new
Canvas(bitmap);
canvas.drawBitmap(src,
0
,
0
,
null
);
canvas.scale(scaleFactor, scaleFactor, srcWidth /
2
, srcHeight /
2
);
canvas.drawBitmap(logo, (srcWidth - logoWidth) /
2
, (srcHeight - logoHeight) /
2
,
null
);
canvas.save(Canvas.ALL_SAVE_FLAG);
canvas.restore();
}
catch
(Exception e) {
bitmap =
null
;
e.getStackTrace();
}
return
bitmap;
}
|
5.二维码下方添加文字段落
逻辑和添加logo是一样的,只不过这里添加的文字是放置在二维码下方的一段文字,需要测定文字的大小,行高等,代码逻辑我就不解释了,主要是是使用级少,项目中有一个分享需要将这个做成图片分享到微信,后来又嫌弃样式太不美观丢弃了~~~确实很烦。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
public
static
Bitmap addTextToBitmap(Bitmap bmpSrc, String text) {
int
srcWidth = bmpSrc.getWidth();
int
srcHeight = bmpSrc.getHeight();
// 先计算text所需要的height
int
textSize =
20
;
int
padding =
3
;
int
textLinePadding =
1
;
// 每行的文字
int
perLineWords = (srcWidth -
2
* padding) / textSize;
int
lineNum = text.length() / perLineWords;
lineNum = text.length() % perLineWords ==
0
? lineNum : lineNum +
1
;
int
textTotalHeight = lineNum * (textSize + textLinePadding) +
2
* padding;
Bitmap bitmap = Bitmap.createBitmap(srcWidth, srcHeight + textTotalHeight,
Bitmap.Config.ARGB_8888);
try
{
Canvas canvas =
new
Canvas(bitmap);
canvas.drawColor(Color.WHITE);
canvas.drawBitmap(bmpSrc,
0
,
0
,
null
);
Paint paint =
new
Paint(Paint.ANTI_ALIAS_FLAG);
paint.setColor(Color.BLACK);
paint.setTextSize(textSize);
String lineText;
for
(
int
i =
0
, startY = srcHeight + textSize, start, end; i < lineNum; i++) {
start = i * perLineWords;
end = start + perLineWords;
lineText = text.substring(start, end > text.length() ? text.length() : end);
canvas.drawText(lineText, padding, startY, paint);
startY += textSize + textLinePadding;
}
canvas.save(Canvas.ALL_SAVE_FLAG);
canvas.restore();
}
catch
(Exception e) {
bitmap =
null
;
e.getStackTrace();
}
return
bitmap;
}
|
实际上,最后的这2个部分都应该是Bitmap和Canvas的内容,这期就先到这里了。