Android二維碼開發(fā)(二)優(yōu)化

[TOC]

前言

上一篇,掃碼的基本功能已經(jīng)實現(xiàn),不過還存在一些問題

  • 掃碼界面不是我們常見的二維碼掃描界面
  • 方法調(diào)用過于繁瑣,有大量的重復(fù)代碼

本篇博文將介紹如何對第三方庫進(jìn)行二次開發(fā),自定義我們想要的掃碼界面以及對方法進(jìn)行封裝方便調(diào)用

自定義掃碼界面

獲取第三方庫源碼

  1. 下載源碼

因為本篇是基于 zxing-android-embedded進(jìn)行開發(fā),因此首先獲取該庫的源碼,打開該庫的github地址,最新版本是3.5.0,,將項目clone或者download下來,下載后的項目結(jié)構(gòu)入下圖所示:

tag.png

其中紅框所示的zxing-android-embedded庫就是我們正在使用的庫,其目錄結(jié)構(gòu)如下所示

tag2.png
  1. 導(dǎo)入源碼到項目中:

這個庫的結(jié)構(gòu)和常見的android studio創(chuàng)建的model不太一樣,為了方便開發(fā),我們選擇重新創(chuàng)建一個android-library的model并將該庫的代碼導(dǎo)入。

第一步:

重新創(chuàng)建一個android-library的model,這里取名為zxingcore

tag3.png

第二步:

zxing-android-embedded目錄下的src里面的所有java代碼拷貝到zxingcore中java目錄下;將res和res-orig兩個目錄下的文件全部拷貝到zxingcore的res目錄下

tag4.png

將AndroidManifest.xml里面的內(nèi)容拷貝到zxingcore中的AndroidManifest.xml。這里主要是權(quán)限的申請和掃碼界面activity的注冊,對于android 6.0及以上版本,需要再實際項目中進(jìn)行動態(tài)權(quán)限的申請。

tag5.png

因為這個庫需要引用到谷歌zxing庫中的core代碼,所以需要再build.gradle中加入對該庫的依賴

 compile 'com.google.zxing:core:3.2.1'

到此這個第三方庫的導(dǎo)入已經(jīng)完成,我們上一篇sample中通過gradle依賴zxing-android-embedded可以更改為依賴我們自己導(dǎo)入的zxingcore

// compile 'com.journeyapps:zxing-android-embedded:3.4.0'
// compile 'com.google.zxing:core:3.2.1'

  compile project(':zxingcore')

自定義掃碼界面

在上一篇中我們通過以下代碼開啟了掃碼界面

 new IntentIntegrator(this)
                .setOrientationLocked(false)
                .setDesiredBarcodeFormats(IntentIntegrator.ALL_CODE_TYPES)
                .setPrompt("將二維碼/條碼放入框內(nèi),即可自動掃描")
                .initiateScan(); // 初始化掃描

這個開啟的界面就是CaptureActivity這個activity,這當(dāng)然不是我們想要的界面,需要調(diào)整的地方有三個

  • 方向調(diào)整,默認(rèn)是橫向的
  • 掃碼框大小調(diào)整
  • 掃碼框樣式調(diào)整
  1. 調(diào)整方向

調(diào)整方向很簡單,把manifest中activity聲明的下面代碼去掉就好

 android:screenOrientation="sensorLandscape"http://去掉這段代碼
  1. 掃碼框大小調(diào)整

打開activity布局文件的zxing_capture.xml,代碼很簡單,只有一個DecoratedBarcodeView

<merge xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <!--
    This Activity is typically full-screen. Therefore we can safely use centerCrop scaling with
    a SurfaceView, without fear of weird artifacts. -->
    <com.journeyapps.barcodescanner.DecoratedBarcodeView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/zxing_barcode_scanner"
        app:zxing_preview_scaling_strategy="centerCrop"
        app:zxing_use_texture_view="false"/>

</merge>

在DecoratedBarcodeView中加入兩行代碼 大小可以自行設(shè)置

 app:zxing_framing_rect_height="150dp"http://掃碼框高
 app:zxing_framing_rect_width="200dp"http://掃碼框?qū)?

重新build項目并運行我們的sample可以看到界面已經(jīng)好看很多了,掃碼框也變成了正常的大小

tag6.png

優(yōu)化掃碼框

掃碼界面雖然變了,不過這還不是我們想要的,先看一下微信的掃碼框

weixin.png

可以看到,正常掃碼框都有一個滑動的條條和四個邊框,接下來就來添加這部分東西。

