关于Android Zxing 3.3.0 的填坑

原创文章,转载请注明出处 http://www.jianshu.com/p/804f1777955d

前言

一开始公司的项目已经集成了Zxing,也实现了扫二维码的功能,但是前一阵子,领导发现在扫码的时候,预览的成像有问题.当时让我去修改,但是我发现公司用的zxing版本比较新,用的3.3.0的版本,我当时在网上大概的找了一些,并没有找到这个版本直接的具体修改方案,但是却提供了思路.最后我是填了一些坑,完成了任务,然后我还把这些代码都集成到一个库中.

因此本文重点是讲如何填坑

  • 扫描预览走样
  • 扫描框走样

源码地址
使用方法都在github的README上面了,所以本文并不会再次讲解.

正文

Zxing 扫描走样

先上一个图说明一下原来的问题:(因为整个手机截图的图片比较大,我就放上关键的地方)

图1
图2

图1和图2相比,在预览成像的后,图1中显示的比例不对,导致硬币的Y轴被拉长,而图2中的预览成像则是正常的
而我们的目的就是要把图1的效果改成图2

接下来就会讲解如何修改.

  • 思路:
    首先这个形成的图像是用Camera来实现的.


在zxing的camera包下可以看到这些类.
可以推测出

CameraConfigurationManager
CameraConfigurationUtils
CameraManager

以上三个类是比较关键的.

在CameraConfigurationManager中的initFromCameraParameters方法里(仅展示重要的地方)

void initFromCameraParameters(OpenCamera camera) {
                    .
                    .
                    .                           
    Point theScreenResolution = new Point();
    display.getSize(theScreenResolution);
    screenResolution = theScreenResolution;
    Log.i(TAG, "Screen resolution in current orientation: " + screenResolution);
    cameraResolution = CameraConfigurationUtils.findBestPreviewSizeValue(parameters, screenResolution);
    Log.i(TAG, "Camera resolution: " + cameraResolution);
    bestPreviewSize = CameraConfigurationUtils.findBestPreviewSizeValue(parameters, screenResolution);
    Log.i(TAG, "Best available preview size: " + bestPreviewSize);
                    .
                    .
                    .
}

