转载请标明出处:http://blog.csdn.net/xx326664162/article/details/50952856 文章出自:薛瑄的博客
你也可以查看我的其他同类文章,也会让你有一定的收货!
ZXing是由谷歌开发的一款二维码扫描工具
官方github地址: https://github.com/zxing/zxing
这个工程包含很多项目,这里主要说一下android方面的。在工程中的android 项目依赖core项目 和android-core项目
ZXing使用maven构建整个工程,可以使用intelli IDEA 打开整个项目,编译打包core项目(核心解码库),得到jar
也可以使用maven中心库http://mvnrepository.com/artifact/com.google.zxing/core
这个项目中只有一个CameraConfigurationUtils类,主要是配置摄像头的一些参数,可以手动合并到android项目中
在这个工程中android项目也是使用maven构建的,Android Studio 中的项目是使用gradle构建的,使用file->new ->import project 导入项目,AS可以自动转换为gradle。
导入core项目的jar包,
至此就可以打包apk了
官方android项目,包含了剪切板,扫描历史,分享,扫描二维码,等等。
如果只使用扫码功能,需要剔除其他的功能,这里为大家提供一个封装好精简的ZXing Lib:
https://github.com/JantHsueh/ZxingAndroid
基于ZXing3.1封装,包含了最新的jar包和代码。
clone 项目。并导入AS后:
CaptureActivity
原版本:ZXing的主Activity。ZXing启动后执行CaptureActivity,在handleDecode()方法中对扫码成功后的动作作处理。
简化后:主Activity是MainActivity,分别有扫描二维码和生成二维码,单击扫描二维码,也是执行CaptureActivity在handleDecode()方法中对扫码成功后的动作作处理。
ViewfinderView
原版本:ZXing扫码窗口的绘制,可以在这个类中改变原版扫描窗口的界面
简化后:ZXing扫码窗口,集成到了CaptureActivity中,抽取到了XML文件中,这样修改起来更加方便了。
CameraConfigurationManager
该类主要负责设置相机的参数信息,获取最佳的预览界面。修改横竖屏、处理变形效果的核心类。
1、public void setDesiredCameraParameters(Camera camera, boolean safeMode)方法中(读取配置设置相机的对焦模式、闪光灯模式等等),可以将扫描改为竖屏:
在方法最后加上:
/** 设置相机预览为竖屏 */
camera.setDisplayOrientation(90);
2、在public void initFromCameraParameters(Camera camera)方法中(计算了屏幕分辨率和当前最适合的相机像素),我们可以对修改为竖屏扫码后,由于像素信息点没有对调造成图像扭曲变形进行修改。
在Log.d(TAG, “Screen resolution: ” + screenResolution);后加上如下的代码:
/** 因为换成了竖屏显示,所以不替换屏幕宽高得出的预览图是变形的 */
Point screenResolutionForCamera = new Point();
screenResolutionForCamera.x = screenResolution.x;
screenResolutionForCamera.y = screenResolution.y;
if (screenResolution.x < screenResolution.y) {
screenResolutionForCamera.x = screenResolution.y;
screenResolutionForCamera.y = screenResolution.x;
}
将screenResolution替换为screenResolutionForCamera:
cameraResolution = findBestPreviewSizeValue(parameters, screenResolutionForCamera);
DecodeHandler.decode
ZXing解码的核心类
CaptureActivityHandler
当DecodeHandler.decode完成解码后,系统会向CaptureActivityHandler发消息。如果编码成功则调用CaptureActivity.handleDecode方法对扫描到的结果进行分类处理。
public class MainActivity extends Activity {
private TextView resultTextView;
private EditText qrStrEditText;
private ImageView qrImgImageView;
private CheckBox mCheckBox;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
resultTextView = (TextView) this.findViewById(R.id.tv_scan_result);
qrStrEditText = (EditText) this.findViewById(R.id.et_qr_string);
qrImgImageView = (ImageView) this.findViewById(R.id.iv_qr_image);
mCheckBox = (CheckBox) findViewById(R.id.logo);
//扫描二维码按钮
Button scanBarCodeButton = (Button) this.findViewById(R.id.btn_scan_barcode);
scanBarCodeButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
//打开扫描界面扫描条形码或二维码
Intent openCameraIntent = new Intent(MainActivity.this, CaptureActivity.class);
startActivityForResult(openCameraIntent, 0);
}
});
//生成二维码按钮
Button generateQRCodeButton = (Button) this.findViewById(R.id.btn_add_qrcode);
generateQRCodeButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
String contentString = qrStrEditText.getText().toString();
if (!contentString.equals("")) {
//根据字符串生成二维码图片并显示在界面上,第二个参数为图片的大小(350*350)
Bitmap qrCodeBitmap = EncodingUtils.createQRCode(contentString, 350, 350,
mCheckBox.isChecked() ?
BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher) :
null);
qrImgImageView.setImageBitmap(qrCodeBitmap);
} else {
Toast.makeText(MainActivity.this, "Text can not be empty", Toast.LENGTH_SHORT).show();
}
}
});
}
//扫描二维码,返回的结果
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == RESULT_OK) {
Bundle bundle = data.getExtras();
String scanResult = bundle.getString("result");
resultTextView.setText(scanResult);
}
}
}
因为在通过相机预览画面识别二维码时,需要知道二维码在相机预览画面中的位置,就会有这么一个问题:相机的分辨率和屏幕的分辨率不一样,怎么使屏幕中显示的扫描框,就是相机所捕捉的扫描框??
解决思路也很简单,按照比例去计算,相机预览画面尺寸/屏幕布局尺寸= 相机预览画面二维码位置/屏幕布局扫描框位置
pt:预览图中二维码图片的左上顶点坐标,也就是手机中相机预览中看到的待扫描二维码的位置
qrheight:预览图中二维码图片的高度
qrwidth:预览图中二维码图片的宽度
pheight:预览图的高度,也即camera的分辨率高度
pwidth:预览图的宽度,也即camera的分辨率宽度
st:布局文件中扫描框的左上顶点坐标
sheight:布局文件中扫描框的高度
swidth:布局文件中扫描框的宽度
cheight:布局文件中相机预览控件的高度
cwidth:布局文件中相机预览控件的宽度
存在下面的等比公式:
ptx / pwidth = stx / cwidth ;
pty / pheight = sty / cheight ;
qrwidth / pwidth = swidth / cwidth ;
qrheight / pheight = sheight / cheight ;
确定相机中捕捉的扫描框大小和尺寸:
ptx = stx * pwidth / cwidth ;
pty = sty * pheight / cheight ;
qrwidth = swidth * pwidth / cwidth ;
qrheight = sheight * pheight / cheight ;
熟悉这个类中几个函数的使用,可以自由使用ZXing功能,改变UI
Android 状态栏、标题栏、屏幕高度、横竖屏
public final class CaptureActivity extends Activity implements SurfaceHolder.Callback {
private static final String TAG = CaptureActivity.class.getSimpleName();
private CameraManager cameraManager;
private CaptureActivityHandler handler;
private InactivityTimer inactivityTimer;
private BeepManager beepManager;
private SurfaceView scanPreview = null;
private RelativeLayout scanContainer;
private RelativeLayout scanCropView;
private ImageView scanLine;
private Rect mCropRect = null;
private boolean isHasSurface = false;
public Handler getHandler() {
return handler;
}
public CameraManager getCameraManager() {
return cameraManager;
}
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
Window window = getWindow();
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
setContentView(R.layout.activity_capture);
//获得UI控件
scanPreview = (SurfaceView) findViewById(R.id.capture_preview);
scanContainer = (RelativeLayout) findViewById(R.id.capture_container);
scanCropView = (RelativeLayout) findViewById(R.id.capture_crop_view);
scanLine = (ImageView) findViewById(R.id.capture_scan_line);
//结束闲置一段时间的activity,如果这个设备时电池供电
inactivityTimer = new InactivityTimer(this);
//蜂鸣器
beepManager = new BeepManager(this);
//扫描动画
TranslateAnimation animation = new TranslateAnimation(Animation.RELATIVE_TO_PARENT, 0.0f, Animation
.RELATIVE_TO_PARENT, 0.0f, Animation.RELATIVE_TO_PARENT, 0.0f, Animation.RELATIVE_TO_PARENT,
0.9f);
//动画持续的时间
animation.setDuration(4500);
//动画重复的此时
animation.setRepeatCount(-1);
//动画如何重复,从下到上,还是重新开始从上到下
animation.setRepeatMode(Animation.RESTART);
scanLine.startAnimation(animation);
}
@Override
protected void onResume() {
super.onResume();
// 下面这句是我自行加入的,在原作者的代码中,转动屏幕,出现崩溃,所以使用下面这条语句,固定屏幕方向为竖屏
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
// CameraManager must be initialized here, not in onCreate(). This is
// necessary because we don't
// want to open the camera driver and measure the screen size if we're
// going to show the help on
// first launch. That led to bugs where the scanning rectangle was the
// wrong size and partially
// off screen.
cameraManager = new CameraManager(getApplication());
handler = null;
if (isHasSurface) {
// The activity was paused but not stopped, so the surface still
// exists. Therefore
// surfaceCreated() won't be called, so init the camera here.
initCamera(scanPreview.getHolder());
} else {
// Install the callback and wait for surfaceCreated() to init the
// camera.
scanPreview.getHolder().addCallback(this);
}
inactivityTimer.onResume();
}
@Override
protected void onPause() {
if (handler != null) {
handler.quitSynchronously();
handler = null;
}
inactivityTimer.onPause();
beepManager.close();
cameraManager.closeDriver();
if (!isHasSurface) {
scanPreview.getHolder().removeCallback(this);
}
super.onPause();
}
@Override
protected void onDestroy() {
inactivityTimer.shutdown();
super.onDestroy();
}
//实现SurfaceHolder.Callback,需要实现下面三个函数
@Override
public void surfaceCreated(SurfaceHolder holder) {
if (holder == null) {
Log.e(TAG, "*** WARNING *** surfaceCreated() gave us a null surface!");
}
if (!isHasSurface) {
isHasSurface = true;
initCamera(holder);
}
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
isHasSurface = false;
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
/**
* A valid barcode has been found, so give an indication of success and show the results.
* 扫描完后的结果在这个函数处理
* @param rawResult The contents of the barcode.
* @param bundle The extras
*/
public void handleDecode(Result rawResult, Bundle bundle) {
inactivityTimer.onActivity();
beepManager.playBeepSoundAndVibrate();
Intent resultIntent = new Intent();
bundle.putInt("width", mCropRect.width());
bundle.putInt("height", mCropRect.height());
bundle.putString("result", rawResult.getText());
resultIntent.putExtras(bundle);
this.setResult(RESULT_OK, resultIntent);
CaptureActivity.this.finish();
}
//初始化摄像机
private void initCamera(SurfaceHolder surfaceHolder) {
if (surfaceHolder == null) {
throw new IllegalStateException("No SurfaceHolder provided");
}
if (cameraManager.isOpen()) {
Log.w(TAG, "initCamera() while already open -- late SurfaceView callback?");
return;
}
try {
//设置取景的使用哪个surfaceView来取景
cameraManager.openDriver(surfaceHolder);
// Creating the handler starts the preview, which can also throw a
// RuntimeException.
if (handler == null) {
//这个类处理所有包括状态机捕获的消息。
handler = new CaptureActivityHandler(this, cameraManager, DecodeThread.ALL_MODE);
}
//初始化截取的矩形区域
initCrop();
} catch (IOException ioe) {
Log.w(TAG, ioe);
displayFrameworkBugMessageAndExit();
} catch (RuntimeException e) {
// Barcode Scanner has seen crashes in the wild of this variety:
// java.?lang.?RuntimeException: Fail to connect to camera service
Log.w(TAG, "Unexpected error initializing camera", e);
displayFrameworkBugMessageAndExit();
}
}
private void displayFrameworkBugMessageAndExit() {
// camera error
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(getString(R.string.app_name));
builder.setMessage("Camera error");
builder.setPositiveButton("OK", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
finish();
}
});
builder.setOnCancelListener(new DialogInterface.OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
finish();
}
});
builder.show();
}
public void restartPreviewAfterDelay(long delayMS) {
if (handler != null) {
handler.sendEmptyMessageDelayed(R.id.restart_preview, delayMS);
}
}
public Rect getCropRect() {
return mCropRect;
}
/**
* 初始化截取的矩形区域
*/
private void initCrop() {
int cameraWidth = cameraManager.getCameraResolution().y;
int cameraHeight = cameraManager.getCameraResolution().x;
/** 获取布局中扫描框的位置信息 */
int[] location = new int[2];
scanCropView.getLocationInWindow(location);
int cropLeft = location[0];
int cropTop = location[1] - getStatusBarHeight();
int cropWidth = scanCropView.getWidth();
int cropHeight = scanCropView.getHeight();
/** 获取布局容器的宽高 */
int containerWidth = scanContainer.getWidth();
int containerHeight = scanContainer.getHeight();
/** 计算最终截取的矩形的左上角顶点x坐标 */
int x = cropLeft * cameraWidth / containerWidth;
/** 计算最终截取的矩形的左上角顶点y坐标 */
int y = cropTop * cameraHeight / containerHeight;
/** 计算最终截取的矩形的宽度 */
int width = cropWidth * cameraWidth / containerWidth;
/** 计算最终截取的矩形的高度 */
int height = cropHeight * cameraHeight / containerHeight;
/** 生成最终的截取的矩形 */
mCropRect = new Rect(x, y, width + x, height + y);
}
//获取状态条的高度
private int getStatusBarHeight() {
try {
Class> c = Class.forName("com.android.internal.R$dimen");
Object obj = c.newInstance();
Field field = c.getField("status_bar_height");
int x = Integer.parseInt(field.get(obj).toString());
return getResources().getDimensionPixelSize(x);
} catch (Exception e) {
e.printStackTrace();
}
return 0;
}
}
至于更深层次的如何处理扫描的结果,图形处理等等,就不深究了,我的水平有限。这些代码都在core项目中
demo地址:https://github.com/JantHsueh/ZxingAndroid
参考:
通过ZXing获取静态二维码图片结果:http://blog.csdn.net/luzhenyuxfcy/article/details/50475670
http://blog.csdn.net/eclipsexys/article/details/47834865
http://blog.csdn.net/xiaanming/article/details/10163203
扫描截取界面的计算
关注我的公众号,轻松了解和学习更多技术