Android:实际运用Zxing集成二维码扫描 及 自定义扫码界面(demo源码)

Posted 鸽一门

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android:实际运用Zxing集成二维码扫描 及 自定义扫码界面(demo源码)相关的知识,希望对你有一定的参考价值。

二维码扫描,各大主流App必不可少的功能,而且google已将轮子替我们造好,直接拿来使用即可。以下是教学如何将Zxing开源库集成到自己项目中,并且自定义扫码界面,后期可根据自己的业务需求进行修改,最后补充了一点由此延伸的学习技能点。

一. 集成Zing开源库到应用中

如上图所示,我使用的集成方法是拷入jar包,然后拷贝相关类即可,具体的资源都已经在Demo中做好了,具体内容都可以直接参考Demo。(看到网上还有其它集成的方法,虽没试过,各位也可以考虑)

  • app 扫描Activity
  • camera 摄像头相关
  • decode 解析二维码相关
  • encode 生成二维码相关
  • util 工具类
  • view 扫描预览视图ViewfinderView




二. 自定义扫码界面

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent" >

    <RelativeLayout
        android:layout_width="fill_parent"
        android:layout_height="fill_parent" >

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

        <cn.edu.wsyu.hgh.zhiliao.component.zxing.view.ViewfinderView
            android:id="@+id/viewfinder_view"
            android:layout_width="fill_parent"
            android:layout_height="fill_parent" />
    </RelativeLayout>

</FrameLayout>

如果集成Zxing成功后打开扫描功能,你会发现扫描页面如上图所示,这是CaptureActivity的布局,CaptureActivity主要来处理扫描界面的逻辑。布局仅有一个SurfaceView,中间一个供扫描的矩形框ViewfinderView就目前而言,扫描的功能已经可以使用了,但是这种UI效果是市场上的app不符,所以需要自定义扫码页面,而理想实现效果如下图所示:

1. 需要实现的需求

对比以上两张图,总结出我们需要实现的需求

  • (1) 增加一个toolbar,内容填充为返回键和界面提示。
  • (2) 中间的扫描区域—–即Zxing为我们绘制的矩形的四个角上分别画了8个绿色小矩形和一条不停上下移动的扫描线。
  • (3) 在最下方加了一个footer,增加了相册、开灯两个功能使得这个扫描功能更加完善。

经过以上分析可得,完成上述3个需求,就可以达到理想的UI效果了,接下来依次实现:



2. 实现头部效果

这个头部实现实在很简单,这里不过多赘述,提供2种实现思路:

  • 1 使用Android提供控件Toolbar,在控件中再加一个TextView用来显示“扫一扫”即可。
  • 2 直接加一个布局View,布局中一个ImageView、TextView完成

布局完成后,在CaptureActivity中设置对应的点击事件,处理返回逻辑即可。



3. 实现扫描框效果

这个扫描框是Zxing自带的一个自定义View,名为ViewfinderView,而目前需要做的需求:

  • 1 扫码框的四个角上,每个角有两条边,总共八条,绘制8条绿色矩形,完成边框。
  • 2 在扫码框中绘制一条不停上下移动的线。
  • 3 在扫描框下方添加一条提示语句

3.1 绘制扫描边框

熟悉自定义View的人首先想到在ViewfinderView中的onDraw方法中完成绘制,首先来看它默认绘制的onDraw方法:

 @Override
    public void onDraw(Canvas canvas) 
       //获得扫描框的矩形
        Rect frame = CameraManager.get().getFramingRect();
        if (frame == null) 
            return;
        

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

        //绘制扫描框矩形
        // 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);

        Collection<ResultPoint> currentPossible = possibleResultPoints;
        Collection<ResultPoint> currentLast = lastPossibleResultPoints;
        if (currentPossible.isEmpty()) 
             lastPossibleResultPoints = null;
         else 
        possibleResultPoints = new HashSet<ResultPoint>(5);
        lastPossibleResultPoints = currentPossible;
         paint.setAlpha(OPAQUE);
         paint.setColor(resultPointColor);
         for (ResultPoint point : currentPossible) 
              canvas.drawCircle(frame.left + point.getX(), frame.top
                     + point.getY(), 6.0f, paint);
                
            
          if (currentLast != null) 
               paint.setAlpha(OPAQUE / 2);
               paint.setColor(resultPointColor);
               for (ResultPoint point : currentLast) 
                    canvas.drawCircle(frame.left + point.getX(), frame.top
                            + point.getY(), 3.0f, paint);
                
            

          postInvalidateDelayed(ANIMATION_DELAY, frame.left, frame.top,
                    frame.right, frame.bottom);
        

