Android进阶 - 二维码扫描

作者: 梦想编织者灬小楠 | 来源:发表于2017-05-22 15:10 被阅读5415次
scan_bg.jpg

摘要

最近,在公司项目上需要加入“二维码扫描”的功能(Android端),笔者在网上查阅了一些资料,实现了这个功能。最后给自己做个笔记,给各位做下分享。

原理说明

“二维码扫描”实际上就是通过手机相机扫描『二维码图片』,将『二维码图片』中的字符串数据通过解码的方式解析出来。

实现方式

借助开源库 ZXing Android Embedded 实现二维码扫描。

Github地址: https://github.com/journeyapps/zxing-android-embedded

接下来,笔者分两部分进行讲解:

  • 第1部分:ZXing Android Embedded简介及使用方法。

  • 第2部分:自定义扫描界面。


一、ZXing Android Embedded简介及使用方法

1.简介

ZXing Android Embedded 是用于Android的条形码扫描库,使用ZXing进行解码。

注:二维码是条形码中的一种,该库也可以扫描二维码。

2.引入方法

添加gradle库依赖:

dependencies {
    ......
    compile 'com.journeyapps:zxing-android-embedded:3.5.0'
}

注意事项:

  • 该库在需要时会自动引入ZXing库,无需额外手动引入。
  • buildToolsVersion '23.0.2'(构建工具的版本要>=23.0.2)
  • compile 'com.android.support:appcompat-v7:23.1.0' (support-v7包版本要在23+以上)
  • 最低支持的Android版本(API level 9+)

想要了解更多详情,可打开Github链接研究学习。

3.使用方法

接下来,笔者用一个实例来介绍一下该库的使用方法。

1.新建一个Android工程。
2.添加gradle库依赖,引入ZXing Android Embedded库。
gradle_setting.png
3.在MainActivity的布局文件中放置一个Button(用于打开二维码扫描界面)。
activity_main.png
4.在MainActivity中为Button设置点击事件,点击后跳转至扫描界面。
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        findViewById(R.id.btn).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 创建IntentIntegrator对象
                IntentIntegrator intentIntegrator = new IntentIntegrator(MainActivity.this);
                // 开始扫描
                intentIntegrator.initiateScan();
            }
        });
    }
}
5.重写onActivityResult方法接收扫描结果。
public class MainActivity extends AppCompatActivity {

    ......

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        // 获取解析结果
        IntentResult result = IntentIntegrator.parseActivityResult(requestCode, resultCode, data);
        if (result != null) {
            if (result.getContents() == null) {
                Toast.makeText(this, "取消扫描", Toast.LENGTH_LONG).show();
            } else {
                Toast.makeText(this, "扫描内容:" + result.getContents(), Toast.LENGTH_LONG).show();
            }
        } else {
            super.onActivityResult(requestCode, resultCode, data);
        }
    }
}

完成此步,基本的二维码扫描功能就已经出来了。

接下来,我们可以准备二维码图片试验一下。如果没有二维码图片,可以用草料二维码生成器在线生成一个二维码使用(如下图所示)。

caoliao_qrcode.png
6.跑一下Android程序,扫描一下二维码。(如下图所示)
qrcode_scan1.gif

我们看到扫描成功了,最后Toast出了http://www.baidu.com这个信息。

但这个扫描过程怎么感觉天旋地转的,一点也不流畅?.../(ㄒoㄒ)/~~

这是由于ZXing Android Embedded库提供的扫码Activity默认是横屏的。

不过,扫描界面的方向是可调的,Github文档也有说明,举个例子。

固定竖屏(仅需在manifest文件中添加如下配置)

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="com.example.wangnan7.qrcodescandemo">

    <application
        
        ......

        <!-- 调整二维码扫描界面为竖屏 -->
        <activity
            android:name="com.journeyapps.barcodescanner.CaptureActivity"
            android:screenOrientation="portrait"
            tools:replace="screenOrientation" />

    </application>

</manifest>
        

重新跑下程序,如下所示:

qrcode_scan2.gif
7.其他配置项

在上述实例中,我们用两行代码(如下所示)实现了启动二维码扫描界面。