從上面的步驟可以看出整個掃碼界面其實就是一個DecoratedBarcodeView,打開DecoratedBarcodeView的代碼可以看到它引用的布局就是layout文件中的zxing_barcode_scanner.xml,打開該布局文件

<merge xmlns:android="http://schemas.android.com/apk/res/android">
    
    //封裝了攝像頭的一個類,用來獲取拍攝畫面
    <com.journeyapps.barcodescanner.BarcodeView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/zxing_barcode_surface"/>
    
    // 類似遮罩的作用覆蓋在BarcodeView上
    <com.journeyapps.barcodescanner.ViewfinderView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/zxing_viewfinder_view"/>
    
    //提示語
    <TextView android:id="@+id/zxing_status_view"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:layout_gravity="bottom|center_horizontal"
              android:background="@color/zxing_transparent"
              android:text="@string/zxing_msg_default_status"
              android:textColor="@color/zxing_status_text"/>
</merge>

三個view我都做了注釋,想要改變看到的掃碼界面和掃碼框,需要重寫的就是ViewfinderView這個類

首先明確一下我們需要加入的東西

  • 取消原有紅色條,加入滑動的掃碼條
  • 四個邊框
  • 提示文字移動到掃碼框下方

首先定義需要的屬性和方法,在修改源碼的時候我習(xí)慣將自己寫的代碼都放在最后面,并且用一條分割線分割,

  1. 定義屬性
 /*-----------------------自定義方法和屬性--------------------------*/
    //畫邊框相關(guān)屬性
    private Paint mLinePaint;//邊框畫筆
    private final int mLineColor = Color.BLUE;//邊框的顏色

    //滑動條相關(guān)屬性
    private Bitmap mLineBm;//滑動條圖片
    private RectF mLineReact;//滑動條區(qū)域
    private final int mStepSize = 12;//滑動條每次滑動的速度
    private final int mLineHeight = 30;//滑動條的高度
    private boolean isBottom = false;//滑動條是否滑動到掃碼框底部

    //文字相關(guān)屬性
    private Paint mTextPaint;//畫提示語的畫筆
    private String mPromptText;//掃碼的提示語
    private int mTextMargin;//提示語距離掃描框的大小
  1. 定義方法

有了屬性自然需要有初始化的方法和操作的邏輯代碼 創(chuàng)建2個新方法,初始化一般放在構(gòu)造方法中,操作的代碼是在onDraw中調(diào)用的

 /**
     * 改方法在構(gòu)造方法中調(diào)用用來初始化屬性
     *
     * @param context
     */
    private void customInit(Context context) {
        //初始化滑動線的畫筆
        mLinePaint = new Paint();
        mLinePaint.setStyle(Paint.Style.FILL);
        mLinePaint.setStrokeWidth(20);
        mLinePaint.setColor(mLineColor);
        //初始化滑動條
        mLineBm = BitmapFactory.decodeResource(getResources(), R.drawable.lan);
        //初始化提示語的畫筆
        mTextPaint = new Paint();
        mTextPaint.setColor(Color.WHITE);
        mTextPaint.setTextSize(sp2px(14));
        mTextPaint.setTextAlign(Paint.Align.CENTER);
        mTextMargin = sp2px(20);
    }



    /**
     * 該方法在onDraw中調(diào)用,放在  
     * Rect frame = framingRect;
     * Rect previewFrame = previewFramingRect;
     * 兩段代碼之后
     *
     * @param frame
     * @param canvas
     */
    private void customDraw(Rect frame, Canvas canvas) {
        drawSlipLine(frame, canvas);//畫滑動的線
        drawEdge(frame, canvas);//畫邊框
        drawPromptText(frame, canvas);//畫提示語
    }



  1. 畫滑動的線 drawSlipLine(frame, canvas)

首先要去掉原先的紅色線,注釋掉onDraw()方法中下圖所示代碼 ,還可以注釋掉下面那一部分畫跳動小點的代碼,取消跳動小點,看個人需求?;瑒拥木€一般是一張圖片,這類我準(zhǔn)備了一張圖放在drawable目錄下 lan.png 圖片可以自行替換

tag8.png

實現(xiàn)drawSlipLine()方法

