Android自定义相机旋转时SurfaceView缩小,需保持图像比例一致
我来帮你解决这个自定义相机的旋转预览缩放和图片尺寸统一问题,咱们一步步拆解解决:
问题核心梳理
你现在遇到两个关键问题:
- 设备旋转时SurfaceView预览会缩小,横竖屏预览比例不一致
- 横竖屏拍摄的图片尺寸方向颠倒,竖屏拍出来是
1080(高)*960(宽),横屏是1080(宽)*960(高),需要统一成相同的尺寸规格
具体解决步骤
1. 修复SurfaceView预览缩放问题
预览缩小的根本原因是没有根据屏幕旋转后的尺寸,匹配相机预览的宽高比。你需要补充surfaceChanged方法(当前代码里缺失这个关键生命周期),在屏幕尺寸变化时重新计算最佳预览尺寸:
首先,实现一个工具方法,从相机支持的预览尺寸中挑选和屏幕比例最接近的:
private Camera.Size getOptimalPreviewSize(List<Camera.Size> sizes, int screenWidth, int screenHeight) { final double ASPECT_TOLERANCE = 0.1; // 比例误差容忍度 double targetRatio = (double) screenWidth / screenHeight; if (sizes == null) return null; Camera.Size optimalSize = null; double minDiff = Double.MAX_VALUE; // 优先匹配比例,再匹配尺寸 for (Camera.Size size : sizes) { double ratio = (double) size.width / size.height; if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE) continue; if (Math.abs(size.height - screenHeight) < minDiff) { optimalSize = size; minDiff = Math.abs(size.height - screenHeight); } } // 如果找不到匹配比例的尺寸,就选最接近屏幕高度的 if (optimalSize == null) { minDiff = Double.MAX_VALUE; for (Camera.Size size : sizes) { if (Math.abs(size.height - screenHeight) < minDiff) { optimalSize = size; minDiff = Math.abs(size.height - screenHeight); } } } return optimalSize; }
然后在surfaceChanged中更新相机参数,重新设置预览:
@Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { if (mSurfaceHolder.getSurface() == null) { return; } // 先停止当前预览 try { mCamera.stopPreview(); } catch (Exception e) { // 忽略停止失败的情况 } // 获取相机参数,设置最佳预览尺寸 Camera.Parameters params = mCamera.getParameters(); Camera.Size previewSize = getOptimalPreviewSize(params.getSupportedPreviewSizes(), width, height); params.setPreviewSize(previewSize.width, previewSize.height); // 提前设置统一的拍照尺寸(比如固定用横屏的1080*960) Camera.Size pictureSize = getOptimalPreviewSize(params.getSupportedPictureSizes(), 1080, 960); params.setPictureSize(pictureSize.width, pictureSize.height); mCamera.setParameters(params); // 重新设置相机显示方向 setCameraDisplayOrientation(activity, mCameraID, mCamera); // 重启预览 try { mCamera.setPreviewDisplay(mSurfaceHolder); mCamera.startPreview(); } catch (Exception e) { Log.d("CameraDebug", "启动预览失败: " + e.getMessage()); } }
2. 统一横竖屏拍摄的图片尺寸和方向
相机传感器的方向是固定的,竖屏拍摄时照片会被旋转,导致尺寸方向颠倒。我们需要在拍照回调中对图片进行旋转+裁剪,统一成目标尺寸:
在takePicture的回调中处理图片:
mCamera.takePicture(null, null, new Camera.PictureCallback() { @Override public void onPictureTaken(byte[] data, Camera camera) { // 把字节数组转成Bitmap Bitmap originalBitmap = BitmapFactory.decodeByteArray(data, 0, data.length); // 获取图片需要旋转的角度 int rotateAngle = getCameraRotationAngle(); // 旋转图片到正确方向 Matrix matrix = new Matrix(); matrix.postRotate(rotateAngle); Bitmap rotatedBitmap = Bitmap.createBitmap(originalBitmap, 0, 0, originalBitmap.getWidth(), originalBitmap.getHeight(), matrix, true); // 裁剪成统一的1080*960尺寸 Bitmap finalBitmap = cropToTargetSize(rotatedBitmap, 1080, 960); // 这里可以保存finalBitmap或者做后续处理 // ... // 重启预览 mCamera.startPreview(); } });
实现旋转角度计算方法:
private int getCameraRotationAngle() { Camera.CameraInfo info = new Camera.CameraInfo(); Camera.getCameraInfo(mCameraID, info); int screenRotation = activity.getWindowManager().getDefaultDisplay().getRotation(); int degrees = 0; switch (screenRotation) { case Surface.ROTATION_0: degrees = 0; break; case Surface.ROTATION_90: degrees = 90; break; case Surface.ROTATION_180: degrees = 180; break; case Surface.ROTATION_270: degrees = 270; break; } int result; if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) { // 前置摄像头需要镜像翻转,所以要反向计算 result = (info.orientation + degrees) % 360; result = (360 - result) % 360; } else { result = (info.orientation - degrees + 360) % 360; } return result; }
实现裁剪方法,确保图片适配目标尺寸:
private Bitmap cropToTargetSize(Bitmap bitmap, int targetWidth, int targetHeight) { int bitmapWidth = bitmap.getWidth(); int bitmapHeight = bitmap.getHeight(); int cropX = 0; int cropY = 0; int cropWidth = bitmapWidth; int cropHeight = bitmapHeight; // 计算裁剪区域,保证比例匹配目标尺寸 if (bitmapWidth * targetHeight > bitmapHeight * targetWidth) { // 图片过宽,裁剪宽度 cropWidth = bitmapHeight * targetWidth / targetHeight; cropX = (bitmapWidth - cropWidth) / 2; } else if (bitmapWidth * targetHeight < bitmapHeight * targetWidth) { // 图片过高,裁剪高度 cropHeight = bitmapWidth * targetHeight / targetWidth; cropY = (bitmapHeight - cropHeight) / 2; } return Bitmap.createBitmap(bitmap, cropX, cropY, cropWidth, cropHeight); }
3. 补充相机资源管理
记得在页面暂停/销毁时释放相机资源,避免内存泄漏:
@Override public void onPause() { super.onPause(); stopCameraPreview(); if (mCamera != null) { mCamera.release(); mCamera = null; } } private void stopCameraPreview() { if (mCamera != null) { try { mCamera.stopPreview(); } catch (Exception e) { // 忽略停止失败的情况 } } }
关键注意点
- 预览和拍照的尺寸都要优先选择相机硬件支持的规格,不要强制设置不支持的尺寸
- 屏幕旋转时必须重新设置相机参数和显示方向,否则预览会异常
- 前置摄像头需要额外处理镜像翻转,不然拍出来的画面是反向的
内容的提问来源于stack exchange,提问作者Shashwat Gupta