IntentIntegrator intentIntegrator = new IntentIntegrator(MainActivity.this);
intentIntegrator.initiateScan();

基本上没有添加什么配置。但是,该库还提供了其他配置项(如下所示)。

other_config.png

接下来,笔者详解一下这8个配置项。


1. setBarcodeImageEnabled(boolean enabled)

该方法用于设置“被扫描的二维码图片”可以保存在本地。

other_config1.png

举个例子说明一下:

接着之前的例子,我们在布局文件中添加一个ImageView(用于显示二维码图片):

other_config2.png

MainActivity修改后的代码如下:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        findViewById(R.id.btn).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                IntentIntegrator intentIntegrator = new IntentIntegrator(MainActivity.this);
                // 设置可以保存条形码(二维码)图片
                intentIntegrator.setBarcodeImageEnabled(true);
                intentIntegrator.initiateScan();
            }
        });
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        // 获取解析结果
        IntentResult result = IntentIntegrator.parseActivityResult(requestCode, resultCode, data);
        if (result != null) {
            if (result.getBarcodeImagePath() != null) {
                // 显示条形码(二维码)图片的保存路径
                Toast.makeText(this, result.getBarcodeImagePath(), Toast.LENGTH_LONG).show();
                // 显示条形码(二维码)图片
                showBarcodeImage(result.getBarcodeImagePath());
            }
        } else {
            super.onActivityResult(requestCode, resultCode, data);
        }
    }

    /**
     * 加载并显示条形码图片
     */
    private void showBarcodeImage(String barcodeImagePath) {
        FileInputStream fis = null;
        try {
            fis = new FileInputStream(new File(barcodeImagePath));
            ((ImageView)findViewById(R.id.iv)).setImageBitmap(BitmapFactory.decodeStream(fis));
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } finally {
            try {
                fis.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

跑下程序,如下图所示:

other_config3.gif

可以看到,笔者Toast出了二维码图片被保存后的路径信息,并根据文件保存路径将二维码图片显示了出来。

所以,如果添加这个配置:

intentIntegrator.setBarcodeImageEnabled(true);

扫描后的二维码图片会被保存;如果不添加这个配置或参数设置为false,二维码图片不会被保存,我们拿到的路径result.getBarcodeImagePath()就会变成null。


2. setCaptureActivity(Class<?> captureActivity)

该方法用于设置扫描Activity。如果你不想用该库提供的扫描Activity,可以自定义一个扫描Activity,将该Acitivty的运行时类作为参数传进去,这个方法后续用到时再详细说明。


3. setBeepEnabled(boolean enabled)

该方法用于设置扫码成功后的提示音,传true为开启,不设置或设置false为关闭。


4. setCameraId(int cameraId)

该方法用于设置相机ID。我们使用的手机一般都有前置和后置摄像头,该方法传0将会使用后置摄像头,传1将会使用前置摄像头。不设置则默认使用后置摄像头。

现在有些手机后置双摄像头,相机ID可能有所变化,有兴趣的朋友请自行研究。


5. setDesiredBarcodeFormats(Collection<String> desiredBarcodeFormats)

该方法用于设置你期望的条形码格式。(该库提供了5种格式,如下所示)

other_config4.png

注:不设置默认为全部类型

所以对于扫描二维码,你可以选择不设置,如果设置可以使用QR_CODE_TYPES和ALL_CODE_TYPES。但是,笔者建议设置QR_CODE_TYPES,即:

intentIntegrator.setDesiredBarcodeFormats(IntentIntegrator.QR_CODE_TYPES);

因为不设置或设置支持全部类型,会附带扫描其他条形码的功能,笔者认为实际功能应与描述功能相一致。


6. setOrientationLocked(boolean locked)

该方法用于设置方向锁。(源码解释如下:)

other_config5.png

这个功能是用来调整扫描界面方向的,可以配合传感器使用,举个例子。

修改一下之前的manifest文件,如下所示:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="com.example.wangnan7.qrcodescandemo">

    <application
        
        ......

        <!-- 调整二维码扫描界面方向为"完全依赖传感器" -->
        <activity
            android:name="com.journeyapps.barcodescanner.CaptureActivity"
            android:screenOrientation="fullSensor"
            tools:replace="screenOrientation" />

    </application>

</manifest>

在MainActivity中添加方向锁设置,如下所示:

other_config6.png

运行一下程序,如下所示:

other_config7.gif

可以看到调整手机方向时,扫描布局也会重新布置,最后笔者按Back返回键取消了扫描。


7. setPrompt(String prompt)

该方法用于设置扫描界面的提示信息。

举个例子,笔者设置一条提示信息(如下图所示)

other_config8.png

运行一下程序,可以看到扫描界面的“提示文字”(如下图所示)

other_config9.png
8. setTimeout(long timeout)

该方法用于设置扫描界面的超时时间。(避免用户打开扫描页面,忘记关闭)

举个例子,笔者设置一个2秒的超时时间(如下图所示)

other_config10.png

运行一下程序,如下图所示:

other_config11.gif

可以看到,2秒后,扫描自动取消了。

ZXing Android Embedded的基本使用方法介绍完了。想了解更多用法的朋友可以通过GitHub链接或查看源码的方式学习。

二、自定义扫描界面

各位可能发现 ZXing Android Embedded库 提供的默认的扫描界面有些简陋(或丑陋),满足不了产品和设计的需求,举个例子:

产品想要下图这种效果,该怎么办呢?

target_effect.png

这时就需要我们自定义扫描界面了...

自定义策略:比着葫芦画瓢

由于源码中的类在AndroidStudio中默认是被加锁的,我们无权直接修改。但我们可以仿写其中的一些类,方便我们添加自己的逻辑。自定义起点可以从Activity开始

1.自定义扫描Activity

在源码中可以查到,我们之前一直在使用一个CaptureActivity进行二维码扫描(如下所示):

capture_activity.png

接下来,我们可以仿照CaptureActivity写一个自己的Activity(直接Copy也可以)。

笔者仿写的代码如下:

/**
 * @Class: CustomCaptureActivity
 * @Description: 自定义条形码/二维码扫描
 * @Author: wangnan7
 * @Date: 2017/5/19
 */

public class CustomCaptureActivity extends AppCompatActivity {

    /**
     * 条形码扫描管理器
     */
    private CaptureManager mCaptureManager;

    /**
     * 条形码扫描视图
     */
    private DecoratedBarcodeView mBarcodeView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        
        setContentView(com.google.zxing.client.android.R.layout.zxing_capture);
        mBarcodeView = (DecoratedBarcodeView)findViewById(com.google.zxing.client.android.R.id.zxing_barcode_scanner);

        mCaptureManager = new CaptureManager(this, mBarcodeView);
        mCaptureManager.initializeFromIntent(getIntent(), savedInstanceState);
        mCaptureManager.decode();
    }

    @Override
    protected void onResume() {
        super.onResume();
        mCaptureManager.onResume();
    }

    @Override
    protected void onPause() {
        super.onPause();
        mCaptureManager.onPause();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mCaptureManager.onDestroy();
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        mCaptureManager.onSaveInstanceState(outState);
    }

    /**
     * 权限处理
     */
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[], @NonNull int[] grantResults) {
        mCaptureManager.onRequestPermissionsResult(requestCode, permissions, grantResults);
    }

    /**
     * 按键处理
     */
    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        return mBarcodeView.onKeyDown(keyCode, event) || super.onKeyDown(keyCode, event);
    }
}

