站在巨人的肩膀上——二维码扫描功能的实现和优化过程

简介: 前段时间,因为开发需求要求加上二维码扫描的功能,所以我也是通过网上一系列的资料学习实现了这个功能,很感谢网上一些大牛提供的学习博客,特别是http://blog.csdn.net/xiaanming/article/details/10163203这篇博客对我的帮助很大,我也是在这个上面做了一些改进和优化,最后终于在我们的项目中加入了二维码扫描的功能。

前段时间,因为开发需求要求加上二维码扫描的功能,所以我也是通过网上一系列的资料学习实现了这个功能,很感谢网上一些大牛提供的学习博客,特别是http://blog.csdn.net/xiaanming/article/details/10163203这篇博客对我的帮助很大,我也是在这个上面做了一些改进和优化,最后终于在我们的项目中加入了二维码扫描的功能。今天就来讲一下我的学习和实现过程:

1.首先,我在这个http://download.csdn.net/detail/xiaanming/5990219网站上下了上面那篇博客中提到的项目源码,它已经在原来的zxing源码上做了很多优化,而我在这个源码的基础上又做了一些处理,基本上解决了一些遗留的问题。     

在这个项目中,其中ViewfinderView这个类是一个自定义控件,也就是扫描页用到的主要的控件,所以就先从这个自定义控件类入手进行修改。

(1)在我运行了这个项目后发现扫描框中总是会出现黄色的小点,其实是扫描点的颜色设置不对,也就是下面这个代码中的“resultPointColor”。

resultPointColor = resources.getColor(R.color.possible_result_points);
把上面这行代码中的possible_result_points设置成透明色即可。

(2)扫描线比较丑,因此我把它修改了成类微信的扫描线,也就是把绘制扫描线的那行代码替换一下,这个问题那篇博客的博主已经提到了,所以就跳过。

(3)在不同机型上扫描框的显示有比较大的不同,为了解决这适配问题,我在这个类加了一个dip转px的方法,代码如下:

<span style="font-size:12px;">public static int dip2px(Context context, float dipValue) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (dipValue * scale + 0.5f);
    }</span>

然后在一些直接使用数字加上这个方法,例如四个边角的宽度:

    <span style="font-size:12px;">/**
     * 四个绿色边角对应的宽度
     */
    private static final int CORNER_WIDTH = 10;</span>
改成下面这样:

    /**
     * 四个绿色边角对应的宽度
     */
    private final int CORNER_WIDTH = dip2px(getContext(), 10);
(4)因为原来的代码其实只是画了四个绿色的直角,边框是由于除扫描框其他的区域灰色比较深造成的,所以我自己画了四个边框,代码如下,很容易理解:

// 画四个边框,左上右下
paint.setColor(Color.GRAY);
canvas.drawLine(frame.left, frame.top, frame.left, frame.bottom, paint);
canvas.drawLine(frame.left, frame.top, frame.right, frame.top, paint);
canvas.drawLine(frame.right - 1, frame.top, frame.right - 1, frame.bottom, paint);
canvas.drawLine(frame.left, frame.bottom - 1, frame.right, frame.bottom - 1, paint);

(5)因为我是想做成类似微信的扫描框,然后发现这个扫描框有点太大了,在代码中有这样一段注释和代码:

//中间的扫描框,你要修改扫描框的大小,去CameraManager里面修改
		Rect frame = CameraManager.get().getFramingRect();

根据这句提示,我修改了CameraManager中的getFramingRect()方法,代码如下:

public Rect getFramingRect() {
    Point screenResolution = configManager.getScreenResolution();
    if (framingRect == null) {
      if (camera == null) {
        return null;
      }
      int width = screenResolution.x * 5 / 9;
      int height = screenResolution.y * 3 / 10;
      int leftOffset = (screenResolution.x - width) / 2;
      int topOffset = (screenResolution.y - height) * 2 / 5;
      framingRect = new Rect(leftOffset, topOffset, leftOffset + width, topOffset + height);
      Log.d(TAG, "Calculated framing rect: " + framingRect);
    }
    return framingRect;
  }

2.通过上面5个步骤,基本上这个扫描框就跟微信的差不多了,下面看一下扫描类中的处理:

因为我们项目是要求从扫描页跳转到一个链接显示的网页,所以我新建了一个SweepResultActivity类,并且加上了从相册选择二维码进行扫描的功能。先说一下扫描类的改动:

(1)处理扫描结果的方法

    /**
     * 处理扫描结果
     * @param result
     * @param barcode
     */
    public void handleDecode(Result result, Bitmap barcode) {
        if (inactivityTimer != null) {
            inactivityTimer.onActivity();
        }
        playBeepSoundAndVibrate();
        String resultString = result.getText();//扫描获取到的文字
        Bitmap bitmap = barcode; // 扫描获取到的二维码图片
        if (resultString.equals("")) {
            Toast.makeText(SweepCodeActivity.this, getString(R.string.sweep_fail), Toast.LENGTH_LONG).show();
        } else {
            // 获取到文字和图片不为空
            Toast.makeText(SweepCodeActivity.this, "扫描成功,链接为" + resultString, Toast.LENGTH_LONG).show();
            Intent it = new Intent();
            it.setClass(SweepCodeActivity.this, SweepResultActivity.class);
            it.putExtra(SweepResultActivity.JUMP_TO_SWEEPRESULTACTIVITY_URL, resultString);
            startActivity(it);
        }
    }
(2)添加从相册选的方法和图片返回的获取和处理

Button btnImagePick = (Button) findViewById(R.id.btn_title_more);
        btnImagePick.setVisibility(View.VISIBLE);
        btnImagePick.setText(getString(R.string.select_code_from_album));
        btnImagePick.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                imagePick();
            }
        });
</pre><pre name="code" class="java">private Handler mHandler = new Handler(){

    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
        mProgress.dismiss();
        switch (msg.what) {
            case PARSE_BARCODE_SUCCESS:
                handleDecode((Result) msg.obj, scanBitmap);
                break;
            case PARSE_BARCODE_FAIL:
                Toast.makeText(SweepCodeActivity.this, (String) msg.obj, Toast.LENGTH_LONG).show();
                break;
        }
    }
};

/**
 * 从相册选取
 */
private void imagePick() {
    try {
        Intent intent = new Intent(Intent.ACTION_PICK, null);
        // 指定调用相机拍照后照片的储存路径
        intent.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "image/*");
        startActivityForResult(intent, REQUEST_CODE);
    } catch (Exception e) {
        Toast.makeText(SweepCodeActivity.this, getString(R.string.edit_user_info_image_no_browser), Toast.LENGTH_LONG).show();
    }
}

/**
 * 处理扫描结果
 * @param result
 * @param barcode
 */
public void handleDecode(Result result, Bitmap barcode) {
    if (inactivityTimer != null) {
        inactivityTimer.onActivity();
    }
    playBeepSoundAndVibrate();
    String resultString = result.getText();//扫描获取到的文字
    Bitmap bitmap = barcode; // 扫描获取到的二维码图片
    if (resultString.equals("")) {
        Toast.makeText(SweepCodeActivity.this, getString(R.string.sweep_fail), Toast.LENGTH_LONG).show();
    } else {
        // 获取到文字和图片不为空
        Toast.makeText(SweepCodeActivity.this, "扫描成功,链接为" + resultString, Toast.LENGTH_LONG).show();
        Intent it = new Intent();
        it.setClass(SweepCodeActivity.this, SweepResultActivity.class);
        it.putExtra(SweepResultActivity.JUMP_TO_SWEEPRESULTACTIVITY_URL, resultString);
        startActivity(it);
    }
}

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if(resultCode == RESULT_OK){
        switch(requestCode){
            case REQUEST_CODE:
                //获取选中图片的路径
                Cursor cursor = getContentResolver().query(data.getData(), null, null, null, null);
                if (cursor.moveToFirst()) {
                    photo_path = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA));
                }
                cursor.close();

                mProgress = new ProgressDialog(SweepCodeActivity.this);
                mProgress.setMessage(getString(R.string.sweeping));
                mProgress.setCancelable(false);
                mProgress.show();

                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        Result result = scanningImage(photo_path);
                        if (result != null) {
                            Message m = mHandler.obtainMessage();
                            m.what = PARSE_BARCODE_SUCCESS;
                            m.obj = result;
                            mHandler.sendMessage(m);
                        } else {
                            Message m = mHandler.obtainMessage();
                            m.what = PARSE_BARCODE_FAIL;
                            m.obj = getString(R.string.sweep_fail);
                            mHandler.sendMessage(m);
                        }
                    }
                }).start();
                break;
        }
    }
}

/**
 * 扫描二维码图片的方法
 *
 * @param path
 * @return
 */
