Android二維碼(仿微信,輕量Zxing)

前言#

要做一個功能,二維碼識別。網上找一堆相關的Demo,但是總不是想要的效果,或者都是多年前的版本,權衡考慮之后,決定親自操刀。不糾結直接選中Zxing框架,https://github.com/zxing/zxing 在網站上直接clone下來,運行,然后就發現問題了...

選Zxing存在的問題#

  • 為什么是橫屏,調成豎屏,居然有問題
  • 這個包居然有好多用不著的代碼
  • 默認識別的界面不是想要的效果
  • 加個Title在頂部之后識別框居然不居中

發現問題,那么本文的優點就來了,且聽一一道來#

  • 集成速度快,相關核心功能都已經再次封裝好
  • 最新的V3.30的工程,識別速度快,基本見圖秒識別
  • 解決橫豎屏的問題,通過設置Activity的android:screenOrientation="portrait" 方式設置,就可以自適應橫屏豎屏
  • 去掉工程中無用的代碼,留下最核心的代碼,實現最最最輕量級
  • 自定義AutoScannerView控件,實現微信識別區域的效果
  • 解決工程之間只以屏幕為居中的問題,目前可以根據設置寬度高度自適應居中
  • 具體示例參考:https://github.com/yangxixi88/ZxingLite

光說不練假把式,上動圖#

模仿微信的效果,真機上效果更好,錄屏的將就著看

默認效果

集成方式#

1、導入zxinglite工程,Andorid Studio通過Import Module方式導入
2、導入目標工程之后,如果有存在R等資源文件未找到,可以在菜單欄Build->Make Module zxinglite 即可
3、添加兩個權限

<uses-permission android:name="android.permission.CAMERA" /> 
<uses-permission android:name="android.permission.VIBRATE" />

4、集成BaseCaptureActivity,實現getSurfaceView()和dealDecode()等方法
5、布局樣式,仿微信效果則用AutoScannerView,默認效果使用com.google.zxing.client.android.ViewfinderView

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_wechat_capture"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="yangxixi.zxinglib.WeChatCaptureActivity">

    <SurfaceView
        android:id="@+id/preview_view"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent" />

    <com.google.zxing.client.android.AutoScannerView
        android:id="@+id/autoscanner_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</RelativeLayout>

6、仿微信效果的Activity

/**
 * 模仿微信的掃描界面
 */
public class WeChatCaptureActivity extends BaseCaptureActivity {

    private static final String TAG = WeChatCaptureActivity.class.getSimpleName();

    private SurfaceView surfaceView;
    private AutoScannerView autoScannerView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_wechat_capture);
        surfaceView = (SurfaceView) findViewById(R.id.preview_view);
        autoScannerView = (AutoScannerView) findViewById(R.id.autoscanner_view);
    }

    @Override
    protected void onResume() {
        super.onResume();
        autoScannerView.setCameraManager(cameraManager);
    }

    @Override
    public SurfaceView getSurfaceView() {
        return (surfaceView == null) ? (SurfaceView) findViewById(R.id.preview_view) : surfaceView;
    }

    @Override
    public void dealDecode(Result rawResult, Bitmap barcode, float scaleFactor) {
        Log.i(TAG, "dealDecode ~~~~~ " + rawResult.getText() + " " + barcode + " " + scaleFactor);
        playBeepSoundAndVibrate(true, false);
        Toast.makeText(this, rawResult.getText(), Toast.LENGTH_LONG).show();
//        對此次掃描結果不滿意可以調用
//        reScan();
    }
}

默認效果Activity

/**
 * 默認的掃描界面
 */
public class DefaultCaptureActivity extends BaseCaptureActivity {

    private static final String TAG = DefaultCaptureActivity.class.getSimpleName();

    private SurfaceView surfaceView;
    private ViewfinderView viewfinderView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_capture);
        surfaceView = (SurfaceView) findViewById(R.id.preview_view);
        viewfinderView = (ViewfinderView) findViewById(R.id.viewfinder_view);
    }

    @Override
    public SurfaceView getSurfaceView() {
        return (surfaceView == null) ? (SurfaceView) findViewById(R.id.preview_view) : surfaceView;
    }

    @Override
    public ViewfinderView getViewfinderHolder() {
        return (viewfinderView == null) ? (ViewfinderView) findViewById(R.id.viewfinder_view) : viewfinderView;
    }

    @Override
    public void dealDecode(Result rawResult, Bitmap barcode, float scaleFactor) {
        Log.i(TAG, "dealDecode ~~~~~ " + rawResult.getText() + " " + barcode + " " + scaleFactor);
        playBeepSoundAndVibrate();
        Toast.makeText(this, rawResult.getText(), Toast.LENGTH_LONG).show();
//        對此次掃描結果不滿意可以調用
//        reScan();
    }
}

自定義AutoScannerView的實現

/**
 * Created by yangxixi on 16/11/22.
 *
 * 自動上下掃描
 */

public class AutoScannerView extends View {

    private static final String TAG = AutoScannerView.class.getSimpleName();

    private Paint maskPaint;
    private Paint linePaint;
    private Paint traAnglePaint;
    private Paint textPaint;
    private CameraManager cameraManager;

    private final int maskColor = Color.parseColor("#60000000");                          //蒙在攝像頭上面區域的半透明顏色
    private final int triAngleColor = Color.parseColor("#76EE00");                        //邊角的顏色
    private final int lineColor = Color.parseColor("#FF0000");                            //中間線的顏色
    private final int textColor = Color.parseColor("#CCCCCC");                            //文字的顏色
    private final int triAngleLength = dp2px(20);                                         //每個角的點距離
    private final int triAngleWidth = dp2px(4);                                           //每個角的點寬度
    private final int textMarinTop = dp2px(30);                                           //文字距離識別框的距離
    private int lineOffsetCount = 0;