注:XML布局还是使用的源码中CaptureActivity的布局。

紧接着,我们可以在manifest文件中声明一下这个新创建的Activity。

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.wangnan7.qrcodescandemo">

    <application
        
        .......

        <!-- 设置二维码扫描界面方向为竖屏 -->
        <activity
            android:name=".CustomCaptureActivity"
            android:label="自定义扫描界面"
            android:screenOrientation="portrait"/>

    </application>

</manifest>

最后,我们就可以在MainActivity中调用这个新的扫描Activity了。

start_custom_capture.png

运行程序,效果如下:

custom_activity_success.gif

可以看到我们自定义的扫描Activity可以正常运行,扫码也成功了。

但是,我们自定义Activty使用的布局还是源码中的布局文件,对于这个布局文件我们没有权限修改,接下来就需要自定义扫描布局了。

2.自定义扫描布局

源码布局如下:

zxing_layout.png

笔者仿写的自定义扫描布局 (activity_zxing_layout.xml):

activity_zxing_layout.png

属性简介:
app:zxing_preview_scaling_strategy : 预览视图的缩放策略,使用centerCrop即可
app:zxing_use_texture_view : 是否使用纹理视图(黑色背景)

接下来,我们就可以把自定义扫描Activity的布局文件给替换掉了。

