Android开发 实现Zxing二维码半屏扫描

最近接到一个半屏扫描二位码的需求,要实现界面的上半屏是扫描二维码,下半屏是扫描结果数据的显示。接到这个需求第一时间想到的是,印象中人生中没有实现过半屏扫描二维码的工作经验!!!难受啊,但是为了保持自己的专业性!就硬着头皮答应了下来:“OK,没问题,我改一下就好”

我是不是答应的太快了??我代码都还没看过呢!!作为一个专业的Android工程师,这样评估一个需求工作量是完成不靠谱的!哎,答应都答应了,那就开干吧。

首先要接入Zxing二维码扫描包

compile files('libs/ZXing-core-3.3.3.jar')

接入后,你会发现扫描的主要页面activity是叫这个名字的:CaptureActivity,然后这个是全屏的扫描,打开的界面大概是下面这个样子的。

而扫描获取到的二维码数据是通过setResult()回调到你需要这个数据的界面。也就是说,我们调用二维码扫描是用startActivityForResult()调的。

好吧,这个简单的知识估计大家都知道。那就是接着继续做吧。首先先显示半屏的功能,我有个大胆想法,要实现半屏是不是可以用个控件把里面的扫描框顶上去就好了。好咧那就试试吧,先粗略看一下这个扫二维码的界面布局是怎样写的。

上图就是扫描的界面,一个SurfaceView,一个ViewfindView,还有一个TextView,简单啦。看我改一下,把它改成半屏。用最快速度改一下布局

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
<RelativeLayout
    android:layout_width="match_parent"
    android:layout_height="0dp"
    android:layout_weight="1"
    android:orientation="vertical" >

    <SurfaceView
        android:id="@+id/surfaceview"
        android:layout_width="match_parent"
        android:layout_height="match_parent" >
    </SurfaceView>

    <com.zxing.android.view.ViewfinderView
        android:id="@+id/viewfinderview"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="#00000000" >
    </com.zxing.android.view.ViewfinderView>

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:gravity="center_horizontal"
        android:text="@string/msg_default_status"
        android:textSize="15sp" >
    </TextView>
    <include layout="@layout/toolbar"/>


</RelativeLayout>

    <TextView
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"/>

</LinearLayout>

运行后效果:

扫描二维码关注公众号,回复: 10049175 查看本文章

仔细看,会发现相机的预览界面被挤压的变形了,而我们想让扫码框往上移的功能也没有实现。很明显这种操作是不行的,难受啊。我们再看看扫描的源码吧,这次要带着目的性去看。带着问题去看,“到底是在哪里控制着扫描框的呢?”。一切的起源是在CaptureActivity。我们看看它里面的代码吧

package com.zxing.android;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger;
import java.util.Vector;

import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.res.AssetFileDescriptor;
import android.graphics.Bitmap;
import android.graphics.Camera;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.media.MediaPlayer.OnCompletionListener;
import android.os.Bundle;
import android.os.Handler;
import android.os.Vibrator;
import android.view.KeyEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceHolder.Callback;
import android.view.SurfaceView;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.widget.TextView;

import com.emms.R;
import com.emms.activity.BaseActivity;
import com.emms.activity.CusActivity;
import com.emms.activity.NfcUtils;
import com.emms.schema.Factory;
import com.emms.util.BaseData;
import com.emms.util.Constants;
import com.emms.util.DataUtil;
import com.emms.util.LocaleUtils;
import com.emms.util.LogUtils;
import com.emms.util.NumberFormatUtil;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.Result;
import com.zxing.android.camera.CameraManager;
import com.zxing.android.decoding.CaptureActivityHandler;
import com.zxing.android.decoding.InactivityTimer;
import com.zxing.android.view.ViewfinderView;

public class CaptureActivity extends BaseActivity implements Callback {
	private CaptureActivityHandler handler;
	private ViewfinderView viewfinderView;
	private SurfaceView surfaceView;
	private boolean hasSurface;
	private Vector<BarcodeFormat> decodeFormats;
	private String characterSet;
	private InactivityTimer inactivityTimer;
	private MediaPlayer mediaPlayer;
	private boolean playBeep;
	// private static final float BEEP_VOLUME = 0.10f;
	private boolean vibrate;
	CameraManager cameraManager;

