今天在学习在Android App实现二维码扫描和识别的功能,在网上找到了到一篇博客,非常详细的介绍的二维码生成与识别的实现以及提供了博主大神封装好的包提供下载。感谢大神
原文链接:玩转Android之二维码生成与识别
由于我使用真机模拟,在Android 6.0的手机上进行测试会出现一些小问题,这里提出,方便急于解决问题的同学
- 问题1:生成的二维码在ImageView中显示问题,图像不清晰,感觉上面有一层白色透明层覆盖
- 问题2:点击扫描二维码跳转扫描界面打开摄像头失败
<uses-permissionandroid:name="android.permission.CAMERA"/>
并且我使用的是华为手机,需要在设置-应用管理-权限 中打开照相选项
进入正题,二维码,我们也称作QRCode,QR表示quick response即快速响应。一般我们开发都使用Google提供的zxing这个类库,如果只是实现生成功能,只需要下载核心jar包,至于地址,大家移步上方博客链接进行下载,(初学者不知道会不会涉及版权,授权扫描的问题),如果需要实现生成功能还要引入library,这里提供按照上方博客,生成的Model方便大家使用,直接导入,在App中依赖就可以了。
下面是一个将二维码生成和识别的整合在一起的例子,代码奉上:
以下是布局参数
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.example.zhao.qrcode.MainActivity">
<Button
android:id="@+id/button_Main"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="生成二维码"
android:onClick="onClick"/>
<Button
android:id="@+id/button_scanner"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="扫描二维码"
android:onClick="onClick"/>
<TextView
android:id="@+id/textView_Main"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"/>
<ImageView
android:id="@+id/imageView_Main"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
/>
</LinearLayout>
主代码
package com.example.zhao.qrcode;
import android.app.Activity;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.EncodeHintType;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.qrcode.QRCodeWriter;
import com.zxing.activity.CaptureActivity;
import org.w3c.dom.Text;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
import java.util.TreeMap;
public class MainActivity extends Activity {
private final static int REQUEST_CODE = 0x00;
private ImageView imageView;
private TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//显示生成的二维码
imageView = ((ImageView) findViewById(R.id.imageView_Main));
//显示扫描二维码并返回的数据
textView = ((TextView) findViewById(R.id.textView_Main));
}
public void onClick(View view) {
switch (view.getId()) {
//生成二维码
case R.id.button_Main:
Bitmap qrBitmap = createQrCodeBitmap("好好学习,天天向上", 800, 800);
Drawable ic = getResources().getDrawable(R.mipmap.ic_launcher);
Bitmap bit = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher);
Bitmap logoBitmap = addLogo(qrBitmap, bit);
imageView.setImageBitmap(qrBitmap);
break;
//扫描二维码
case R.id.button_scanner:
//跳转到扫描二维码界面
startActivityForResult(new Intent(this, CaptureActivity.class), REQUEST_CODE);
break;
}
}
/**
* 生成二维码图片
* @param content 原始数据
* @param width 指定图片宽度
* @param height 指定图片高度
* @return 生成的二维码图片
*/
private Bitmap createQrCodeBitmap(String content, int width, int height) {
//核心类, 获取ZXing中的二维码生成类QRCodeWriter
QRCodeWriter writer = new QRCodeWriter();
//设置信息的编码格式
Hashtable<EncodeHintType, String> hints = new Hashtable<>();
hints.put(EncodeHintType.CHARACTER_SET, "utf-8");
try{
/**
* 调用encode方法之后,会将字符串以二维码的形式显示在矩阵当中
* 通过调用矩阵的get(x, y)来判断出当前点是否有像素
*/
BitMatrix bitMatrix = writer.encode(content, BarcodeFormat.QR_CODE, width, height,hints);
//声明数组用来保存二维码所需要显示颜色值
int[] pixels = new int[width * height];
//循环遍历矩阵中所有的像素点,并分配黑白颜色值
for(int i = 0; i<height; i++){
for(int j = 0; j<width; j++){
if(bitMatrix.get(j, i)){//说明当前点有像素
pixels[width * i +j ] = 0x00000000;
}else{
pixels[width * i +j] = 0xffffffff;
}
}
}
//根据填充好的颜色值数组,生成新的二维码Bitmap对象并返回
Bitmap bitmap = Bitmap.createBitmap(pixels, width,height, Bitmap.Config.RGB_565);
return bitmap;
}catch (Exception e){
e.printStackTrace();
}
return null;
}
/**
* 生成带Logo的二维码
* @param qrBitmap 二维码图片
* @param logoBitmap Logo图片
* @return
*/
private Bitmap addLogo(Bitmap qrBitmap, Bitmap logoBitmap) {
int qrBitmapWidth = qrBitmap.getWidth();
int qrBitmapHeight = qrBitmap.getHeight();
int logoBitmapWidth = logoBitmap.getWidth();
int logoBitmapHeight = logoBitmap.getHeight();
Bitmap blankBitmap = Bitmap.createBitmap(qrBitmapWidth, qrBitmapHeight, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(blankBitmap);
canvas.drawBitmap(qrBitmap, 0, 0, null);
canvas.save(Canvas.ALL_SAVE_FLAG);
float scaleSize = 1.0f;
while ((logoBitmapWidth / scaleSize) > (qrBitmapWidth / 5) || (logoBitmapHeight / scaleSize) > (qrBitmapHeight / 5)) {
scaleSize *= 2;
}
float sx = 1.0f / scaleSize;
canvas.scale(sx, sx, qrBitmapWidth / 2, qrBitmapHeight / 2);
canvas.drawBitmap(logoBitmap, (qrBitmapWidth - logoBitmapWidth) / 2, (qrBitmapHeight - logoBitmapHeight) / 2, null);
canvas.restore();
return blankBitmap;
}
/**
* 扫描二维码返回数据
* @param requestCode 请求码
* @param resultCode 响应码
* @param data 返回的Intent
*/
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
String str =data.getStringExtra("result");
textView.setText(str);
}
}
附上效果图:
点击生成二维码:
点击识别二维码:
点击生成二维码会在当前页按钮下显示生成的二维码图片。而点击识别二维码,会跳转到封装好的library中的CaptureActivity,实现二维码识别的功能,解析二维码并数据返回。但是这个界面感觉和微信扫一扫界面还有点距离。既然它是跳转到新的Activity中进行扫描识别功能,说明扫描界面的布局是和CaptureActivity进行绑定的,我们点进CaptureActivity中找到它的onCreate方法看看,
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.camera);
//ViewUtil.addTopView(getApplicationContext(), this, R.string.scan_card);
CameraManager.init(getApplication());
viewfinderView = (ViewfinderView) findViewById(R.id.viewfinder_view);
cancelScanButton = (Button) this.findViewById(R.id.btn_cancel_scan);
hasSurface = false;
inactivityTimer = new InactivityTimer(this);
}
果然我们可以很轻松的找到它的布局文件camera,再点进去
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
<SurfaceView
android:id="@+id/preview_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
<com.zxing.view.ViewfinderView
android:id="@+id/viewfinder_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<RelativeLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_gravity="center"
android:orientation="vertical" >
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_centerInParent="true"
android:background="@drawable/navbar"
android:gravity="center"
android:paddingBottom="10dp"
android:paddingTop="10dp"
android:text="Scan Barcode"
android:textColor="@android:color/white"
android:textSize="18sp"
android:textStyle="bold" />
<Button
android:id="@+id/btn_cancel_scan"
android:layout_width="230dp"
android:layout_height="40dp"
android:layout_alignParentBottom="true"
android:layout_centerInParent="true"
android:layout_marginBottom="75dp"
android:text="Cancel"
android:textSize="15sp"
android:textStyle="bold" />
<!-- <LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:orientation="vertical" >
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_margin="2dp"
android:gravity="center"
android:text="@string/scan_prompt_info"
android:textColor="@android:color/white"
android:textSize="15sp"
android:textStyle="bold" />
</LinearLayout> -->
</RelativeLayout>
</FrameLayout>
我们可以看到布局分为三部分,surfaceView没什么好说的,用来限定
camera所能扫描到区域大小。再看其他控件,viewfinderView什么,从控件名看得出是zxing封装好的自定义View,在源码中我们可以看到注释:
/**
* This view is overlaid on top of the camera preview. It adds the viewfinder rectangle and partial
* transparency outside it, as well as the laser scanner animation and result points.
*/
这是一个View,覆盖在顶部的摄像头预览。它增加了取景器矩形的部分,在它以外的透明度,以及激光扫描仪动画和结果点。看来是显示界面黑灰色部分,扫描仪动画,
那么剩下的相对布局就是决定界面控件的了,TextView和Button分别是标题栏和cancel按钮,那么我们就可以直接在这里进行修改,换成我们喜欢的布局,
下面是在它原本的布局基础上修改了,仿照微信扫一扫界面实现的
代码附上:
<RelativeLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_gravity="center"
android:orientation="vertical" >
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_centerInParent="true"
android:background="@drawable/navbar"
android:gravity="center"
android:paddingBottom="10dp"
android:paddingTop="10dp"
android:text="Scan Barcode"
android:textColor="@android:color/white"
android:textStyle="bold" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="100dp"
android:orientation="horizontal"
android:layout_alignParentBottom="true"
android:layout_marginBottom="10dp">
<LinearLayout
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical">
<ImageButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/aqn"
android:background="#00000000"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="扫码"
/>
</LinearLayout>
<LinearLayout
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
>
<ImageButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/aqk"
android:background="#00000000"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="封面"/>
</LinearLayout>
<LinearLayout
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical">
<ImageButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/aqq"
android:background="#00000000"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="街景"/>
</LinearLayout>
<LinearLayout
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical">
<ImageButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/aqs"
android:background="#00000000"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="翻译"/>
</LinearLayout>
</LinearLayout>
<!-- <LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:orientation="vertical" >
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_margin="2dp"
android:gravity="center"
android:text="@string/scan_prompt_info"
android:textColor="@android:color/white"
android:textSize="15sp"
android:textStyle="bold" />
</LinearLayout> -->
</RelativeLayout>
实现效果:
但是细心的同学会发现微信扫一扫二维码功能在四种扫码模式切换下 扫码区域大小是不一样的。那么这个又是怎么实现的呢?既然扫一扫是通过camera进行获取视图的,那么这个框会不会和camera有关呢,果然我们在captureActivity 中可以找到CameraManager这个类
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.camera);
//ViewUtil.addTopView(getApplicationContext(), this, R.string.scan_card);
CameraManager.init(getApplication());
viewfinderView = (ViewfinderView) findViewById(R.id.viewfinder_view);
//cancelScanButton = (Button) this.findViewById(R.id.btn_cancel_scan);
hasSurface = false;
inactivityTimer = new InactivityTimer(this);
}
既然有Camera的Manager类,那么肯定这个类中肯定有Camera的参数设置,我们点击去看会发现,在CameraManager中有这样四个成员变量
private static final int MIN_FRAME_WIDTH = 240;
private static final int MIN_FRAME_HEIGHT = 240;
private static final int MAX_FRAME_WIDTH = 800;
private static final int MAX_FRAME_HEIGHT = 660;
最大/最小 边框宽度/高度,这不是很明显吗,往下拉我们会看到这样一个方法
/**
* Calculates the framing rect which the UI should draw to show the user where to place the
* barcode. This target helps with alignment as well as forces the user to hold the device
* far enough away to ensure the image will be in focus.
*
* @return The rectangle to draw on screen in window coordinates.
*/
public Rect getFramingRect() {
Point screenResolution = configManager.getScreenResolution();
if (framingRect == null) {
if (camera == null) {
return null;
}
int width = screenResolution.x * 3 / 4;
if (width < MIN_FRAME_WIDTH) {
width = MIN_FRAME_WIDTH;
} else if (width > MAX_FRAME_WIDTH) {
width = MAX_FRAME_WIDTH;
}
int height = screenResolution.y * 3 / 4;
if (height < MIN_FRAME_HEIGHT) {
height = MIN_FRAME_HEIGHT;
} else if (height > MAX_FRAME_HEIGHT) {
height = MAX_FRAME_HEIGHT;
}
int leftOffset = (screenResolution.x - width) / 2;
int topOffset = (screenResolution.y - height) / 2;
framingRect = new Rect(leftOffset, topOffset, leftOffset + width, topOffset + height);
Log.d(TAG, "Calculated framing rect: " + framingRect);
}
return framingRect;
}
从注释我们也可以看出,这就设置扫描区域边框的方法,因此,当我们想要修改扫描框大小时,直接设置上方的四个成员变量即可,至于如何实现4个扫一扫模式之间的切换改变不同扫描框大小,可以封装一个接口,在CaptureActivity中实现并动态修改,这里就不给大家演示了。
以上就是今天学习二维码扫码和识别的个人理解,如果有什么地方有问题欢迎提出,共同探讨相互学习。