/**
 * @Class: CustomCaptureActivity
 * @Description: 自定义条形码/二维码扫描
 * @Author: wangnan7
 * @Date: 2017/5/19
 */

public class CustomCaptureActivity extends AppCompatActivity {

    ......

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

        setContentView(R.layout.activity_zxing_layout);
        mBarcodeView = (DecoratedBarcodeView)findViewById(R.id.zxing_barcode_scanner);

        ......
    }
    
    ......
}

最后,我们跑程序验证一下:

use_texture_view.gif

可以看到我们的自定义布局文件也没有问题。

我们的自定义Activity和自定义布局文件都完成了,剩下的就是修改扫描视图的样式了。

3.修改扫描视图的样式

想要修改扫描视图的样式,需要略微研究下DecoratedBarcodeView的源码。

1.DecoratedBarcodeView初始化分析

source_code1.png

补充:可以看到 scannerLayout 最后被作为扫描布局inflate进了DecorateBarcodeView中。

2.默认布局R.layout.zxing_barcode_scanner分析

source_code2.png

分析到这里,我们需要做的工作就显现出来了。那就是:

自定义View(继承ViewfinderView),重写onDraw方法,然后替换掉这里的ViewfinderView。

因为R.layout.zxing_barcode_scanner是源码中的布局文件,无法直接修改,所以还要重写一份布局文件给DecoratedBarcodeView加载。那么,接下来需要做两步准备工作:

(1)仿写默认布局文件R.layout.zxing_barcode_scanner

custom_barcode_scanner.png

(2)让DecoratedBarcodeView加载刚刚仿写布局,不再使用默认布局。

load_custom_scanner.png

3.开始自定义扫描视图(继承ViewfinderView重写onDraw方法)

小技巧:如果不知道如何开始,可以先将原ViewfinderView的onDraw方法copy进来一点一点研究修改。

笔者直接将自己的自定义扫描布局粘贴出来,需要的朋友可以借鉴或Copy:

/**
 * @Class: CustomViewfinderView
 * @Description: 自定义扫描框样式
 * @Author: wangnan7
 * @Date: 2017/5/22
 */

public class CustomViewfinderView extends ViewfinderView {

    /**
     * 重绘时间间隔
     */
    public static final long CUSTOME_ANIMATION_DELAY = 16;

    /* ******************************************    边角线相关属性    ************************************************/

    /**
     * "边角线长度/扫描边框长度"的占比 (比例越大,线越长)
     */
    public float mLineRate = 0.1F;

