Android 集成zxing二维码扫描、自定义_scanoptions scancontract zxing-程序员宅基地

技术标签: Android基础  android  kotlin  android studio  

项目主要有zxing的基本使用,包含扫描回调、连续扫描、自定义扫描框:

一、依赖库

implementation 'com.journeyapps:zxing-android-embedded:4.3.0'

Github 这个库是zxing Android端的,封装了一些基本的使用方法

二、基本使用

这里使用的是startActivityForResult的替代方法,registerForActivityResult(),不了解的可以看下这篇Android 带回调的启动Activity 推荐使用registerForActivityResult

  1. 创建一个ActivityResultLauncher对象,用于配置启动界面和回调结果
    barcodeLauncher = registerForActivityResult(ScanContract()) { result ->
                //获取回调结果
                if (result.contents == null) {
                    Toast.makeText(this, "取消", Toast.LENGTH_LONG).show()
                } else {
                    Toast.makeText(
                        this,
                        "扫描结果: " + result.contents + ",${result.barcodeImagePath}",
                        Toast.LENGTH_LONG
                    ).show()
    
                    if(result.barcodeImagePath.isNotEmpty()){
                        ivImage.setImageURI(Uri.parse(result.barcodeImagePath))
                    }
                }
            }

  2. 配置扫描的基本参数,启动扫码界面

       //配置扫描时的基本参数
            val options = ScanOptions()
            options.apply {
                setDesiredBarcodeFormats(ScanOptions.QR_CODE)//图形码的格式:商品码、一维码、二维码、数据矩阵、全部类型
                setPrompt("请将条形码置于取景框内扫描")
                setCameraId(0) //0 后置摄像头  1 前置摄像头
                setBeepEnabled(true)//开启成功声音
                setTimeout(5000)//设置超时时间
                setBarcodeImageEnabled(true)//是否保存图片,扫描成功会截取扫描框的图形保存到手机并在result中返回路径
            }
            //启动扫描二维码界面
            barcodeLauncher.launch(options)

三、连续扫描

创建一个可连续扫描二维码的ContinuousCaptureActivity,布局文件有一个DecoratedBarcodeView(二维码扫描的主要控件)和一个TextView显示结果。

DecoratedBarcodeView : 主要由三个view组成 BarcodeView(相机扫描时的预览控件)、ViewfinderView(扫码框、遮罩和底部提示文字,后面自定义扫描框就是对其自定义)、TextView(提示文本)

  1. 布局        
    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        app:layout_behavior="@string/appbar_scrolling_view_behavior">
        <com.journeyapps.barcodescanner.DecoratedBarcodeView
            android:id="@+id/barcode_scanner"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1">
    
        </com.journeyapps.barcodescanner.DecoratedBarcodeView>
    
        <TextView
            android:id="@+id/tv_result"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>
    
    </LinearLayout>

  2. ContinuousCaptureActivity中的主要代码

     override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_continuous_capture)
            tvResult = findViewById(R.id.tv_result)
            barcodeView = findViewById(R.id.barcode_scanner)
            //码格式
            val formats: Collection<BarcodeFormat> =
                listOf(BarcodeFormat.QR_CODE, BarcodeFormat.CODE_39)
            barcodeView.barcodeView.decoderFactory = DefaultDecoderFactory(formats)
            //初始化barcodeView
            barcodeView.initializeFromIntent(intent)
            //监听扫描结果
            barcodeView.decodeContinuous(callBack)
        }
    
        private val callBack: BarcodeCallback = object : BarcodeCallback {
            @SuppressLint("SetTextI18n")
            override fun barcodeResult(result: BarcodeResult?) {
                result?.let {
                    //扫描结果为空或者两次扫描的结果相同
                    if (it.text.isNullOrEmpty() || it.text == lastText) {
                        return
                    }
                    lastText = it.text
                    tvResult.text = "${tvResult.text}\n$it"
                    Log.e("yufs",lastText)
                }
            }
        }

四、自定义扫描框

        上面有提到DecoratedBarcodeView,主要的三个view:BarcodeView、ViewfinderView、TextView。这里对包括扫描框的ViewfinderView进行自定义。ViewfinderView的默认效果从演示效果图中可以看到是中间的一根红色线,渐隐渐现,扫描时在还能出现些许黄色的小圆点。

        这里我们要做的是去掉中间红色的线,换成我们绘制的扫描线,并在四个角绘制边角。这里我们的自定义view只需要继承原先的ViewfinderView,并重写onDraw(),去掉不需要的,绘制我们的需要的即可。

