背景
近两日辞了实习的工作以准备毕业,现在写完了毕设论文,只待飞赴学校。现在无事可做,便从二维码的学习开始,再次学习新的技术知识,于是记录这个demo项目的完成过程。
导包
在项目gradle文件的指明使用仓库里,加上jitpack.io
buildscript {
repositories {
...
maven { url 'https://jitpack.io' }
}
...
}
allprojects {
repositories {
...
maven { url 'https://jitpack.io' }
}
}
...
再到模块gradle文件里,加上依赖
implementation 'com.github.jwkj:LibZXing:v1.0.4'
主界面
主界面包括三个控件:一个编辑区、一个按钮和一张二维码图片
编辑区用来输入内容,根据输入的内容生成二维码
按钮用来启动二维码的生成或扫描
二维码图片长摁保存到内部存储根目录下
主界面的xml文件内容如下
<?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"
android:gravity="center"
tools:context=".MainActivity">
<EditText
android:id="@+id/content_str"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<Button
android:id="@+id/btn_generateZxing"
android:text="@string/generate_qr_code"
android:layout_gravity="center"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<ImageView
android:id="@+id/content_zxing"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
主界面的逻辑实现
权限的赋予
主要用到摄像头和写存储两个权限,安卓高版本需要动态申请。我这里用了一个第三方权限库来实现之
PermissonUtil.checkPermission(this, new PermissionListener() {
@Override
public void havePermission() {
}
@Override
public void requestPermissionFail() {
finish();
}
}, Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE);
依赖地址如下
implementation 'com.github.tyhjh:picturePickUtil:v1.0.6'
设置控件的监听
主要是按钮的点击和图片的长摁
final EditText editText = findViewById(R.id.content_str);
final ImageView contentZxing = findViewById(R.id.content_zxing);
final Button btnGenerateQRCode = findViewById(R.id.btn_generateZxing);
btnGenerateQRCode.setText(mRes.getString(R.string.generate_qr_code));
btnGenerateQRCode.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
....
}
});
contentZxing.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View view) {
....
return false;
}
});
二维码的生成和扫描
首先获取一下QRCodeManager的单例,因为它是二维码生成、扫描和结果处理的管家
mQRCodeManager = QRCodeManager.getInstance();
然后让MainActivity覆写onActivityResult(),在里面调用QRCodeManager的同名方法,让其进行处理。只有这样,我们设置的扫描回调方法才能被调用。
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
mQRCodeManager.onActivityResult(requestCode, resultCode, data);
}
然后就可以在按钮的点击监听里,根据按钮的提示文字的不同,来实现二维码的生成和扫描回调的设置了
btnGenerateQRCode.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (btnGenerateQRCode.getText().toString().equals(mRes.getString(R.string.generate_qr_code))) {
String text = TextUtils.isEmpty(editText.getText()) ? "szc" : editText.getText().toString();
contentZxing.setImageBitmap(mQRCodeManager.createQRCode(text, 500, 500));
btnGenerateQRCode.setText(mRes.getString(R.string.begin_scan));
} else {
mQRCodeManager.with(MainActivity.this).setReqeustType(0).scanningQRCode(new OnQRCodeScanCallback() {
@Override
public void onCompleted(String s) {
System.out.println("扫描完成:" + s);
}
@Override
public void onError(Throwable throwable) {
throwable.printStackTrace();
}
@Override
public void onCancel() {
System.out.println("扫描取消");
}
});
btnGenerateQRCode.setText(mRes.getString(R.string.generate_qr_code));
}
}
});
其中生成二维码用到的方法是mQRCodeManager.createQRCode(text, widthPix, heightPix),这个方法会生成一个二维码位图对象,如果要生成带头像的二维码对象,则调用重载方法mQRCodeManager.createQRCode(text, widthPix, heightPix, bitmap)就行,最后一个参数就是头像的位图对象,可以用本地的,也可以用Picasso等加载网络的。
二维码的扫描里,主要是回调接口里的三个方法,其中成功时的回调onCompleted()的参数s,就是二维码的内容,可能是一个网址,这样就可以让webview进行加载了。
保存二维码图片
这个功能在图片的长摁里实现
contentZxing.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View view) {
new Thread(new Runnable() {
@Override
public void run() {
try {
Context context = MainActivity.this;
contentZxing.setDrawingCacheEnabled(true);
final Bitmap qrCodeBitmap = contentZxing.getDrawingCache();
File destFile = new File(getRootIntervalStorage(), "qrCode.jpg");
if (destFile.exists()) {
destFile.delete();
}
destFile.createNewFile();
BufferedOutputStream outputStream = new BufferedOutputStream(new FileOutputStream(destFile));
qrCodeBitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStream);
outputStream.flush();
outputStream.close();
MediaScannerConnection.scanFile(context, new String[]{destFile.toString()}, null, new MediaScannerConnection.OnScanCompletedListener() {
@Override
public void onScanCompleted(String s, Uri uri) {
System.out.println("扫描完成:" + s + ",uri:" + uri.toString());
}
});
contentZxing.setDrawingCacheEnabled(false);
System.out.println("保存成功");
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
return false;
}
});
由于是文件操作,所以新开了一个线程。
获取imageView的位图对象,是通过获取其缓存实现的,关键代码如下
contentZxing.setDrawingCacheEnabled(true);
final Bitmap qrCodeBitmap = contentZxing.getDrawingCache();
...
contentZxing.setDrawingCacheEnabled(false);
其中,使能和禁止缓存的方法要成对出现,否则可能报错位图已经被回收或位图为空
然后就是位图对象的压缩和输出,对应代码
File destFile = new File(getRootIntervalStorage(), "qrCode.jpg");
if (destFile.exists()) {
destFile.delete();
}
destFile.createNewFile();
BufferedOutputStream outputStream = new BufferedOutputStream(new FileOutputStream(destFile));
qrCodeBitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStream);
outputStream.flush();
outputStream.close();
其中为了保存到内部存储根目录下,我自定义了方法getRootIntervalStorage()。
private File getRootIntervalStorage() {
File appCacheFile = getExternalCacheDir();
while (!appCacheFile.getAbsolutePath().endsWith("0") && !appCacheFile.getAbsolutePath().endsWith("0" + File.separator)) {
appCacheFile = appCacheFile.getParentFile();
}
return appCacheFile;
}
由于应用的外部缓存目录路径是/storage/emulated/0/Android/data/包名/cache,而内部存储根目录的路径是/storage/emulated/0,所以我们就能进行回溯了。
最后要通知一下系统图库进行扫描,否则在扫描本地二维码图片时,读取不到图片数据(这个库是用cursor读取本地图片的,如果不更新图库的话,会导致cursor读不到_data这一列,因此无法识别二维码,还报错Failure delievery...,其实是数组越界)。对应代码
MediaScannerConnection.scanFile(context, new String[]{destFile.toString()}, null, new MediaScannerConnection.OnScanCompletedListener() {
@Override
public void onScanCompleted(String s, Uri uri) {
System.out.println("扫描完成:" + s + ",uri:" + uri.toString());
}
});
结果测试
应用起来时的主界面