    /**
     * 边角线厚度 (建议使用dp)
     */
    public float mLineDepth =  TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 4, getResources().getDisplayMetrics());

    /**
     * 边角线颜色
     */
    public int mLineColor = Color.WHITE;

    /* *******************************************    扫描线相关属性    ************************************************/

    /**
     * 扫描线起始位置
     */
    public int mScanLinePosition = 0;

    /**
     * 扫描线厚度
     */
    public float mScanLineDepth = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 4, getResources().getDisplayMetrics());

    /**
     * 扫描线每次重绘的移动距离
     */
    public float mScanLineDy = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 3, getResources().getDisplayMetrics());

    /**
     * 线性梯度
     */
    public LinearGradient mLinearGradient;

    /**
     * 线性梯度位置
     */
    public float[] mPositions = new float[]{0f, 0.5f, 1f};

    /**
     * 线性梯度各个位置对应的颜色值
     */
    public int[] mScanLineColor = new int[]{0x00FFFFFF, Color.WHITE, 0x00FFFFFF};


    public CustomViewfinderView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public void onDraw(Canvas canvas) {
        refreshSizes();
        if (framingRect == null || previewFramingRect == null) {
            return;
        }

        Rect frame = framingRect;
        Rect previewFrame = previewFramingRect;

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

        //绘制4个角
        paint.setColor(mLineColor); // 定义画笔的颜色
        canvas.drawRect(frame.left, frame.top, frame.left + frame.width() * mLineRate, frame.top + mLineDepth, paint);
        canvas.drawRect(frame.left, frame.top, frame.left + mLineDepth, frame.top + frame.height() * mLineRate, paint);

        canvas.drawRect(frame.right - frame.width() * mLineRate, frame.top, frame.right, frame.top + mLineDepth, paint);
        canvas.drawRect(frame.right - mLineDepth, frame.top, frame.right, frame.top + frame.height() * mLineRate, paint);

        canvas.drawRect(frame.left, frame.bottom - mLineDepth, frame.left + frame.width() * mLineRate, frame.bottom, paint);
        canvas.drawRect(frame.left, frame.bottom - frame.height() * mLineRate, frame.left + mLineDepth, frame.bottom, paint);

        canvas.drawRect(frame.right - frame.width() * mLineRate, frame.bottom - mLineDepth, frame.right, frame.bottom, paint);
        canvas.drawRect(frame.right - mLineDepth, frame.bottom - frame.height() * mLineRate, frame.right, frame.bottom, paint);

        // Draw the exterior (i.e. outside the framing rect) darkened
        paint.setColor(resultBitmap != null ? resultColor : maskColor);
        canvas.drawRect(0, 0, width, frame.top, paint);
        canvas.drawRect(0, frame.top, frame.left, frame.bottom + 1, paint);
        canvas.drawRect(frame.right + 1, frame.top, width, frame.bottom + 1, paint);
        canvas.drawRect(0, frame.bottom + 1, width, height, paint);

        if (resultBitmap != null) {
            // Draw the opaque result bitmap over the scanning rectangle
            paint.setAlpha(CURRENT_POINT_OPACITY);
            canvas.drawBitmap(resultBitmap, null, frame, paint);
        } else {
            // 绘制扫描线
            mScanLinePosition += mScanLineDy;
            if(mScanLinePosition > frame.height()){
                mScanLinePosition = 0;
            }
            mLinearGradient = new LinearGradient(frame.left, frame.top + mScanLinePosition, frame.right, frame.top + mScanLinePosition, mScanLineColor, mPositions, Shader.TileMode.CLAMP);
            paint.setShader(mLinearGradient);
            canvas.drawRect(frame.left, frame.top + mScanLinePosition, frame.right, frame.top + mScanLinePosition + mScanLineDepth, paint);
            paint.setShader(null);

            float scaleX = frame.width() / (float) previewFrame.width();
            float scaleY = frame.height() / (float) previewFrame.height();

            List<ResultPoint> currentPossible = possibleResultPoints;
            List<ResultPoint> currentLast = lastPossibleResultPoints;
            int frameLeft = frame.left;
            int frameTop = frame.top;
            if (currentPossible.isEmpty()) {
                lastPossibleResultPoints = null;
            } else {
                possibleResultPoints = new ArrayList<>(5);
                lastPossibleResultPoints = currentPossible;
                paint.setAlpha(CURRENT_POINT_OPACITY);
                paint.setColor(resultPointColor);
                for (ResultPoint point : currentPossible) {
                    canvas.drawCircle(frameLeft + (int) (point.getX() * scaleX),
                            frameTop + (int) (point.getY() * scaleY),
                            POINT_SIZE, paint);
                }
            }
            if (currentLast != null) {
                paint.setAlpha(CURRENT_POINT_OPACITY / 2);
                paint.setColor(resultPointColor);
                float radius = POINT_SIZE / 2.0f;
                for (ResultPoint point : currentLast) {
                    canvas.drawCircle(frameLeft + (int) (point.getX() * scaleX),
                            frameTop + (int) (point.getY() * scaleY),
                            radius, paint);
                }
            }
        }

        // Request another update at the animation interval, but only repaint the laser line,
        // not the entire viewfinder mask.
        postInvalidateDelayed(CUSTOME_ANIMATION_DELAY,
                frame.left,
                frame.top,
                frame.right,
                frame.bottom);
    }
}