public Result scanningImage(String path) {
    if (TextUtils.isEmpty(path)) {
        return null;
    }
    Hashtable<DecodeHintType, String> hints = new Hashtable<DecodeHintType, String>();
    hints.put(DecodeHintType.CHARACTER_SET, "UTF-8"); //设置二维码内容的编码

    BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true; // 先获取原大小
    scanBitmap = BitmapFactory.decodeFile(path, options);
    options.inJustDecodeBounds = false; // 获取新的大小
    int sampleSize = (int) (options.outHeight / (float) 200);
    if (sampleSize <= 0)
        sampleSize = 1;
    options.inSampleSize = sampleSize;
    scanBitmap = BitmapFactory.decodeFile(path, options);
    RGBLuminanceSource source = new RGBLuminanceSource(scanBitmap);
    BinaryBitmap bitmap1 = new BinaryBitmap(new HybridBinarizer(source));
    QRCodeReader reader = new QRCodeReader();
    try {
        return reader.decode(bitmap1, hints);
    } catch (NotFoundException e) {
        e.printStackTrace();
    } catch (ChecksumException e) {
        e.printStackTrace();
    } catch (FormatException e) {
        e.printStackTrace();
    }
    return null;
}

这样SweepCodeActivity也算是完善了,不过运行的时候会发现会出现扫描的页面会变形,一个正方形的二维码会变高一些。

3.其实解决这个办法很简单,只要在CameraConfigurationManager类的initFromCameraParameters()中判断一下显示的高度和宽度大小就可以了,代码如下:

void initFromCameraParameters(Camera camera) {
        Camera.Parameters parameters = camera.getParameters();
        previewFormat = parameters.getPreviewFormat();
        previewFormatString = parameters.get("preview-format");
        WindowManager manager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        Display display = manager.getDefaultDisplay();
        int width = display.getWidth();
        int height = display.getHeight();
        screenResolution = new Point(width, height);
        if (width < height) {
            cameraResolution = getCameraResolution(parameters, new Point(height, width));
        } else {
            cameraResolution = getCameraResolution(parameters, screenResolution);
        }
    }


目录
相关文章
|
7月前
|
存储 JavaScript 前端开发
“纯血鸿蒙”要来了,赶紧入手学习吧
1月18日,华为宣布HarmonyOSNEXT鸿蒙星河版面向开发者开放申请,这一最新版本的鸿蒙系统也被喻为“纯血鸿蒙”。赶紧入手学习吧
376 1
|
算法 C++
站在巨人的肩膀上,C++最常用的200个函数
站在巨人的肩膀上,C++最常用的200个函数
|
程序员
阿云漫画 | "工作了,又仿佛没在工作"是一种什么体验
编者按: 程序员的日常大多和电脑有关,那么在等待计算机操作结束的这段时间,要如何高效利用呢?要说程序员等待编译的时候都在做神马,就不得不祭出这篇经典的漫画。
105 0
|
前端开发
前端知识学习案例12-开发企业网站12-团队介绍部分1
前端知识学习案例12-开发企业网站12-团队介绍部分1
51 0
前端知识学习案例12-开发企业网站12-团队介绍部分1
|
前端开发
前端知识学习案例13-开发企业网站12-团队介绍部分2
前端知识学习案例13-开发企业网站12-团队介绍部分2
62 0
前端知识学习案例13-开发企业网站12-团队介绍部分2
|
存储 编解码 视频直播
相亲app开发,从哪些角度入手提升音视频体验
相亲app开发,从哪些角度入手提升音视频体验
|
机器人
手撸了一个开源工具,V 站热点实时看,真香!
今天给大家介绍我的一个开源工具 V2EX Action,它可以自动将 V 站热门发送到指定的 webhook 地址,如企业微信群机器人。可配置 workflow 的触发条件为 schedule,实现周期性定时发送热门内容。
203 0
手撸了一个开源工具,V 站热点实时看,真香!
|
边缘计算 UED CDN
陪玩源码如何优化用户体验?功能和技术缺一不可
陪玩源码如何优化用户体验?功能和技术缺一不可
|
数据安全/隐私保护 UED
直播带货源码开发的直播页面怎么做才能满足用户需求
直播带货源码开发的直播页面是开发的核心功能,在直播页面提升用户体验感受,能够提高流量的变现效率。本文为大家盘点一下,如何从直播入手,开发一款用户喜欢的直播带货源码。