精华内容
参与话题
问答
  • Android扫一扫

    2016-07-26 20:21:54
    各种Android扫码问题解决方法
    展开全文
  • Android扫一扫获取结果

    2018-08-08 11:41:30
    Android扫一扫二维码,获取二维码结果代码,解压可直接使用。
  • 第三方zxing的GitHub地址 用法: 国际惯例,先上图: Step 1 :添加依赖 ...uses-permission android:name="android.permission.CAMERA"/> Step 3 :activity_main.xml布局 添加 测试用的两个控件 <?

    第三方Zxing的GitHub地址:

    说明:

    这里一共有四种扫描界面,分别为:
    (tip:可根据需要,自行滑到相应的标题学习即可)

    一:基本用法:
    也就是Zxing的基本使用,最简便的扫描界面

    二:自定义扫描界面:带闪光灯
    在基本的界面上,添加了有闪光灯的按钮

    三:自定义扫描界面:带闪光灯 并修改其扫描界面
    这一种,就是对Zxing的扫描界面进行了大更改,可自定义扫描条,边框,等等,只要认真看代码步骤,自己也可以定义

    四:自定义扫描界面:改进版,仿微信扫描条

    这种,是仿照微信的界面的,扫描条是用图片通过动画做的,也可以更换图片,推荐使用这种!!

    相比第三种,性能上也比较好,因为第三种是不断的刷新界面形成扫描条动画,而刷新界面是很浪费性能的,所以,就会造成扫描条运动不流畅的视觉(加快运动更明显)
    而第四种的扫描条是通过图片动画实现的,所以运动是很平滑流畅的,也没有不断刷新界面,不浪费性能,所以,建议使用第四种!!!

    基本用法:

    国际惯例,先上图:
    在这里插入图片描述

    Step 1 :添加依赖

        //第三方zxing
        implementation 'com.journeyapps:zxing-android-embedded:3.6.0'
    

    Step 2 :添加权限

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

    Step 3 :activity_main.xml布局 添加 测试用的两个控件

    <?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"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        tools:context=".MainActivity">
    
        
        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="开始"
            android:id="@+id/button"/>
    
        <ImageView
            android:id="@+id/iv_image"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>
    </LinearLayout>
    

    Step 4 :MainActivity 代码:

    public class MainActivity extends AppCompatActivity {
    
        private Button button;
        private ImageView ivImage;
    
        //  Step 1 : 初始化 获取控件 设置监听
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            //获取测试的控件
            button = findViewById(R.id.button);//点击跳转到扫码活动
            ivImage = findViewById(R.id.iv_image);//输出二维码图片
    
            //控件监听
            listenerView();
        }
    
    
        private void listenerView() {
    
            //  Step 2 :跳转到扫描活动
            button.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
    
                    //=======设置扫描活动  可根据需求设置以下内容
                    IntentIntegrator intentIntegrator = new IntentIntegrator(MainActivity.this);
    
                    //  1.扫描成功后的提示音,默认关闭
                    intentIntegrator.setBeepEnabled(true);
    
                    //  2.启动后置摄像头扫描,若为 1 为前置摄像头,默认后置
                    intentIntegrator.setCameraId(0);
    
                    /*  3.设置扫描的条码的格式:默认为所有类型
                     *   IntentIntegrator.PRODUCT_CODE_TYPES:商品码类型
                     *   IntentIntegrator.ONE_D_CODE_TYPES:一维码类型
                     *   IntentIntegrator.QR_CODE:二维码
                     *   IntentIntegrator.DATA_MATRIX:数据矩阵类型
                     *   IntentIntegrator.ALL_CODE_TYPES:所类有型
                     * */
                    intentIntegrator.setDesiredBarcodeFormats(IntentIntegrator.ALL_CODE_TYPES);
    
                    /*  4.方向锁:true为锁定,false反之,默认锁定.
                    ps:在AndroidManifest.xml里设置以下属性,则扫码界面完全依赖传感器(tools红色提示,指向它会提示,点击左边蓝色Create...即可)
                    <activity
                        android:name="com.journeyapps.barcodescanner.CaptureActivity"
                        android:screenOrientation="fullSensor"
                        tools:replace="screenOrientation" />
                    * */
                    intentIntegrator.setOrientationLocked(true);
    
                    //  5.设置扫描界面的提示信息:默认为:请将条码置于取景框内扫描。(ps:设置没提示文字:setPrompt(""))
                    intentIntegrator.setPrompt("请选择二维码");
    
                    //  6.设置关闭扫描的时间(单位:毫秒),不设置不关闭
                    intentIntegrator.setTimeout(60000);
    
                    //  7.保存二维码图片:在onActivityResult方法里可获取保存的路径,根据需要来是否需要保存
                    intentIntegrator.setBarcodeImageEnabled(true);
    
                    //启动扫描
                    intentIntegrator.initiateScan();
    
                }
            });
    
        }
    
    
        //  Step 3 :处理扫码后返回的结果
        @Override
        protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
            IntentResult result = IntentIntegrator.parseActivityResult(requestCode,resultCode,data);
    
            if(result!=null){
    
                //==是否扫到内容
                if (result.getContents()!=null){
                    Toast.makeText(this,"扫描结果:"+result.getContents(),Toast.LENGTH_LONG).show();
                }else{
                    Toast.makeText(this,"取消扫码",Toast.LENGTH_LONG).show();
                }
    
                //==是否有保存照片的路径  在intentIntegrator已设置保存照片
                if(result.getBarcodeImagePath()!=null){
                    
                    FileInputStream file=null;
                    try {
                        file=new FileInputStream(new File(result.getBarcodeImagePath()));
                        ivImage.setImageBitmap(BitmapFactory.decodeStream(file));//显示获取的照片
                    } catch (FileNotFoundException e) {
                        e.printStackTrace();
                    }finally {
                        try {
                            file.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
    
                }
                
                /*  获取条码种类:在intentIntegrator.setDesiredBarcodeFormats那设置扫码格式后(点击格式可进入查看该格式有多少个类型)
    
                    例如:PRODUCT_CODE_TYPES:商品码类型,它就有 UPC_A, UPC_E, EAN_8, EAN_13, RSS_14 种类
                    public static final Collection<String> PRODUCT_CODE_TYPES = list(UPC_A, UPC_E, EAN_8, EAN_13, RSS_14);
    
                    根据getFormatName获取到的种类,就知道是哪个扫码格式,进而根据需求进行相关操作
                 */
                if (result.getFormatName()!=null){
                    Toast.makeText(this,"图片格式:"+result.getFormatName(),Toast.LENGTH_LONG).show();
                }
    
    
            }else{
                super.onActivityResult(requestCode, resultCode, data);
            }
    
        }
        
    
    }
    

    自定义扫描界面:带闪光灯**

    国际惯例,先上图:图中白点为闪光灯按钮
    在这里插入图片描述

    Step 1 :引入依赖:

        //第三方zxing
        implementation 'com.journeyapps:zxing-android-embedded:3.6.0'
    
    

    Step 2 :申请权限:

        <!--相机-->
        <uses-permission android:name="android.permission.CAMERA"/>
        <!--若需要闪光灯权限 ,请加入此权限(自测不需要)-->
    <!--
        <uses-permission android:name="android.permission.FLASHLIGHT" />
    -->
    

    Step 3 :准备3个布局

    activity_main.xml

    <?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"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        tools:context=".MainActivity">
    
        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="开始"
            android:id="@+id/button"/>
    
    </LinearLayout>
    

    content_scan.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">
    
        <!--
        layout_width、layout_height:启动扫描界面的布局参数
    
        zxing_framing_rect_width、zxing_framing_rect_height:
        在扫描界面中,只能扫描二维码的宽高,去掉后会有默认的宽高
        -->
        <com.journeyapps.barcodescanner.BarcodeView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/zxing_barcode_surface"
            app:zxing_framing_rect_width="250dp"
            app:zxing_framing_rect_height="250dp"/>
    
        <com.journeyapps.barcodescanner.ViewfinderView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/zxing_viewfinder_view"
            app:zxing_possible_result_points="@color/zxing_custom_possible_result_points"
            app:zxing_result_view="@color/zxing_custom_result_view"
            app:zxing_viewfinder_laser="@color/zxing_custom_viewfinder_laser"
            app:zxing_viewfinder_mask="@color/zxing_custom_viewfinder_mask"/>
    
    </merge>
    

    activity_scan.xml

    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        xmlns:app="http://schemas.android.com/apk/res-auto">
    
        <!--装扫描界面的控件
        @layout/content_scan:为嵌入content_scan.xml的布局
        -->
        <com.journeyapps.barcodescanner.DecoratedBarcodeView
            android:id="@+id/dbv"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_alignParentStart="true"
            app:zxing_scanner_layout="@layout/content_scan">
        </com.journeyapps.barcodescanner.DecoratedBarcodeView>
    
        <!--闪光灯图片 自行找图片样式
            @drawable/ic_flashlight_close 关闭时的图片
        -->
        <ImageButton
            android:id="@+id/ib_flashlight_close"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentBottom="true"
            android:layout_centerHorizontal="true"
            android:layout_marginBottom="60dp"
            android:background="@drawable/ic_flashlight_close"/>
    </RelativeLayout>
    

    Step 4 :ScanActivity.java

    public class ScanActivity extends AppCompatActivity {
        private CaptureManager capture;
        private ImageButton ibFlashlight;
        private DecoratedBarcodeView barcodeScannerView;
        private boolean bTorch = false;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
    
            //==设置布局、获取控件
            setContentView(R.layout.activity_scan);
            barcodeScannerView = findViewById(R.id.dbv);
            ibFlashlight= findViewById(R.id.ib_flashlight_close);
    
            //==监听: 根据barcodeScannerView设置闪光灯ibFlashlight状态
            barcodeScannerView.setTorchListener(new DecoratedBarcodeView.TorchListener() {
                @Override
                public void onTorchOn() {//开灯
    
                    //R.drawable.ic_flashlight_open)  开灯显示的图片 自行找图片样式
                    ibFlashlight.setBackground(getResources().getDrawable(R.drawable.ic_flashlight_open));
                    bTorch = true;
                }
    
                @Override
                public void onTorchOff() {//关灯
    
                    //R.drawable.ic_flashlight_close)  关灯显示的图片 自行找图片样式
                    ibFlashlight.setBackground(getResources().getDrawable(R.drawable.ic_flashlight_close));
                    bTorch = false;
                }
            });
    
            //==开或关灯
            ibFlashlight.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    if(bTorch){
                        barcodeScannerView.setTorchOff();
                    } else {
                        barcodeScannerView.setTorchOn();
                    }
    
                }
            });
    
            //==初始化活动
            capture = new CaptureManager(this, barcodeScannerView);
    
            capture.initializeFromIntent(getIntent(), savedInstanceState);
    
            capture.decode();
        }
    
    
        @Override
        protected void onResume() {
            super.onResume();
            capture.onResume();
        }
    
        @Override
        protected void onPause() {
            super.onPause();
            capture.onPause();
            barcodeScannerView.setTorchOff();
        }
    
        @Override
        protected void onDestroy() {
            super.onDestroy();
            capture.onDestroy();
        }
    
        @Override
        protected void onSaveInstanceState(Bundle outState) {
            super.onSaveInstanceState(outState);
            capture.onSaveInstanceState(outState);
        }
    
        @Override
        public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[], @NonNull int[] grantResults) {
            capture.onRequestPermissionsResult(requestCode, permissions, grantResults);
        }
    
        @Override
        public boolean onKeyDown(int keyCode, KeyEvent event) {
            return barcodeScannerView.onKeyDown(keyCode, event) || super.onKeyDown(keyCode, event);
        }
    }
    

    Step 5 :MainActivity 代码:

    public class MainActivity extends AppCompatActivity {
    
        private Button button;
    
        //  Step 1 : 初始化 获取控件 设置监听
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            //获取测试的控件
            button = findViewById(R.id.button);//点击跳转到扫码活动
    
            //控件监听
            listenerView();
        }
    
    
        private void listenerView() {
    
            //  Step 2 :跳转到扫描活动
            button.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
    
                    //=======设置扫描活动  可根据需求设置以下内容
                    IntentIntegrator intentIntegrator = new IntentIntegrator(MainActivity.this);
    
                    //启动自定义的扫描活动,不设置则启动默认的活动
                    intentIntegrator.setCaptureActivity(ScanActivity.class);
    
                    //启动扫描
                    intentIntegrator.initiateScan();
    
                }
            });
    
        }
    
    
        //  Step 3 :处理扫码后返回的结果
        @Override
        protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
            IntentResult result = IntentIntegrator.parseActivityResult(requestCode,resultCode,data);
    
            if(result!=null){
    
                //==是否扫到内容
                if (result.getContents()!=null){
                    Toast.makeText(this,"扫描结果:"+result.getContents(),Toast.LENGTH_LONG).show();
                }else{
                    Toast.makeText(this,"取消扫码",Toast.LENGTH_LONG).show();
                }
    
    
            }else{
                super.onActivityResult(requestCode, resultCode, data);
            }
    
        }
        
    
        
    }
    

    自定义扫描界面:带闪光灯 并修改其扫描界面**

    国际惯例,先上图(ScanWidget代码里有介绍去掉四个角样式)本来是动图的
    本来是动图的

    tip:上面已经有介绍添加依赖和权限了,这里不多说,

    并且,所用到的xml和activity和第一种的相同。

    不同之处:
    1.新建一个自定义的扫描活动:ScanWidget,代码如下

    public class ScanWidget extends ViewfinderView {
    
        /* ******************************************    边角线相关属性    ************************************************/
    
        /**
         * "边角线长度/扫描边框长度"的占比 (比例越大,线越长)
         */
        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, 5, getResources().getDisplayMetrics());
    
        /**
         * 线性梯度
         */
        public LinearGradient mLinearGradient;
    
        /**
         * 线性梯度位置
         */
        public float[] mPositions = new float[]{0f, 0.5f, 1f};
    
        /**
         * 线性梯度各个位置对应的颜色值
         */
        public int[] mScanLineColor = new int[]{0x00FFFFFF, Color.WHITE, 0x00FFFFFF};
    
    
    
        // This constructor is used when the class is built from an XML resource.
        public ScanWidget(Context context, AttributeSet attrs) {
            super(context, attrs);
    
        }
    
    
        @Override
        public void onDraw(Canvas canvas) {
            refreshSizes();
            if (framingRect == null || previewFramingRect == null) {
                return;
            }
    
            final Rect frame = framingRect;
            final Rect previewFrame = previewFramingRect;
    
            //=====绘制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);
    
    
            //=======扫描框为的颜色,灰色遮罩层,删除则无灰色遮罩层
            /*
            int width = canvas.getWidth();
            int height = canvas.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);
             */
    
            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);
    
    
    
                final float scaleX = frame.width() / (float) previewFrame.width();
                final float scaleY = frame.height() / (float) previewFrame.height();
    
                final int frameLeft = frame.left;
                final int frameTop = frame.top;
    
                /*去掉扫描区域的闪光点
                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(
                                frameLeft + (int) (point.getX() * scaleX),
                                frameTop + (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(
                                frameLeft + (int) (point.getX() * scaleX),
                                frameTop + (int) (point.getY() * scaleY),
                                POINT_SIZE, paint
                        );
                    }
    
                    // swap and clear buffers
                    final List<ResultPoint> temp = possibleResultPoints;
                    possibleResultPoints = lastPossibleResultPoints;
                    lastPossibleResultPoints = temp;
                    possibleResultPoints.clear();
                }
    
                // Request another update at the animation interval, but only repaint the laser line,
                // not the entire viewfinder mask.
                postInvalidateDelayed(ANIMATION_DELAY,
                        frame.left - POINT_SIZE,
                        frame.top - POINT_SIZE,
                        frame.right + POINT_SIZE,
                        frame.bottom + POINT_SIZE);
            }
        }
    
    }
    

    2.content_scan.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">
    
        <!--
        layout_width、layout_height:启动扫描界面的布局参数
    
        zxing_framing_rect_width、zxing_framing_rect_height:
        在扫描界面中,只能扫描二维码的宽高,去掉后会有默认的宽高
        -->
        <com.journeyapps.barcodescanner.BarcodeView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/zxing_barcode_surface" />
    
        <!--使用的是自定义的扫描活动-->
        <com.gx.test.widget.ScanWidget
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/zxing_viewfinder_view" />
    
    </merge>
    

    自定义扫描界面:改进版,仿微信扫描条**

    扫描条是图片,利用动画实现扫描条活动,第二种的扫描条是绘制的线,调快移动距离的话会感觉一卡一卡的,效果不好。
    国际惯例,先上图:因虚拟机录制,看着扫描条一卡一卡的,实际用手机调试,是像微信的扫码条那样流畅的
    在这里插入图片描述

    ps:如果想从上到下扫描后再下往上,下面的Step 4步骤里的设置自己的扫描条的动画解释里有写怎样设置。

    在这里插入图片描述

    Step 1 :引入依赖:

        //第三方zxing
        implementation 'com.journeyapps:zxing-android-embedded:3.6.0'
    

    Step 2 :申请权限:

        <!--相机-->
        <uses-permission android:name="android.permission.CAMERA"/>
        <!--若需要闪光灯权限 ,请加入此权限(自测不需要)-->
    <!--
        <uses-permission android:name="android.permission.FLASHLIGHT" />
    -->
    

    Step 3 :自定义 MyApplication

    public class MyApplication extends Application {
        
        private View view;
    
        public View getView() {
            return view;
        }
    
        public void setView(View view) {
            this.view = view;
        }
    }
    

    并在AndroidManifest.xml配置好

        <application
            android:name=".MyApplication"
    

    Step 4 :ScanWidget 自定义扫描活动界面:

    //  Step 1 :继承  ViewfinderView 并 加控制器
    public class ScanWidget extends ViewfinderView {
    
    
        //边角线厚度
        public float mLineDepth =  TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 4, getResources().getDisplayMetrics());
    
        //边角线长度/扫描边框长度"的占比 (比例越大,线越长)
        public float mLineRate = 0.1F;
    
    
        public ScanWidget(Context context, AttributeSet attrs) {
            super(context, attrs);
        }
    
        //  Step 2 : 重写此方法:在此方法删除原来的扫描条等等样式,并加入自己的扫描样式
        @Override
        public void onDraw(Canvas canvas) {
    
            refreshSizes();
            if (framingRect == null || previewFramingRect == null) {
                return;
            }
    
            final Rect frame = framingRect;
            final Rect previewFrame = previewFramingRect;
    
            final int width = canvas.getWidth();
            final int height = canvas.getHeight();
    
    
            //====================自己加入的扫描条动画在此处(扫描条其实是View控件放了个背景,view加入动画就实现了扫描条运动)↓
            //ps:若是启用下面代码(带有 PS 的注释那段,请看其作用),请在全局定义boolean b=false,然后例:
            //if(!b){这里写这段自己加入的动画代码; b=true;}  否则出现扫描条不运动,也不会因下面那PS提示的代码让这段代码反复执行。
    
            //=====加入扫描条
            MyApplication myApplication= (MyApplication) this.getContext().getApplicationContext();
    
            //设置扫描条的参数
            View view=myApplication.getView();
            FrameLayout.LayoutParams params= (FrameLayout.LayoutParams) view.getLayoutParams();
            params.width=frame.right-frame.left;//这是计算扫描框的宽度,进而设置扫描条的宽度
            params.setMargins(frame.left,0,0,0);//设置左边距,让扫描条在横方向在扫描框里
            view.setLayoutParams(params);
    
            //=========第一种:设置扫描条的动画 ,仿微信,从同到尾循环扫,并且块到尾时逐渐变完全透明
            AnimationSet set=new AnimationSet(true);
    
            //参数 3:运动开始的地方:frame.top是扫描框离屏幕顶部的距离,减70是因为 这个扫描条 的高是 70 px,
            // 参数3的单位也是px,所以运动开始的地方就是 frame.top-70;参数4作用同3
            Animation trans=new TranslateAnimation(0, 0, frame.top-70, frame.bottom-70);
            trans.setDuration(2000);
            trans.setRepeatMode(Animation.RESTART);
            trans.setRepeatCount(Animation.INFINITE);
            set.addAnimation(trans);
    
            //==这段就是接近尾部后加入的透明,不需要可删除
            Animation alpha=new AlphaAnimation(1,0);
            //注意 1500+500是等于time的 ,因为在移动动画执行3分之2后才执行透明的
            alpha.setStartOffset(1500);
            alpha.setDuration(500);
            alpha.setRepeatMode(Animation.RESTART);
            alpha.setRepeatCount(Animation.INFINITE);
            alpha.setFillAfter(false);
            set.addAnimation(alpha);
    
            //添加动画
            view.startAnimation(set);
    
            //=========第二种:设置扫描条的动画,从上扫到下后又从下往上,如此循环的
    /*        Animation animation = new TranslateAnimation(0, 0, frame.top-70, frame.bottom-70);
            animation.setRepeatMode(Animation.REVERSE);
            animation.setRepeatCount(Animation.INFINITE);
            animation.setDuration(2000);
            view.startAnimation(animation);*/
            
            //清除内存
            myApplication.setView(null);
    
            //=====为矩形扫描区域四个角加上边框  根据情况可以去掉该段代码,像微信扫码了
            paint.setColor(Color.GREEN); // 定义四个角画笔的颜色(本身整个扫描界面都为此颜色,通过设置四个角距离而被覆盖,进而形成四个角)
            //左上角
            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);
    
            //============================自己加入动画↑=========================
    
    
            // 灰色遮罩层  可以去掉
            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);
    
    /*
    
            //===========PS:以下方法,不断执行onDraw方法绘制扫描线等样式进而产生自带的扫描线和闪光点,
            // 若是扫描到了,就会把结果图绘制在矩形框上,根据情况选择是否注释以下代码或部分动画代码
    
            if (resultBitmap != null) {
                //扫描到后在矩形上绘制不透明的图
                // Draw the opaque result bitmap over the scanning rectangle
                paint.setAlpha(CURRENT_POINT_OPACITY);
                canvas.drawBitmap(resultBitmap, null, frame, paint);
            } else {
    
                //自带的红色扫描线
                // Draw a red "laser scanner" line through the middle to show decoding is active
                paint.setColor(laserColor);
                paint.setAlpha(SCANNER_ALPHA[scannerAlpha]);
                scannerAlpha = (scannerAlpha + 1) % SCANNER_ALPHA.length;
                final int middle = frame.height() / 2 + frame.top;
                canvas.drawRect(frame.left + 2, middle - 1, frame.right - 1, middle + 2, paint);
    
                final float scaleX = frame.width() / (float) previewFrame.width();
                final float scaleY = frame.height() / (float) previewFrame.height();
    
                final int frameLeft = frame.left;
                final int frameTop = frame.top;
    
                // 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(
                                frameLeft + (int) (point.getX() * scaleX),
                                frameTop + (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(
                                frameLeft + (int) (point.getX() * scaleX),
                                frameTop + (int) (point.getY() * scaleY),
                                POINT_SIZE, paint
                        );
                    }
    
                    // swap and clear buffers
                    final List<ResultPoint> temp = possibleResultPoints;
                    possibleResultPoints = lastPossibleResultPoints;
                    lastPossibleResultPoints = temp;
                    possibleResultPoints.clear();
                }
    
                //不断调用执行绘制该活动界面进出现自动的动画
                // Request another update at the animation interval, but only repaint the laser line,
                // not the entire viewfinder mask.
                postInvalidateDelayed(ANIMATION_DELAY,
                        frame.left - POINT_SIZE,
                        frame.top - POINT_SIZE,
                        frame.right + POINT_SIZE,
                        frame.bottom + POINT_SIZE);
            }
    
    */
    
    
    
        }
    
    
    }
    

    Step 5 :准备3个布局:

    activity_main.xml

    <?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"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        tools:context=".MainActivity">
    
        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="开始"
            android:id="@+id/button"/>
    
    </LinearLayout>
    

    content_scan.xml 其实这个布局,BarcodeView和ScanWidget相当于不在布局里,所以该布局像只有View控件,进而达到扫描条运动

    <?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">
    
        <!--
        layout_width、layout_height:启动扫描界面的布局参数
    
        zxing_framing_rect_width、zxing_framing_rect_height:
        在扫描界面中,只能扫描二维码的宽高,去掉后会有默认的宽高
        -->
        <com.journeyapps.barcodescanner.BarcodeView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/zxing_barcode_surface" />
    
        <!--注意自定义的扫描活动路径-->
        <com.gx.qr.ScanWidget
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/zxing_viewfinder_view"
            app:zxing_possible_result_points="@color/zxing_custom_possible_result_points"
            app:zxing_result_view="@color/zxing_custom_result_view"
            app:zxing_viewfinder_laser="@color/zxing_custom_viewfinder_laser"
            app:zxing_viewfinder_mask="@color/zxing_custom_viewfinder_mask"/>
    
        <!--@drawable/bmt 扫描条图标  请自行找素材-->
        <View
            android:id="@+id/scan_the"
            android:background="@drawable/bmt"
            android:layout_width="wrap_content"
            android:layout_height="70px" />
    
    </merge>
    

    activity_scan.xml 其实这个布局,DecoratedBarcodeView相当于不在布局里,所以该布局像只有ImageButton控件

    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        xmlns:app="http://schemas.android.com/apk/res-auto">
    
        <!--装扫描界面的控件
        @layout/content_scan:为嵌入content_scan.xml的布局
        -->
        <com.journeyapps.barcodescanner.DecoratedBarcodeView
            android:id="@+id/dbv"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_alignParentStart="true"
            app:zxing_scanner_layout="@layout/content_scan">
        </com.journeyapps.barcodescanner.DecoratedBarcodeView>
    
        <!--闪光灯图片 自行找图片样式
            @drawable/ic_flashlight_close 关闭时的图片
        -->
        <ImageButton
            android:id="@+id/ib_flashlight_close"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentBottom="true"
            android:layout_centerHorizontal="true"
            android:layout_marginBottom="60dp"
            android:background="@drawable/ic_flashlight_close"/>
    </RelativeLayout>
    

    Step 6 :ScanActivity

    public class ScanActivity extends AppCompatActivity {
        private CaptureManager capture;
        private ImageButton ibFlashlight;
        private DecoratedBarcodeView barcodeScannerView;
        private boolean bTorch = false;
    
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
    
            //==设置布局、获取控件
            setContentView(R.layout.activity_scan);
            barcodeScannerView = findViewById(R.id.dbv);
            ibFlashlight= findViewById(R.id.ib_flashlight_close);
    
            //==保存扫描条到Application里
            View view = findViewById(R.id.scan_the);
            MyApplication myApplication= (MyApplication) getApplication();
            myApplication.setView(view);
    
            //==监听: 根据barcodeScannerView设置闪光灯ibFlashlight状态
            barcodeScannerView.setTorchListener(new DecoratedBarcodeView.TorchListener() {
                @Override
                public void onTorchOn() {//开灯
    
                    //R.drawable.ic_flashlight_open)  开灯显示的图片 自行找图片样式
                    ibFlashlight.setBackground(getResources().getDrawable(R.drawable.ic_flashlight_open));
                    bTorch = true;
                }
    
                @Override
                public void onTorchOff() {//关灯
    
                    //R.drawable.ic_flashlight_close)  关灯显示的图片 自行找图片样式
                    ibFlashlight.setBackground(getResources().getDrawable(R.drawable.ic_flashlight_close));
                    bTorch = false;
                }
            });
    
            //==开或关灯
            ibFlashlight.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    if(bTorch){
                        barcodeScannerView.setTorchOff();
                    } else {
                        barcodeScannerView.setTorchOn();
                    }
    
                }
            });
    
            //==初始化活动
            capture = new CaptureManager(this, barcodeScannerView);
    
            capture.initializeFromIntent(getIntent(), savedInstanceState);
    
            capture.decode();
        }
    
    
        @Override
        protected void onResume() {
            super.onResume();
            capture.onResume();
        }
    
        @Override
        protected void onPause() {
            super.onPause();
            capture.onPause();
            barcodeScannerView.setTorchOff();
        }
    
        @Override
        protected void onDestroy() {
            super.onDestroy();
            capture.onDestroy();
        }
    
        @Override
        protected void onSaveInstanceState(Bundle outState) {
            super.onSaveInstanceState(outState);
            capture.onSaveInstanceState(outState);
        }
    
        @Override
        public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[], @NonNull int[] grantResults) {
            capture.onRequestPermissionsResult(requestCode, permissions, grantResults);
        }
    
        @Override
        public boolean onKeyDown(int keyCode, KeyEvent event) {
            return barcodeScannerView.onKeyDown(keyCode, event) || super.onKeyDown(keyCode, event);
        }
    }
    

    Step 7 :MainActivity

    public class MainActivity extends AppCompatActivity {
    
        private Button button;
    
        //  Step 1 : 初始化 获取控件 设置监听
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            button = findViewById(R.id.button);//点击跳转到扫码活动
    
            //控件监听
            listenerView();
        }
    
    
        private void listenerView() {
    
            //  Step 2 :跳转到扫描活动
            button.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
    
                    //=======设置扫描活动  可根据需求设置以下内容
                    IntentIntegrator intentIntegrator = new IntentIntegrator(MainActivity.this);
    
                    //启动自定义的扫描活动,不设置则启动默认的活动
                    intentIntegrator.setCaptureActivity(ScanActivity.class);
    
                    //启动扫描
                    intentIntegrator.initiateScan();
    
                }
            });
    
        }
    
    
        //  Step 3 :处理扫码后返回的结果
        @Override
        protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
            IntentResult result = IntentIntegrator.parseActivityResult(requestCode,resultCode,data);
    
            if(result!=null){
    
                //==是否扫到内容
                if (result.getContents()!=null){
                    Toast.makeText(this,"扫描结果:"+result.getContents(),Toast.LENGTH_LONG).show();
                }else{
                    Toast.makeText(this,"取消扫码",Toast.LENGTH_LONG).show();
                }
    
            }else{
                super.onActivityResult(requestCode, resultCode, data);
            }
    
        }
    
    
    
    }
    

    所用到的图片:

    ic_flashlight_close.png
    在这里插入图片描述

    ic_flashlight_open.png

    在这里插入图片描述

    bmt.png
    在这里插入图片描述

    OK!打完收工

    展开全文
  • 在实现扫一扫的功能的时候,我们需要绘制一个中间为透明的扫码框,其余部分为半透明。通常情况下,例如微信或者支付宝的扫码框都是矩形的,如果中间的扫码框是一个矩形,那么布局是很简单的,可是如果扫码框是一个...

    一、概述
    在实现扫一扫的功能的时候,我们需要绘制一个中间为透明的扫码框,其余部分为半透明。通常情况下,例如微信或者支付宝的扫码框都是矩形的,如果中间的扫码框是一个矩形,那么布局是很简单的,可是如果扫码框是一个圆角矩形,或者圆形等情况怎么办呢?这篇文章主要是记录绘制一个中间透明带圆角的矩形。

    按照惯例,我们先来看看效果图 :

    二、按照流程我们就开始来看看代码啦
    1、CustomDrawable,支持中间出现透明区域的drawable

    package per.juan.scandome;
    
    import android.graphics.Canvas;
    import android.graphics.ColorFilter;
    import android.graphics.Paint;
    import android.graphics.Path;
    import android.graphics.PorterDuff;
    import android.graphics.PorterDuffXfermode;
    import android.graphics.drawable.Drawable;
    import android.support.annotation.NonNull;
    import android.support.annotation.Nullable;
    
    /**
     * 支持中间出现透明区域的drawable
     * 通过{@link #setSrcPath(Path)}设定透明区域的形状
     * Created by juan on 2018/07/20.
     */
    public class CustomDrawable extends Drawable {
        private Paint srcPaint;
        private Path srcPath = new Path();
    
        private Drawable innerDrawable;
    
    
        public CustomDrawable(Drawable innerDrawable) {
            this.innerDrawable = innerDrawable;
            srcPath.addRect(100, 100, 200, 200, Path.Direction.CW);
            srcPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
            srcPaint.setColor(0xffffffff);
        }
    
        /**
         * 设置内部透明的部分
         *
         * @param srcPath
         */
        public void setSrcPath(Path srcPath) {
            this.srcPath = srcPath;
        }
    
        @Override
        public void draw(@NonNull Canvas canvas) {
            innerDrawable.setBounds(getBounds());
            if (srcPath == null || srcPath.isEmpty()) {
                innerDrawable.draw(canvas);
            } else {
                //将绘制操作保存到新的图层,因为图像合成是很昂贵的操作,将用到硬件加速,这里将图像合成的处理放到离屏缓存中进行
                int saveCount = canvas.saveLayer(0, 0, canvas.getWidth(), canvas.getHeight(), srcPaint, Canvas.ALL_SAVE_FLAG);
    
                //dst 绘制目标图
                innerDrawable.draw(canvas);
    
                //设置混合模式
                srcPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
                //src 绘制源图
                canvas.drawPath(srcPath, srcPaint);
                //清除混合模式
                srcPaint.setXfermode(null);
                //还原画布
                canvas.restoreToCount(saveCount);
            }
        }
    
        @Override
        public void setAlpha(int alpha) {
            innerDrawable.setAlpha(alpha);
        }
    
        @Override
        public void setColorFilter(@Nullable ColorFilter colorFilter) {
            innerDrawable.setColorFilter(colorFilter);
        }
    
        @Override
        public int getOpacity() {
            return innerDrawable.getOpacity();
        }
    }

    (1)主要用到的技术是PorterDuffXfermode的PorterDuff.Mode.XOR模式
    (2)核心思想是先正常绘制出整个drawable,然后将指定的区域混合成透明色

    2、CustomLayout

    package per.juan.scandome;
    
    import android.annotation.SuppressLint;
    import android.content.Context;
    import android.graphics.Path;
    import android.support.annotation.NonNull;
    import android.support.annotation.Nullable;
    import android.util.AttributeSet;
    import android.view.View;
    import android.widget.FrameLayout;
    
    /**
     * 根据layout中子View的位置,确定局部透明区域
     * Created by juan on 2018/07/20.
     */
    public class CustomLayout extends FrameLayout {
    
        private Context mContext;
        private CustomDrawable background;
    
        public CustomLayout(@NonNull Context context) {
            super(context);
            initView(context, null, 0);
        }
    
        public CustomLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
            super(context, attrs);
            this.mContext=context;
            initView(context, attrs, 0);
        }
    
        public CustomLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            initView(context, attrs, defStyleAttr);
        }
    
        @SuppressLint("NewApi")
        private void initView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
            background = new CustomDrawable(getBackground());
            setBackground(background);
        }
    
        @Override
        protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
            super.onLayout(changed, left, top, right, bottom);
            resetBackgroundHoleArea();
        }
    
        @SuppressLint("NewApi")
        private void resetBackgroundHoleArea() {
            Path path = null;
            // 以子View为范围构造需要透明显示的区域
            View view = findViewById(R.id.iv_scan);
            if (view != null) {
                path = new Path();
                // 矩形透明区域
                path.addRoundRect(view.getLeft(), view.getTop(), view.getRight(), view.getBottom(), dp2Px(mContext,10), dp2Px(mContext,10),Path.Direction.CW);
            }
            if (path != null) {
                background.setSrcPath(path);
            }
        }
    
        public int dp2Px(Context context, float dp) {
            final float scale = context.getResources().getDisplayMetrics().density;
            return (int) (dp * scale + 0.5f);
        }
    }
    

    3、然后在XML布局中声明我们的自定义View

    <?xml version="1.0" encoding="utf-8"?>
    <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:id="@+id/frame_layout"
        android:layout_height="match_parent">
    
        <ImageView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="@mipmap/bg_image" />
    
        <per.juan.scandome.CustomLayout
            android:layout_width="match_parent"
            android:id="@+id/layout"
            android:background="#8c565658"
            android:layout_height="match_parent">
    
            <!-- 根据这个子View所在的位置,计算出透明矩形的位置 -->
            <FrameLayout
                android:id="@+id/iv_scan"
                android:layout_width="200dp"
                android引用块内容center" />
    
        </per.juan.scandome.CustomLayout>
    </FrameLayout>

    好了,本篇文章就这样了,存在不足的地方还望指导,感谢^_^

    附录:
    自定义Drawable之:在Drawable中部指定透明区域

    展开全文
  • 0. 最近项目里需要实现二维码的扫描功能,扫描两个二维码然后得到数据进行绑定。目前比较常见的二维码扫描库就是zxing和zbar了,zxing是google官方的开源项目,有专门的维护,java编写。zbar使用C语言写的,而且...

    0.

    最近项目里需要实现二维码的扫描功能,扫描两个二维码然后得到数据进行绑定。目前比较常见的二维码扫描库就是zxing和zbar了,zxing是google官方的开源项目,有专门的维护,java编写。zbar使用C语言写的,而且github上多年没有代码提交了,所以我决定选用zxing。

    1.

    附上zxing的项目地址:
    zxing
    打开zxing的github地址,发现似乎没有如何接入的文档。没关心,没有文档,但是有demo,我们要做的就是修改demo,移除无用的功能,只保留二维码的扫描和识别。

    2.

     

     

    下载项目后,里面很多东西我们是不需要的,我们需要的就是这个,如图所示

    screenshot.png

     

    这个就是刚才所说的android的demo,新建一个android项目,将这个module导入工程并命名为zxinglib,在这个module里的gradle文件里添加依赖。

    dependencies{
        api 'com.google.zxing:android-core:3.3.0'
        api 'com.google.zxing:core:3.3.2'
    }
    

    运行这个module,你会发现这就是一个已经集成好zxing二维码扫描的app,同时还有一些不需要的功能,比如创建二维码,历史记录等等,而且还是相机预览还是横屏。下面我们就分析一下这个demo的二维码识别流程

    3.

    首先打开AndroidManifest文件,找到含有

        <intent-filter>
          <action android:name="android.intent.action.MAIN"/>
          <category     android:name="android.intent.category.LAUNCHER"/>
        </intent-filter>
    

    的Activity,发现是CaptureActivity,发现他还有好多其他intent-filter,CaptureActivity是可以被其他应用打开的,既然找到了入口,那就进去分析吧。
    先看onCreate方法:

    @Override
      public void onCreate(Bundle icicle) {
       super.onCreate(icicle);
    Window window = getWindow();   window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
    setContentView(R.layout.capture);
    hasSurface = false;
    inactivityTimer = new InactivityTimer(this);
    beepManager = new BeepManager(this);
    ambientLightManager = new AmbientLightManager(this);
    
    PreferenceManager.setDefaultValues(this, R.xml.preferences, false);}
    

    window设置标志位,保证屏幕常亮不会黑屏,inactivityTimer保证在电量较低的时候且一段时间没有激活的时候,关闭CaptureActivity,
    beepManager是用来扫码时发出声音和震动的,ambientLightManager是用来控制感光的,以此来控制闪光灯的开闭。然后我们再来看布局文件:

    <SurfaceView android:id="@+id/preview_view"
               android:layout_width="fill_parent"
               android:layout_height="fill_parent"/>
    
      <com.google.zxing.client.android.ViewfinderView
      android:id="@+id/viewfinder_view"
      android:layout_width="fill_parent"
      android:layout_height="fill_parent"/>
    

    布局文件里比较重要的就是这两个,一个surfaceview和一个viewfinderView,一个是照相机用来预览的界面,一个是取景框的界面,剩下的控价都是用来展示扫描结果的,和我们的需求没有太大关系,这里就不说了。

    4.

    扫描二维码自然不能少的就是相机的调用了,在AndroidManifest文件里,我们也看到了相关权限的声明

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

    有相机,震动和闪光灯的权限。
    接着看一下是在哪里初始化相机并调用预览的,在onReumse里
    有一段代码

    SurfaceView surfaceView = (SurfaceView) findViewById(R.id.preview_view);
    SurfaceHolder surfaceHolder = surfaceView.getHolder();
    if (hasSurface) {
      // The activity was paused but not stopped, so the surface still exists. Therefore
      // surfaceCreated() won't be called, so init the camera here.
      initCamera(surfaceHolder);
    } else {
      // Install the callback and wait for surfaceCreated() to init the camera.
      surfaceHolder.addCallback(this);
    }
    

    这里初始化了surfaceview,同时判断surface是否为true,true就调用initcamera,否在就为surfaceholder添加回调。刚才已经看到了,hasSurface在onCreate的时候赋值为false,之后在onResume也没有进行true的赋值,所以这里在第一次打开的时候,hasSurface=false。
    那我们就要关注surfaceHolder的回调了

     @Override
     public void surfaceCreated(SurfaceHolder holder) {
    if (holder == null) {
      Log.e(TAG, "*** WARNING *** surfaceCreated() gave us a null surface!");
    }
    if (!hasSurface) {
      hasSurface = true;
      initCamera(holder);
    }
      }
    

    在这里我们发现在surface创建后还是调用了initCamera,进入initCamera看看里面有什么

    private void initCamera(SurfaceHolder surfaceHolder) {
    if (surfaceHolder == null) {
      throw new IllegalStateException("No SurfaceHolder provided");
    }
    if (cameraManager.isOpen()) {
      Log.w(TAG, "initCamera() while already open -- late SurfaceView callback?");
      return;
    }
    try {
      cameraManager.openDriver(surfaceHolder);
      // Creating the handler starts the preview, which can also throw a RuntimeException.
      if (handler == null) {
        handler = new CaptureActivityHandler(this, decodeFormats, decodeHints, characterSet, cameraManager);
      }
      decodeOrStoreSavedBitmap(null, null);
    } catch (IOException ioe) {
      Log.w(TAG, ioe);
      displayFrameworkBugMessageAndExit();
    } catch (RuntimeException e) {
      // Barcode Scanner has seen crashes in the wild of this variety:
      // java.?lang.?RuntimeException: Fail to connect to camera service
      Log.w(TAG, "Unexpected error initializing camera", e);
      displayFrameworkBugMessageAndExit();
     }
      }
    

    这里我们关心两个点,cameraManager.openDriver(surfaceHolder)和CaptureActivityHandler,进入openDriver这个方法

    public synchronized void openDriver(SurfaceHolder holder) throws IOException {
    OpenCamera theCamera = camera;
    if (theCamera == null) {
      theCamera = OpenCameraInterface.open(requestedCameraId);
      if (theCamera == null) {
        throw new IOException("Camera.open() failed to return object from driver");
      }
      camera = theCamera;
    }
    
    if (!initialized) {
      initialized = true;
      configManager.initFromCameraParameters(theCamera);
      if (requestedFramingRectWidth > 0 && requestedFramingRectHeight > 0) {
        setManualFramingRect(requestedFramingRectWidth, requestedFramingRectHeight);
        requestedFramingRectWidth = 0;
        requestedFramingRectHeight = 0;
      }
    }
    
    Camera cameraObject = theCamera.getCamera();
    Camera.Parameters parameters = cameraObject.getParameters();
    String parametersFlattened = parameters == null ? null : parameters.flatten(); // Save these, temporarily
    try {
      configManager.setDesiredCameraParameters(theCamera, false);
    } catch (RuntimeException re) {
      // Driver failed
      Log.w(TAG, "Camera rejected parameters. Setting only minimal safe-mode parameters");
      Log.i(TAG, "Resetting to saved camera params: " + parametersFlattened);
      // Reset:
      if (parametersFlattened != null) {
        parameters = cameraObject.getParameters();
        parameters.unflatten(parametersFlattened);
        try {
          cameraObject.setParameters(parameters);
          configManager.setDesiredCameraParameters(theCamera, true);
        } catch (RuntimeException re2) {
          // Well, darn. Give up
          Log.w(TAG, "Camera rejected even safe-mode parameters! No configuration");
        }
      }
    }
    cameraObject.setPreviewDisplay(holder);
      }
    

    这里可以发现,主要是针对camera的一些参数的设定,另外还要说明的一点是sdk21以后,以前的camera类已经废弃了,google又给出了camera2来替他他们,但是目前zxing这个库里还没有使用camera2,关于camera的相关问题,等以后有时间单独来写一篇文章,这里我们主要针对的是流程的分析。这个方法里我们发现调用了CameraConfigurationManager的initFromCameraParameters和setDesiredCameraParameters,这两个方法里找出了相机预览的最佳大小和根据屏幕进行camera方向的旋转,感兴趣的话可以看一下这两个方法。
    接下来我们来看CaptureActivityHandler

    CaptureActivityHandler(CaptureActivity activity,
                         Collection<BarcodeFormat> decodeFormats,
                         Map<DecodeHintType,?> baseHints,
                         String characterSet,
                         CameraManager cameraManager) {
    this.activity = activity;
    decodeThread = new DecodeThread(activity, decodeFormats, baseHints, characterSet,
        new ViewfinderResultPointCallback(activity.getViewfinderView()));
    decodeThread.start();
    state = State.SUCCESS;
    
    // Start ourselves capturing previews and decoding.
    this.cameraManager = cameraManager;
    cameraManager.startPreview();
    restartPreviewAndDecode();
    }
    

    在它的构造函数里我们发现它开启了相机的预览,同时启动了DecodeThread,再看看DeocodeThread的run方法

     @Override
      public void run() {
        Looper.prepare();
        handler = new DecodeHandler(activity, hints);
        handlerInitLatch.countDown();
        Looper.loop();
      }
    

    这里又实例化了一个DeoceHandler,好,那就进入DeoceHandler,看看这货又是什么

    private void decode(byte[] data, int width, int height) {
    long start = System.currentTimeMillis();
    Result rawResult = null;
    PlanarYUVLuminanceSource source = activity.getCameraManager().buildLuminanceSource(data, width, height);
    if (source != null) {
      BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
      try {
        rawResult = multiFormatReader.decodeWithState(bitmap);
      } catch (ReaderException re) {
        // continue
      } finally {
        multiFormatReader.reset();
      }
    }
    
    Handler handler = activity.getHandler();
    if (rawResult != null) {
      // Don't log the barcode contents for security.
      long end = System.currentTimeMillis();
      Log.d(TAG, "Found barcode in " + (end - start) + " ms");
      if (handler != null) {
        Message message = Message.obtain(handler, R.id.decode_succeeded, rawResult);
        Bundle bundle = new Bundle();
        bundleThumbnail(source, bundle);        
        message.setData(bundle);
        message.sendToTarget();
      }
    } else {
      if (handler != null) {
        Message message = Message.obtain(handler, R.id.decode_failed);
        message.sendToTarget();
      }
    }
      }
    

    这个decode方法,就是我们心心念念的用来解析二维码的地方,multiFormatReader.decodeWithState(bitmap);得到结果后,返回给CaptureActivityHandler,captureActivityHandler在接到R.id.decode_succeededde message后,会调用CaptureActivity的handleDecode方法,在这里会调用

     // Put up our own UI for how to handle the decoded contents.
    

    private void handleDecodeInternally(Result rawResult, ResultHandler resultHandler, Bitmap barcode) {

    maybeSetClipboard(resultHandler);
    
    SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
    
    if (resultHandler.getDefaultButtonID() != null && prefs.getBoolean(PreferencesActivity.KEY_AUTO_OPEN_WEB, false)) {
      resultHandler.handleButtonPress(resultHandler.getDefaultButtonID());
      return;
    }
    
    statusView.setVisibility(View.GONE);
    viewfinderView.setVisibility(View.GONE);
    resultView.setVisibility(View.VISIBLE);
    
    ImageView barcodeImageView = (ImageView) findViewById(R.id.barcode_image_view);
    if (barcode == null) {
      barcodeImageView.setImageBitmap(BitmapFactory.decodeResource(getResources(),
          R.drawable.launcher_icon));
    } else {
      barcodeImageView.setImageBitmap(barcode);
    }
    
    TextView formatTextView = (TextView) findViewById(R.id.format_text_view);
    formatTextView.setText(rawResult.getBarcodeFormat().toString());
    
    TextView typeTextView = (TextView) findViewById(R.id.type_text_view);
    typeTextView.setText(resultHandler.getType().toString());
    
    DateFormat formatter = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT);
    TextView timeTextView = (TextView) findViewById(R.id.time_text_view);
    timeTextView.setText(formatter.format(rawResult.getTimestamp()));
    
    
    TextView metaTextView = (TextView) findViewById(R.id.meta_text_view);
    View metaTextViewLabel = findViewById(R.id.meta_text_view_label);
    metaTextView.setVisibility(View.GONE);
    metaTextViewLabel.setVisibility(View.GONE);
    Map<ResultMetadataType,Object> metadata = rawResult.getResultMetadata();
    if (metadata != null) {
      StringBuilder metadataText = new StringBuilder(20);
      for (Map.Entry<ResultMetadataType,Object> entry : metadata.entrySet()) {
        if (DISPLAYABLE_METADATA_TYPES.contains(entry.getKey())) {
          metadataText.append(entry.getValue()).append('\n');
        }
      }
      if (metadataText.length() > 0) {
        metadataText.setLength(metadataText.length() - 1);
        metaTextView.setText(metadataText);
        metaTextView.setVisibility(View.VISIBLE);
        metaTextViewLabel.setVisibility(View.VISIBLE);
      }
    }
    
    CharSequence displayContents = resultHandler.getDisplayContents();
    TextView contentsTextView = (TextView) findViewById(R.id.contents_text_view);
    contentsTextView.setText(displayContents);
    int scaledSize = Math.max(22, 32 - displayContents.length() / 4);
    contentsTextView.setTextSize(TypedValue.COMPLEX_UNIT_SP, scaledSize);
    
    TextView supplementTextView = (TextView) findViewById(R.id.contents_supplement_text_view);
    supplementTextView.setText("");
    supplementTextView.setOnClickListener(null);
    if (PreferenceManager.getDefaultSharedPreferences(this).getBoolean(
        PreferencesActivity.KEY_SUPPLEMENTAL, true)) {
      SupplementalInfoRetriever.maybeInvokeRetrieval(supplementTextView,
                                                     resultHandler.getResult(),
                                                     historyManager,
                                                     this);
    }
    
    int buttonCount = resultHandler.getButtonCount();
    ViewGroup buttonView = (ViewGroup) findViewById(R.id.result_button_view);
    buttonView.requestFocus();
    for (int x = 0; x < ResultHandler.MAX_BUTTON_COUNT; x++) {
      TextView button = (TextView) buttonView.getChildAt(x);
      if (x < buttonCount) {
        button.setVisibility(View.VISIBLE);
        button.setText(resultHandler.getButtonText(x));
        button.setOnClickListener(new ResultButtonListener(resultHandler, x));
      } else {
        button.setVisibility(View.GONE);
      }
    }
    }
    

    用来展示最终的结果。
    那么DeoceHandler又是什么时候调用deocode方法的呢?

    @Override
      public void handleMessage(Message message) {
       if (message == null || !running) {
      return;
    }
    switch (message.what) {
      case R.id.decode:
        decode((byte[]) message.obj, message.arg1, message.arg2);
        break;
      case R.id.quit:
        running = false;
        Looper.myLooper().quit();
        break;
    }
      }
    

    DeoceHandler在收到what==R.id.deocde的message时会调用decode方法,那么是谁发送的这个message呢?还记得CaptureActivityHandler的构造函数里,调用了restartPreviewAndDeocode方法

    private void restartPreviewAndDecode() {
    if (state == State.SUCCESS) {
      state = State.PREVIEW;
      cameraManager.requestPreviewFrame(decodeThread.getHandler(), R.id.decode);
      activity.drawViewfinder();
    }
    

    这个方法里不仅调用了drawViewFinder,绘制了取景框,还调用了
    cameraManager.requestPreviewFrame(decodeThread.getHandler(), R.id.decode);这里传入了decodehandler,它又做了什么?

    public synchronized void requestPreviewFrame(Handler handler, int message) {
    OpenCamera theCamera = camera;
    if (theCamera != null && previewing) {
      previewCallback.setHandler(handler, message);
      theCamera.getCamera().setOneShotPreviewCallback(previewCallback);
    }
      }
    

    原来这个方法为camera设置了previewcallback,同时previewcallback还持有decodehandler的引用,这个previewcallback的

    public void onPreviewFrame(byte[] data, Camera camera) {
    Point cameraResolution = configManager.getCameraResolution();
    Handler thePreviewHandler = previewHandler;
    if (cameraResolution != null && thePreviewHandler != null) {
      Message message = thePreviewHandler.obtainMessage(previewMessage, cameraResolution.x,
          cameraResolution.y, data);
      message.sendToTarget();
      previewHandler = null;
    } else {
      Log.d(TAG, "Got preview callback, but no handler or resolution available");
    }
    

    }
    在这里Message message = thePreviewHandler.obtainMessage(previewMessage, cameraResolution.x,
    cameraResolution.y, data);
    message.sendToTarget();
    发送了what=previewMessage,而这个previewMessage就是之前CaptureActivityHandler传入的R.id.decode。
    那么camera的setOneShotPreviewCallback这个方法是用来干什么的?查看源码看注释
    单个预览帧将被返回给提供的处理程序。 数据将以byte []形式到达在message.obj字段中,宽度和高度编码为message.arg1message.arg2。
    至此一个从预览到识别解析的流程差不多就分析完了,围绕这些,那些demo里的不需要的东西就可以删除了。

    最后附上git地址:
    github



    作者:滑板上的老砒霜
    链接:https://www.jianshu.com/p/a4ba10da4231
    來源:简书
    简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

    转载于:https://my.oschina.net/JiangTun/blog/3009237

    展开全文

空空如也

1 2 3 4 5 ... 20
收藏数 1,753
精华内容 701
关键字:

android 扫一扫