20
2017
02

安卓zxing实现二维码扫描

使用谷歌的开源库zxing.jar实现二维码扫描功能。

zxing.jar实现二维码扫描的教程网上一搜一大堆,不过大都是千篇一律,而且比较复杂,封装的也不太好。

比如,大多数都会有这么一大堆文件(如下图),来回用handler传消息,很难看懂。布局也基本上是固定的,SurfaceView只能全屏,想要改界面还得先看懂,比较费劲。包里边既有Activity,又有View,又用到了R中的资源。自由度太低了。总的来说,感觉封装的很差劲。

经过学习,总结,最终发现,其实zxing.jar只是提供了二维码数据的解码,扫码过程中图片预览用的是摄像头Camera+SurfaceView,和zxing完全没有关系。

实现打开摄像头,并显示摄像头拍到的画面,只需要调用

camera=Camera.open();//打开摄像头
camera.setPreviewDisplay(surfaceView.getHolder());//用SurfaceView显示画面
camera.startPreview();//开始预览

就这么简单,何必写那一大堆类。

扫面二维码过程中,有一个矩形框,实际识别的是矩形框中的图片。矩形框的尺寸和位置其实是我们自己定义的。

我们只需要定义一个Rect对象,然后再定义一个ViewfinderView类来根据Rect对象画出矩形框和一条扫描线。

这么一来,从表面上看,扫描二维码的app已经做好了,但是最核心的扫描功能还没有加上。

我们需要从摄像头捕获的图像中,获取到我们自己定义的Rect对象区域内的部分,交给zxing.jar来进行解码。

实际上,我们不是从SurfaceView中截图,因为1、从SurfaceView中截图并不简单,2、效率会很差。

为了提高效率,Camera有一个setPreviewCallback方法,设置一个PreviewCallback对象,PreviewCallback对象需要实现一个方法:

public void onPreviewFrame(byte[] arg0, Camera arg1)

其中byte[]就是Camera捕获的图片的数据(水平不够,具体格式不了解)。

实际上,我们是在这里把数据进行转换然后交给zxing进行解码的。

具体转换过程可以参考别人写好的代码。

由于解码耗时较长,大约需要几十毫秒,为了界面看起来比较流畅,需要开一个独立的线程用于解码。

另外,byte[]数组得到的图片的尺寸和surfaceview的尺寸一般不会一样,需要了解一下Camera的一些方法,比如:getParameters().getPreviewSize()方法。


大致思路就是如此。并不需要做的那么复杂。

而且这么封装,自由度也高,可以任意布局。


核心类JcQrCodeScanner.java:

package cn.jucheng.jclibs.qrcode;

import java.io.IOException;
import java.util.concurrent.CountDownLatch;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Point;
import android.graphics.Rect;
import android.hardware.Camera;
import android.hardware.Camera.PreviewCallback;
import android.hardware.Camera.Size;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
import android.view.SurfaceView;
import com.google.zxing.MultiFormatReader;

/**
 * 二维码扫描。
 * <h3>使用方式:</h3>
 * <h4>开启扫描:</h4>
 * <pre>
 * JcQrCodeScanner.init(context);
 * JcQrCodeScanner.get().hd=hd;
 * JcQrCodeScanner.get().openDriver(surfaceView);
 * JcQrCodeScanner.get().startPreview();
 * </pre>
 * <h4>停止扫描:</h4>
 * <pre>
 * if(JcQrCodeScanner.get()!=null){
 *  	JcQrCodeScanner.get().stopPreview();
 *  	JcQrCodeScanner.get().closeDriver();
 *  	JcQrCodeScanner.get().destroy();
 * }
 * </pre>
 * <p>可以通过{@link #setFramingRect}设置扫描区域。</p>
 * <p>可以通过{@link #getFramingRect}获取扫描区域,用于显示扫描框。扫描框可参考{@link cn.jucheng.jclibs.qrcode.ViewfinderView},当然你也可以自己定义。</p>
 * <p>可以获取扫描区域的原始图像{@link #captureFramingBitmap},摄像头捕获的原始图像{@link #captureBitmap}</p>
 * <h4>注意:</h4>
 * <p>依赖zxing3.2.1.jar。记得导入jar包,注意版本号。</p>
 * <p>记得在AndroidManifest.xml中添加Camera权限。</p>
 * <pre><uses-permission android:name="android.permission.CAMERA"/></pre>
 * 
 * @author 作者:hanyeah
 * @date 创建时间:2017-2-16 上午10:34:42 
 */