/**
     * 畫移動的短線
     *
     * @param frame
     * @param canvas
     */
    private void drawSlipLine(Rect frame, Canvas canvas) {
       if (mLineReact == null) {
            mLineReact = new RectF(frame.left + 5, frame.top, frame.right - 5, frame.top + mLineHeight);
        }

        if (isBottom) {
            mLineReact.set(frame.left + 5, frame.top, frame.right - 5, frame.top + mLineHeight);
        }
        mLineReact.offset(0, mStepSize);
        canvas.drawBitmap(mLineBm, null, mLineReact, null);

        isBottom = mLineReact.bottom + mStepSize > frame.bottom;
    }

效果

tag9.png
  1. 畫邊框 drawEdge(frame, canvas)

邊框的顏色,在邊界內(nèi)還是邊界外可以自行調(diào)整位置,這里面?zhèn)魅氲膄ram是掃描框所在的長方形Rect

 /**
     * 畫框邊的四個角
     *
     * @param frame
     * @param canvas
     */
    private void drawEdge(Rect frame, Canvas canvas) {
        canvas.drawRect(frame.left - 10, frame.top, frame.left, frame.top + 50, mLinePaint);
        canvas.drawRect(frame.left - 10, frame.top - 10, frame.left + 50, frame.top, mLinePaint);
        canvas.drawRect(frame.right - 50, frame.top - 10, frame.right + 10, frame.top, mLinePaint);
        canvas.drawRect(frame.right, frame.top, frame.right + 10, frame.top + 50, mLinePaint);

        canvas.drawRect(frame.left - 10, frame.bottom - 50, frame.left, frame.bottom, mLinePaint);
        canvas.drawRect(frame.left - 10, frame.bottom, frame.left + 50, frame.bottom + 10, mLinePaint);
        canvas.drawRect(frame.right - 50, frame.bottom, frame.right, frame.bottom + 10, mLinePaint);
        canvas.drawRect(frame.right, frame.bottom - 50, frame.right + 10, frame.bottom + 10, mLinePaint);

    }

效果

tag10.png
  1. 畫提示語

從上面DecoratedBarcodeView的布局可以看出,提示語是一個textview,位置是bottom,這不符合我們的需要,通過調(diào)整textview距離底邊框的margin也可以調(diào)整位置,不過我這邊采用的是在ViewfinderView遮罩層畫這個提示語,取消textview

第一步,注釋掉zxing_barcode_scanner.xml里面的textview

<merge xmlns:android="http://schemas.android.com/apk/res/android">

    <com.journeyapps.barcodescanner.BarcodeView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/zxing_barcode_surface"/>

    <com.journeyapps.barcodescanner.ViewfinderView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/zxing_viewfinder_view"/>

    <!--<TextView android:id="@+id/zxing_status_view"-->
              <!--android:layout_width="wrap_content"-->
              <!--android:layout_height="wrap_content"-->
              <!--android:layout_gravity="bottom|center_horizontal"-->
              <!--android:background="@color/zxing_transparent"-->
              <!--android:text="@string/zxing_msg_default_status"-->
              <!--android:textColor="@color/zxing_status_text"/>-->
</merge>

第二步,需要將提示語傳遞到ViewfinderView內(nèi)部去,還記得提示語是在哪里設(shè)置的嗎?重新看一下我們打開掃碼頁面的代碼,

  new IntentIntegrator(this)
                .setOrientationLocked(false)
                .setDesiredBarcodeFormats(IntentIntegrator.ALL_CODE_TYPES)
                .setPrompt("將二維碼/條碼放入框內(nèi),即可自動掃描")
                .initiateScan(); // 初始化掃描

通過setPrompt()方法傳遞,跟蹤這個方法可以發(fā)現(xiàn)所有設(shè)置的信息最后都會放到一個intent并傳遞給CaptureActivity,重新打開CaptureActivity,在onCreate()中將設(shè)置的參數(shù)傳遞給一個CaptureManager

  @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        barcodeScannerView = initializeContent();
        //初始化配置掃碼界面
        capture = new CaptureManager(this, barcodeScannerView);
        //intent中攜帶了通過IntentIntegrator設(shè)置的參數(shù)
        capture.initializeFromIntent(getIntent(), savedInstanceState);
        capture.decode();
    }

CaptureManager內(nèi)部再次把intent傳遞給DecoratedBarcodeView的initializeFromIntent(Intent intent)方法,看這個方法中有這么一句

  String customPromptMessage = intent.getStringExtra(Intents.Scan.PROMPT_MESSAGE);
        if (customPromptMessage != null) {
            setStatusText(customPromptMessage);
        }
    /**
    
 /**
     * 設(shè)置提示語
     * @param text
     */
    public void setStatusText(String text) {
        // statusView is optional when using a custom layout
        if(statusView != null) {
            statusView.setText(text);
        }
    }        
        

