在Android开发中,ZXing是最常用的二维码处理库之一,默认提供的扫描界面和生成结果都比较朴素:扫描页只有暗色遮罩、白色取景框和一条红线,生成的二维码则固定为黑色方块。实际产品通常需要品牌色扫描框、动态扫描线、自定义按钮,以及带有Logo的彩色二维码。本文将围绕ZXing的定制方法,拆解扫描界面绘制、二维码颜色与纠错、Logo嵌入和扫描区域调整几个关键部分。

一、扫描界面样式定制:从遮罩、取景框到扫描线动画
ZXing的扫描界面核心是CaptureActivity和ViewFinderView。默认的ViewFinderView在onDraw中绘制遮罩和扫描框,遮罩由四个黑色半透明矩形拼接而成,扫描框本身只是四个角线,中间没有边框,扫描线是一条红色直线,通过Handler定时重绘实现上下移动。定制时不必修改核心库,只需继承ViewFinderView并重写onDraw,或者直接复制源码到自己的项目中修改。
具体绘制时,扫描框尺寸通常取屏幕宽度的60%到70%,可以使用Math.min(width, height) * 0.7来计算。四个遮罩矩形的位置要始终保证中间为透明扫描区,同时注意矩形坐标的先后顺序,避免出现1像素的缝隙或重叠。扫描线的颜色、粗细、渐变甚至是粒子效果都可以在onDraw中自由绘制。建议使用ValueAnimator或postInvalidateDelayed驱动扫描线位置变化,每次移动3到5像素,到达底部后回到顶部,形成循环动画。
下面的代码示例演示了如何自定义一个扫描框视图,其中遮罩使用半透明黑色,扫描框四个角使用品牌绿色,扫描线使用渐变蓝色。绘制完成后,在activity_capture.xml中用自定义类替换原来的com.google.zxing.client.android.ViewFinderView,并把相关尺寸、颜色定义到资源文件中,方便多主题切换。需要注意的是,扫描框坐标要和后面解码区域一致,否则可能出现扫描框内识别不到的现象。
public class CustomFinderView extends View {
private Paint maskPaint;
private Paint cornerPaint;
private Paint scanLinePaint;
private int scanLineTop;
private int frameSize;
private int cornerLength;
private int cornerWidth;
public CustomFinderView(Context context) {
super(context);
init();
}
private void init() {
maskPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
maskPaint.setColor(Color.parseColor("#66000000"));
cornerPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
cornerPaint.setColor(Color.parseColor("#00CC66"));
cornerPaint.setStrokeWidth(8);
scanLinePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
scanLinePaint.setColor(Color.parseColor("#3399FF"));
scanLinePaint.setStrokeWidth(4);
frameSize = 600;
cornerLength = 50;
cornerWidth = 10;
scanLineTop = 0;
postInvalidateDelayed(new Runnable() {
@Override
public void run() {
scanLineTop += 5;
if (scanLineTop > getHeight()) {
scanLineTop = 0;
}
invalidate();
postInvalidateDelayed(this, 16);
}
});
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int width = getWidth();
int height = getHeight();
int left = (width - frameSize) / 2;
int top = (height - frameSize) / 2;
int right = left + frameSize;
int bottom = top + frameSize;
// 绘制四个半透明遮罩区域
canvas.drawRect(0, 0, width, top, maskPaint);
canvas.drawRect(0, top, left, bottom + 1, maskPaint);
canvas.drawRect(right + 1, top, width, bottom + 1, maskPaint);
canvas.drawRect(0, bottom + 1, width, height, maskPaint);
// 绘制扫描框四个角
canvas.drawRect(left, top, left + cornerLength, top + cornerWidth, cornerPaint);
canvas.drawRect(left, top, left + cornerWidth, top + cornerLength, cornerPaint);
canvas.drawRect(right - cornerLength, top, right, top + cornerWidth, cornerPaint);
canvas.drawRect(right - cornerWidth, top, right, top + cornerLength, cornerPaint);
canvas.drawRect(left, bottom - cornerWidth, left + cornerLength, bottom, cornerPaint);
canvas.drawRect(left, bottom - cornerLength, left + cornerWidth, bottom, cornerPaint);
canvas.drawRect(right - cornerLength, bottom - cornerWidth, right, bottom, cornerPaint);
canvas.drawRect(right - cornerWidth, bottom - cornerLength, right, bottom, cornerPaint);
// 绘制扫描线
canvas.drawLine(left, scanLineTop, right, scanLineTop, scanLinePaint);
}
}
二、生成二维码的颜色与纠错等级定制
默认情况下,ZXing的QRCodeWriter只生成BitMatrix布尔矩阵,并不包含颜色信息。把矩阵转成Bitmap时,开发者可以完全控制每个模块的前景色和背景色。常见品牌二维码会使用深蓝、深灰或品牌主色作为前景,但要注意与背景保持足够对比度。背景色不一定必须是白色,但在浅色背景上打印或展示时尽量保持纯白或极浅色,否则会导致扫描器难以区分模块边界。
纠错等级是二维码样式中容易被忽略的一环。ErrorCorrectionLevel提供L、M、Q、H四个级别,对应约7%、15%、25%、30%的纠错能力。选择H级会增加二维码模块数量,视觉上更密;如果只是普通文本链接,M级足够。只有需要嵌入Logo或放在复杂背景上时,才建议使用H级,以抵消遮挡带来的采样误差。留白margin参数同样影响扫描效果,默认留白为4个模块,建议不要小于2,否则识别率会明显下降。
生成过程需要注意Bitmap像素写入方式、留白margin以及尺寸。使用Bitmap.createBitmap(width, height, ARGB_8888)后,通过setPixels一次性写入所有像素,比逐个setPixel效率高。下面的代码封装了颜色和留白参数,可直接输出任意颜色的二维码,同时支持设置纠错等级和字符编码。
public Bitmap createQRCodeBitmap(String content, int width, int height,
int margin, int foregroundColor, int backgroundColor) {
Map<EncodeHintType, Object> hints = new HashMap<>();
hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");
hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);
hints.put(EncodeHintType.MARGIN, margin);
QRCodeWriter writer = new QRCodeWriter();
try {
BitMatrix matrix = writer.encode(content, BarcodeFormat.QR_CODE, width, height, hints);
int[] pixels = new int[width * height];
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
if (matrix.get(x, y)) {
pixels[y * width + x] = foregroundColor;
} else {
pixels[y * width + x] = backgroundColor;
}
}
}
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
bitmap.setPixels(pixels, 0, width, 0, 0, width, height);
return bitmap;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
三、为二维码嵌入Logo并实现圆角方块样式
嵌入Logo的本质是在二维码Bitmap上叠加一张缩放后的图片。直接叠加会遮挡模块,导致扫描器无法识别中间区域的编码。因此必须选择较高的纠错等级,并且Logo面积不要超过二维码总面积的30%,通常控制在20%左右。Logo背景建议先绘制白色圆角底板,避免透明Logo与二维码模块混杂。白色底板还能提升边缘对比度,让扫描器更容易区分Logo区域与二维码数据区。
如果希望二维码本身不是传统方块,而是圆角模块或圆形模块,可以在绘制阶段不直接使用setPixels,而是遍历BitMatrix,对每个深色模块调用Canvas.drawRoundRect或drawCircle。这种方式生成的二维码视觉更柔和,但生成速度会下降,适合生成静态或低频二维码,不适合大量实时生成。圆角半径一般取模块宽度的20%到30%,过大会让模块之间粘连,降低识别率。
以下代码展示如何先将彩色二维码绘制到Canvas,再在中央绘制带白色底板的Logo。其中RectF需要导入,Paint开启抗锯齿可以让Logo边缘更平滑。若二维码需要圆形模块,可在此基础上修改模块绘制逻辑,把每个深色模块绘制为圆形而非矩形。
public Bitmap addLogoToQRCode(Bitmap qrBitmap, Bitmap logoBitmap, int logoSize) {
int qrWidth = qrBitmap.getWidth();
int qrHeight = qrBitmap.getHeight();
Bitmap result = Bitmap.createBitmap(qrWidth, qrHeight, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(result);
canvas.drawBitmap(qrBitmap, 0, 0, null);
int left = (qrWidth - logoSize) / 2;
int top = (qrHeight - logoSize) / 2;
Bitmap scaledLogo = Bitmap.createScaledBitmap(logoBitmap, logoSize, logoSize, true);
Paint bgPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
bgPaint.setColor(Color.WHITE);
int padding = 12;
RectF bgRect = new RectF(left - padding, top - padding,
left + logoSize + padding, top + logoSize + padding);
canvas.drawRoundRect(bgRect, 20, 20, bgPaint);
canvas.drawBitmap(scaledLogo, left, top, null);
return result;
}
四、同步调整扫描识别区域与相机参数
很多开发者遇到一个现象:自定义扫描框后,二维码必须放到屏幕正中间很小区域才能识别,或者扫描框内明明对准了却识别失败。这是因为ZXing默认的解码区域可能仍然使用全屏或旧的坐标。扫描框位置变化后,需要把扫描框的屏幕坐标映射到相机预览帧的坐标,再裁剪PlanarYUVLuminanceSource。否则解码器会在错误的区域寻找二维码,扫描框看起来再漂亮也无法提升识别效率。
在DecodeHandler或相关处理类中,将扫描框Rect传入,并在buildLuminanceSource时使用。需要注意预览帧尺寸与屏幕尺寸的比例关系,通常需要先计算缩放比例:scaleX = dataWidth / screenWidth,scaleY = dataHeight / screenHeight,再把扫描框坐标乘以对应比例。否则裁剪区域会偏移,出现扫描框下半部分识别不到的情况。这个坐标映射是定制界面中最容易出错的环节,建议在调试时打印原始坐标和转换后的坐标。
除了区域映射,还可以通过修改CameraConfigurationUtils来设置连续自动对焦、微距模式或固定帧率,提升扫码速度。部分设备在暗光下自动对焦很慢,可以在预览回调中增加定时触发对焦的逻辑。最后,多格式支持也要在MultiFormatReader中配置,避免只识别QR码而漏掉Data Matrix等格式。以下代码展示了如何将屏幕坐标转换为预览帧坐标,并裁剪出指定区域用于解码。
public PlanarYUVLuminanceSource buildLuminanceSource(byte[] data, int width, int height,
Rect scanRect, int screenWidth, int screenHeight) {
float scaleX = (float) width / screenWidth;
float scaleY = (float) height / screenHeight;
int left = Math.round(scanRect.left * scaleX);
int top = Math.round(scanRect.top * scaleY);
int right = Math.round(scanRect.right * scaleX);
int bottom = Math.round(scanRect.bottom * scaleY);
int cropWidth = right - left;
int cropHeight = bottom - top;
return new PlanarYUVLuminanceSource(data, width, height,
left, top, cropWidth, cropHeight, false);
}
通过上述四个方面的定制,你可以让ZXing扫描界面和生成的二维码完全符合产品视觉规范,同时保持较高的识别速度和稳定性。实际项目中建议将颜色、尺寸、Logo资源等提取到配置类或资源文件,方便后续切换主题或适配不同屏幕尺寸。
Android二维码Zxing定制二维码样式修改时间:2026-08-27 14:10:09