	/** Called when the activity is first created. */
	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);

		requestWindowFeature(Window.FEATURE_NO_TITLE);
		setContentView(R.layout.activity_capture);
		((TextView)findViewById(R.id.tv_title)).setText(LocaleUtils.getI18nValue("sacn_qr_code"));
		findViewById(R.id.btn_right_action).setOnClickListener(new View.OnClickListener() {
			@Override
			public void onClick(View v) {
				finish();
			}
		});
		surfaceView = (SurfaceView) findViewById(R.id.surfaceview);
		viewfinderView = (ViewfinderView) findViewById(R.id.viewfinderview);

		Window window = getWindow();
		window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);

		hasSurface = false;
		inactivityTimer = new InactivityTimer(this);
	}

	@Override
	protected void onResume() {
		super.onResume();
		if (getRequestedOrientation() != ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) {
			setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
		}
		// CameraManager.init(getApplication());
		cameraManager = new CameraManager(getApplication());

		viewfinderView.setCameraManager(cameraManager);

		SurfaceHolder surfaceHolder = surfaceView.getHolder();
		if (hasSurface) {
			initCamera(surfaceHolder);
		} else {
			surfaceHolder.addCallback(this);
			surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
		}
		decodeFormats = null;
		characterSet = null;

		playBeep = true;
		AudioManager audioService = (AudioManager) getSystemService(AUDIO_SERVICE);
		if (audioService.getRingerMode() != AudioManager.RINGER_MODE_NORMAL) {
			playBeep = false;
		}
		initBeepSound();
		vibrate = true;
	}

	@Override
	protected void onPause() {
		super.onPause();
		if (handler != null) {
			handler.quitSynchronously();
			handler = null;
		}
		cameraManager.closeDriver();
	}

	@Override
	protected void onDestroy() {
		inactivityTimer.shutdown();
		super.onDestroy();
	}

	private void initCamera(SurfaceHolder surfaceHolder) {
		try {
			// CameraManager.get().openDriver(surfaceHolder);
			cameraManager.openDriver(surfaceHolder);
		} catch (IOException ioe) {
			return;
		} catch (RuntimeException e) {
			return;
		}
		if (handler == null) {
			handler = new CaptureActivityHandler(this, decodeFormats, characterSet);
		}
	}

	@Override
	public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

	}

	@Override
	public void surfaceCreated(SurfaceHolder holder) {
		if (!hasSurface) {
			hasSurface = true;
			initCamera(holder);
		}
	}

	@Override
	public void surfaceDestroyed(SurfaceHolder holder) {
		hasSurface = false;

	}

	public CameraManager getCameraManager() {
		return cameraManager;
	}

	public ViewfinderView getViewfinderView() {
		return viewfinderView;
	}

	public Handler getHandler() {
		return handler;
	}

	public void drawViewfinder() {
		viewfinderView.drawViewfinder();

	}

	public void handleDecode(Result obj, Bitmap barcode) {
		inactivityTimer.onActivity();
		playBeepSoundAndVibrate();
		LogUtils.e("获取到扫描数据----->"+obj.toString());
		showResult(obj, barcode);
	}

	protected void showResult(final Result rawResult, Bitmap barcode) {
		inactivityTimer.onActivity();
		Intent it = new Intent(CaptureActivity.this, CusActivity.class);
		if(BaseData.getConfigData().get(BaseData.TASK_GET_EQUIPMENT_DATA_FROM_ICCARD_ID)==null) {
			switch (getLoginInfo().getFromFactory()) {
				case Factory.FACTORY_GEW: {
					it.putExtra("result", rawResult.getText());
					break;
				}
				case Factory.FACTORY_EGM: {
					try {
						//rawResult.getText().toCharArray();
						it.putExtra("result",new BigInteger(rawResult.getText(),16).toString());
//						it.putExtra("result", NumberFormatUtil.HexToLongString(rawResult.getText()));
					} catch (Exception e) {
						it.putExtra("result", rawResult.getText());
					}
					break;
				}
				default: {
					it.putExtra("result", rawResult.getText());
					break;
				}
			}
		}else {
			switch (DataUtil.isDataElementNull(BaseData.getConfigData().get(BaseData.TASK_GET_EQUIPMENT_DATA_FROM_ICCARD_ID))){
				case "1":{
					try {
						it.putExtra("result", new BigInteger(rawResult.getText(),16).toString());
					} catch (Exception e) {
						it.putExtra("result", rawResult.getText());
					}
					break;
				}
				case "2":{
					it.putExtra("result", rawResult.getText());
					break;
				}
				default:{
					it.putExtra("result", rawResult.getText());
					break;
				}
			}
		}
		setResult(Constants.RESULT_CODE_CAPTURE_ACTIVITY_TO_TASK_DETAIL, it);
		finish();
	}

	public void restartPreviewAfterDelay(long delayMS) {
		if (handler != null) {
			handler.sendEmptyMessageDelayed(MessageIDs.restart_preview, delayMS);
		}
	}

	private void initBeepSound() {
		if (playBeep && mediaPlayer == null) {
			// The volume on STREAM_SYSTEM is not adjustable, and users found it
			// too loud,
			// so we now play on the music stream.
			setVolumeControlStream(AudioManager.STREAM_MUSIC);
			mediaPlayer = new MediaPlayer();
			mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
			mediaPlayer.setOnCompletionListener(beepListener);

			try {
				AssetFileDescriptor fileDescriptor = getAssets().openFd("qrbeep.ogg");
				this.mediaPlayer.setDataSource(fileDescriptor.getFileDescriptor(), fileDescriptor.getStartOffset(),
						fileDescriptor.getLength());
				this.mediaPlayer.setVolume(0.1F, 0.1F);
				this.mediaPlayer.prepare();
			} catch (IOException e) {
				this.mediaPlayer = null;
			}
		}
	}

	private static final long VIBRATE_DURATION = 200L;

	private void playBeepSoundAndVibrate() {
		if (playBeep && mediaPlayer != null) {
			mediaPlayer.start();
		}
		if (vibrate) {
			Vibrator vibrator = (Vibrator) getSystemService(VIBRATOR_SERVICE);
			vibrator.vibrate(VIBRATE_DURATION);
		}
	}

	/**
	 * When the beep has finished playing, rewind to queue up another one.
	 */
	private final OnCompletionListener beepListener = new OnCompletionListener() {
		public void onCompletion(MediaPlayer mediaPlayer) {
			mediaPlayer.seekTo(0);
		}
	};

	@Override
	public boolean onKeyDown(int keyCode, KeyEvent event) {
		if (keyCode == KeyEvent.KEYCODE_BACK) {
			setResult(RESULT_CANCELED);
			finish();
			return true;
		} else if (keyCode == KeyEvent.KEYCODE_FOCUS || keyCode == KeyEvent.KEYCODE_CAMERA) {
			return true;
		}
		return super.onKeyDown(keyCode, event);
	}
	public static String str2HexStr(String str)
	{

		char[] chars = "0123456789ABCDEF".toCharArray();
		StringBuilder sb = new StringBuilder("");
		byte[] bs = str.getBytes();
		int bit;

		for (int i = 0; i < bs.length; i++)
		{
			bit = (bs[i] & 0x0f0) >> 4;
			sb.append(chars[bit]);
			bit = bs[i] & 0x0f;
			sb.append(chars[bit]);
			sb.append(' ');
		}
		return sb.toString().trim();
	}

}