代码简介:

(1)onDraw方法中的大部分代码Copy自ViewfinderView,笔者添加了两部分逻辑:第一部分是边角线的绘制;第二部分是用“扫描线”替换掉了原有的“激光线”。

(2)代码的核心是在onDraw方法的第5行代码:

Rect frame = framingRect;

这个矩阵记录了扫描框四个顶点的坐标,有了这个变量,各位可以发挥想象力自定义自己需要的扫描样式。

接下来,我们用CustomViewfinderView替换掉ViewfinderView(如下图所示)

custom_viewfinderview.png

最后,跑下程序(如下图所示)

custom_success.gif

4.样式调整(UI优化)

我们的自定义扫描界面搞定了,但UI样式还需要再优化一下:

(1) 框体大小调整 (DecoratedBarcodeView有属性支持修改)

zxing_frame_change.png

调整后的效果图:

zxing_frame_change2.png

(2) 将扫描界面底部文字平移至扫描框底部

zxing_frame_change3.png

调整后的效果图:

zxing_frame_change4.png

(3) 将扫描框向上平移

扫描框在默认情况下是相对于相机视图居中的,想要调整扫描框的位置还要去修改源码...

笔者想了一个投机取巧的办法:透明掉标题栏和状态栏让相机预览视图向上延伸,使扫描框在视觉上略微上移

这部分代码和二维码扫描没有直接关系,笔者就不贴代码了,各位可以尝试自己实现,但最后笔者会附上本Demo的GitHub链接。

最终的效果:

final_scan.gif

Demo的Github链接:

https://github.com/sinawangnan7/QRCodeScanDemo

相关文章

