Android之 Zxing二维码详解
一 简介
1.1 ZXing
目前Android扫描二维码,条形码主要用google官方的工具Zxing,支持扫码,相册解码,生成带logo的二维码等功能
Zxing github 示例地址:https://github.com/zxing/zxing
1.2 ZBar
由于zxing是基于java编写的,扫码速度和解析上可能没那么快,但大部分场合足够用。也有基于c/c++的库zbar,需要编译通过才能用,下面是官网,有兴趣的可以编译试试:
ZBar官网:http://zbar.sourceforge.net/
ZBar GitHub地址:https://github.com/ZBar/ZBar
1.3 华为ScanKit
目前体验最好的华为统一扫码SDK,基本可以做到秒扫和快速识别,支持多码识别和二维码生成。但该服务必须在华为开发者联盟平台注册应用,配置包名和服务json
https://gitee.com/hms-core/hms-scan-demo华为官方demo示例 gitee地址:https://gitee.com/hms-core/hms-scan-demo
二 Zxing使用
2.1 依赖远程zxing库
dependencies {
//zxing的core库
implementation "com.google.zxing:core:3.5.1"
//zxing
implementation "com.google.zxing:zxing-parent:3.5.1"
//zxing
implementation 'com.journeyapps:zxing-android-embedded:4.1.0'
}
或直接使用下面库,目前识别比较快的Zxing库
implementation 'com.journeyapps:zxing-android-embedded:4.3.0'
2.2 添加权限
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.FLASHLIGHT" />
2.3 调用SDK的扫码页面并返回结果
/**
* 跳转到扫码界面扫码
*/
private void goScan(){
Intent intent = new Intent(MainActivity.this, CaptureActivity.class);
startActivityForResult(intent, REQUEST_CODE_SCAN);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
// 扫描二维码/条码回传
if (requestCode == REQUEST_CODE_SCAN && resultCode == RESULT_OK) {
if (data != null) {
//返回的文本内容
String content = data.getStringExtra(DECODED_CONTENT_KEY);
//返回的BitMap图像
Bitmap bitmap = data.getParcelableExtra(DECODED_BITMAP_KEY);
}
}
}
三 自定义扫码页面
3.1 效果图
3.2 activity_scan.xml
<?xml version="1.0" encoding="UTF-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:my_view="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:id="@+id/rim"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#C0C0C0">
<SurfaceView
android:id="@+id/surfaceView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"/>
</FrameLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="56dp"
android:gravity="center_vertical">
<TextView
android:layout_marginStart="10sp"
android:layout_toEndOf="@+id/back_img"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:text="扫码"
android:textAllCaps="false"
android:textColor="#FFFFFF"
android:textSize="20sp"
android:textStyle="bold" />
<ImageView
android:id="@+id/back_img"
android:layout_width="48dp"
android:layout_height="48dp"
android:padding="12dp"
android:layout_alignParentStart="true"
android:layout_marginStart="12dp"
android:layout_marginTop="4dp"
android:gravity="center"
android:src="@drawable/back" />
<TextView
android:id="@+id/tv_decode"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:text="解码器"
android:textAllCaps="false"
android:textColor="#FFFFFF"
android:textSize="20sp"
android:textStyle="bold"
android:layout_alignParentEnd="true"
android:layout_marginEnd="12dp"
android:layout_marginTop="4dp" />
<ImageView
android:id="@+id/img_btn"
android:layout_width="48dp"
android:layout_height="48dp"
android:padding="12dp"
android:layout_alignParentEnd="true"
android:layout_marginEnd="12dp"
android:layout_marginTop="4dp"
android:gravity="center"
android:visibility="gone"
android:src="@drawable/photo" />
</RelativeLayout>
</FrameLayout>
3.3 ScanActivity.java
public class ScanActivity extends Activity {
public static final int REQUEST_CODE_PHOTO = 0X1113;
private static final String TAG = "ScanActivity";
private Camera camera = null;
private Camera.Parameters parameters = null;
private boolean isPreview = false;
private FrameCallback frameCallback = new FrameCallback();
private int width = 1920;
private int height = 1080;
private double defaultZoom = 1.0;
private SurfaceHolder surfaceHolder;
private SurfaceCallBack surfaceCallBack;
private CommonHandler handler;
private boolean isShow;
private FrameLayout rim;
private SurfaceView surfaceView;
private ImageView backImg;
private TextView tvDecode;
private ImageView imgBtn;
private int shortSize;
private int longSize;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Window window = getWindow();
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_common);
rim = (FrameLayout) findViewById(R.id.rim);
surfaceView = (SurfaceView) findViewById(R.id.surfaceView);
backImg = (ImageView) findViewById(R.id.back_img);
tvDecode = (TextView) findViewById(R.id.tv_decode);
imgBtn = (ImageView) findViewById(R.id.img_btn);
checkCameraPermission();
}
//权限请求
public final int REQUEST_CAMERA_PERMISSION = 1;
private String cameraPermission = Manifest.permission.CAMERA;
private void checkCameraPermission() {
//检查是否有相机权限
if (ContextCompat.checkSelfPermission(this, cameraPermission) != PackageManager.PERMISSION_GRANTED) {
//没权限,请求权限
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA},
REQUEST_CAMERA_PERMISSION);
} else {
//有权限
createSurfaceView();
}
}
//权限请求回调
@Override
public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
switch (requestCode) {
case REQUEST_CAMERA_PERMISSION:
if (grantResults != null && grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
//用户同意权限
createSurfaceView();
} else {
// 权限被用户拒绝了,可以提示用户,关闭界面等等。
Toast.makeText(this, "拒绝权限,请去设置里面手动开启权限", Toast.LENGTH_SHORT).show();
}
break;
}
}
/**
* 创建预览
*/
private void createSurfaceView() {
surfaceCallBack = new SurfaceCallBack();
adjustSurface(surfaceView);
surfaceHolder = surfaceView.getHolder();
isShow = false;
setBackOperation();
setPictureScanOperation();
setDecodeSelectOperation();
}
private void adjustSurface(SurfaceView cameraPreview) {
FrameLayout.LayoutParams paramSurface = (FrameLayout.LayoutParams) cameraPreview.getLayoutParams();
if (getSystemService(Context.WINDOW_SERVICE) != null) {
WindowManager windowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
Display defaultDisplay = windowManager.getDefaultDisplay();
Point outPoint = new Point();
defaultDisplay.getRealSize(outPoint);
int sceenWidth = outPoint.x;
int sceenHeight = outPoint.y;
shortSize = Math.min(sceenWidth, sceenHeight);
longSize = shortSize;
//横屏
paramSurface.width = shortSize;
paramSurface.height = shortSize;
// float rate;
// if (sceenWidth / (float) 1080 > sceenHeight / (float) 1920) {
// rate = sceenWidth / (float) 1080;
// int targetHeight = (int) (1920 * rate);
// paramSurface.width = FrameLayout.LayoutParams.MATCH_PARENT;
// paramSurface.height = targetHeight;
// int topMargin = (int) (-(targetHeight - sceenHeight) / 2);
// if (topMargin < 0) {
// paramSurface.topMargin = topMargin;
// }
// } else {
// rate = sceenHeight / (float) 1920;
// int targetWidth = (int) (1080 * rate);
// paramSurface.width = targetWidth;
// paramSurface.height = FrameLayout.LayoutParams.MATCH_PARENT;
// int leftMargin = (int) (-(targetWidth - sceenWidth) / 2);
// if (leftMargin < 0) {
// paramSurface.leftMargin = leftMargin;
// }
// }
}
}
private void setBackOperation() {
backImg.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
finish();
}
});
}
private void setPictureScanOperation() {
imgBtn = findViewById(R.id.img_btn);
imgBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent pickIntent = new Intent(Intent.ACTION_PICK,
MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
pickIntent.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "image/*");
ScanActivity.this.startActivityForResult(pickIntent, REQUEST_CODE_PHOTO);
}
});
}
private int decodeType = 1;
private void setDecodeSelectOperation() {
tvDecode.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (decodeType == 1) {
decodeType = 2;
tvDecode.setText("Sunmin");
} else if (decodeType == 2) {
decodeType = 3;
tvDecode.setText("HuaweiHms");
} else if (decodeType == 3) {
decodeType = 1;
tvDecode.setText("Zxing");
}
}
});
tvDecode.setText("Zxing");
}
@Override
protected void onResume() {
super.onResume();
if (isShow) {
initCamera();
} else {
surfaceHolder.addCallback(surfaceCallBack);
}
}
@Override
protected void onPause() {
if (handler != null) {
handler.quit();
handler = null;
}
close();
if (!isShow) {
surfaceHolder.removeCallback(surfaceCallBack);
}
super.onPause();
}
@Override
protected void onDestroy() {
super.onDestroy();
}
private void initCamera() {
open(surfaceHolder);
if (handler == null) {
handler = new CommonHandler();
} else {
startPreview();
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
// if (resultCode != RESULT_OK || data == null || requestCode != REQUEST_CODE_PHOTO) {
// return;
// }
// try {
// decodeMultiSyn(MediaStore.Images.Media.getBitmap(this.getContentResolver(), data.getData()));
// } catch (Exception e) {
// Log.e(TAG, Objects.requireNonNull(e.getMessage()));
// }
}
class SurfaceCallBack implements SurfaceHolder.Callback {
@Override
public void surfaceCreated(SurfaceHolder holder) {
if (!isShow) {
isShow = true;
initCamera();
}
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
isShow = false;
}
}
///
/**
* Open up the camera.
*/
public synchronized void open(SurfaceHolder holder) {
try {
camera = Camera.open(0);
parameters = camera.getParameters();
//获取合适的预览尺寸,保证不变形
Camera.Size bestSize = getBestSize(parameters);
//设置预览大小
parameters.setPreviewSize(bestSize.width, bestSize.height);
parameters.setPictureSize(width, height);
parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
parameters.setPictureFormat(ImageFormat.NV21);
camera.setPreviewDisplay(holder);
camera.setDisplayOrientation(90);
//camera.setDisplayOrientation(270);
camera.setParameters(parameters);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 获取预览最佳尺寸
*/
private Camera.Size getBestSize(Camera.Parameters parameters) {
List<Camera.Size> sizes = parameters.getSupportedPreviewSizes();
Camera.Size bestSize = null;
float uiRatio = (float) longSize / shortSize;
float minRatio = uiRatio;
for (Camera.Size previewSize : sizes) {
float cameraRatio = (float) previewSize.width / previewSize.height;
//如果找不到比例相同的,找一个最近的,防止预览变形
float offset = Math.abs(cameraRatio - minRatio);
if (offset < minRatio) {
minRatio = offset;
bestSize = previewSize;
}
//比例相同
if (uiRatio == cameraRatio) {
bestSize = previewSize;
break;
}
}
return bestSize;
}
public synchronized void close() {
if (camera != null) {
camera.stopPreview();
camera.release();
camera = null;
}
}
public synchronized void startPreview() {
if (camera != null && !isPreview) {
camera.startPreview();
isPreview = true;
}
}
public synchronized void stopPreview() {
if (camera != null && isPreview) {
camera.stopPreview();
frameCallback.setProperties(null);
isPreview = false;
}
}
public synchronized void callbackFrame(Handler handler, double zoomValue) {
if (camera != null && isPreview) {
frameCallback.setProperties(handler);
if (camera.getParameters().isZoomSupported() && zoomValue != defaultZoom) {
//Auto zoom.
//parameters.setZoom(convertZoomInt(zoomValue));
camera.setParameters(parameters);
}
camera.setOneShotPreviewCallback(frameCallback);
}
}
public int convertZoomInt(double zoomValue) {
List<Integer> allZoomRatios = parameters.getZoomRatios();
float maxZoom = Math.round(allZoomRatios.get(allZoomRatios.size() - 1) / 100f);
if (zoomValue >= maxZoom) {
return allZoomRatios.size() - 1;
}
for (int i = 1; i < allZoomRatios.size(); i++) {
if (allZoomRatios.get(i) >= (zoomValue * 100) && allZoomRatios.get(i - 1) <= (zoomValue * 100)) {
return i;
}
}
return -1;
}
class FrameCallback implements Camera.PreviewCallback {
private Handler handler;
public void setProperties(Handler handler) {
this.handler = handler;
}
@Override
public void onPreviewFrame(byte[] data, Camera camera) {
if (handler != null) {
Message message = handler.obtainMessage(0, camera.getParameters().getPreviewSize().width,
camera.getParameters().getPreviewSize().height, data);
message.sendToTarget();
handler = null;
}
}
}
//
class CommonHandler extends Handler {
private static final String TAG = "MainHandler";
private static final double DEFAULT_ZOOM = 1.0;
private HandlerThread decodeThread;
private Handler decodeHandle;
private BeepManager beepManager;
private long preTime;
public CommonHandler() {
beepManager = new BeepManager(ScanActivity.this);
decodeThread = new HandlerThread("DecodeThread");
decodeThread.start();
decodeHandle = new Handler(decodeThread.getLooper()) {
@Override
public void handleMessage(Message msg) {
if (msg == null) {
return;
}
long startTime=System.currentTimeMillis();
String result = decodeSyn(msg.arg1, msg.arg2, (byte[]) msg.obj);
long endTime=System.currentTimeMillis();
if (result == null) {
restart(DEFAULT_ZOOM);
} else {
Message message = new Message();
message.what = msg.what;
message.obj = result;
message.arg1= (int) (endTime-startTime);
CommonHandler.this.sendMessage(message);
}
}
};
startPreview();
restart(DEFAULT_ZOOM);
}
@Override
public void handleMessage(Message message) {
if (message.what == 0) {
String result = (String) message.obj;
Log.e(TAG, result);
long currentTime = System.currentTimeMillis();
if (currentTime - preTime > 200) {
preTime = currentTime;
beepManager.playBeepSoundAndVibrate();
restart(DEFAULT_ZOOM);
stopPreview();
Intent intent = new Intent(ScanActivity.this, ResultActivity.class);
intent.putExtra("result", result);
intent.putExtra("time", message.arg1);
startActivity(intent);
}
}
}
public void quit() {
try {
stopPreview();
decodeHandle.getLooper().quit();
decodeThread.join(500);
} catch (InterruptedException e) {
Log.w(TAG, e);
}
}
public void restart(double zoomValue) {
callbackFrame(decodeHandle, zoomValue);
}
/**
* Call the MultiProcessor API in synchronous mode.
*/
private String decodeSyn(int width, int height, byte[] data) {
String result="";
switch (decodeType){
case 1://Zxing
result = ZxingUtils.decode(width, height, data);
break;
case 2://商米
result = SunmiUtils.decode(width, height, data);
break;
case 3://华为
result = HuaweiHmsUtils.decode(width, height, data, ScanActivity.this);
break;
}
return result;
}
}
}
3.4 解析工具类
public class ZxingUtils {
public static String decode(int width, int height, byte[] data) {
Map<DecodeHintType, Object> hints = new EnumMap<>(DecodeHintType.class);
hints.put(DecodeHintType.CHARACTER_SET, "utf-8");
Result rawResult = null;
PlanarYUVLuminanceSource source = buildLuminanceSource(data, width, height);
MultiFormatReader multiFormatReader = null;
if (source != null) {
BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
try {
multiFormatReader = new MultiFormatReader();
multiFormatReader.setHints(hints);
rawResult = multiFormatReader.decodeWithState(bitmap);
} catch (ReaderException re) {
// continue
} finally {
multiFormatReader.reset();
}
}
if(rawResult==null){
return null;
}else {
return rawResult.getText();
}
}
/**
* A factory method to build the appropriate LuminanceSource object based on the format
* of the preview buffers, as described by Camera.Parameters.
*
* @param data A preview frame.
* @param width The width of the image.
* @param height The height of the image.
* @return A PlanarYUVLuminanceSource instance.
*/
public static PlanarYUVLuminanceSource buildLuminanceSource(byte[] data, int width, int height) {
Rect rect = new Rect(0,0,width,height);
if (rect == null) {
return null;
}
// Go ahead and assume it's YUV rather than die.
return new PlanarYUVLuminanceSource(data, width, height, rect.left, rect.top,
rect.width(), rect.height(), false);
}
}
四 相册选区图片解析二维码
4.1 选择相册图片
private void setPictureScanOperation() {
imgBtn = findViewById(R.id.img_btn);
imgBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent pickIntent = new Intent(Intent.ACTION_PICK,
MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
pickIntent.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "image/*");
ZxingActivity.this.startActivityForResult(pickIntent, REQUEST_CODE_PHOTO);
}
});
}
4.2 本地图片文件转换成可解码二维码的 Bitmap
/**
* 将本地图片文件转换成可解码二维码的 Bitmap。为了避免图片太大,这里对图片进行了压缩。
*
* @param picturePath 本地图片文件路径
*/
public static Bitmap getDecodeAbleBitmap(String picturePath) {
try {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(picturePath, options);
int sampleSize = options.outHeight / 400;
if (sampleSize <= 0) {
sampleSize = 1;
}
options.inSampleSize = sampleSize;
options.inJustDecodeBounds = false;
return BitmapFactory.decodeFile(picturePath, options);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
4.3 解析二维码图片
/**
* 同步解析bitmap二维码。该方法是耗时操作,请在子线程中调用。
*
* @param bitmap 要解析的二维码图片
* @return 返回二维码图片里的内容 或 null
*/
public static String syncDecodeQRCode(Bitmap bitmap) {
Result result;
RGBLuminanceSource source = null;
try {
int width = bitmap.getWidth();
int height = bitmap.getHeight();
int[] pixels = new int[width * height];
bitmap.getPixels(pixels, 0, width, 0, 0, width, height);
source = new RGBLuminanceSource(width, height, pixels);
result = new MultiFormatReader().decode(new BinaryBitmap(new HybridBinarizer(source)), new EnumMap<>(DecodeHintType.class));
return result.getText();
} catch (Exception e) {
e.printStackTrace();
if (source != null) {
try {
result = new MultiFormatReader().decode(new BinaryBitmap(new GlobalHistogramBinarizer(source)), new EnumMap<>(DecodeHintType.class));
return result.getText();
} catch (Throwable e2) {
e2.printStackTrace();
}
}
return null;
}
}
五 生成二维码
5.1 支持修改边框颜色大小,二维码颜色大小,背景颜色,logo样式和圆角
5.2 activity_qrcode_style_xml
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/v_image"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginLeft="40dp"
android:layout_marginRight="40dp" />
</FrameLayout>
5.3 核心生成二维码源码,QRCodeEncoder.java
public class QRCodeEncoder {
public static final Map<EncodeHintType, Object> HINTS = new EnumMap<>(EncodeHintType.class);
static {
HINTS.put(EncodeHintType.CHARACTER_SET, "utf-8");
HINTS.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);
HINTS.put(EncodeHintType.MARGIN, 0);
}
private QRCodeEncoder() {
}
/**
* 同步创建黑色前景色、白色背景色的二维码图片。该方法是耗时操作,请在子线程中调用。
*
* @param content 要生成的二维码图片内容
* @param size 图片宽高,单位为px
*/
public static Bitmap syncEncodeQRCode(String content, int size) {
return syncEncodeQRCode(content, size, Color.BLACK, Color.WHITE, null);
}
/**
* 同步创建指定前景色、白色背景色的二维码图片。该方法是耗时操作,请在子线程中调用。
*
* @param content 要生成的二维码图片内容
* @param size 图片宽高,单位为px
* @param foregroundColor 二维码图片的前景色
*/
public static Bitmap syncEncodeQRCode(String content, int size, int foregroundColor) {
return syncEncodeQRCode(content, size, foregroundColor, Color.WHITE, null);
}
/**
* 同步创建指定前景色、白色背景色、带logo的二维码图片。该方法是耗时操作,请在子线程中调用。
*
* @param content 要生成的二维码图片内容
* @param size 图片宽高,单位为px
* @param foregroundColor 二维码图片的前景色
* @param logo 二维码图片的logo
*/
public static Bitmap syncEncodeQRCode(String content, int size, int foregroundColor, Bitmap logo) {
return syncEncodeQRCode(content, size, foregroundColor, Color.WHITE, logo);
}
/**
* 同步创建指定前景色、指定背景色、带logo的二维码图片。该方法是耗时操作,请在子线程中调用。
*
* @param content 要生成的二维码图片内容
* @param size 图片宽高,单位为px
* @param foregroundColor 二维码图片的前景色
* @param backgroundColor 二维码图片的背景色
* @param logo 二维码图片的logo
*/
public static Bitmap syncEncodeQRCode(String content, int size, int foregroundColor, int backgroundColor, Bitmap logo) {
try {
BitMatrix matrix = new MultiFormatWriter().encode(content, BarcodeFormat.QR_CODE, size, size, HINTS);
int[] pixels = new int[size * size];
for (int y = 0; y < size; y++) {
for (int x = 0; x < size; x++) {
if (matrix.get(x, y)) {
pixels[y * size + x] = foregroundColor;
} else {
pixels[y * size + x] = backgroundColor;
}
}
}
Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
bitmap.setPixels(pixels, 0, size, 0, 0, size, size);
return addLogoToQRCode(bitmap, logo);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 同步创建指定前景色、指定背景色、带logo的二维码图片。该方法是耗时操作,请在子线程中调用。
*
* @param content 要生成的二维码图片内容
* @param size 图片宽高,单位为px
* @param foregroundColor 二维码图片的前景色
* @param backgroundColor 二维码图片的背景色
* @param logo 二维码图片的logo
* @param borderColor 边框颜色
*/
public static Bitmap syncEncodeQRCode(String content, int size, int foregroundColor, int backgroundColor, Bitmap logo ,int border , int borderColor) {
int borderWidth= (int) dp2px(MyApp.getInstance(),4);
size-=borderWidth*2;
try {
BitMatrix matrix = new MultiFormatWriter().encode(content, BarcodeFormat.QR_CODE, size, size, HINTS);
int[] pixels = new int[size * size];
for (int y = 0; y < size; y++) {
for (int x = 0; x < size; x++) {
if (matrix.get(x, y)) {
pixels[y * size + x] = foregroundColor;
} else {
pixels[y * size + x] = backgroundColor;
}
}
}
Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
bitmap.setPixels(pixels, 0, size, 0, 0, size, size);
if(bitmap!=null){
bitmap=addLogoToQRCode(bitmap, logo);
}
bitmap=addBorderToQRCode(bitmap,backgroundColor ,border,borderColor);
return bitmap;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 添加logo到二维码图片上
*/
private static Bitmap addBorderToQRCode(Bitmap src, int backgroundColor, int border , int borderColor) {
if (src == null) {
return src;
}
if(borderColor==0){
borderColor=Color.parseColor("#63C99B");
}
int srcWidth = src.getWidth();
int srcHeight = src.getHeight();
int borderWidth= (int) dp2px(MyApp.getInstance(),4);
Bitmap bitmap = Bitmap.createBitmap(srcWidth+borderWidth, srcHeight+borderWidth, Bitmap.Config.ARGB_8888);
try {
Canvas canvas = new Canvas(bitmap);
canvas.drawColor(backgroundColor);
if(border!=0){
Paint paintRect = new Paint();
paintRect.setColor(borderColor);
paintRect.setStrokeWidth(borderWidth);
if(border==1){
paintRect.setPathEffect(new DashPathEffect(new float[]{8, 8}, 0)); // 设置虚线样式
}
paintRect.setStyle(Paint.Style.STROKE);
canvas.drawRect(0, 0, bitmap.getWidth(), bitmap.getHeight(), paintRect);
}
canvas.drawBitmap(src, borderWidth/2f, borderWidth/2f, null);
canvas.save();
canvas.restore();
} catch (Exception e) {
e.printStackTrace();
bitmap = null;
}
return bitmap;
}
/**
* 添加logo到二维码图片上
*/
private static Bitmap addLogoToQRCode(Bitmap src, Bitmap logo) {
if (src == null || logo == null) {
return src;
}
int srcWidth = src.getWidth();
int srcHeight = src.getHeight();
int logoWidth = logo.getWidth();
int logoHeight = logo.getHeight();
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.restore();
} catch (Exception e) {
e.printStackTrace();
bitmap = null;
}
return bitmap;
}
/**
* 同步创建条形码图片
*
* @param content 要生成条形码包含的内容
* @param width 条形码的宽度,单位px
* @param height 条形码的高度,单位px
* @param textSize 字体大小,单位px,如果等于0则不在底部绘制文字
* @return 返回生成条形的位图
*/
public static Bitmap syncEncodeBarcode(String content, int width, int height, int textSize) {
if (TextUtils.isEmpty(content)) {
return null;
}
Map<EncodeHintType, Object> hints = new HashMap<>();
hints.put(EncodeHintType.CHARACTER_SET, "utf-8");
hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);
hints.put(EncodeHintType.MARGIN, 0);
try {
BitMatrix bitMatrix = new MultiFormatWriter().encode(content, BarcodeFormat.CODE_128, width, height, hints);
int[] pixels = new int[width * height];
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
if (bitMatrix.get(x, y)) {
pixels[y * width + x] = 0xff000000;
} else {
pixels[y * width + x] = 0xffffffff;
}
}
}
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
bitmap.setPixels(pixels, 0, width, 0, 0, width, height);
if (textSize > 0) {
bitmap = showContent(bitmap, content, textSize);
}
return bitmap;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 显示条形的内容
*
* @param barcodeBitmap 已生成的条形码的位图
* @param content 条形码包含的内容
* @param textSize 字体大小,单位px
* @return 返回生成的新条形码位图
*/
private static Bitmap showContent(Bitmap barcodeBitmap, String content, int textSize) {
if (TextUtils.isEmpty(content) || null == barcodeBitmap) {
return null;
}
Paint paint = new Paint();
paint.setColor(Color.BLACK);
paint.setAntiAlias(true);
paint.setStyle(Paint.Style.FILL);
paint.setTextSize(textSize);
paint.setTextAlign(Paint.Align.CENTER);
int textWidth = (int) paint.measureText(content);
Paint.FontMetrics fm = paint.getFontMetrics();
int textHeight = (int) (fm.bottom - fm.top);
float scaleRateX = barcodeBitmap.getWidth() * 1.0f / textWidth;
if (scaleRateX < 1) {
paint.setTextScaleX(scaleRateX);
}
int baseLine = barcodeBitmap.getHeight() + textHeight;
Bitmap bitmap = Bitmap.createBitmap(barcodeBitmap.getWidth(), barcodeBitmap.getHeight() + 2 * textHeight, Bitmap.Config.ARGB_4444);
Canvas canvas = new Canvas();
canvas.drawColor(Color.WHITE);
canvas.setBitmap(bitmap);
canvas.drawBitmap(barcodeBitmap, 0, 0, null);
canvas.drawText(content, barcodeBitmap.getWidth() / 2, baseLine, paint);
canvas.save();
canvas.restore();
return bitmap;
}
public static float dp2px(Context context, float dpValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return dpValue * scale + 0.5f;
}
}
5.4 异步调用QRCodeEncoder生成二维码
private void updateQrcode(){
new GenerateQrcodeTask().execute(content);
}
public class GenerateQrcodeTask extends AsyncTask<String, Void, Bitmap> {
@Override
protected Bitmap doInBackground(String... strings) {
Bitmap logoBitmap=null;
if(logo!=0){
logoBitmap = BitmapFactory.decodeResource(getResources(), logo);
}
int borderColor22=TextUtils.isEmpty(borderColor)?0:Color.parseColor(borderColor);
return QRCodeEncoder.syncEncodeQRCode(strings[0], (int) StatuesBarUtils.dp2px(mContext, 300f), Color.parseColor(qianColor),Color.parseColor(beinColor),logoBitmap, border , borderColor22);
}
@Override
protected void onPostExecute(Bitmap bitmap) {
super.onPostExecute(bitmap);
qrBitmap=bitmap;
if (bitmap != null) {
mDataBinding.vImage.setImageBitmap(qrBitmap);
} else {
Toast.makeText(mContext, "生成維碼失敗", Toast.LENGTH_SHORT).show();
}
}
}
5.5 保存二维码,FileUtil.java工具类
public class FileUtil {
public static String saveToImage(Bitmap bitmap) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
return MediaStore.Images.Media.insertImage(MyApp.getInstance().getContentResolver(), bitmap, "", "");
} else {
try {
File dir = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "qrcode");
if (!dir.exists()) {
dir.mkdirs();
}
File picFile = new File(dir, System.currentTimeMillis() + ".png");
FileOutputStream fos = new FileOutputStream(picFile);
bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos);
fos.flush();
fos.close();
return picFile.getAbsolutePath();
} catch (Exception e) {
e.printStackTrace();
}
}
return null;
}
异步调用FileUtil,保存二维码
public class SaveBitmapTask extends AsyncTask<Bitmap, Void, String> {
@Override
protected String doInBackground(Bitmap... bitmaps) {
return FileUtil.saveToImage(bitmaps[0]);
}
@Override
protected void onPostExecute(String result) {
super.onPostExecute(result);
if (result != null) {
Intent intent = new Intent(mContext, QrBarcodeEditResultActivity.class);
intent.putExtra("filePath", result);
startActivity(intent);
} else {
Toast.makeText(mContext, "保存二維碼失敗", Toast.LENGTH_SHORT).show();
}
}
}
5.6 核心源码解析,我们知道bitmap是一个保存所有像素内容的容器,那二维码原理即是把内容解析到每一个像素里面。如下
/**
*
* @param content 要生成的二维码图片内容
* @param size 图片宽高,单位为px
* @param foregroundColor 二维码图片的前景色
* @param backgroundColor 二维码图片的背景色
* @param logo 二维码图片的logo
* @param borderColor 边框颜色
*/
public static Bitmap syncEncodeQRCode(String content, int size, int foregroundColor, int backgroundColor, Bitmap logo ,int border , int borderColor) {
int borderWidth= (int) dp2px(MyApp.getInstance(),4);
size-=borderWidth*2;
try {
//内容解析到字节矩阵
BitMatrix matrix = new MultiFormatWriter().encode(content, BarcodeFormat.QR_CODE, size, size, HINTS);
//设置宽高像素数组
int[] pixels = new int[size * size];
//遍历记录图片每个像素信息
for (int y = 0; y < size; y++) {
for (int x = 0; x < size; x++) {
if (matrix.get(x, y)) {
pixels[y * size + x] = foregroundColor;
} else {
pixels[y * size + x] = backgroundColor;
}
}
}
//创建bitmap,设置像素数组
Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
bitmap.setPixels(pixels, 0, size, 0, 0, size, size);
if(bitmap!=null){
bitmap=addLogoToQRCode(bitmap, logo);
}
//添加logo
bitmap=addBorderToQRCode(bitmap,backgroundColor ,border,borderColor);
return bitmap;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}