都说看代码从oncreate方法开始看,所以我们可以看到这里面初始化了两个关键的控件,surfaceView和viewfinderView。这两个控件一个是用来做相机预览的(surfaceView),一个是就是那个我们要找的扫码框(viewfinderView)。所以我们可以解析了为什么预览界面会变形了,因为原本全屏的surfaceView被我强制挤压变成了半屏。所以我们还是调整回原来的布局,其实我们只要想办法把viewfinderView往上移,就可以显示半屏显示了。

我们找找哪里有配置viewfinderView的,Ctrl+F后你会发现,在onResume方法有用到viewfinderView

@Override
	protected void onResume() {
		super.onResume();
		if (getRequestedOrientation() != ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) {
			setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
		}
		// CameraManager.init(getApplication());
		cameraManager = new CameraManager(getApplication());

		viewfinderView.setCameraManager(cameraManager);

		SurfaceHolder surfaceHolder = surfaceView.getHolder();
		if (hasSurface) {
			initCamera(surfaceHolder);
		} else {
			surfaceHolder.addCallback(this);
			surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
		}
		decodeFormats = null;
		characterSet = null;

		playBeep = true;
		AudioManager audioService = (AudioManager) getSystemService(AUDIO_SERVICE);
		if (audioService.getRingerMode() != AudioManager.RINGER_MODE_NORMAL) {
			playBeep = false;
		}
		initBeepSound();
		vibrate = true;
	}