    public AutoScannerView(Context context, AttributeSet attrs) {
        super(context, attrs);
        maskPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        maskPaint.setColor(maskColor);

        traAnglePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        traAnglePaint.setColor(triAngleColor);
        traAnglePaint.setStrokeWidth(triAngleWidth);
        traAnglePaint.setStyle(Paint.Style.STROKE);

        linePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        linePaint.setColor(lineColor);

        textPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        textPaint.setColor(textColor);
        textPaint.setTextSize(dp2px(14));
    }

    public void setCameraManager(CameraManager cameraManager) {
        this.cameraManager = cameraManager;
        invalidate();//重新進入可能不刷新,所以調用一次。
    }

    @Override
    protected void onDraw(Canvas canvas) {
        if (cameraManager == null)
            return;
        Rect frame = cameraManager.getFramingRect();
        Rect previewFrame = cameraManager.getFramingRectInPreview();
        if (frame == null || previewFrame == null) {
            return;
        }

        int width = canvas.getWidth();
        int height = canvas.getHeight();

        // 除了中間的識別區域,其他區域都將蒙上一層半透明的圖層
        canvas.drawRect(0, 0, width, frame.top, maskPaint);
        canvas.drawRect(0, frame.top, frame.left, frame.bottom + 1, maskPaint);
        canvas.drawRect(frame.right + 1, frame.top, width, frame.bottom + 1, maskPaint);
        canvas.drawRect(0, frame.bottom + 1, width, height, maskPaint);

        String text = "將二維碼放入框內,即可自動掃描";
        canvas.drawText(text, (width - textPaint.measureText(text)) / 2, frame.bottom + textMarinTop, textPaint);

        // 四個角落的三角
        Path leftTopPath = new Path();
        leftTopPath.moveTo(frame.left + triAngleLength, frame.top + triAngleWidth / 2);
        leftTopPath.lineTo(frame.left + triAngleWidth / 2, frame.top + triAngleWidth / 2);
        leftTopPath.lineTo(frame.left + triAngleWidth / 2, frame.top + triAngleLength);
        canvas.drawPath(leftTopPath, traAnglePaint);

        Path rightTopPath = new Path();
        rightTopPath.moveTo(frame.right - triAngleLength, frame.top + triAngleWidth / 2);
        rightTopPath.lineTo(frame.right - triAngleWidth / 2, frame.top + triAngleWidth / 2);
        rightTopPath.lineTo(frame.right - triAngleWidth / 2, frame.top + triAngleLength);
        canvas.drawPath(rightTopPath, traAnglePaint);

        Path leftBottomPath = new Path();
        leftBottomPath.moveTo(frame.left + triAngleWidth / 2, frame.bottom - triAngleLength);
        leftBottomPath.lineTo(frame.left + triAngleWidth / 2, frame.bottom - triAngleWidth / 2);
        leftBottomPath.lineTo(frame.left + triAngleLength, frame.bottom - triAngleWidth / 2);
        canvas.drawPath(leftBottomPath, traAnglePaint);

        Path rightBottomPath = new Path();
        rightBottomPath.moveTo(frame.right - triAngleLength, frame.bottom - triAngleWidth / 2);
        rightBottomPath.lineTo(frame.right - triAngleWidth / 2, frame.bottom - triAngleWidth / 2);
        rightBottomPath.lineTo(frame.right - triAngleWidth / 2, frame.bottom - triAngleLength);
        canvas.drawPath(rightBottomPath, traAnglePaint);

        //循環劃線,從上到下
        if (lineOffsetCount > frame.bottom - frame.top - dp2px(10)) {
            lineOffsetCount = 0;
        } else {
            lineOffsetCount = lineOffsetCount + 6;
//            canvas.drawLine(frame.left, frame.top + lineOffsetCount, frame.right, frame.top + lineOffsetCount, linePaint);    //畫一條紅色的線
            Rect lineRect = new Rect();
            lineRect.left = frame.left;
            lineRect.top = frame.top + lineOffsetCount;
            lineRect.right = frame.right;
            lineRect.bottom = frame.top + dp2px(10) + lineOffsetCount;
            canvas.drawBitmap(((BitmapDrawable)(getResources().getDrawable(R.drawable.scanline))).getBitmap(), null, lineRect, linePaint);
        }
        postInvalidateDelayed(10L, frame.left, frame.top, frame.right, frame.bottom);
    }

    private int dp2px(int dp) {
        float density = getContext().getResources().getDisplayMetrics().density;
        return (int) (dp * density + 0.5f);
    }
}

集成之后,基本就可以,如果需要設置橫屏豎屏直接設置screenOrientation就好了,里面的效果適配效果都已經實現;直接在dealDecode中處理掃描之后的結果,playBeepSoundAndVibrate可以設置響鈴,振動或者同時都可以。想設置微信識別框的參數可以再AutoScannerView中修改,這里就不精細去實現噠

結語#

到這里,大致流程就介紹完了。有什么不足的,歡迎指出。
歡迎在下方評論和喜歡,謝謝,轉載請說明出處。

具體示例,請跳轉Github查看,地址:https://github.com/yangxixi88/ZxingLite 歡迎StarStarStar啊~~~

讀完思考#

自定義AutoScannerView是勻速從上到下,而細細觀察微信的效果,是兩頭慢,中間快,如果所以實現微信那種效果,需要從哪些方面改進呢,歡迎討論~~

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容