看到这里,可以明显的看到这里就是设置预览的分辨率的地方了.
这里先是获取到屏幕的分辨率,并把这个分辨率传入到CameraConfigurationUtils.findBestPreviewSizeValue方法里,以获取到最合适的预览大小
因此我们再进入到CameraConfigurationUtils.findBestPreviewSizeValue方法里面,对这个方法进行解释.(因为这个方法是关键的地方,以此全部拿出来进行解释)

  public static Point findBestPreviewSizeValue(Camera.Parameters parameters, Point screenResolution) {
    //在这里获取全部支持预览的分辨率
    List<Camera.Size> rawSupportedSizes = parameters.getSupportedPreviewSizes();
    if (rawSupportedSizes == null) {
      Log.w(TAG, "Device returned no supported preview sizes; using default");
      Camera.Size defaultSize = parameters.getPreviewSize();
      if (defaultSize == null) {
        throw new IllegalStateException("Parameters contained no preview size!");
      }
      return new Point(defaultSize.width, defaultSize.height);
    }

    // Sort by size, descending 对所有的分辨率进行排序
    List<Camera.Size> supportedPreviewSizes = new ArrayList<>(rawSupportedSizes);
    Collections.sort(supportedPreviewSizes, new Comparator<Camera.Size>() {
      @Override
      public int compare(Camera.Size a, Camera.Size b) {
        int aPixels = a.height * a.width;
        int bPixels = b.height * b.width;
        if (bPixels < aPixels) {
          return -1;
        }
        if (bPixels > aPixels) {
          return 1;
        }
        return 0;
      }
    });
     
     //这里会把所有支持预览的分辨率打印出来,这里可以方便进行调试    
    if (Log.isLoggable(TAG, Log.INFO)) {
      StringBuilder previewSizesString = new StringBuilder();
      for (Camera.Size supportedPreviewSize : supportedPreviewSizes) {
        previewSizesString.append(supportedPreviewSize.width).append('x')
            .append(supportedPreviewSize.height).append(' ');
      }
      Log.i(TAG, "Supported preview sizes: " + previewSizesString);
    }

    // 分辨率比例,这个一个关键点1
    double screenAspectRatio = screenResolution.y / (double) screenResolution.x;
//    double screenAspectRatio = screenResolution.x / (double) screenResolution.y;

    // Remove sizes that are unsuitable
    Iterator<Camera.Size> it = supportedPreviewSizes.iterator();
    while (it.hasNext()) {
      Camera.Size supportedPreviewSize = it.next();
      int realWidth = supportedPreviewSize.width;
      int realHeight = supportedPreviewSize.height;
      if (realWidth * realHeight < MIN_PREVIEW_PIXELS) {
        it.remove();
        continue;
      }

      boolean isCandidatePortrait = realWidth < realHeight;
      int maybeFlippedWidth = isCandidatePortrait ? realHeight : realWidth;
      int maybeFlippedHeight = isCandidatePortrait ? realWidth : realHeight;
      double aspectRatio = maybeFlippedWidth / (double) maybeFlippedHeight;
      double distortion = Math.abs(aspectRatio - screenAspectRatio);
      if (distortion > MAX_ASPECT_DISTORTION) {
        it.remove();
        continue;
      }

      if (maybeFlippedWidth == screenResolution.x && maybeFlippedHeight == screenResolution.y) {
        Point exactPoint = new Point(realWidth, realHeight);
        Log.i(TAG, "Found preview size exactly matching screen size: " + exactPoint);
        return exactPoint;
      }
    }

    // If no exact match, use largest preview size. This was not a great idea on older devices because
    // of the additional computation needed. We're likely to get here on newer Android 4+ devices, where
    // the CPU is much more powerful.
    if (!supportedPreviewSizes.isEmpty()) {
    //关键点2 ,这里是添加上去进行优化的.
      for (int i = 0;i<supportedPreviewSizes.size();i++){
        Camera.Size largestPreview = supportedPreviewSizes.get(i);
        if (largestPreview.width == screenResolution.y && largestPreview.height == screenResolution.x){
          Log.i(TAG, "Using same suitable preview size: " + largestPreview);
          return new Point(largestPreview.width, largestPreview.height);
        }
      }
      Camera.Size largestPreview = supportedPreviewSizes.get(0);
      Point largestSize = new Point(largestPreview.width, largestPreview.height);
      Log.i(TAG, "Using largest suitable preview size: " + largestSize);
      return largestSize;
    }

    // If there is nothing at all suitable, return current preview size
    Camera.Size defaultPreview = parameters.getPreviewSize();
    if (defaultPreview == null) {
      throw new IllegalStateException("Parameters contained no preview size!");
    }
//    Point defaultSize = new Point(defaultPreview.width, defaultPreview.height);
    Point defaultSize = new Point(defaultPreview.height, defaultPreview.width);
//    Log.i(TAG, "defaultPreview " + defaultPreview.width + " " + defaultPreview.height);
    Log.i(TAG, "No suitable preview sizes, using default: " + defaultSize);
    return defaultSize;
  }

在这里解释一下在上述代码中注释上写的关键点1的部分

// 分辨率比例,这个一个关键点1(修改后)
    double screenAspectRatio = screenResolution.y / (double) screenResolution.x;
// 修改前
//    double screenAspectRatio = screenResolution.x / (double) screenResolution.y;

可以看到这里部分的代码我仅仅是调换x和y而已,但是就是这么简单就可以基本的修改了扫描走样的问题.
原因是,例如在之前传入的分辨率Point(1080, 1920)和在接下来的分辨率对比获取最佳的分辨率的时候,刚好是相反的,具体看下log

举个例子:
传入的是1080/1920=0.5625,而下面的代码计算的比例是 1920/1080=1.7777...
所以显示比例永远不对.
因此只要把x和y对换就可以了.在这样对比的时候,就能获取到分辨率比例一样的分辨率,从而解决预览走样的问题.
但是这样仅仅是第一步.
接下来讲解下关键点2
在算了传入的分辨率比例后,和支持的分辨率比例做对比,只要比例不同的就会移除.
但是问题来,例如:
3840x2160和1920x1080的比例是一样的.
而原本的代码是直接返回来了 supportedPreviewSizes.get(0);
所以导致比例一样,分辨率不一样,在形成图像上是不会有问题,但是会影响扫描的速度.在一些设备上会有很明显的差异!!!

 if (!supportedPreviewSizes.isEmpty()) {
    //关键点2 ,这里是添加上去进行优化的.
      for (int i = 0;i<supportedPreviewSizes.size();i++){
        Camera.Size largestPreview = supportedPreviewSizes.get(i);
        if (largestPreview.width == screenResolution.y && largestPreview.height == screenResolution.x){
          Log.i(TAG, "Using same suitable preview size: " + largestPreview);
          return new Point(largestPreview.width, largestPreview.height);
        }
      }
      Camera.Size largestPreview = supportedPreviewSizes.get(0);
      Point largestSize = new Point(largestPreview.width, largestPreview.height);
      Log.i(TAG, "Using largest suitable preview size: " + largestSize);
      return largestSize;
    }

