前几天做了电脑端二维码识别,突然想在手机上做一个二维码识别功能,发现过程还是有点麻烦,问题主要出现在要实现预览,预览界面的视频比例问题,最终也未能完美解决,实际效果尚可
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" app:layout_behavior="@string/appbar_scrolling_view_behavior" tools:showIn="@layout/activity_main"> <Button android:id="@+id/btnQRCodeScan" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="扫描二维码" /> <TextureView android:id="@+id/textureQRCodeScan" android:layout_gravity="center" android:layout_width="match_parent" android:layout_height="match_parent" /> <TextView android:id="@+id/textviewQRCodeScan" android:visibility="gone" android:layout_width="match_parent" android:layout_height="match_parent" /> </LinearLayout>
MainActivity代码:
using System; using Android.App; using Android.OS; using Android.Runtime; using Android.Support.Design.Widget; using Android.Support.V4.App; using Android.Support.V4.Content; using Android.Support.V7.App; using Android.Views; using Android.Widget; using System.Linq; using static Android.Manifest; using Android.Content; using Android.Provider; using Android.Graphics; using Android.Hardware.Camera2; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Android.Net.Wifi.Aware; using Android.Util; namespace QRCodeScanner { [Activity(Label = "@string/app_name", Theme = "@style/AppTheme.NoActionBar", MainLauncher = true)] public class MainActivity : AppCompatActivity { private static CameraCaptureSession cameraSession = null; private static readonly object sessionLocker = new object(); public class CameraStateListener : CameraDevice.StateCallback { TextureView textureView = null; public CameraStateListener(TextureView texture) { textureView = texture; } public override void OnOpened(CameraDevice cameraDevice) { var surfaces = new List<Surface> { new Surface(textureView.SurfaceTexture) }; var createCaptureSessionCallback = new CameraCaptureSessionStateListener(cameraDevice, textureView); cameraDevice.CreateCaptureSession(surfaces, createCaptureSessionCallback, null); } public override void OnDisconnected(CameraDevice cameraDevice) { } public override void OnError(CameraDevice cameraDevice, CameraError error) { } } public class CameraCaptureSessionStateListener : CameraCaptureSession.StateCallback { private CameraDevice cameraDevice = null; private TextureView textureView = null; public CameraCaptureSessionStateListener(CameraDevice camera, TextureView texture) { cameraDevice = camera; textureView = texture; } public override void OnConfigureFailed(CameraCaptureSession session) { //do nothing } public override void OnConfigured(CameraCaptureSession session) { var requestBuilder = cameraDevice.CreateCaptureRequest(CameraTemplate.Preview); requestBuilder.AddTarget(new Surface(textureView.SurfaceTexture)); var request = requestBuilder.Build(); lock (sessionLocker) { cameraSession = session; cameraSession.SetRepeatingRequest(request, null, null); } } } private void stopCapture() { lock (sessionLocker) { if (cameraSession != null) { cameraSession.StopRepeating(); cameraSession = null; } } } protected override void OnCreate(Bundle savedInstanceState) { base.OnCreate(savedInstanceState); SetContentView(Resource.Layout.activity_main); Android.Support.V7.Widget.Toolbar toolbar = FindViewById<Android.Support.V7.Widget.Toolbar>(Resource.Id.toolbar); SetSupportActionBar(toolbar); FloatingActionButton fab = FindViewById<FloatingActionButton>(Resource.Id.fab); fab.Click += FabOnClick; var btnQRCodeScan = FindViewById<Button>(Resource.Id.btnQRCodeScan); btnQRCodeScan.Click += BtnQRCodeScan_Click; var timer = new Timer(new TimerCallback(delegate (object state) { this.RunOnUiThread(() => { var textureQRCodeScan = FindViewById<TextureView>(Resource.Id.textureQRCodeScan); var textviewQRCodeScan = FindViewById<TextView>(Resource.Id.textviewQRCodeScan); var image = textureQRCodeScan.Bitmap; if (image == null) return; lock (sessionLocker) { if (cameraSession == null) return; } var task = new Task(()=> { lock (sessionLocker) { if (cameraSession == null) return; } var sw = new System.Diagnostics.Stopwatch(); sw.Start(); var data = ReadQRCode(image); sw.Stop(); System.Diagnostics.Debug.Print("time cost:" + sw.ElapsedMilliseconds); if (data.Length > 0) { this.RunOnUiThread(() => { stopCapture(); System.Diagnostics.Debug.Print("data:" + data); FindViewById<TextureView>(Resource.Id.textureQRCodeScan).Visibility = ViewStates.Gone; FindViewById<TextView>(Resource.Id.textviewQRCodeScan).Text = data; FindViewById<TextView>(Resource.Id.textviewQRCodeScan).Visibility = ViewStates.Visible; FindViewById<Button>(Resource.Id.btnQRCodeScan).Visibility = ViewStates.Visible; }); } }); task.Start(); }); }), null, 0, 200); } public override bool OnCreateOptionsMenu(IMenu menu) { MenuInflater.Inflate(Resource.Menu.menu_main, menu); return true; } public override bool OnOptionsItemSelected(IMenuItem item) { int id = item.ItemId; if (id == Resource.Id.action_settings) { return true; } return base.OnOptionsItemSelected(item); } private void FabOnClick(object sender, EventArgs eventArgs) { View view = (View) sender; Snackbar.Make(view, "Replace with your own action", Snackbar.LengthLong) .SetAction("Action", (Android.Views.View.IOnClickListener)null).Show(); } private void BtnQRCodeScan_Click(object sender, EventArgs e) { if (Build.VERSION.SdkInt >= Android.OS.BuildVersionCodes.M) { var permission = new string[] { Permission.Camera, "android.hardware.camera.autofocus" }; var requestPermissions = permission.Where(p => ContextCompat.CheckSelfPermission(this, p) != Android.Content.PM.Permission.Granted).ToList(); if (requestPermissions.Count > 0) ActivityCompat.RequestPermissions(this, requestPermissions.ToArray(), 1); } var textureQRCodeScan = FindViewById<TextureView>(Resource.Id.textureQRCodeScan); textureQRCodeScan.Visibility = ViewStates.Visible; var textviewQRCodeScan = FindViewById<TextView>(Resource.Id.textviewQRCodeScan); textviewQRCodeScan.Visibility = ViewStates.Gone; textviewQRCodeScan.Text = string.Empty; var btnQRCodeScan = FindViewById<Button>(Resource.Id.btnQRCodeScan); btnQRCodeScan.Visibility = ViewStates.Gone; var cameraManager = (Android.Hardware.Camera2.CameraManager)GetSystemService(Context.CameraService); var cameraID = cameraManager.GetCameraIdList()[0]; var characteristics = cameraManager.GetCameraCharacteristics(cameraID); var map = characteristics.Get(CameraCharacteristics.ScalerStreamConfigurationMap) as Android.Hardware.Camera2.Params.StreamConfigurationMap; var jpegSizes = map.GetOutputSizes((int)ImageFormatType.Jpeg); var sizes = new List<KeyValuePair<int, int>>(); for (var i = 0; i < jpegSizes.Length; i++) { sizes.Add(new KeyValuePair<int, int>(jpegSizes[i].Width, jpegSizes[i].Height)); System.Diagnostics.Debug.Print(jpegSizes[i].Width.ToString() + "X" + jpegSizes[i].Height.ToString()); } var metrics = new DisplayMetrics(); WindowManager.DefaultDisplay.GetMetrics(metrics); var screenWidth = metrics.WidthPixels; var screenHeight = metrics.HeightPixels; var ratedSizes = sizes.Select(x => new { size = x, rate = (float)x.Key / (float)x.Value }) .Select(x => new { size = x, differ = Math.Abs(x.rate - (float)screenWidth / (float)screenHeight) }).ToList(); var bestSize = ratedSizes.Where(x => x.differ == ratedSizes.Min(y => y.differ)).First().size.size; int videoWidth = bestSize.Key; int videoHeight = bestSize.Value; var textureWidth = textureQRCodeScan.MeasuredWidth; var textureHeight = textureQRCodeScan.MeasuredHeight; //以高为基准进行缩放,宽度未超出,则以高为基准进行缩放 if (videoWidth * textureHeight / videoHeight > textureWidth) { textureQRCodeScan.LayoutParameters.Width = videoWidth * textureHeight / videoHeight; textureQRCodeScan.LayoutParameters.Height = textureQRCodeScan.MeasuredHeight; } //以宽为基准进行缩放 else { textureQRCodeScan.LayoutParameters.Width = textureQRCodeScan.MeasuredWidth; textureQRCodeScan.LayoutParameters.Height = textureWidth * videoHeight / videoWidth; } cameraManager.OpenCamera(cameraID, new CameraStateListener(textureQRCodeScan), null); } /// <summary> /// 生成二维码 /// </summary> /// <param name="data"></param> /// <param name="height"></param> /// <param name="width"></param> /// <param name="margin"></param> /// <returns></returns> public static byte[] CreateQRCode(string data, int height = 100 , int width = 100, int margin = 0) { byte[] bytes = null; var barcodeWriter = new ZXing.Android.BarcodeWriter() { Format = ZXing.BarcodeFormat.QR_CODE, Options = new ZXing.QrCode.QrCodeEncodingOptions() { Height = height, Width = width, Margin = margin } }; using (var image = barcodeWriter.Write(data)) { using (var stream = new System.IO.MemoryStream()) { image.Compress(Bitmap.CompressFormat.Png, 100, stream); bytes = stream.ToArray(); } } return bytes; } /// <summary> /// 识别二维码 /// </summary> /// <param name="bytes"></param> /// <returns></returns> public static string ReadQRCode(Bitmap image) { var result = string.Empty; var barcodeReader = new ZXing.Android.BarcodeReader(); var decoded = barcodeReader.Decode(image); if (decoded != null) result = decoded.Text; return result; } } }