接入zxing二维码开源项目,直接使用写好的CaptureActivity需要进行修改,使用不便,将CaptureActivity修改为抽象类方便接入实现定制化的需求。
效果图:
直接贴CaptureActivity代码:
package com.google.zxing.activity;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.os.Handler;
import android.view.SurfaceHolder;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.Result;
import com.google.zxing.camera.CameraManager;
import com.google.zxing.decoding.CaptureActivityHandler;
import com.google.zxing.decoding.InactivityTimer;
import com.google.zxing.view.ViewfinderView;
import java.io.IOException;
import java.util.Vector;
abstract public class CaptureActivity extends AppCompatActivity {
public CaptureActivityHandler handler;
public ViewfinderView viewfinderView;
public Vector<BarcodeFormat> decodeFormats;
public String characterSet;
public InactivityTimer inactivityTimer;
public Handler getHandler() {
return handler;
}
public ViewfinderView getViewfinderView() {
return viewfinderView;
}
/**
* Handler scan result
*
* @param result
* @param barcode
*/
public void handleDecode(Result result, Bitmap barcode) {
inactivityTimer.onActivity();
onPauseScan();
scanResult(result, barcode);
}
abstract public void scanResult(Result result, Bitmap barcode);
public abstract void init();
public void drawViewfinder() {
viewfinderView.drawViewfinder();
}
/**
* {@inheritDoc}
* <p>
* Perform initialization of all fragments.
*
* @param savedInstanceState
*/
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
initCamera();
}
public void initCamera() {
CameraManager.init(getApplication());
inactivityTimer = new InactivityTimer(this);
}
public void onPauseScan() {
if (handler != null) {
handler.quitSynchronously();
handler = null;
}
CameraManager.get().closeDriver();
}
public void initCamera(SurfaceHolder surfaceHolder) {
try {
CameraManager.get().openDriver(surfaceHolder);
} catch (IOException ioe) {
return;
} catch (RuntimeException e) {
return;
}
if (handler == null) {
handler = new CaptureActivityHandler(this, decodeFormats, characterSet);
}
}
}
修改后接入在自己的activity上,首先在layout中对布局文件进行修改,以activity_main.xml为例:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<FrameLayout
android:id="@+id/qrcode_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<SurfaceView
android:id="@+id/qrcode_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center" />
<com.google.zxing.view.ViewfinderView
android:id="@+id/viewfinder_content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:corner_color="#5677fc"
app:frame_color="#00000000"
app:label_text=""
app:label_text_color="#ff4081"
app:laser_color="#5677fc"
app:mask_color="#99000000"
app:result_color="#000000"
app:result_point_color="#FFFB00" />
</FrameLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
只需要将FrameLayout内容加入进你的布局文件即可。
SurfaceView用于摄像头画面显示,ViewfinderView用于二维码扫描动画UI(可换成自己的UI)。
以MainActivity.kt为例,先贴代码:
package com.rainblog.scanqr
import android.Manifest
import android.content.Intent
import android.content.pm.PackageManager
import android.graphics.Bitmap
import android.os.Bundle
import android.view.SurfaceHolder
import android.view.SurfaceView
import androidx.appcompat.app.AlertDialog
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import com.google.zxing.activity.CaptureActivity
class MainActivity : CaptureActivity(), SurfaceHolder.Callback {
private var hasSurface = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
init()
}
/**
* Dispatch onResume() to fragments. Note that for better inter-operation
* with older versions of the platform, at the point of this call the
* fragments attached to the activity are *not* resumed.
*/
override fun onResume() {
super.onResume()
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.CAMERA), 1)
} else onRestartScan()
}
/**
* Dispatch onPause() to fragments.
*/
override fun onPause() {
onPauseScan()
super.onPause()
}
override fun onDestroy() {
inactivityTimer.shutdown()
super.onDestroy()
}
/**
* Callback for the result from requesting permissions. This method
* is invoked for every call on [.requestPermissions].
*
*
* **Note:** It is possible that the permissions request interaction
* with the user is interrupted. In this case you will receive empty permissions
* and results arrays which should be treated as a cancellation.
*
*
* @param requestCode The request code passed in [.requestPermissions].
* @param permissions The requested permissions. Never null.
* @param grantResults The grant results for the corresponding permissions
* which is either [android.content.pm.PackageManager.PERMISSION_GRANTED]
* or [android.content.pm.PackageManager.PERMISSION_DENIED]. Never null.
*
* @see .requestPermissions
*/
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == 1 && permissions.size > 0) {
for (s in permissions) {
if (ContextCompat.checkSelfPermission(this, s) != PackageManager.PERMISSION_GRANTED) {
val dialog = AlertDialog.Builder(this).setMessage("缺少相机权限").create()
dialog.setOnDismissListener {
finish()
}
dialog.show()
} else {
finish()
startActivity(Intent(this,MainActivity::class.java))
}
}
}
}
override fun scanResult(result: com.google.zxing.Result?, barcode: Bitmap?) {
val dialog = AlertDialog.Builder(this).setMessage("扫描结果").setMessage(result?.text).create()
dialog.setOnDismissListener {
onRestartScan()
}
dialog.show()
}
override fun init() {
viewfinderView = findViewById(R.id.viewfinder_content)
}
/**
* This is called immediately after the surface is first created.
* Implementations of this should start up whatever rendering code
* they desire. Note that only one thread can ever draw into
* a [Surface], so you should not draw into the Surface here
* if your normal rendering will be in another thread.
*
* @param holder The SurfaceHolder whose surface is being created.
*/
override fun surfaceCreated(holder: SurfaceHolder) {
if (!hasSurface) {
hasSurface = true
initCamera(holder)
}
}
/**
* This is called immediately after any structural changes (format or
* size) have been made to the surface. You should at this point update
* the imagery in the surface. This method is always called at least
* once, after [.surfaceCreated].
*
* @param holder The SurfaceHolder whose surface has changed.
* @param format The new [PixelFormat] of the surface.
* @param width The new width of the surface.
* @param height The new height of the surface.
*/
override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {}
/**
* This is called immediately before a surface is being destroyed. After
* returning from this call, you should no longer try to access this
* surface. If you have a rendering thread that directly accesses
* the surface, you must ensure that thread is no longer touching the
* Surface before returning from this function.
*
* @param holder The SurfaceHolder whose surface is being destroyed.
*/
override fun surfaceDestroyed(holder: SurfaceHolder) {
hasSurface = false
}
private fun onRestartScan() {
val surfaceView = findViewById<SurfaceView>(R.id.qrcode_view)
val surfaceHolder = surfaceView.holder
if (hasSurface) {
initCamera(surfaceHolder)
} else {
surfaceHolder.addCallback(this)
surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS)
}
decodeFormats = null
characterSet = null
}
}
需要继承和引入CaptureActivity(), SurfaceHolder.Callback,摄像头初始化已经在抽象类CaptureActivity中调用,只需要在自己的activity上判断摄像头权限即可,在onResume中进行摄像头画面的显示,CaptureActivity中获取到扫描结果后会自动暂停扫描,此时需要在scanResult中进行处理是否继续扫描,onRestartScan()方法可以使暂停的扫描继续。非常简单。
最后还需要在主项目的gradle中引入implementation 'com.google.zxing:core:3.3.3'
项目git库:私有库