下面为CustomViewfinderView的全部代码

/**
 * 自定义扫描框样式
 */
public class CustomViewfinderView extends ViewfinderView {

    //重绘时间间隔
    public static final long INT_ANIMATION_DELAY = 12;

    /* ******************************************    边角线相关属性    ************************************************/
    //"边角线长度/扫描边框长度"的占比 (比例越大,线越长)
    public float mLineRate = 0.1F;
    //边角线厚度 (建议使用dp)
    public float mLineDepth =  dp2px(4);
    //边角线颜色
    public int mLineColor;

    /* *******************************************    扫描线相关属性    ************************************************/
    //扫描线起始位置
    public int mScanLinePosition = 0;
    //扫描线厚度
    public float mScanLineDepth = dp2px(4);
    //扫描线每次移动距离
    public float mScanLineDy = dp2px(3);
    //渐变线
    public LinearGradient mLinearGradient;
    //图形paint
    public Paint mBitmapPaint;
    ///颜色在渐变中所占比例,此处均衡渐变
    public float[] mPositions = new float[]{0f, 0.5f, 1f};
    //线性梯度各个位置对应的颜色值
    public int[] mScanLineColor = new int[]{0x00000000, Color.YELLOW, 0x00000000};

    //扫描框宽、高
    public float mScanFrameWidth;
    public float mScanFrameHeight;

    public CustomViewfinderView(Context context, AttributeSet attrs) {
        super(context, attrs);
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CustomViewfinderView);
        mLineColor = typedArray.getColor(R.styleable.CustomViewfinderView_lineColor,Color.YELLOW);
        mScanLineColor[1]=typedArray.getColor(R.styleable.CustomViewfinderView_cornerColor,Color.YELLOW);
        mScanFrameWidth = typedArray.getDimension(R.styleable.CustomViewfinderView_scanFrameWidth,dp2px(160));
        mScanFrameHeight = typedArray.getDimension(R.styleable.CustomViewfinderView_scanFrameHeight,dp2px(160));
        typedArray.recycle();
        mBitmapPaint = new Paint();
        mBitmapPaint.setAntiAlias(true);
    }

    @SuppressLint({ "DrawAllocation"})
    @Override
    public void onDraw(Canvas canvas) {
        refreshSizes();
        if (framingRect == null||previewSize==null ) {
            return;
        }

        final Rect frame = framingRect;

        final int width = getWidth();
        final int height = getHeight();

        //绘制扫描框外部遮罩
        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);

        //绘制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);


        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);

            //绘制资源图片扫描线
//            Rect lineRect = new Rect();
//            lineRect.left = frame.left;
//            lineRect.top = frame.top + mScanLinePosition;
//            lineRect.right = frame.right;
//            lineRect.bottom = frame.top + dp2px(6) + mScanLinePosition;
//            Bitmap bitmap = BitmapFactory.decodeResource(getResources(),R.drawable.img_line);
//            canvas.drawBitmap(bitmap, null, lineRect, mBitmapPaint);


            //============绘制扫描时小圆点,效果为默认=======================

            final float scaleX = this.getWidth() / (float) previewSize.width;
            final float scaleY = this.getHeight() / (float) previewSize.height;
            // draw the last possible result points
            if (!lastPossibleResultPoints.isEmpty()) {
                paint.setAlpha(CURRENT_POINT_OPACITY / 2);
                paint.setColor(resultPointColor);
                float radius = POINT_SIZE / 2.0f;
                for (final ResultPoint point : lastPossibleResultPoints) {
                    canvas.drawCircle(
                            (int) (point.getX() * scaleX),
                            (int) (point.getY() * scaleY),
                            radius, paint
                    );
                }
                lastPossibleResultPoints.clear();
            }

            // draw current possible result points
            if (!possibleResultPoints.isEmpty()) {
                paint.setAlpha(CURRENT_POINT_OPACITY);
                paint.setColor(resultPointColor);
                for (final ResultPoint point : possibleResultPoints) {
                    canvas.drawCircle(
                            (int) (point.getX() * scaleX),
                            (int) (point.getY() * scaleY),
                            POINT_SIZE, paint
                    );
                }

                // swap and clear buffers
                final List<ResultPoint> temp = possibleResultPoints;
                possibleResultPoints = lastPossibleResultPoints;
                lastPossibleResultPoints = temp;
                possibleResultPoints.clear();
            }

            //============绘制扫描时小圆点,效果为默认 end=======================
        }

        //定时刷新扫描框
        postInvalidateDelayed(INT_ANIMATION_DELAY,
                frame.left - POINT_SIZE,
                frame.top - POINT_SIZE,
                frame.right + POINT_SIZE,
                frame.bottom + POINT_SIZE);

    }

    protected void refreshSizes() {
        if (cameraPreview == null) {
            return;
        }
        //添加设置边框大小代码
        cameraPreview.setFramingRectSize(new Size((int) mScanFrameWidth,(int)mScanFrameHeight));

        Rect framingRect = cameraPreview.getFramingRect();
        Size previewSize = cameraPreview.getPreviewSize();
        if (framingRect != null && previewSize != null) {
            this.framingRect = framingRect;
            this.previewSize = previewSize;
        }
    }

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