网友评论

  • f53bcbbc7c3e:感谢博主分享,写的太详细了
  • 4a681b789d57:膜拜大神
  • 010b9fd3d6ba:太感谢了,找了那么多就你写的详细清晰。
  • 寒涵:E/ExtMediaPlayer-JNI: env->IsInstanceOf fails
    E/MediaPlayer-JNI: JNIMediaPlayerFactory: bIsQCMediaPlayerPresent 0
    E/ExtMediaPlayer-JNI: env->IsInstanceOf fails
    E/MediaPlayer-JNI: JNIMediaPlayerFactory: bIsQCMediaPlayerPresent 0

    这个错作者有没有遇见过?
    梦想编织者灬小楠:没有,但应该不是ZXing Android Embedded这个库的原因...这个库没用到JNI...
  • 5ef947bf2bd1:使用前置摄像头的话,是镜像模式,无法识别二维码,怎么翻转预览界面坐标?
    梦想编织者灬小楠:@E_Sun伊森 这个还真没试过,你真用前置摄像头扫描二维码吗?:joy:
    5ef947bf2bd1:@E_Sun伊森 切换ID能够调用前置摄像头,但是镜像,无法识别二维码
    梦想编织者灬小楠:扫描二维码用前置摄像头吗,还是没有后置摄像头?换下相机ID试试...
  • 林里icer:很详细呢,受教了:smiley:
    林里icer:@梦想编织者灬小楠 谢谢解答:smile:
    梦想编织者灬小楠:@林里icer 手机录屏是用的AndroidStudio自带的工具,把视频转成gif用工具的是GifCam
    林里icer:对了,那个GIF是怎么录制的呀
  • 475f0be588e7:大神还在吗?我现在想移动扫描框的位置,一直找不到源码,求教一下。
    梦想编织者灬小楠:源码CameraPreview.java的385行代码:
    framingRect = calculateFramingRect(container, surfaceRect);
    这个方法计算了扫描框的位置。
    扫描框的位置是相对于预览视图居中的,不太好改,除非你自定义CameraPreview。

    有一个简单的方法,但可能无法达到你想要的效果,你可以通过修改(DecoratedBarcodeView)整体视图的大小和位置,调整扫描框位置,如下图所示:
    https://upload-images.jianshu.io/upload_images/2693519-72fa57a6be496b8f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240
  • a9d55bea5a63:大神大神,请问有没有遇到过华为mate9,mate10 莱卡双摄像头无法扫描二维码的问题?急
    a9d55bea5a63:@梦想编织者灬小楠 好的 多谢
    梦想编织者灬小楠:你可以改下相机ID试试~
    梦想编织者灬小楠:笔者没有用过后置双摄像头的手机,但文章中提到过这点:

    4. setCameraId(int cameraId)
    该方法用于设置相机ID。我们使用的手机一般都有前置和后置摄像头,该方法传0将会使用后置摄像头,传1将会使用前置摄像头。不设置则默认使用后置摄像头。

    现在有些手机后置双摄像头,相机ID可能有所变化,有兴趣的朋友请自行研究,
  • 简书取名好难:大佬 我自定义的二维码 如果扫描别的二维码就是扫描失败 扫描失败我不想他结束扫描的这个页面 只是给他提示 这个该怎么做 主要是扫描失败不返回还继续停在扫描页面这个怎么做 谢啦
    梦想编织者灬小楠:@简书取名好难 这个好像实现不了,这个库扫描失败(二维码图形解析失败)是会继续扫描的,只有扫描成功才会退出扫描界面。
    你如果想扫描成功不退出扫描界面,需要自定义(继承)CaptureManager,重写CaptureManager的returnResult方法(394行代码)。
    protected void returnResult(BarcodeResult rawResult) {
    Intent intent = resultIntent(rawResult, getBarcodeImagePath(rawResult));
    activity.setResult(Activity.RESULT_OK, intent);
    closeAndFinish();
    }
    简书取名好难:@梦想编织者灬小楠 像微信那样 扫描错误的二维码在扫描界面给他个提示 有没有好的方法
    梦想编织者灬小楠:这个开源库(https://github.com/journeyapps/zxing-android-embedded)
    扫描(识别)二维码失败是不会关闭扫描界面的,它会重新聚焦扫描。如果返回了,一定是扫描出了结果,但是扫描出来的字符串“是不是对的”就需要你在返回界面做判断了,这个库目前提供的方法是无法提前到扫描界面判断字符串的正确性的...:sweat:
  • 67b0f3f3905d:在一开始的时候能够流畅使用,但是,随着代码的增加(没有修改扫描的activity),识别速度就变慢了,后来就基本扫不出来了,是怎么回事呢?
    梦想编织者灬小楠:@Zwei11 是出现扫描卡顿了吗?
    67b0f3f3905d:@梦想编织者灬小楠 项目简单的时候没有问题,就算是比较长的内容解析速度也还可以,项目复杂后就变得不行了。有几次是扫描没有结果后手机离开了二维码一会儿后才出结果
    梦想编织者灬小楠:我的Demo也是吗?你调节下距离试试,换个手机试试~

    猜测的原因有可能是:
    1.写入的二维码字符串变长了,需要解析的时间变长了。
    2.手机的相机聚焦出现了问题。
    3.周围出现了黑白之外的颜色,影响到了二维码解析(其实ZXing的解码效果也不是很好,但开源、稳定的就这一个库):sweat:
  • 森木_林木子:扫面二维码的界面如果不使用沉浸式状态栏 会出现黑边(有的手机是上下,有的是左右) 这个如何解决啊
    梦想编织者灬小楠:@森木_林木子 我刚刚用Github的Demo做了下测试,没有黑边啊...效果图:
    http://upload-images.jianshu.io/upload_images/2693519-77d489379c9f9662.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240
    你换个手机或模拟器试试...
    森木_林木子:@梦想编织者灬小楠 恩恩 用了透明状态栏就没有了 如果不用就会有,黑边就是扫描二维码俩侧不是全部透明的 二是有黑色,我用你的代码试了一下也有
    梦想编织者灬小楠:什么样的黑边?用了透明状态栏就没黑边了吗?
  • Vander丶:很高质量的文章,看完之后有一个问题 ,那个TextView提示的位置 您是怎么了解到MarginTop为120dp的...
    Vander丶:@梦想编织者灬小楠 恩恩.很棒.
    梦想编织者灬小楠:@Vander丶 嗯,是的。根据界面效果调的。
    Vander丶:一点一点试出来的么 ..
  • 807dc7f3cc7a:非常详细,谢谢
  • 90cebce3d0d1:大神~\(≧▽≦)/~
  • 890cb3078f40:大佬可以讲解一下IntentResult中的几个get方法什么时候使用吗?
    梦想编织者灬小楠:@Lucas_615e 应该是自定义扫描布局有问题(扫描布局里面有3部分View,BarcodeView提供相机预览视图、ViewfinderView提供扫描框和扫描线、TextView提供提示文本)可能是BarcodeView出现了问题,可以去的Github链接用笔者的自定义布局试试,然后对比看看...
    890cb3078f40:@梦想编织者灬小楠 好的,谢谢大佬了呢
    梦想编织者灬小楠:详细用法可以查看源码(IntentResult.java)注释。
    简单说下几个笔者用过的方法:
    public String getContents() // 获取条形码内容
    public String getFormatName() // 获取条形码格式名称(如扫描的是二维码,会返回“QR_CODE”)
    public String getErrorCorrectionLevel() // 获取条形码容错级别(如高容错级别会返回"H")
    public String getBarcodeImagePath() // 获取条形码图片保存路径(文中已说)
    public byte[] getRawBytes() // 获取条形码内容(返回的不是String,而是字节数组)
    剩下的方法可参看源码,使用场景由自己的需求决定,正常情况下只用第一个方法就够了:smile:
  • 16d80e09c958:你好,我想请问一下,我二维码里面的是图片,应该怎样去获取二维码里面的图片
    Wezarp:大哥您好,那个zxing扫描区域太小,要拉很远才能取图,离得近的时候根本扫描不出来,这块您是怎么处理的呢?
    16d80e09c958:@梦想编织者灬小楠 用图片生成的二维码
    梦想编织者灬小楠:“二维码里面的是图片”是指:二维码的中间贴上了一个Logo小图标吗?
  • e206c3b457f5:非常详细的demo,傻瓜按流程写都可以做出来了,谢谢分享!赞
  • e9c3cda7b91f:我跟着步骤 怎么搞不出这个效果
    梦想编织者灬小楠:基础用法可以看Github文档:https://github.com/journeyapps/zxing-android-embedded
    我的效果可以去Github下载我的Demo:https://github.com/sinawangnan7/QRCodeScanDemo
  • ac299578b197:大神,这怎么实现访问跳转呀😬
    梦想编织者灬小楠:@正在从小白到大白的JY 对,这个方法确实是打开扫描界面。二维码扫描的原理是“图片解析”,通过扫描二维码图片解析出来一个字符串(这个字符串可以是一个网址)。你在onActivityResult里能拿到这个网址,你可以判断这个字符串是不是以“http”或“https”开头的,如果是可以通过隐式启动系统浏览器进行网页跳转,跳转方法如下:
    /**
    * 启动系统浏览器
    *
    * @param context
    * @param url 网页地址
    */
    public static void turn2WebClient(Context context, String url) {
    Intent intent = new Intent(Intent.ACTION_VIEW);
    intent.setData(Uri.parse(url));
    context.startActivity(intent);
    }
    890cb3078f40:@梦想编织者灬小楠 这个不是打开扫描界面吗?如果扫描到是网址,怎么跳转到网页上?
    梦想编织者灬小楠:其实跳转就用下面这行代码实现的:
    intentIntegrator.initiateScan();
    给你看下这个方法的源码:
    /**
    * Initiates a scan for all known barcode types with the default camera.
    */
    public final void initiateScan() {
    startActivityForResult(createScanIntent(), REQUEST_CODE);
    }
    有时间可以看下源码:grin:
  • coco猫:这篇文章很详细很棒
    梦想编织者灬小楠:@coco猫 谢谢

本文标题:Android进阶 - 二维码扫描

本文链接:https://www.haomeiwen.com/subject/jmwbxxtx.html