You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Android自定义相机旋转时SurfaceView缩小,需保持图像比例一致

我来帮你解决这个自定义相机的旋转预览缩放和图片尺寸统一问题,咱们一步步拆解解决:

问题核心梳理

你现在遇到两个关键问题:

  1. 设备旋转时SurfaceView预览会缩小,横竖屏预览比例不一致
  2. 横竖屏拍摄的图片尺寸方向颠倒,竖屏拍出来是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

火山引擎 最新活动