Zxing 扫描框走样

这个问题我一开始也并没有发现,但是我经过测试过其他一些设备发现,在一些分辨率上,扫码框会严重变形,成一个长方形...(这个大家可以自行脑补一下,我就不上图了,刚好身边没有设备)
在ViewfinderView.java这个类中,在ondraw中有

 Rect frame = cameraManager.getFramingRect();
 Rect previewFrame = cameraManager.getFramingRectInPreview();

从这里看到扫描框的大小就是从cameraManager.getFramingRect()获取的.进入到这个具体的方法来分析一下
在这个类中可以看到设定宽高的最大最小值

    private static final int MIN_FRAME_WIDTH = 240;
    private static final int MAX_FRAME_WIDTH = 675; 

    private static final int MIN_FRAME_HEIGHT = 240;
    private static final int MAX_FRAME_HEIGHT = 675; // = 5/8 * 1080

进入到getFramingRect()

/**
     * Calculates the framing rect which the UI should draw to show the user where to place the
     * barcode. This target helps with alignment as well as forces the user to hold the device
     * far enough away to ensure the image will be in focus.
     *
     * @return The rectangle to draw on screen in window coordinates.
     */
    public synchronized Rect getFramingRect() {
        if (framingRect == null) {
            if (camera == null) {
                return null;
            }
            Point screenResolution = configManager.getScreenResolution();
            if (screenResolution == null) {
                // Called early, before init even finished
                return null;
            }
            Log.i(TAG, "getFramingRect: " + screenResolution);
//            width
            int width = findDesiredDimensionInRange(screenResolution.x, MIN_FRAME_WIDTH,
                    MAX_FRAME_WIDTH);
//            height
            int height = findDesiredDimensionInRange(screenResolution.y, MIN_FRAME_HEIGHT,
                    MAX_FRAME_HEIGHT);
            int min = width > height ? height : width ;
            Log.i(TAG, "findDesiredDimensionInRange: width" + width + " height "+height);
            int leftOffset = (screenResolution.x - min) / 2;
            int topOffset = (screenResolution.y - min) / 2;
            framingRect = new Rect(leftOffset, topOffset, leftOffset + min, topOffset + min);
            Log.i(TAG, "getFramingRect: "+framingRect);
            if (DEBUG)
                Log.d(TAG, "Calculated framing rect: " + framingRect + ", w: " + framingRect.width() + ", h: " + framingRect.height());
        }
        return framingRect;
    }

    private static int findDesiredDimensionInRange(int resolution, int hardMin, int hardMax) {
        int dim = 5 * resolution / 8; // Target 5/8 of each dimension
        if (dim < hardMin) {
            return hardMin;
        }
        if (dim > hardMax) {
            return hardMax;
        }
        return dim;
    }

首先按照上面的代码进行修改就可以解决扫描框走样的问题.
可以到上面的代码中计算width和height ,因为屏幕的宽和高是肯定不一样的,所以在源码是分别算入到Rect中,出来的效果肯定不可能是个正方形,因为,只有选择width和height较小值算入Rect后,出来的效果才是一个正方形.(这里涉及到具体的view一些知识就不细讲了),当然,这里是长方形还是正方形本身都是没有错的,都是看项目的具体需求来定,所以想要什么形状都可以按照这个思路来改~

后记

  1. 如果说想方便快速接入zxing的话,可以直接去源码地址 下载代码,先看看demo的部分,然后把zxing库整个放到项目中导入即可使用!十分快捷没有副作用!至于为什么没有打包,因为这个扫描的UI肯定是需要根据不同的项目要求来进行修改的,如果打包了就不方便根据需求自定义了.
  2. 如果想自己导入官方的zxing 的话,可以根据上述的思路进行修改即可.
  3. 若有任何问题和意见都可以在issues上留言给我.
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,723评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,003评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,512评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,825评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,874评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,841评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,812评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,582评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,033评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,309评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,450评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,158评论 5 341
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,789评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,409评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,609评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,440评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,357评论 2 352

推荐阅读更多精彩内容