代码中主要看onDraw(),里面的关键代码已添加注释,同时里面的扫描线可以替换成我们需要的图形,有需要打开onDraw中注释的代码即可

自定义的属性文件attrs.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="CustomViewfinderView">
        <attr format="color" name="lineColor"/><!--扫描线的颜色-->
        <attr format="color" name="cornerColor"/><!--四边角的颜色-->
        <attr format="dimension" name="scanFrameWidth"/><!--扫描框的宽度-->
        <attr format="dimension" name="scanFrameHeight"/><!--扫描框的高度-->
    </declare-styleable>
</resources>

如何使用?

  1. 创建custom_barcode_scanner.xml
    <?xml version="1.0" encoding="utf-8"?>
    <merge xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto">
    
        <com.journeyapps.barcodescanner.BarcodeView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:id="@+id/zxing_barcode_surface"/>
    
        <com.home.testzxing.CustomViewfinderView
            android:layout_width="match_parent"
            app:lineColor="#ff8100"
            app:cornerColor="#ff8100"
            app:scanFrameWidth="180dp"
            app:scanFrameHeight="180dp"
            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替换成我们的CustomViewfinderView,另外这三个View的id不可改变

  2. 在DecoratedBarcodeView的布局文件中通过zxing_scanner_layout属性引入我们创建的xml

       <com.journeyapps.barcodescanner.DecoratedBarcodeView
            android:id="@+id/zxing_barcode_scanner"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:zxing_scanner_layout="@layout/custom_barcode_scanner">
    
        </com.journeyapps.barcodescanner.DecoratedBarcodeView>

五、总结

        主要了解registerForActivityResult()的启动activity、自定义view中LinearGradient绘制渐变的扫描线以及postInvalidateDelayed定时重绘扫描框。

代码下载

参考

Android进阶 - 二维码扫描 - 简书

Android 基于google Zxing实现二维码、条形码扫描,仿微信二维码扫描效果_Mobile Internet developer-程序员宅基地

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/Ikulm/article/details/121529753

智能推荐

Linux的用户管理-程序员宅基地

文章浏览阅读66次。用户管理:一.用户的配置文件:用户配置文件的路径:/etc/passwd/etc/passwd的一部分内容:每一行都是一个用户的信息(内容如下):用户名 :密码 :uid :gid :用户的备注 :用户的家目录 :和根交互使用的shell路径 二.用户超级用户root ----- uid为 0系统用户 ----- 使用的shell的路径为 /sbin/nologin ,uid为 201 —— 999,添加系统用户不会默认创建家目录和邮箱普通用户 ----- uid为 1000 ——

心田花开:二年级语文阅读《黄山奇石》附答案解析_黄山奇石阅读题及答案二年级-程序员宅基地

文章浏览阅读6.9k次。心田花开为大家分享了二年级语文上册黄山奇石练习题,本部分练习题包括了看拼音,写词语;选择合适的字;填空题;造句子及句子赏析,最后附加课文原文,希望能对大家学习有帮助。【原文】黄山奇石闻名中外的黄山风景区在我国安徽省南部。那里景色秀丽神奇,尤其是那些怪石,有趣极了。就说“仙桃石”吧,它好像从天上飞下来的一个大桃子,落在山顶的石盘上。在一座陡峭的山峰上,有一只“猴子”。它两只胳膊抱着腿,一动不动..._黄山奇石阅读题及答案二年级

css实现tab导航下划线动画效果(从中间过渡到两边)_css过度动画从中间向两边移动-程序员宅基地

文章浏览阅读3.2k次,点赞45次,收藏7次。tab导航的中间开始然后向两边过渡的动画效果,增加页面的美观性..._css过度动画从中间向两边移动