public class JcQrCodeScanner {

	private static final String TAG="JcQrCodeScanner";
	/**版本号**/
	public static final String VERSION = "1.0.1";
	private static JcQrCodeScanner jcQrCodeScanner;
	private Context context;
	private Camera camera;
	private SurfaceView surfaceView;
	private Rect framingRect;
	private Rect framingRectInPreview;
	private Boolean previewing=false;
	private int previewFormat;
	/**用于传递消息,比如解码成功。**/
	public Handler hd;
	private String result="";
	
	public static final int DECODE_SUCCESS=1;
	
	private JcQrCodeScanner(Context context) {
		// TODO Auto-generated constructor stub
		this.context=context;
		decodeThread=new DecodeThread();
		decodeThread.start();
	}
	/**
	 * 获取最近一次解码的结果。
	 * @return
	 */
	public String getReault(){
		return result;
	}
	/**
	 * 初始化实例。
	 * @param context	使用该类的Activity对象。
	 */
	public static void init(Context context){
		if(jcQrCodeScanner==null){
			jcQrCodeScanner=new JcQrCodeScanner(context);
		}
	}
	/**
	 * 获取实例。
	 * <p>获取之前需要先调用init进行初始化。</p>
	 * @return	该类的实例
	 */
	public static JcQrCodeScanner get(){
		return jcQrCodeScanner;
	}
	/**
	 * 打开摄像头。
	 * @param surfaceView	用于显示摄像头捕获的图像的SurfaceView对象。
	 * @return	Boolean
	 */
	public Boolean openDriver(SurfaceView surfaceView){
		this.surfaceView=surfaceView;
		if(camera==null){
			camera=Camera.open();
			if(camera==null){
				return false;
			}
			try {
				camera.setPreviewDisplay(surfaceView.getHolder());
				previewFormat=camera.getParameters().getPreviewFormat();
				cameraSize=camera.getParameters().getPreviewSize();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		return true;
	}
	/**
	 * 关闭摄像头。
	 */
	public void closeDriver(){
		if(camera!=null){
			camera.release();
			camera=null;
		}
	}
	/**
	 * 开始预览,在之前设置的surfaceView中显示摄像头捕捉到的画面。
	 */
	public void startPreview(){
		if(camera!=null&&!previewing){
			camera.startPreview();
			previewing=true;
			isSucceed=false;
			camera.setPreviewCallback(previewCallback);
		}
	}
	/**
	 * 停止预览。
	 */
	public void stopPreview(){
		if(camera!=null&&previewing){
			camera.setPreviewCallback(null);
			camera.stopPreview();
			previewing=false;
		}
	}
	/**
	 * 彻底销毁。
	 * 解码线程,对象实例都会销毁。
	 */
	public void destroy(){
		stopPreview();
		closeDriver();
	    Message quit = Message.obtain(decodeThread.getHandler(), 0);
	    quit.sendToTarget();
	    try {
	      decodeThread.join();
	    } catch (InterruptedException e) {
	    	e.printStackTrace();
	    	Log.e(TAG,e.getMessage());
	    }
	    jcQrCodeScanner=null;
	}
	/**
	 * 设置Camera的预览尺寸。Camera的预览尺寸并不是连续的,所以实际值可能会和设置的值不符。
	 * @param width
	 * @param height
	 */
	public void setCameraPreviewSize(int width,int height){
		if(camera!=null){
			Camera.Parameters par=camera.getParameters();
			par.setPreviewSize(width, height);
			par.setPictureSize(width, height);
			camera.setParameters(par);
			cameraSize=camera.getParameters().getPreviewSize();
			framingRectInPreview=null;
			Log.d(TAG, "cameraSize="+cameraSize.width+","+cameraSize.height);
		}
	}
	
	/**
	 * 设置预览框的矩形区域。
	 * @param rect
	 */
	public void setFramingRect(Rect rect){
		framingRect=new Rect(rect);
		framingRectInPreview=null;
	}
	/**
	 * 获取预览框的矩形区域。
	 * <p>实际显示尺寸。</p>
	 * @return	Rect
	 */
	public Rect getFramingRect(){
		if(framingRect==null){
			Point p=new Point(surfaceView.getWidth(), surfaceView.getHeight());
			framingRect=new Rect(p.x/4, p.y/4, p.x*3/4, p.y*3/4);
		}
		return framingRect;
	}
	
	/**
	 * 获取预览框的矩形区域。
	 * <p>相对于Camera获取的原始图片的尺寸。</p>
	 * @return
	 */
	public Rect getFramingRectInPreview(){
		if(framingRectInPreview==null){
			Point p=new Point(surfaceView.getWidth(), surfaceView.getHeight());
			Rect rect = new Rect(getFramingRect());
			rect.left = rect.left * cameraSize.width / p.x;
			rect.right = rect.right * cameraSize.width / p.x;
			rect.top = rect.top * cameraSize.height / p.y;
			rect.bottom = rect.bottom * cameraSize.height / p.y;
			framingRectInPreview = rect;
		}
		return framingRectInPreview;
	}
	
	/**
	 * <p>扫描区域截图。</p>
	 * <p>截取的是扫描区域的原始图像,可能和实际显示的大小不一致。</p>
	 * @return	Bitmap
	 */
	public Bitmap captureFramingBitmap(){
		Rect rect = getFramingRectInPreview();
		return JcQrCode.capture(data,previewFormat, cameraSize.width, cameraSize.height, rect.left, rect.top, rect.width(), rect.height());
	}
	/**
	 * <p>Camera截图。</p>
	 * <p>截取的是Camera捕获的原始图像。</p>
	 * <p>也可以考虑用camera.takePicture实现。</p>
	 * @return	Bitmap
	 */
	public Bitmap captureBitmap(){
		return JcQrCode.capture(data,previewFormat, cameraSize.width, cameraSize.height, 0,0,cameraSize.width, cameraSize.height);
	}
	
	/**
	 * 保存Camera捕获的用于解码的图像数据。在PreviewCallback中更新。由于解码比较耗时,所以该数据的更新并不及时。
	 */
	private byte[] data;
	/**
	 * 是否正在解码
	 */
	private Boolean isDecoding=false;
	/**
	 * 是否解码成功
	 */
	private Boolean isSucceed=false;
	/**
	 * Camera的预览尺寸,通过camera.getParameters().getPreviewSize()获取。
	 */
	private Size cameraSize;
	private PreviewCallback previewCallback=new Camera.PreviewCallback() {
		
		@Override
		public void onPreviewFrame(byte[] arg0, Camera arg1) {
			// TODO Auto-generated method stub
			if(isDecoding||isSucceed){
				return;
			}
			isDecoding=true;
			data=arg0;
			decodeThread.getHandler().sendEmptyMessage(1);
		}
	};
	
	
	/**
	 * 解码成功处理。
	 * <p>解码线程调用。</p>
	 * @param result
	 */
	private void decodeSuccess(String result){
		Log.d(TAG, "解码成功:"+result);
		this.result=result;
		stopPreview();
		closeDriver();
		if(hd!=null){
			Message msg=hd.obtainMessage(DECODE_SUCCESS);
			msg.obj=result;
			hd.sendMessage(msg);
		}
	}
	
	private final DecodeThread decodeThread;
	/**
	 * 解码线程。
	 * <p>解码比较耗时(几十毫秒),需要放到子线程中。</p>
	 * @author 作者:hanyeah
	 * @date 创建时间:2017-2-16 下午3:03:19
	 */
	private class DecodeThread extends Thread{
		private final MultiFormatReader multiFormatReader;
		private Handler handler;
		private final CountDownLatch handlerInitLatch;//CountDownLatch这个要学习一下。
		public DecodeThread() {
			// TODO Auto-generated constructor stub
			multiFormatReader = new MultiFormatReader();
			handlerInitLatch = new CountDownLatch(1);
		}
		
		Handler getHandler() {
			try {
				handlerInitLatch.await();
			} catch (InterruptedException ie) {
				ie.printStackTrace();
				Log.e(TAG, ie.getMessage());
				// continue?
			}
			return handler;
		}
		/**
		 * 解码
		 */
		private void decode(){
			try{
				Rect rect = getFramingRectInPreview();
				//Log.d(TAG, rect.toString());
				//Log.d(TAG, String.format("%d,%d , %d",cameraSize.width, cameraSize.height,data.length));
				String result=JcQrCode.decode(data, cameraSize.width, cameraSize.height, rect.left, rect.top, rect.width(), rect.height());
				if(result!=""){
					//解码成功,停止扫描,发送成功消息。
					isSucceed=true;
					decodeSuccess(result);
				}
				isDecoding=false;
			}
			catch(Exception e){
				e.printStackTrace();
				Log.d(TAG, "解码出错:"+e);
			}
		}
		
		public void run() {
			Looper.prepare();
			//在线程中创建handler。
			handler=new Handler(){
				public void handleMessage(Message msg) {
					switch(msg.what){
						case 1:
							decode();
							break;
						case 0:
							Looper.myLooper().quit();
							break;
					}
				};
			};
			handlerInitLatch.countDown();
			Looper.loop();
		};
	};
	
}

用到了自己封装的一个工具类JcQrCode.java

package cn.jucheng.jclibs.qrcode;

import java.io.ByteArrayOutputStream;
import java.util.HashMap;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Rect;
import android.graphics.YuvImage;
import android.util.Log;

import com.google.zxing.BarcodeFormat;
import com.google.zxing.BinaryBitmap;
import com.google.zxing.ChecksumException;
import com.google.zxing.DecodeHintType;
import com.google.zxing.EncodeHintType;
import com.google.zxing.FormatException;
import com.google.zxing.LuminanceSource;
import com.google.zxing.NotFoundException;
import com.google.zxing.PlanarYUVLuminanceSource;
import com.google.zxing.RGBLuminanceSource;
import com.google.zxing.Result;
import com.google.zxing.WriterException;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.common.HybridBinarizer;
import com.google.zxing.qrcode.QRCodeReader;
import com.google.zxing.qrcode.QRCodeWriter;
/**
 * 二维码编码解码。
 * @author 作者:hanyeah
 * @date 创建时间:2017-2-15 上午11:51:27 
 */
public class JcQrCode {

	private static final String TAG="JcQrCode";
	
	/**
	 * 字符串编码为二维码图片。
	 * @param url		要编码的字符串
	 * @param qrWidth	二维码宽
	 * @param qrHeight	二维码高
	 * @return	Bitmap	二维码图片
	 */
	public static Bitmap encode(String url,int qrWidth,int qrHeight)
	{
		try
		{
			//判断URL合法性
			if (url == null || "".equals(url) || url.length() < 1)
			{
				Log.d(TAG, "二维码参数不合法");
				return null;
			}
			HashMap<EncodeHintType, String> encodeHints = new HashMap<EncodeHintType, String>();
			encodeHints.put(EncodeHintType.CHARACTER_SET, "utf-8");
			//图像数据转换,使用了矩阵转换
			BitMatrix bitMatrix = new QRCodeWriter().encode(url, BarcodeFormat.QR_CODE, qrWidth, qrHeight, encodeHints);
			int[] pixels = new int[qrWidth * qrHeight];
			//下面这里按照二维码的算法,逐个生成二维码的图片,
			//两个for循环是图片横列扫描的结果
			for (int y = 0; y < qrHeight; y++)
			{
				for (int x = 0; x < qrWidth; x++)
				{
					if (bitMatrix.get(x, y))
					{
						pixels[y * qrWidth + x] = 0xff000000;
					}
					else
					{
						pixels[y * qrWidth + x] = 0xffffffff;
					}
				}
			}
			//生成二维码图片的格式,使用ARGB_8888
			Bitmap bitmap = Bitmap.createBitmap(qrWidth, qrHeight, Bitmap.Config.ARGB_8888);
			bitmap.setPixels(pixels, 0, qrWidth, 0, 0, qrWidth, qrHeight);
			return bitmap;
		}
		catch (WriterException e)
		{
			e.printStackTrace();
		}
		
		return null;
	}
	
	
	/**
	 * 二维码图片解码为字符串。
	 * @param bmp	二维码图片
	 * @return	String	二维码图片中的信息,解码失败返回""。
	 */
	public static String decode(Bitmap bmp){
		
	    int[] intArray = new int[bmp.getWidth()*bmp.getHeight()];  
	    bmp.getPixels(intArray, 0, bmp.getWidth(), 0, 0, bmp.getWidth(), bmp.getHeight());  
	    LuminanceSource source = new RGBLuminanceSource(bmp.getWidth(), bmp.getHeight(), intArray);
	    BinaryBitmap binaryBitmap = new BinaryBitmap(new HybridBinarizer(source));
		try {
			HashMap<DecodeHintType, String> decodeHints = new HashMap<DecodeHintType, String>();
			decodeHints.put(DecodeHintType.CHARACTER_SET, "utf-8");
			Result result=new QRCodeReader().decode(binaryBitmap,decodeHints);
			return result.getText();
		} catch (NotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (ChecksumException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (FormatException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return "";
	}
	/**
	 * 图片数据中指定二维码区域进行解码。
	 * @param data			byte[]	图片数据,具体格式不知道。
	 * @param dataWidth		int		图片宽
	 * @param dataHeight	int		图片高
	 * @param left			int
	 * @param top			int
	 * @param width			int
	 * @param height		int
	 * @return	String
	 */
	public static String decode(byte[] data,int dataWidth,int dataHeight,int left,int top,int width,int height){
		PlanarYUVLuminanceSource source=new PlanarYUVLuminanceSource(data, dataWidth, dataHeight, 
				left, top, width, height, false);
		BinaryBitmap binaryBitmap = new BinaryBitmap(new HybridBinarizer(source));
		Result result;
		try {
			HashMap<DecodeHintType, String> decodeHints = new HashMap<DecodeHintType, String>();
			decodeHints.put(DecodeHintType.CHARACTER_SET, "utf-8");
			result = new QRCodeReader().decode(binaryBitmap,decodeHints);
			return result.getText();
		} catch (NotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (ChecksumException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (FormatException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return "";
	}
	/**
	 * 截取指定区域的图片。
	 * @param data			byte[]	源数据源
	 * @param format		int		camera.getParameters().getPreviewFormat()
	 * @param dataWidth		int		数据宽
	 * @param dataHeight	int		数据高
	 * @param left			int
	 * @param top			int
	 * @param width			int
	 * @param height		int
	 * @return				Bitmap
	 */
	static Bitmap capture(byte[] data,int format,int dataWidth,int dataHeight,int left,int top,int width,int height){
		//参考:http://blog.csdn.net/hipilee/article/details/8629234
	    YuvImage yuvImg = new YuvImage(data,format,dataWidth,dataHeight,null);
		ByteArrayOutputStream outputstream = new ByteArrayOutputStream();
		Rect rect=new Rect(left,top,left+width,top+height); 
        yuvImg.compressToJpeg(rect, 100, outputstream); 
        Bitmap bitmap = BitmapFactory.decodeByteArray(outputstream.toByteArray(), 0,outputstream.size());
	    return bitmap;
	}
	/**
	 * 截取指定区域的图片。
	 * 灰度图。
	 * @param data
	 * @param dataWidth
	 * @param dataHeight
	 * @param left
	 * @param top
	 * @param width
	 * @param height
	 * @return
	 */
	static Bitmap renderCroppedGreyscaleBitmap(byte[] data,int dataWidth,int dataHeight,int left,int top,int width,int height) {
	    int[] pixels = new int[width * height];
	    byte[] yuv = data;
	    int inputOffset = top * dataWidth + left;

	    for (int y = 0; y < height; y++) {
	      int outputOffset = y * width;
	      for (int x = 0; x < width; x++) {
	        int grey = yuv[inputOffset + x] & 0xff;
	        pixels[outputOffset + x] = 0xFF000000 | (grey * 0x00010101);
	      }
	      inputOffset += dataWidth;
	    }

	    Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
	    bitmap.setPixels(pixels, 0, width, 0, 0, width, height);
	    return bitmap;
	  }
	
}

扫描框类ViewfinderView.java

/*
 * Copyright (C) 2008 ZXing authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package cn.jucheng.jclibs.qrcode;

import java.util.Collection;
import java.util.HashSet;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.util.AttributeSet;
import android.view.View;

import com.google.zxing.ResultPoint;

/**
 * 扫描框。
 * <p>使用方式:</p>
 * <p>在启动JcQrCodeScanner之后调用</p>
 * <pre>
 * viewfinderView.stopSaomiao=false;
 * viewfinderView.invalidate();
 * </pre>
 * <p>停止扫描时,调用</p>
 * <pre>
 * vf.stopSaomiao=true;
 * </pre>
 */
public final class ViewfinderView extends View {
	private static final String TAG = "ViewfinderView";
	/**
	 * 刷新界面的时间间隔(毫秒)。
	 */
	public int animationDelay = 20;

	/**
	 * 四个绿色边角对应的长度
	 */
	public int cornerLineLength=10;

	/**
	 * 四个绿色边角对应的宽度
	 */
	public int cornerLineWidth = 3;
	
	/**
	 * 四个绿色边角对应的颜色
	 */
	public int cornerLineColor=0xFFFFCE85;
	
	/**
	 * 扫描框中的中间线的宽度
	 */
	public int middleLineWidth = 2;

	/**
	 * 扫描框中的中间线的与扫描框左右的间隙
	 */
	public int middleLinePadding = 5;

	/**
	 * 中间那条线每次刷新移动的距离
	 */
	public float speed = 5f;
	
	/**
	 * 扫描线的位置(y方向坐标)
	 */
	public float middleLineLocation=0f;

	/**
	 * 字体大小
	 */
	private int textSize = 16;
	/**
	 * 字体颜色
	 */
	private int textColor=Color.WHITE;
	/**
	 * 字体距离扫描框下面的距离
	 */
	private int textPaddingTop = 30;
	/**
	 * 显示的文字
	 */
	private String text="扫描中...";

	/**
	 * 画笔对象的引用
	 */
	private Paint paint = new Paint();

	/**
	 * 二维码区域外的颜色
	 */
	public int maskColor = 0x88000000;
	
	/**
	 * 停止扫描
	 */
	public Boolean stopSaomiao=false;
	/**
	 * 
	 */
	public int resultPointColor = Color.YELLOW;;
	private Collection<ResultPoint> possibleResultPoints = new HashSet<ResultPoint>(5);
	private Collection<ResultPoint> lastPossibleResultPoints;

	public ViewfinderView(Context context, AttributeSet attrs) {
		super(context, attrs);
	}

	@Override
	public void onDraw(Canvas canvas) {
		if (JcQrCodeScanner.get() == null) {
			return;
		}
		Rect frame = JcQrCodeScanner.get().getFramingRect();
		if (frame == null) {
			return;
		}
		// 获取屏幕的宽和高
		int width = canvas.getWidth();
		int height = canvas.getHeight();
		paint.setColor(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(stopSaomiao)return;

		// 画扫描框边上的角,总共8个部分
		paint.setColor(cornerLineColor);
		canvas.drawRect(frame.left, frame.top, frame.left + cornerLineLength, frame.top + cornerLineWidth, paint);
		canvas.drawRect(frame.left, frame.top, frame.left + cornerLineWidth, frame.top + cornerLineLength, paint);
		canvas.drawRect(frame.right - cornerLineLength, frame.top, frame.right, frame.top + cornerLineWidth, paint);
		canvas.drawRect(frame.right - cornerLineWidth, frame.top, frame.right, frame.top + cornerLineLength, paint);
		canvas.drawRect(frame.left, frame.bottom - cornerLineWidth, frame.left + cornerLineLength, frame.bottom, paint);
		canvas.drawRect(frame.left, frame.bottom - cornerLineLength, frame.left + cornerLineWidth, frame.bottom, paint);
		canvas.drawRect(frame.right - cornerLineLength, frame.bottom - cornerLineWidth, frame.right, frame.bottom, paint);
		canvas.drawRect(frame.right - cornerLineWidth, frame.bottom - cornerLineLength, frame.right, frame.bottom, paint);

		// 绘制中间的线,每次刷新界面,中间的线往下移动SPEEN_DISTANCE
		middleLineLocation += speed;
		if (middleLineLocation >= frame.bottom||middleLineLocation<frame.top) {
			middleLineLocation = frame.top;
		}
		canvas.drawRect(frame.left + middleLinePadding, middleLineLocation - middleLineWidth / 2, frame.right - middleLinePadding, middleLineLocation + middleLineWidth / 2, paint);
		
		//画小黄点
		Collection<ResultPoint> currentPossible = possibleResultPoints;
		Collection<ResultPoint> currentLast = lastPossibleResultPoints;
		if (currentPossible.isEmpty()) {
			lastPossibleResultPoints = null;
		} else {
			possibleResultPoints = new HashSet<ResultPoint>(5);
			lastPossibleResultPoints = currentPossible;
			paint.setColor(resultPointColor);
			for (ResultPoint point : currentPossible) {
				canvas.drawCircle(frame.left + point.getX(), frame.top + point.getY(), 6.0f, paint);
			}
		}
		if (currentLast != null) {
			paint.setColor(resultPointColor);
			for (ResultPoint point : currentLast) {
				canvas.drawCircle(frame.left + point.getX(), frame.top + point.getY(), 3.0f, paint);
			}
		}
		// 画扫描框下面的字
		drawText(canvas,frame);
			
		// 只刷新扫描框的内容,其他地方不刷新,刷新区域没有用到
		postInvalidateDelayed(animationDelay, frame.left, frame.top, frame.right, frame.bottom);
	}
	/**
	 * 画文字
	 * @param canvas
	 * @param frame
	 */
	protected void drawText(Canvas canvas,Rect frame){
		paint.setColor(textColor);
		paint.setTextSize(textSize);
		paint.setTypeface(Typeface.create("System", Typeface.BOLD));
		canvas.drawText(text, frame.left, (float) (frame.bottom + (float) textPaddingTop), paint);
	}
	/**
	 * 添加小黄点。
	 * 小黄点是什么意思,还不了解。
	 * @param point
	 */
	public void addPossibleResultPoint(ResultPoint point) {
		possibleResultPoints.add(point);
	}

}



源码打包下载

« 上一篇下一篇 »

相关文章:

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。