初步了解后,需要使用canvas来绘制8个小矩形,代码不难如下

paint.setColor(Color.BLUE);
            canvas.drawRect(frame.left, frame.top, frame.left + ScreenRate,
                    frame.top + CORNER_WIDTH, paint);
            canvas.drawRect(frame.left, frame.top, frame.left + CORNER_WIDTH,
                    frame.top + ScreenRate, paint);
            canvas.drawRect(frame.right - ScreenRate, frame.top, frame.right,
                    frame.top + CORNER_WIDTH, paint);
            canvas.drawRect(frame.right - CORNER_WIDTH, frame.top, frame.right,
                    frame.top + ScreenRate, paint);
            canvas.drawRect(frame.left, frame.bottom - CORNER_WIDTH, frame.left
                    + ScreenRate, frame.bottom, paint);
            canvas.drawRect(frame.left, frame.bottom - ScreenRate, frame.left
                    + CORNER_WIDTH, frame.bottom, paint);
            canvas.drawRect(frame.right - ScreenRate, frame.bottom
                    - CORNER_WIDTH, frame.right, frame.bottom, paint);
            canvas.drawRect(frame.right - CORNER_WIDTH, frame.bottom
                    - ScreenRate, frame.right, frame.bottom, paint);

这值得注意的是ScreenRate,即小矩形的长度,这里并没有把它固定化,而是将理想值乘以屏幕密度,来更好地适配不同大小屏幕:

            density = context.getResources().getDisplayMetrics().density;
            ScreenRate = (int) (15 * density);

3.2 绘制移动的线

  //定义好扫描线每秒移动端的像素
            slideTop += SPEEN_DISTANCE;
            //判断扫描线的长度是否大于扫描框底部,若大于则重新回到扫描框头部再进行扫描
            if (slideTop >= frame.bottom) 
                slideTop = frame.top;
            

            //动态定义出扫描线矩形
            Rect lineRect = new Rect();
            lineRect.left = frame.left;
            lineRect.right = frame.right;
            lineRect.top = slideTop;
            lineRect.bottom = slideTop + 18;
            //绘制上去
            canvas.drawBitmap(((BitmapDrawable) (getResources()
                            .getDrawable(R.drawable.fle))).getBitmap(), null, lineRect,
                    paint);

3.3 绘制文字

  paint.setColor(Color.WHITE);
            paint.setTextSize(TEXT_SIZE * density);
            paint.setAlpha(0x40);
            paint.setTypeface(Typeface.create("System", Typeface.BOLD));
            String text = getResources().getString(R.string.scan_text);
            float textWidth = paint.measureText(text);

            canvas.drawText(
                    text,
                    (width - textWidth) / 2,
                    (float) (frame.bottom + (float) TEXT_PADDING_TOP * density),
                    paint);



三. 扫码功能完善

1. 扫码界面功能完善

以上界面完成后,需要在CaptureActivity中稍加完善几个按钮(开闪光灯、相册)的功能:
之所以选择Zxing开源库的一大原因是因为它将所有的方法都封装完善,只需我们调用即可,例如开启或关闭闪光灯。

     @Override
    public void onClick(View v) 
        switch (v.getId()) 
            case R.id.flash_btn:// 闪光灯开关
                if (isTorchOn) 
                    isTorchOn = false;
                    flash_btn.setText("关灯");
                    CameraManager.start();
                 else 
                    isTorchOn = true;
                    flash_btn.setText("开灯");
                    CameraManager.stop();
                
                break;
            case R.id.photo_btn: // 选择相册
                selectPhoto();
            
            break;
            case R.id.button_back: // 返回
                finish();
            
            break;
            default:
                break;
        
    


2 主页跳转到扫码界面

MainActivity

    private static final int REQUEST_QRCODE = 0x01;

    Intent intent = new Intent(mContext, CaptureActivity.class);
    startActivityForResult(intent, REQUEST_QRCODE);