S7-1515-2pn 带pn总线设备有 库卡机器人 西门子S120伺服驱动器 sew伺服驱动器_pn总线伺服-程序员宅基地

文章浏览阅读175次。S7-1515-2pn 带pn总线设备有 库卡机器人 西门子S120伺服驱动器 sew伺服驱动器 pn绝对值编码器 SSI编码器应用 7个触摸屏包含程序 一个上位机组态画面包含程序 包含graph语言编写的程序 STL SCL语言编写的程序 模拟量采集 是学习西门子工艺对象组态运动控制 机器人等不可多得呢学习资料 全部程序均调试通过可以立即应用。通过对S7-1515-2pn带pn总线设备的应用,我们可以实现各种工业自动化控制,比如各种机器人的控制、数控机床的控制等等。_pn总线伺服

oracle查看表锁并解锁_oracle锁表查询和解锁方法-程序员宅基地

文章浏览阅读2.1k次。当在一个应用程序能改动数据库,而其他应用程序都不能改动时,基本就说明表被锁了。_oracle锁表查询和解锁方法

DBSCAN聚类算法及其参数配置-python实现_dbscan调参-程序员宅基地

文章浏览阅读1.5k次,点赞21次,收藏38次。DBSCAN聚类算法是一种基于空间密度有传递性质的聚类算法,将簇定义为密度相连的点的最大的集合,可以将高密度点区域划分为簇,并有效地过滤低密度点区域,可以在含有噪声的数据集中识别任意形状和数量的簇。_dbscan调参

随便推点

【MATLAB】解决MATLAB安装后出现 “License Manager Error -8”(亲测有效)_matlab激活后报错error8-程序员宅基地

文章浏览阅读2w次,点赞2次,收藏10次。把应用程序拉取到桌面上。_matlab激活后报错error8

【C++】局部变量、全局变量、静态变量与动态对象的性质_动态局部对象-程序员宅基地

文章浏览阅读4.4k次,点赞17次,收藏46次。 【fishing-pan:https://blog.csdn.net/u013921430转载请注明出处】概述 局部变量 在一个函数内部定义的变量(包括函数形参)是局部变量。 全局变量 在函数外定义的变量是局部变量。 静态变量 静态全局变量 在全局变量..._动态局部对象

计算机怎么配置最好,怎么样才能把电脑的配置调到最高或者最好?-程序员宅基地

文章浏览阅读928次。1、加速网上邻居在Windows XP中访问网上邻居是相当恼人的,系统会搜索自己的共享目录和可作为网络共享的打印机以及计划任务中和网络相关的计划任务,然后才显示出来,显然这样速度就会比Windows 9x中慢很多。其实这些功能我们并没有使用上,与其不用还不如删除它们,这样速度就会明显加快。打开注册表编辑器,找到HKEY_LOCAL_ MACHINE/sofeware/Microsoft/Windo..._prefetchparameters

学习axios必知必会(1)~axios基本介绍、axios配置、json-server接口模拟工具_axios httpagent-程序员宅基地

文章浏览阅读5.4k次,点赞16次,收藏31次。学习axios必知必会(1)~axios基本介绍、axios配置、json-server接口模拟工具_axios httpagent

好故事-程序员宅基地

文章浏览阅读192次。我是一个老程序员,最近因为公司,因为家事,心情一直比较郁闷,偶尔翻翻,在网上看到了一个故事,内容写的就是一个毕业的学生如何打拼职场的,一开始感觉一般,但随着故事的推进,能体会到主人公的辛酸,更重要的是,发现了自己当年的影子,在这里推荐给大家,我每日发一篇,有可能会侵占版权,是吗,呵呵,但愿没有,随便吧,我想作者还应该感谢我呢。 公司杀手  一个刚毕业的大学生,对社会、对工作、对生活、对爱情充满了美好的向往,可没想到他却在跌跌撞撞中前行。他的职场之

Mybatis学习之路 第一篇_为什么dao接口和xml的包位置一致呢-程序员宅基地

文章浏览阅读143次。Mybatis学习之路 第一篇该系列教程只能帮你怎么用,并不涉及源码以及讲解。本文涉及以下内容:1. JDBC存在的问题极其代码操作2. 基于xml的Mybatis的入门程序案例3. 基于注解的Mybatis的入门程序1. JDBC存在的问题JDBC的操作非常繁琐,有很多的操作是我们开发者不需要去关注的,我们的重点应该只是SQL语句的本身。使用 preparedStatem..._为什么dao接口和xml的包位置一致呢

推荐文章

热门文章

相关标签