这里先创建了一个CameraManager的对象,然后viewfinderView把对象给设置进去了。看看里面用的CameraManager对象干了什么事情

Override
	public void onDraw(Canvas canvas) {
		// 中间的扫描框,你要修改扫描框的大小,去CameraManager里面修改
		Rect frame = cameraManager.getFramingRect();
		if (frame == null) {
			return;
		}

可以看到在ViewFinder的onDraw方法里面用了cameraManager.getFramingRect(),这里的意思就是获取需要扫描框要显示的位置,或者说是摆放的位置,来了来了,真的来了。也就是说,CameraManager这个东西是决定ViewFinderView的摆放位置的。我们去看看CameraManager,下面就是我们的关键代码了。

/**
	 * 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 Rect getFramingRect() {
		if (framingRect == null) {
			if (camera == null) {
				return null;
			}
			if(configManager.getScreenResolution()==null){
               return null;
			}
			Point screenResolution = configManager.getScreenResolution();

			int width = screenResolution.x * 3 / 4;
			LogUtils.e("执行这里的width--->"+width);
			if (width < MIN_FRAME_WIDTH) {
				width = MIN_FRAME_WIDTH;
			} else if (width > 800) {
				width = MAX_FRAME_WIDTH;
			} else {
				width = MID_FRAME_WIDTH;
			}

			int height = screenResolution.y * 3 / 4;
			if (height < MIN_FRAME_HEIGHT) {
				height = MIN_FRAME_HEIGHT;
			} else if (height > 1400) {
				height = MAX_FRAME_HEIGHT;
			} else {
				height = MID_FRAME_HEIGHT;
			}

			LogUtils.e("width相减的结果---->"+(screenResolution.x-width));

			LogUtils.e("height相减的结果---->"+(screenResolution.y-height));
			
			int leftOffset = (screenResolution.x - width)/2;
			int topOffset = (screenResolution.y - height)*2/5;

			LogUtils.e("最后的左边距--->"+leftOffset);
			LogUtils.e("最后的顶边距--->"+topOffset);

			if (isHalfSize){
				framingRect = new Rect(leftOffset, 80, leftOffset + width, width);
			}else{
				framingRect = new Rect(leftOffset, topOffset, leftOffset + width, topOffset + height);
			}

//			framingRect = new Rect(leftOffset, topOffset, leftOffset + width, topOffset + height);
			LogUtils.e("底部的高度--->"+height);


			Log.d(TAG, "Calculated framing rect: " + framingRect);
		}
		return framingRect;
	}

在CameraManager的getFramingRect()方法里面

if (isHalfSize){
				framingRect = new Rect(leftOffset, 80, leftOffset + width, width);
			}else{
				framingRect = new Rect(leftOffset, topOffset, leftOffset + width, topOffset + height);
			}

上面的两行代码决定了扫描框的摆放位置,所以我加了一个判断,isHalfSize是否半屏显示来控制扫描框的位置,从而实现半屏的显示。说了这么多,贴了这么多代码,终于找到了需要修改的地方。真的是看代码三天,修改三分钟。修改完成后,我们运行显示看一下。

没想到,我截图上传居然变形了,大家不要误会,这是图片变形了,并不是扫描界面变形了。所以说,最后的成果就是这样啦,显示了,上面扫描下面同步显示数据。这样的一个功能就完成了。除了实现半屏以外,其实还可以实现自定义界面,不一定要在CaptureActivity界面修改,我们还可以在需要的界面要接入扫描的功能,去自定义。不过这里面还涉及了要修改其他的文件和代码,这里就不一一展开了。需要实现类型的功能的,可以私信我,我们一起研究哈哈!

希望可以帮到你们,我是一名Android工程师,确不甘于只有工程师!我们一起加油,向往美好的生活。

发布了47 篇原创文章 · 获赞 29 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/Ruan_Number3/article/details/91450191