输入一段文字后,点击生成二维码

长摁二维码图片后日志输出和图库显示结果


点击扫描二维码

![]()
结果分析及源码简读
从扫描时可以看出来,他调用QRCodeManager.scanningQRCode()时启动了一个新的activitiy,对应源码如下
public QRCodeManager scanningQRCode(OnQRCodeScanCallback callback) {
this.callback = callback;
this.scanning(410);
return this;
}
void scanning(int requestCode) {
this.curRequestCode = requestCode;
Intent intent = new Intent(this.context, CaptureActivity.class);
intent.putExtra("type", this.requestType);
this.context.startActivityForResult(intent, 410);
}
启动的是CaptureActivity,我们点进去看看它的onCreate()方法,其中主要代码如下
this.scanPreview = (SurfaceView)this.findViewById(id.capture_preview);
this.scanContainer = (RelativeLayout)this.findViewById(id.capture_container);
this.scanCropView = (RelativeLayout)this.findViewById(id.capture_crop_view);
this.scanLine = (ImageView)this.findViewById(id.capture_scan_line);
this.ivBack = (ImageView)this.findViewById(id.iv_back);
this.ivMullt = (ImageView)this.findViewById(id.iv_mudle);
this.tvAlbum = (TextView)this.findViewById(id.tv_capture_select_album);
this.tvAlbum.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
Intent intent = new Intent("android.intent.action.GET_CONTENT");
intent.addCategory("android.intent.category.OPENABLE");
intent.setType("image/*");
CaptureActivity.this.startActivityForResult(intent, 101);
}
});
this.ivBack.setTag(123);
this.ivMullt.setTag(124);
this.ivBack.setOnClickListener(this);
this.ivMullt.setOnClickListener(this);
this.inactivityTimer = new InactivityTimer(this);
this.beepManager = new BeepManager(this);
if (this.captureType == 1) {
this.ivMullt.setVisibility(4);
}
TranslateAnimation animation = new TranslateAnimation(2, 0.0F, 2, 0.0F, 2, 0.0F, 2, 0.9F);
animation.setDuration(4500L);
animation.setRepeatCount(-1);
animation.setRepeatMode(1);
this.scanLine.startAnimation(animation);
其中scanPreview就是预览画面,scanCropView是预览框,scanLine是扫描线。扫描线用TranslateAnimation控制其上下移动,也就是个动画
而从相册中读取图片则是从tvAlbum的点击开始的,对应那一段onClick()回调。从内容可以看到,是读取了系统图库,获取内容。然后请求码是101,在onActivityResult()中处理
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode == -1 && requestCode == 101) {
String picPath = SelectAlbumUtils.getPicPath(this, data);
Result result = DecodeBitmap.scanningImage(picPath);
if (result == null) {
Toast.makeText(this, this.getString(string.pic_no_qrcode), 0).show();
} else {
this.beepManager.playBeepSoundAndVibrate();
String scanResult = DecodeBitmap.parseReuslt(result.toString());
this.scanDeviceSuccess(scanResult, new Bundle());
}
}
}
找到图片后,先获取路径,再扫描图片,最后处理结果
获取路径的话,调用SelectAlbumUtils.getPicPath()方法,此方法会最终走到SelectAlbumUtils.getImagePath()方法,代码如下
private static String getImagePath(Context context, Uri uri, String selection) {
String Path = null;
Cursor cursor = context.getContentResolver().query(uri, (String[])null, selection, (String[])null, (String)null);
if (cursor != null) {
if (cursor.moveToFirst()) {
Path = cursor.getString(cursor.getColumnIndex("_data"));
}
cursor.close();
}
return Path;
}
保存二维码图片后,如果不通知图库进行更新,cursor.getColumnIndex("_data")会返回-1,断点调试发现游标里没有_data这一列,因此报错退出。
如果是用相机扫描的话,从获取相机预览数据到结果回调,调用的关键方法历次是PreviewCallback.onPreviewFrame()、DecodeHandler.decode()、CaptureActivityHandler.handleMessage()、CaptureActivity.handleDecode()、MainActivity.onActivityResult()、QRCodeManager.onActivityResult()、OnQRCodeScanCallback.onCompleted()。有兴趣可以看看怎么解码的
结语
这个库挺好用,只是扫描界面不能自定义,不过可以把库克隆下来,把UI界面改一改,就可以做到自定义了。

1330

被折叠的 条评论
为什么被折叠?