可以看出在這個方法里面將提示語設(shè)置給了statusView(即我們上面注釋掉的textview)

理清了這個再進(jìn)行修改就容易了

  • 首先去掉DecoratedBarcodeView中statusView變量和與它相關(guān)的代碼,注釋掉就好了。
  • 在ViewfinderView中添加一個方法,mPromptText屬性的set()方法,這個屬性我們一開始已經(jīng)定義過了。在DecoratedBarcodeView的setStatusText中調(diào)用
  • 完善之前在ViewfinderView中定義的drawPromptText(frame, canvas)方法將文字畫到掃描界面
 /**
     * 傳入提示語
     *
     * @param text
     */
    public void setPromptText(String text) {
        this.mPromptText = text;
    }

修改DecoratedBarcodeView的setStatusText()方法

   public void setStatusText(String text) {
        // statusView is optional when using a custom layout
//        if(statusView != null) {
//            statusView.setText(text);
//        }
        //viewFinder就是DecoratedBarcodeView持有的ViewfinderView
        viewFinder.setPromptText(text);
    }

這樣我們已經(jīng)把提示語傳遞到了ViewfinderView中,接下來就是將它畫到掃描框的下方,具體想要畫的位置可以自行調(diào)整。

實現(xiàn)drawPromptText(frame, canvas)的代碼, mTextPaint的初始化代碼customInit()中,文字樣式可以自行調(diào)整

 /**
     * 畫提示語
     *
     * @param frame
     * @param canvas
     */
    private void drawPromptText(Rect frame, Canvas canvas) {
        int startX = frame.left + frame.width() / 2;
        int startY = frame.bottom + mTextMargin;
        if (!TextUtils.isEmpty(mPromptText)) {
            canvas.drawText(mPromptText, startX, startY, mTextPaint);
        }
    }

效果

tag11.png

掃碼框位置調(diào)整

默認(rèn)掃碼框是在整個屏幕的中間,如果想要調(diào)整掃碼框的位置,比如上移或者下一的話,找到CameraPreview中的calculateFramingRect方法,添加如下代碼 intersection.offset(0,-150); 具體調(diào)整根據(jù)需求而定。

 protected Rect calculateFramingRect(Rect container, Rect surface) {
        // intersection is the part of the container that is used for the preview
        Rect intersection = new Rect(container);
        boolean intersects = intersection.intersect(surface);

        if(framingRectSize != null) {
            // Specific size is specified. Make sure it's not larger than the container or surface.
            int horizontalMargin = Math.max(0, (intersection.width() - framingRectSize.width) / 2);
            int verticalMargin = Math.max(0, (intersection.height() - framingRectSize.height) / 2);
            intersection.inset(horizontalMargin, verticalMargin);
            
            /**將默認(rèn)的掃碼框位置上調(diào)150px*/
            intersection.offset(0,-150);
            
            return intersection;
        }
        // margin as 10% (default) of the smaller of width, height
        int margin = (int)Math.min(intersection.width() * marginFraction, intersection.height() * marginFraction);
        intersection.inset(margin, margin);
        if (intersection.height() > intersection.width()) {
            // We don't want a frame that is taller than wide.
            intersection.inset(0, (intersection.height() - intersection.width()) / 2);
        }
        return intersection;
    }

總結(jié)

通過對源碼的二次開發(fā),優(yōu)化了掃碼界面,初步實現(xiàn)了常見的掃碼框效果,下一篇將介紹對API方法的封裝。如有不對的地方還請指正,感謝。

demo下載

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,351評論 25 708
  • 1、通過CocoaPods安裝項目名稱項目信息 AFNetworking網(wǎng)絡(luò)請求組件 FMDB本地數(shù)據(jù)庫組件 SD...
    陽明AI閱讀 16,236評論 3 119
  • 一滴陽光閱讀 706評論 0 0
  • 朋友就是已經(jīng)很了解你過去滄桑不堪的經(jīng)歷,也知道你滿身是處的缺點,更清楚你艱難險阻的未來。但依然沒有離你遠(yuǎn)去,沒有棄...
    我天下無敵閱讀 396評論 0 1
  • 好久不見,你還好嗎我親愛的那個陌生人時間繁如沙,只是我傻半個年輪獨自一人描摹傷疤你換朋友我沒有說話你我本就是陌生人...
    Mr橘子閱讀 247評論 1 4

友情鏈接更多精彩內(nèi)容