以上代码非常简单,假设是在MainActivity中进行操作,点击对应按钮进入到CaptureActivity中,唯一需要注意的是跳转页面使用的方法是startActivityForResult,因为我们需要获取到扫描后的数据,然后做下一步的处理。



3二维码生成

生成二维码的方式:

   /**
     * 二维码生成
     */
    private void qrCodeGenerated() 

        String str = "点个赞噻";

        DisplayMetrics metric = new DisplayMetrics();
        getWindowManager().getDefaultDisplay().getMetrics(metric);
        int width = metric.widthPixels;
        try 

            Bitmap qrCode = Utils.createQRCode(str, width / 2);
            qrcodeImg.setImageBitmap(qrCode);

         catch (WriterException e) 
            // TODO Auto-generated catch block
            e.printStackTrace();
        
    

str代表二维码中的数据,主要依赖的是Util工具类的方法,其中还有二维码中含Logo的方式,自己可修改,两种方法如下:

    /**
     * 生成一个二维码图像
     * 
     * @param url
     *            传入的字符串,通常是一个URL
     * @param widthAndHeight
     *            图像的宽高
     * @return
     */
    private static final int BLACK = 0xff000000;

    public static Bitmap createQRCode(String str, int widthAndHeight)
    /**
     * 生成一个有logo的二维码图像
     * 
     * @param url
     *            传入的字符串,通常是一个URL
     * @param widthAndHeight
     *            图像的宽高
     * @return
     */

    public static Bitmap createQRCodeLogo(String str, int widthAndHeight,
                                          Bitmap bit)


4. 主页获取扫码数据并处理

要想在MainActivity 中的 onActivityResult方法获取到扫描的数据,必须要知道什么时候将数据setResult进来的,获取数据的Key值是什么?

解析二维码是个耗时的操作,必然在子线程中执行,所以我们直接查看CaptureActivity对应的Handle——CaptureActivityHandle,通过类注释可知,这个类负责处理所有扫码相关的业务,既然是个Handle,那我们直接来看handleMessage方法:

 @Override
    public void handleMessage(Message message) 

        ......

       case R.id.decode_succeeded:// 解码成功
            Log.d(TAG, "Got decode succeeded message");
            state = State.SUCCESS;
            Bundle bundle = message.getData();

            /****************************************************/
            Bitmap barcode = bundle == null ? null : (Bitmap) bundle
                    .getParcelable(DecodeThread.BARCODE_BITMAP);

            //将扫码后得的数据返回给CaptureActivity去处理
            activity.handleDecode((Result) message.obj, barcode);
            break;

        ...... 
    

handleMessage方法中考虑了好几种情况,我们只需要了解这一种即可,将状态标记为“成功”,将扫码后得的数据返回给CaptureActivity去处理,所以最后还是回到CaptureActivityhandleDecode方法:

    /**
     * 处理扫描结果
     *
     * @param result
     * @param barcode
     */
    public void handleDecode(Result result, Bitmap barcode) 
        inactivityTimer.onActivity();
        playBeepSoundAndVibrate();
        String resultString = result.getText();
        if (resultString.equals("")) 
            Toast.makeText(this, "扫描失败!", Toast.LENGTH_SHORT)
                    .show();
         else 
            Intent data = new Intent();
            data.putExtra("SCAN_RESULT", resultString);
            setResult(RESULT_OK, data);
            finish();
        
    

主要逻辑为将获取到扫码后的数据放入到Intent中,结束当前扫码页面。重要的是:我们获取到了Key值:SCAN_RESULT此时,我们可以在MainActivity中获取到扫码后的数据并处理它。以上只是举一个实例,不仅这个Key值,还有其余需要使用的都可在CaptureActivity找到,如下表格:

含义KeyresultCode
直接扫码后获得的数据SCAN_RESULTActivity.RESULT_OK
相册图片扫码后获得的数据LOCAL_PHOTO_RESULT300
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) 
        switch (requestCode) 
            case REQUEST_QRCODE:
                if (resultCode == Activity.RESULT_OK) 
                    //扫描后的业务逻辑
                    String code = data.getStringExtra("SCAN_RESULT");
                    if (code.contains("http") || code.contains("https")) 
                        //打开链接
                        /*Intent intent = new Intent(this, AdBrowserActivity.class);
                        intent.putExtra(AdBrowserActivity.KEY_URL, code);
                        startActivity(intent);*/
                        Toast.makeText(this, code, Toast.LENGTH_SHORT).show();

                     else 
                        Toast.makeText(this, code, Toast.LENGTH_SHORT).show();
                    
                else if(resultCode == 300)
                    //从本地相册扫描后的业务逻辑
                    String code = data.getStringExtra("LOCAL_PHOTO_RESULT");
                    Toast.makeText(this, code, Toast.LENGTH_SHORT).show();
                
                break;
        

    

