问题现象
项目中的扫一扫界面打开以后,扫描二维码的界面显示的二维码被拉伸,图片如下:
问题原因
通常,拍照预览页面的视图拉伸主要与下面两个因素有关:
- Surfaceview的大小
- Camera中的Preview的大小
如果手机surfaceview大小为比例为16:9,而预览尺寸大小为比例为4:3,从上面的二维码可以看到明显的拉伸。正因为surfaceview的宽高比例跟camera preview的宽高比例不一样才会产生这样的效果。如果surfaceview尺寸比例跟预览尺寸比例相同,那便不会产生变形。由此可以得出结论:
- 1.手机surfaceview与默认的camera preview的宽高比不一致
- 2.camera preview的宽高比设置不正确
解决方法
然后道长翻找ZXing的代码(具体版本可能非常老了),找到如下一段代码:
/**
* 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();
}
}
其中有两个方法调用,一个是初始化相机参数的initFromCameraParameters()和设置相机参数的setDesiredCameraParameters()。然后我发现在设置相机参数的方法中已经设置对应的parameters,setDesiredCameraParameters()代码如下:
/**
* 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);
camera.setParameters(parameters);
}
在上面的代码中,这一行代码便是设置相机预览界面的分辨率的。
parameters.setPreviewSize(cameraResolution.x, cameraResolution.y);
然后在初始化相机参数的方法initFromCameraParameters()中获取了cameraResolution这个参数的值,代码如下:
/**
* 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);
}
然后把上面代码中getCameraResolution()方法改为了网上以为老哥的方法,代码如下:
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);
List<Camera.Size> sizeList = parameters.getSupportedPreviewSizes();//获取所有支持的camera尺寸
cameraResolution = getOptimalPreviewSize(true, sizeList, screenResolution.x, screenResolution.y);//获取一个最为适配的屏幕尺寸
}
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;
}
/**
* 通过对比得到与宽高比最接近的预览尺寸(如果有相同尺寸,优先选择)
*
* @param isPortrait 是否竖屏
* @param surfaceWidth 需要被进行对比的原宽
* @param surfaceHeight 需要被进行对比的原高
* @param preSizeList 需要对比的预览尺寸列表
* @return 得到与原宽高比例最接近的尺寸
*/
public static Point getOptimalPreviewSize(boolean isPortrait, List<Camera.Size> preSizeList, int surfaceWidth, int surfaceHeight) {
int reqTmpWidth;
int reqTmpHeight;
// 当屏幕为垂直的时候需要把宽高值进行调换,保证宽大于高
if (isPortrait) {
reqTmpWidth = surfaceHeight;
reqTmpHeight = surfaceWidth;
} else {
reqTmpWidth = surfaceWidth;
reqTmpHeight = surfaceHeight;
}
//先查找preview中是否存在与surfaceview相同宽高的尺寸
for(Camera.Size size : preSizeList){
if((size.width == reqTmpWidth) && (size.height == reqTmpHeight)){
return new Point(size.width, size.height);
}
}
// 得到与传入的宽高比最接近的size
float reqRatio = ((float) reqTmpWidth) / reqTmpHeight;
float curRatio, deltaRatio;
float deltaRatioMin = Float.MAX_VALUE;
Camera.Size retSize = null;
for (Camera.Size size : preSizeList) {
curRatio = ((float) size.width) / size.height;
deltaRatio = Math.abs(reqRatio - curRatio);
if (deltaRatio < deltaRatioMin) {
deltaRatioMin = deltaRatio;
retSize = size;
}
}
return new Point(retSize.width, retSize.height);
}
到此,道长已经解决了Camera预览界面拉伸的问题,希望这篇博客对你有所帮助。