首页 > 代码库 > ``Accordian'' Patience
``Accordian'' Patience
终于解决了一个忧伤好久的问题,严重拖了项目进度,深感惭愧!一直被一系列的问题所困扰,然后又只能自己一个人摸索,也是一段辛酸忧伤史,现在小结一下上个月在做二维码的过程中所碰到的问题以及解决办法,现在庆幸终于解决好了,终于能将这个功能告一段落,一下小结也是分享一下Unity的某些“坑”,让同行少走弯路,起码在二维码这方面应该会有所启迪,欣慰的是接下来几天终于可以做自己应该做的事情了!
效果图:
先小结一下碰到的问题:
1.Unity工程屏幕方向与Android工程屏幕方向要一致的问题
本来我测试Android扫码的功能,已经测是通过了,可以参考之前写的博文:http://blog.csdn.net/dingxiaowei2013/article/details/24677795,后来将这这个插件添加到现有的项目中,但导出项目apk后,打开应用怎么就是闪退,表示很忧伤,后来一步一步的精简对比测试,表示自己五一那天一直摸索到凌晨三点才发现这一问题根源,有了这个基础才有接下来思路的转变。我的Android工程设置的屏幕旋转方向是portrait,但由于项目原因,Unity的导出方向必须是landscapeleft,这两者方向不一致会导致应用异常奔溃闪退!当然我的解决思路可能是比较笨的,我将Android工程做成的插件的屏幕方向也做成了横屏的,为了和Unity的方向一致,我想应该就不会出现那种问题。我原本的思路是将Unity导出屏幕设置成auto,然后代码控制屏幕的方向,但貌似还是不成功,没辙只能还是换种思路!网上有不少Android的扫码工程,最多的就是基于zxing,但是都是有很多冗余,不适合初学者,尤其是像我这种Android初学者,我倒是看到一个比较适合入门的精简的扫码工程,可惜是纵屏的,上一篇文章就是介绍的这个,但并不适合我的项目需求,所以我就想办法将这个工程改成横屏工程,本以为只要改改xml配置文件就OK的,结果并不是想象的那么简单,仅仅那么该会出现扫的过程中图片压缩以及移动别扭等现象,还是需要修改里面的工程的!这里可以参考:http://dingxiaowei2013.blog.163.com/blog/static/21965310720144595534507/,这篇是将横屏改纵屏的解决方案,然后将其逆序操作,但我还是出现了一些问题,比如扫到一半的时候应用异常崩溃,又表示很忧伤,为啥都不是一帆风顺呢!然后反复重做,终于成功了!精简版的工程源码我会贴出来共享!为了实现这个功能,可谓是一次有一次的导出,一次有一次的测试,千言万语无法言表其中的忧伤,总而言之,坚韧,是程序猿必须具备的品质!
2.Unity与Android场景跳转的问题
Unity和Android交互不仅仅是调用函数,交互数据,很大的一个还是需要交互视图和场景,将其很好的进行切换。当然场景的跳转或者是切换还是通过调用接口函数来实现,但这里通过了一个很重要的Activity来实现了这一效果,就是UnityPlayerActivity,Android的接口视图就是继承了这个UnityPlayerActivity,这个是为Unity和Android搭建了一个桥梁,当然这个类必须导入Unity的接口包,在Unity的安装目录下,详细可以参考上一篇文章,继承了UnityPlayerActivity的视图是作为Unity和Android的一个通用视图,它是Android插件的一个入口,无论是在Unity切换到Android界面还是Android切换到Unity界面,必须都要通过这个Activity来操作,记得是必须,这里我也是吃过苦头,我想在其他的Activity中来调用Unity的接口函数来实现Unity界面的跳转,但都尝试失败!后来再重头再来,这些都是经过了一遍又一遍的实验才得出的结论!
3.LG的手机作为Unity的测试机貌似有一些问题(纯属个人感觉)
附带修改的Android精简版的扫码工程代码(只贴修改过的脚本):
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.qr_codescan" android:versionCode="1" android:versionName="1.0" > <uses-sdk android:minSdkVersion="8" android:targetSdkVersion="16" /> <application android:allowBackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@android:style/Theme.NoTitleBar" > <activity android:name="com.example.qr_codescan.MainActivity" android:screenOrientation="landscape" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <activity android:name=".MipcaActivityCapture" android:configChanges="orientation|keyboardHidden" android:screenOrientation="landscape" android:windowSoftInputMode="stateAlwaysHidden" > </activity> </application> <uses-permission android:name="android.permission.VIBRATE" /> <uses-permission android:name="android.permission.CAMERA" /> <uses-feature android:name="android.hardware.camera" /> <uses-feature android:name="android.hardware.camera.autofocus" /> </manifest>
MainActivity.java
package com.example.qr_codescan; import android.app.Activity; import android.content.Intent; import android.graphics.Bitmap; import android.os.Bundle; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.ImageView; import android.widget.TextView; import com.unity3d.player.UnityPlayer; import com.unity3d.player.UnityPlayerActivity; public class MainActivity extends UnityPlayerActivity { private final static int SCANNIN_GREQUEST_CODE = 1; /** * 显示扫描结果 */ // private TextView mTextView ; /** * 显示扫描拍的图片 */ // private ImageView mImageView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // setContentView(R.layout.activity_main); // // mTextView = (TextView) findViewById(R.id.result); // mImageView = (ImageView) findViewById(R.id.qrcode_bitmap); // // //点击按钮跳转到二维码扫描界面,这里用的是startActivityForResult跳转 // //扫描完了之后调到该界面 // Button mButton = (Button) findViewById(R.id.button1); // mButton.setOnClickListener(new OnClickListener() { // // @Override // public void onClick(View v) { // Intent intent = new Intent(); // intent.setClass(MainActivity.this, MipcaActivityCapture.class); // intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); // startActivityForResult(intent, SCANNIN_GREQUEST_CODE); // } // }); } public void Show() { Intent intent = new Intent(); intent.setClass(MainActivity.this, MipcaActivityCapture.class); intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); startActivityForResult(intent, SCANNIN_GREQUEST_CODE); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); switch (requestCode) { case SCANNIN_GREQUEST_CODE: if(resultCode == RESULT_OK){ Bundle bundle = data.getExtras(); //显示扫描到的内容 // mTextView.setText(bundle.getString("result")); //显示 // mImageView.setImageBitmap((Bitmap) data.getParcelableExtra("bitmap")); UnityPlayer.UnitySendMessage("GameObject","GetString",bundle.getString("result")); } break; } } }
MipcaActivityCapture.java
CameraConfigurationManager.java
/* * Copyright (C) 2010 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 com.mining.app.zxing.camera; import android.content.Context; import android.graphics.Point; import android.hardware.Camera; import android.os.Build; import android.util.Log; import android.view.Display; import android.view.WindowManager; import java.lang.reflect.Method; import java.util.regex.Pattern; final class CameraConfigurationManager { private static final String TAG = CameraConfigurationManager.class.getSimpleName(); private static final int TEN_DESIRED_ZOOM = 27; private static final int DESIRED_SHARPNESS = 30; private static final Pattern COMMA_PATTERN = Pattern.compile(","); private final Context context; private Point screenResolution; private Point cameraResolution; private int previewFormat; private String previewFormatString; CameraConfigurationManager(Context context) { this.context = context; } /** * Reads, one time, values from the camera that are needed by the app. */ void initFromCameraParameters(Camera camera) { Camera.Parameters parameters = camera.getParameters(); previewFormat = parameters.getPreviewFormat(); previewFormatString = parameters.get("preview-format"); Log.d(TAG, "Default preview format: " + previewFormat + ‘/‘ + previewFormatString); WindowManager manager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); Display display = manager.getDefaultDisplay(); screenResolution = new Point(display.getWidth(), display.getHeight()); Log.d(TAG, "Screen resolution: " + screenResolution); cameraResolution = getCameraResolution(parameters, screenResolution); Log.d(TAG, "Camera resolution: " + screenResolution); } /** * Sets the camera up to take preview images which are used for both preview and decoding. * We detect the preview format here so that buildLuminanceSource() can build an appropriate * LuminanceSource subclass. In the future we may want to force YUV420SP as it‘s the smallest, * and the planar Y can be used for barcode scanning without a copy in some cases. */ void setDesiredCameraParameters(Camera camera) { Camera.Parameters parameters = camera.getParameters(); Log.d(TAG, "Setting preview size: " + cameraResolution); parameters.setPreviewSize(cameraResolution.x, cameraResolution.y); setFlash(parameters); setZoom(parameters); //setSharpness(parameters); //modify here // camera.setDisplayOrientation(90); //兼容2.1 //setDisplayOrientation(camera, 90); camera.setParameters(parameters); } Point getCameraResolution() { return cameraResolution; } Point getScreenResolution() { return screenResolution; } int getPreviewFormat() { return previewFormat; } String getPreviewFormatString() { return previewFormatString; } private static Point getCameraResolution(Camera.Parameters parameters, Point screenResolution) { String previewSizeValueString = parameters.get("preview-size-values"); // saw this on Xperia if (previewSizeValueString == null) { previewSizeValueString = parameters.get("preview-size-value"); } Point cameraResolution = null; if (previewSizeValueString != null) { Log.d(TAG, "preview-size-values parameter: " + previewSizeValueString); cameraResolution = findBestPreviewSizeValue(previewSizeValueString, screenResolution); } if (cameraResolution == null) { // Ensure that the camera resolution is a multiple of 8, as the screen may not be. cameraResolution = new Point( (screenResolution.x >> 3) << 3, (screenResolution.y >> 3) << 3); } return cameraResolution; } private static Point findBestPreviewSizeValue(CharSequence previewSizeValueString, Point screenResolution) { int bestX = 0; int bestY = 0; int diff = Integer.MAX_VALUE; for (String previewSize : COMMA_PATTERN.split(previewSizeValueString)) { previewSize = previewSize.trim(); int dimPosition = previewSize.indexOf(‘x‘); if (dimPosition < 0) { Log.w(TAG, "Bad preview-size: " + previewSize); continue; } int newX; int newY; try { newX = Integer.parseInt(previewSize.substring(0, dimPosition)); newY = Integer.parseInt(previewSize.substring(dimPosition + 1)); } catch (NumberFormatException nfe) { Log.w(TAG, "Bad preview-size: " + previewSize); continue; } int newDiff = Math.abs(newX - screenResolution.x) + Math.abs(newY - screenResolution.y); if (newDiff == 0) { bestX = newX; bestY = newY; break; } else if (newDiff < diff) { bestX = newX; bestY = newY; diff = newDiff; } } if (bestX > 0 && bestY > 0) { return new Point(bestX, bestY); } return null; } private static int findBestMotZoomValue(CharSequence stringValues, int tenDesiredZoom) { int tenBestValue = http://www.mamicode.com/0;>CameraManager.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 com.mining.app.zxing.camera; import java.io.IOException; import android.content.Context; import android.graphics.PixelFormat; import android.graphics.Point; import android.graphics.Rect; import android.hardware.Camera; import android.os.Build; import android.os.Handler; import android.util.Log; import android.view.SurfaceHolder; /** * This object wraps the Camera service object and expects to be the only one talking to it. The * implementation encapsulates the steps needed to take preview-sized images, which are used for * both preview and decoding. * */ public final class CameraManager { private static final String TAG = CameraManager.class.getSimpleName(); private static final int MIN_FRAME_WIDTH = 240; private static final int MIN_FRAME_HEIGHT = 240; private static final int MAX_FRAME_WIDTH = 480; private static final int MAX_FRAME_HEIGHT = 360; private static CameraManager cameraManager; static final int SDK_INT; // Later we can use Build.VERSION.SDK_INT static { int sdkInt; try { sdkInt = Integer.parseInt(Build.VERSION.SDK); } catch (NumberFormatException nfe) { // Just to be safe sdkInt = 10000; } SDK_INT = sdkInt; } private final Context context; private final CameraConfigurationManager configManager; private Camera camera; private Rect framingRect; private Rect framingRectInPreview; private boolean initialized; private boolean previewing; private final boolean useOneShotPreviewCallback; /** * Preview frames are delivered here, which we pass on to the registered handler. Make sure to * clear the handler so it will only receive one message. */ private final PreviewCallback previewCallback; /** Autofocus callbacks arrive here, and are dispatched to the Handler which requested them. */ private final AutoFocusCallback autoFocusCallback; /** * Initializes this static object with the Context of the calling Activity. * * @param context The Activity which wants to use the camera. */ public static void init(Context context) { if (cameraManager == null) { cameraManager = new CameraManager(context); } } /** * Gets the CameraManager singleton instance. * * @return A reference to the CameraManager singleton. */ public static CameraManager get() { return cameraManager; } private CameraManager(Context context) { this.context = context; this.configManager = new CameraConfigurationManager(context); // Camera.setOneShotPreviewCallback() has a race condition in Cupcake, so we use the older // Camera.setPreviewCallback() on 1.5 and earlier. For Donut and later, we need to use // the more efficient one shot callback, as the older one can swamp the system and cause it // to run out of memory. We can‘t use SDK_INT because it was introduced in the Donut SDK. //useOneShotPreviewCallback = Integer.parseInt(Build.VERSION.SDK) > Build.VERSION_CODES.CUPCAKE; useOneShotPreviewCallback = Integer.parseInt(Build.VERSION.SDK) > 3; // 3 = Cupcake previewCallback = new PreviewCallback(configManager, useOneShotPreviewCallback); autoFocusCallback = new AutoFocusCallback(); } /** * Opens the camera driver and initializes the hardware parameters. * * @param holder The surface object which the camera will draw preview frames into. * @throws IOException Indicates the camera driver failed to open. */ public void openDriver(SurfaceHolder holder) throws IOException { if (camera == null) { camera = Camera.open(); if (camera == null) { throw new IOException(); } camera.setPreviewDisplay(holder); if (!initialized) { initialized = true; configManager.initFromCameraParameters(camera); } configManager.setDesiredCameraParameters(camera); //FIXME // SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); //是否使用前灯 // if (prefs.getBoolean(PreferencesActivity.KEY_FRONT_LIGHT, false)) { // FlashlightManager.enableFlashlight(); // } FlashlightManager.enableFlashlight(); } } /** * Closes the camera driver if still in use. */ public void closeDriver() { if (camera != null) { FlashlightManager.disableFlashlight(); camera.release(); camera = null; } } /** * Asks the camera hardware to begin drawing preview frames to the screen. */ public void startPreview() { if (camera != null && !previewing) { camera.startPreview(); previewing = true; } } /** * Tells the camera to stop drawing preview frames. */ public void stopPreview() { if (camera != null && previewing) { if (!useOneShotPreviewCallback) { camera.setPreviewCallback(null); } camera.stopPreview(); previewCallback.setHandler(null, 0); autoFocusCallback.setHandler(null, 0); previewing = false; } } /** * A single preview frame will be returned to the handler supplied. The data will arrive as byte[] * in the message.obj field, with width and height encoded as message.arg1 and message.arg2, * respectively. * * @param handler The handler to send the message to. * @param message The what field of the message to be sent. */ public void requestPreviewFrame(Handler handler, int message) { if (camera != null && previewing) { previewCallback.setHandler(handler, message); if (useOneShotPreviewCallback) { camera.setOneShotPreviewCallback(previewCallback); } else { camera.setPreviewCallback(previewCallback); } } } /** * Asks the camera hardware to perform an autofocus. * * @param handler The Handler to notify when the autofocus completes. * @param message The message to deliver. */ public void requestAutoFocus(Handler handler, int message) { if (camera != null && previewing) { autoFocusCallback.setHandler(handler, message); //Log.d(TAG, "Requesting auto-focus callback"); camera.autoFocus(autoFocusCallback); } } /** * 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() { Point screenResolution = configManager.getScreenResolution(); if (framingRect == null) { if (camera == null) { return null; } int width = screenResolution.x * 3 / 4; if (width < MIN_FRAME_WIDTH) { width = MIN_FRAME_WIDTH; } else if (width > MAX_FRAME_WIDTH) { width = MAX_FRAME_WIDTH; } int height = screenResolution.y * 3 / 4; if (height < MIN_FRAME_HEIGHT) { height = MIN_FRAME_HEIGHT; } else if (height > MAX_FRAME_HEIGHT) { height = MAX_FRAME_HEIGHT; } int leftOffset = (screenResolution.x - width) / 2; int topOffset = (screenResolution.y - height) / 2; framingRect = new Rect(leftOffset, topOffset, leftOffset + width, topOffset + height); Log.d(TAG, "Calculated framing rect: " + framingRect); } return framingRect; } /** * Like {@link #getFramingRect} but coordinates are in terms of the preview frame, * not UI / screen. */ public Rect getFramingRectInPreview() { if (framingRectInPreview == null) { Rect rect = new Rect(getFramingRect()); Point cameraResolution = configManager.getCameraResolution(); Point screenResolution = configManager.getScreenResolution(); //modify here rect.left = rect.left * cameraResolution.x / screenResolution.x; rect.right = rect.right * cameraResolution.x / screenResolution.x; rect.top = rect.top * cameraResolution.y / screenResolution.y; rect.bottom = rect.bottom * cameraResolution.y / screenResolution.y; //rect.left = rect.left * cameraResolution.y / screenResolution.x; //rect.right = rect.right * cameraResolution.y / screenResolution.x; //rect.top = rect.top * cameraResolution.x / screenResolution.y; //rect.bottom = rect.bottom * cameraResolution.x / screenResolution.y; framingRectInPreview = rect; } return framingRectInPreview; } /** * Converts the result points from still resolution coordinates to screen coordinates. * * @param points The points returned by the Reader subclass through Result.getResultPoints(). * @return An array of Points scaled to the size of the framing rect and offset appropriately * so they can be drawn in screen coordinates. */ /* public Point[] convertResultPoints(ResultPoint[] points) { Rect frame = getFramingRectInPreview(); int count = points.length; Point[] output = new Point[count]; for (int x = 0; x < count; x++) { output[x] = new Point(); output[x].x = frame.left + (int) (points[x].getX() + 0.5f); output[x].y = frame.top + (int) (points[x].getY() + 0.5f); } return output; } */ /** * A factory method to build the appropriate LuminanceSource object based on the format * of the preview buffers, as described by Camera.Parameters. * * @param data A preview frame. * @param width The width of the image. * @param height The height of the image. * @return A PlanarYUVLuminanceSource instance. */ public PlanarYUVLuminanceSource buildLuminanceSource(byte[] data, int width, int height) { Rect rect = getFramingRectInPreview(); int previewFormat = configManager.getPreviewFormat(); String previewFormatString = configManager.getPreviewFormatString(); switch (previewFormat) { // This is the standard Android format which all devices are REQUIRED to support. // In theory, it‘s the only one we should ever care about. case PixelFormat.YCbCr_420_SP: // This format has never been seen in the wild, but is compatible as we only care // about the Y channel, so allow it. case PixelFormat.YCbCr_422_SP: return new PlanarYUVLuminanceSource(data, width, height, rect.left, rect.top, rect.width(), rect.height()); default: // The Samsung Moment incorrectly uses this variant instead of the ‘sp‘ version. // Fortunately, it too has all the Y data up front, so we can read it. if ("yuv420p".equals(previewFormatString)) { return new PlanarYUVLuminanceSource(data, width, height, rect.left, rect.top, rect.width(), rect.height()); } } throw new IllegalArgumentException("Unsupported picture format: " + previewFormat + ‘/‘ + previewFormatString); } public Context getContext() { return context; } }
PlanarYUVLuminanceSource.java
/* * Copyright 2009 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 com.mining.app.zxing.camera; import com.google.zxing.LuminanceSource; import android.graphics.Bitmap; /** * This object extends LuminanceSource around an array of YUV data returned from the camera driver, * with the option to crop to a rectangle within the full data. This can be used to exclude * superfluous pixels around the perimeter and speed up decoding. * * It works for any pixel format where the Y channel is planar and appears first, including * YCbCr_420_SP and YCbCr_422_SP. * * @author dswitkin@google.com (Daniel Switkin) */ public final class PlanarYUVLuminanceSource extends LuminanceSource { private final byte[] yuvData; private final int dataWidth; private final int dataHeight; private final int left; private final int top; public PlanarYUVLuminanceSource(byte[] yuvData, int dataWidth, int dataHeight, int left, int top, int width, int height) { super(width, height); if (left + width > dataWidth || top + height > dataHeight) { throw new IllegalArgumentException("Crop rectangle does not fit within image data."); } this.yuvData = http://www.mamicode.com/yuvData;>
能解决这一系列的问题还要感觉龙哥的指导,在此感谢!