以上代码通过Key值获取到扫码后的数据data.getStringExtra("SCAN_RESULT")。这里多做了一个判断,如果数据包含http或者https时,可以用浏览器打开,这用用户体验会更好,当然,你也可以按照自己的想法去处理。




四. 拓展学习——闪光灯使用

在集成使用一个开源库时,可是适当学习它的源码设计、封装风格等等,例如Zxing中已实现的使用闪光灯,单独将此代码拿出来,平常在开发中也会有使用到闪光灯的场景,大可以将此方法放到自己的工具库中,使用的时候调用即可,此刻可以很好的参考参考大神封装的方法:

CameraManager.java

    private Camera camera;
    public static void init(Context context) 
        if (cameraManager == null) 
            cameraManager = new CameraManager(context);
        
    

    /**
     * 通过设置Camera打开闪光灯
     */
    public void turnLightOn() 
        if (camera == null) 
            return;
        
        Parameters parameters = camera.getParameters();
        if (parameters == null) 
            return;
        

        List<String> flashModes = parameters.getSupportedFlashModes();
        if (flashModes == null) 
            return;
        
        String flashMode = parameters.getFlashMode();
        Log.i(TAG, "Flash mode: " + flashMode);
        Log.i(TAG, "Flash modes: " + flashModes);
        // 闪光灯关闭状态
        if (!Parameters.FLASH_MODE_TORCH.equals(flashMode)) 
            // Turn on the flash
            if (flashModes.contains(Parameters.FLASH_MODE_TORCH)) 
                parameters.setFlashMode(Parameters.FLASH_MODE_TORCH);
                camera.setParameters(parameters);
                camera.startPreview();
             else 
            
        
    

    /**
     * 通过设置Camera关闭闪光灯
     * 
     * @param mCamera
     */
    public void turnLightOff() 
        if (camera == null) 
            return;
        
        Parameters parameters = camera.getParameters();
        if (parameters == null) 
            return;
        
        List<String> flashModes = parameters.getSupportedFlashModes();
        String flashMode = parameters.getFlashMode();
        // Check if camera flash exists
        if (flashModes == null) 
            return;
        
        // 闪光灯打开状态
        if (!Parameters.FLASH_MODE_OFF.equals(flashMode)) 
            // Turn off the flash
            if (flashModes.contains(Parameters.FLASH_MODE_OFF)) 
                parameters.setFlashMode(Parameters.FLASH_MODE_OFF);
                camera.setParameters(parameters);
             else 
                Log.e(TAG, "FLASH_MODE_OFF not supported");
            
        
    



最后附加一点自己的最近的想法:

最近这几个月写博客写的不像初学Android时那么勤了,与同伴在做一个App,项目中用了不少开源库,这个Zxing也是其中一个。写完这篇博客不由得扪心自问,这些东西仅仅停留在会使用的阶段,不算是真正的学习,就像我这篇博客,是一篇参考型文章,并非学习类文章,通篇看下来也不会有恍然大悟的感觉,作为笔者的我感觉也没有深层次的学到什么,正如很多大牛说过的:许多开源库不是会用就够了,我们还需要学习它的实现封装等等,这才能使我们进步(鸡汤奉上)……

所以接下来打算学习一些偏向底层的干货,缓解近期的迷茫。将自己的想法分享给各位志同道合的friends,在未来杠 Android的路上继续努力,哈哈哈~


希望对你们有帮助 :)


源码奉上~~
ZxingDemo源码

以上是关于Android:实际运用Zxing集成二维码扫描 及 自定义扫码界面(demo源码)的主要内容,如果未能解决你的问题,请参考以下文章

android中zxing扫描条码没有声音

android开发 如何实现扫描本地二维码图片

android zxing 怎么获得扫码时间

android zxing扫码 扫描二维码有对焦的黄点怎么去除

Android实战——Zxing实现二维码扫描

Android项目实战(四十四):Zxing二维码切换横屏扫描