Android开源之行之走进zxing,轻松实现二维码扫描(二)
对于Zxing开源项目的简化上文已给出,源码经过测试且不断修改。众所周知,Zxing项目的扫描是横向的,这么引用的用户体验确实不好;然而盲目的修改会出现拉伸以及样本采集的偏离。所以这里说一下如何将横屏修改为竖屏扫描
解决办法引用原文:http://blog.csdn.net/aaawqqq/article/details/24804939
一、Zxing扫描框架竖屏切换
1、menifest.xml中,Activitiy必须要设为竖屏的,添加属性
android:screenOrientation="portrait"
2、camera扫描过程中,有两个视图:当前扫描view(取景框)和预览preview。为了修正预览的90度的偏离,我们需要修正PreView。因此我们要修改CameraManager中的 getFramingRectInPreview()的preView的边框
1 rect.left = rect.left * cameraResolution.y / screenResolution.x; 2 rect.right = rect.right * cameraResolution.y / screenResolution.x; 3 rect.top = rect.top * cameraResolution.x / screenResolution.y; 4 rect.bottom = rect.bottom * cameraResolution.x / screenResolution.y;
3、在CameraConfigurationManager中setDesiredCameraParameters()设置我们需要设置的Camera参数,设置preView大小,因此我们我们需要在这里添加 setDisplayOrientation()来设置camera旋转90度旋转;调用位置:setDesiredCameraParameters()中camera.setParameters(parameters)之前
1 void setDisplayOrientation(Camera camera, int angle) { 2 3 Method method; 4 try { 5 method = camera.getClass().getMethod("setDisplayOrientation", 6 new Class[] { int.class }); 7 if (method != null) 8 method.invoke(camera, new Object[] { angle }); 9 } catch (Exception e1) { 10 e1.printStackTrace(); 11 } 12 }
4、在DecodeHandler中,decode(byte[] data, int width, int height)在PlanarYUVLuminanceSource source = activity.getCameraManager().buildLuminanceSource(data, width, height)之前添加以下代码,防止转化的bitmap与取景框得到的扫描图不一致
1 byte[] rotatedData = new byte[data.length]; 2 for (int y = 0; y < height; y++) { 3 for (int x = 0; x < width; x++) 4 rotatedData[x * height + height - y - 1] = data[x + y * width]; 5 } 6 int tmp = width; // Here we are swapping, that's the difference to #11 7 width = height; 8 height = tmp; 9 data = rotatedData;
5、在CameraConfigurationManager中,initFromCameraParameters(Camera camera)替换为如下代码
1 void initFromCameraParameters(Camera camera) { 2 Camera.Parameters parameters = camera.getParameters(); 3 WindowManager manager = (WindowManager) context 4 .getSystemService(Context.WINDOW_SERVICE); 5 Display display = manager.getDefaultDisplay(); 6 Point theScreenResolution = new Point(display.getWidth(), 7 display.getHeight()); 8 screenResolution = theScreenResolution; 9 Log.i(TAG, "Screen resolution: " + screenResolution); 10 11 /************** 竖屏更改4 ******************/ 12 Point screenResolutionForCamera = new Point(); 13 screenResolutionForCamera.x = screenResolution.x; 14 screenResolutionForCamera.y = screenResolution.y; 15 16 // preview size is always something like 480*320, other 320*480 17 if (screenResolution.x < screenResolution.y) { 18 screenResolutionForCamera.x = screenResolution.y; 19 screenResolutionForCamera.y = screenResolution.x; 20 } 21 22 cameraResolution = CameraConfigurationUtils.findBestPreviewSizeValue( 23 parameters, screenResolutionForCamera); 24 Log.i(TAG, "Camera resolution: " + cameraResolution); 25 26 }
非常感谢NDK-baozi的解决方法!
二、解决自定义取景框的问题
Zxing中,在取景框中央的红色扫描线或许跟我们需要的循环移动扫描还有些出入,这就需要我们来自定义属于自己的取景框。实现自定义取景框,我们需要改写ViewfinderView来绘制自 己的View。
首先需要确定一点,在Zxing源代码中,有两个Rect:一个是frame,通过getFramingRect()取得,这是我们要的取景框的Rect;而另一个previewFrame则是预览视图。我们要做的是以frame为参照物进行我们自己的绘制。
1、绘制描述的文字,也即取景框上方的提示消息
1 private void drawStatusText(Canvas canvas, Rect frame, int width) { 2 3 String statusText1 = getResources().getString( 4 R.string.viewfinderview_status_text1); 5 String statusText2 = getResources().getString( 6 R.string.viewfinderview_status_text2); 7 int statusTextSize = 45; 8 int statusPaddingTop = 180; 9 10 paint.setColor(statusColor); 11 paint.setTextSize(statusTextSize); 12 13 int textWidth1 = (int) paint.measureText(statusText1); 14 canvas.drawText(statusText1, (width - textWidth1) / 2, frame.top 15 - statusPaddingTop, paint); 16 17 int textWidth2 = (int) paint.measureText(statusText2); 18 canvas.drawText(statusText2, (width - textWidth2) / 2, frame.top 19 - statusPaddingTop + 60, paint); 20 }
2、绘制取景框边角,也即四个角的蓝色拐角
1 private void drawFrameBounds(Canvas canvas, Rect frame) { 2 3 paint.setColor(Color.WHITE); 4 paint.setStrokeWidth(2); 5 paint.setStyle(Paint.Style.STROKE); 6 7 canvas.drawRect(frame, paint); 8 9 paint.setColor(Color.BLUE); 10 paint.setStyle(Paint.Style.FILL); 11 12 int corWidth = 15; 13 int corLength = 45; 14 15 // 左上角 16 canvas.drawRect(frame.left - corWidth, frame.top, frame.left, frame.top 17 + corLength, paint); 18 canvas.drawRect(frame.left - corWidth, frame.top - corWidth, frame.left 19 + corLength, frame.top, paint); 20 // 右上角 21 canvas.drawRect(frame.right, frame.top, frame.right + corWidth, 22 frame.top + corLength, paint); 23 canvas.drawRect(frame.right - corLength, frame.top - corWidth, 24 frame.right + corWidth, frame.top, paint); 25 // 左下角 26 canvas.drawRect(frame.left - corWidth, frame.bottom - corLength, 27 frame.left, frame.bottom, paint); 28 canvas.drawRect(frame.left - corWidth, frame.bottom, frame.left 29 + corLength, frame.bottom + corWidth, paint); 30 // 右下角 31 canvas.drawRect(frame.right, frame.bottom - corLength, frame.right 32 + corWidth, frame.bottom, paint); 33 canvas.drawRect(frame.right - corLength, frame.bottom, frame.right 34 + corWidth, frame.bottom + corWidth, paint); 35 }
3、绘制循环移动的扫描线
1 private void drawScanLight(Canvas canvas, Rect frame) { 2 3 if (scanLineTop == 0) { 4 scanLineTop = frame.top; 5 } 6 7 if (scanLineTop >= frame.bottom) { 8 scanLineTop = frame.top; 9 } else { 10 scanLineTop += SCAN_VELOCITY; 11 } 12 Rect scanRect = new Rect(frame.left, scanLineTop, frame.right, 13 scanLineTop + 30); 14 canvas.drawBitmap(scanLight, null, scanRect, paint); 15 }
通过以上的三步绘制,我们就可以实现展示图的效果。整体的ViewfinderView代码:
1 /** 2 * This view is overlaid on top of the camera preview. It adds the viewfinder 3 * rectangle and partial transparency outside it, as well as the laser scanner 4 * animation and result points. 这是一个位于相机顶部的预览view,它增加了一个外部部分透明的取景框,以及激光扫描动画和结果组件 5 * 6 * @author dswitkin@google.com (Daniel Switkin) 7 */ 8 public final class ViewfinderView extends View { 9 10 private static final int[] SCANNER_ALPHA = { 0, 64, 128, 192, 255, 192, 11 128, 64 }; 12 private static final long ANIMATION_DELAY = 80L; 13 private static final int CURRENT_POINT_OPACITY = 0xA0; 14 private static final int MAX_RESULT_POINTS = 20; 15 private static final int POINT_SIZE = 6; 16 17 private CameraManager cameraManager; 18 private final Paint paint; 19 private Bitmap resultBitmap; 20 private final int maskColor; // 取景框外的背景颜色 21 private final int resultColor;// result Bitmap的颜色 22 private final int laserColor; // 红色扫描线的颜色 23 private final int resultPointColor; // 特征点的颜色 24 private final int statusColor; // 提示文字颜色 25 private int scannerAlpha; 26 private List<ResultPoint> possibleResultPoints; 27 private List<ResultPoint> lastPossibleResultPoints; 28 // 扫描线移动的y 29 private int scanLineTop; 30 // 扫描线移动速度 31 private final int SCAN_VELOCITY = 5; 32 // 扫描线 33 Bitmap scanLight; 34 35 public ViewfinderView(Context context, AttributeSet attrs) { 36 super(context, attrs); 37 38 // Initialize these once for performance rather than calling them every 39 // time in onDraw(). 40 paint = new Paint(Paint.ANTI_ALIAS_FLAG); 41 Resources resources = getResources(); 42 maskColor = resources.getColor(R.color.viewfinder_mask); 43 resultColor = resources.getColor(R.color.result_view); 44 laserColor = resources.getColor(R.color.viewfinder_laser); 45 resultPointColor = resources.getColor(R.color.possible_result_points); 46 statusColor = resources.getColor(R.color.status_text); 47 scannerAlpha = 0; 48 possibleResultPoints = new ArrayList<ResultPoint>(5); 49 lastPossibleResultPoints = null; 50 scanLight = BitmapFactory.decodeResource(resources, 51 R.drawable.scan_light); 52 } 53 54 public void setCameraManager(CameraManager cameraManager) { 55 this.cameraManager = cameraManager; 56 } 57 58 @SuppressLint("DrawAllocation") 59 @Override 60 public void onDraw(Canvas canvas) { 61 if (cameraManager == null) { 62 return; // not ready yet, early draw before done configuring 63 } 64 65 // frame为取景框 66 Rect frame = cameraManager.getFramingRect(); 67 Rect previewFrame = cameraManager.getFramingRectInPreview(); 68 if (frame == null || previewFrame == null) { 69 return; 70 } 71 int width = canvas.getWidth(); 72 int height = canvas.getHeight(); 73 74 // Draw the exterior (i.e. outside the framing rect) darkened 75 // 绘制取景框外的暗灰色的表面,分四个矩形绘制 76 paint.setColor(resultBitmap != null ? resultColor : maskColor); 77 canvas.drawRect(0, 0, width, frame.top, paint);// Rect_1 78 canvas.drawRect(0, frame.top, frame.left, frame.bottom + 1, paint); // Rect_2 79 canvas.drawRect(frame.right + 1, frame.top, width, frame.bottom + 1, 80 paint); // Rect_3 81 canvas.drawRect(0, frame.bottom + 1, width, height, paint); // Rect_4 82 83 if (resultBitmap != null) { 84 // Draw the opaque result bitmap over the scanning rectangle 85 // 如果有二维码结果的Bitmap,在扫取景框内绘制不透明的result Bitmap 86 paint.setAlpha(CURRENT_POINT_OPACITY); 87 canvas.drawBitmap(resultBitmap, null, frame, paint); 88 } else { 89 // Draw a red "laser scanner" line through the middle to show 90 // decoding is active 91 drawFrameBounds(canvas, frame); 92 drawStatusText(canvas, frame, width); 93 94 // 绘制扫描线 95 // paint.setColor(laserColor); 96 // paint.setAlpha(SCANNER_ALPHA[scannerAlpha]); 97 // scannerAlpha = (scannerAlpha + 1) % SCANNER_ALPHA.length; 98 // int middle = frame.height() / 2 + frame.top; 99 // canvas.drawRect(frame.left + 2, middle - 1, frame.right - 1, 100 // middle + 2, paint); 101 102 drawScanLight(canvas, frame); 103 104 float scaleX = frame.width() / (float) previewFrame.width(); 105 float scaleY = frame.height() / (float) previewFrame.height(); 106 107 // 绘制扫描线周围的特征点 108 List<ResultPoint> currentPossible = possibleResultPoints; 109 List<ResultPoint> currentLast = lastPossibleResultPoints; 110 int frameLeft = frame.left; 111 int frameTop = frame.top; 112 if (currentPossible.isEmpty()) { 113 lastPossibleResultPoints = null; 114 } else { 115 possibleResultPoints = new ArrayList<ResultPoint>(5); 116 lastPossibleResultPoints = currentPossible; 117 paint.setAlpha(CURRENT_POINT_OPACITY); 118 paint.setColor(resultPointColor); 119 synchronized (currentPossible) { 120 for (ResultPoint point : currentPossible) { 121 canvas.drawCircle(frameLeft 122 + (int) (point.getX() * scaleX), frameTop 123 + (int) (point.getY() * scaleY), POINT_SIZE, 124 paint); 125 } 126 } 127 } 128 if (currentLast != null) { 129 paint.setAlpha(CURRENT_POINT_OPACITY / 2); 130 paint.setColor(resultPointColor); 131 synchronized (currentLast) { 132 float radius = POINT_SIZE / 2.0f; 133 for (ResultPoint point : currentLast) { 134 canvas.drawCircle(frameLeft 135 + (int) (point.getX() * scaleX), frameTop 136 + (int) (point.getY() * scaleY), radius, paint); 137 } 138 } 139 } 140 141 // Request another update at the animation interval, but only 142 // repaint the laser line, 143 // not the entire viewfinder mask. 144 postInvalidateDelayed(ANIMATION_DELAY, frame.left - POINT_SIZE, 145 frame.top - POINT_SIZE, frame.right + POINT_SIZE, 146 frame.bottom + POINT_SIZE); 147 } 148 } 149 150 /** 151 * 绘制取景框边框 152 * 153 * @param canvas 154 * @param frame 155 */ 156 private void drawFrameBounds(Canvas canvas, Rect frame) { 157 158 paint.setColor(Color.WHITE); 159 paint.setStrokeWidth(2); 160 paint.setStyle(Paint.Style.STROKE); 161 162 canvas.drawRect(frame, paint); 163 164 paint.setColor(Color.BLUE); 165 paint.setStyle(Paint.Style.FILL); 166 167 int corWidth = 15; 168 int corLength = 45; 169 170 // 左上角 171 canvas.drawRect(frame.left - corWidth, frame.top, frame.left, frame.top 172 + corLength, paint); 173 canvas.drawRect(frame.left - corWidth, frame.top - corWidth, frame.left 174 + corLength, frame.top, paint); 175 // 右上角 176 canvas.drawRect(frame.right, frame.top, frame.right + corWidth, 177 frame.top + corLength, paint); 178 canvas.drawRect(frame.right - corLength, frame.top - corWidth, 179 frame.right + corWidth, frame.top, paint); 180 // 左下角 181 canvas.drawRect(frame.left - corWidth, frame.bottom - corLength, 182 frame.left, frame.bottom, paint); 183 canvas.drawRect(frame.left - corWidth, frame.bottom, frame.left 184 + corLength, frame.bottom + corWidth, paint); 185 // 右下角 186 canvas.drawRect(frame.right, frame.bottom - corLength, frame.right 187 + corWidth, frame.bottom, paint); 188 canvas.drawRect(frame.right - corLength, frame.bottom, frame.right 189 + corWidth, frame.bottom + corWidth, paint); 190 } 191 192 /** 193 * 绘制提示文字 194 * 195 * @param canvas 196 * @param frame 197 * @param width 198 */ 199 private void drawStatusText(Canvas canvas, Rect frame, int width) { 200 201 String statusText1 = getResources().getString( 202 R.string.viewfinderview_status_text1); 203 String statusText2 = getResources().getString( 204 R.string.viewfinderview_status_text2); 205 int statusTextSize = 45; 206 int statusPaddingTop = 180; 207 208 paint.setColor(statusColor); 209 paint.setTextSize(statusTextSize); 210 211 int textWidth1 = (int) paint.measureText(statusText1); 212 canvas.drawText(statusText1, (width - textWidth1) / 2, frame.top 213 - statusPaddingTop, paint); 214 215 int textWidth2 = (int) paint.measureText(statusText2); 216 canvas.drawText(statusText2, (width - textWidth2) / 2, frame.top 217 - statusPaddingTop + 60, paint); 218 } 219 220 /** 221 * 绘制移动扫描线 222 * 223 * @param canvas 224 * @param frame 225 */ 226 private void drawScanLight(Canvas canvas, Rect frame) { 227 228 if (scanLineTop == 0) { 229 scanLineTop = frame.top; 230 } 231 232 if (scanLineTop >= frame.bottom) { 233 scanLineTop = frame.top; 234 } else { 235 scanLineTop += SCAN_VELOCITY; 236 } 237 Rect scanRect = new Rect(frame.left, scanLineTop, frame.right, 238 scanLineTop + 30); 239 canvas.drawBitmap(scanLight, null, scanRect, paint); 240 } 241 242 public void drawViewfinder() { 243 Bitmap resultBitmap = this.resultBitmap; 244 this.resultBitmap = null; 245 if (resultBitmap != null) { 246 resultBitmap.recycle(); 247 } 248 invalidate(); 249 } 250 251 /** 252 * Draw a bitmap with the result points highlighted instead of the live 253 * scanning display. 254 * 255 * @param barcode 256 * An image of the decoded barcode. 257 */ 258 public void drawResultBitmap(Bitmap barcode) { 259 resultBitmap = barcode; 260 invalidate(); 261 } 262 263 public void addPossibleResultPoint(ResultPoint point) { 264 List<ResultPoint> points = possibleResultPoints; 265 synchronized (points) { 266 points.add(point); 267 int size = points.size(); 268 if (size > MAX_RESULT_POINTS) { 269 // trim it 270 points.subList(0, size - MAX_RESULT_POINTS / 2).clear(); 271 } 272 } 273 } 274 275 }