Android相机预览被拉伸

144

我一直在开发Android上的自定义相机活动,但当旋转相机时,SurfaceView的纵横比会混乱。

在我的活动onCreate方法中,我设置了一个FrameLayout来容纳SurfaceView,以显示相机的参数。

//FrameLayout that will hold the camera preview
        FrameLayout previewHolder = (FrameLayout) findViewById(R.id.camerapreview);

        //Setting camera's preview size to the best preview size
        Size optimalSize = null;
        camera = getCameraInstance();
        double aspectRatio = 0;
        if(camera != null){
            //Setting the camera's aspect ratio
            Camera.Parameters parameters = camera.getParameters();
            List<Size> sizes = parameters.getSupportedPreviewSizes();
            optimalSize = CameraPreview.getOptimalPreviewSize(sizes, getResources().getDisplayMetrics().widthPixels, getResources().getDisplayMetrics().heightPixels);
            aspectRatio = (float)optimalSize.width/optimalSize.height;
        }

        if(optimalSize!= null){
            RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(LayoutParams.MATCH_PARENT, (int)(getResources().getDisplayMetrics().widthPixels*aspectRatio));
            previewHolder.setLayoutParams(params);
            LayoutParams surfaceParams = new LayoutParams(LayoutParams.MATCH_PARENT, (int)(getResources().getDisplayMetrics().widthPixels*aspectRatio));
            cameraPreview.setLayoutParams(surfaceParams);

        }

        cameraPreview.setCamera(camera);

        //Adding the preview to the holder
        previewHolder.addView(cameraPreview);

然后,在Surface视图中,我设置相机的参数以进行显示。

public void setCamera(Camera camera) {
        if (mCamera == camera) { return; }

        mCamera = camera;

        if (mCamera != null) {
            requestLayout();

            try {
                mCamera.setPreviewDisplay(mHolder);
            } catch (IOException e) {
                e.printStackTrace();
            }


            if(mCamera != null){
                //Setting the camera's aspect ratio
                Camera.Parameters parameters = mCamera.getParameters();
                List<Size> sizes = parameters.getSupportedPreviewSizes();
                Size optimalSize = getOptimalPreviewSize(sizes, getResources().getDisplayMetrics().widthPixels, getResources().getDisplayMetrics().heightPixels);

                parameters.setPreviewSize(optimalSize.width, optimalSize.height);
                mCamera.setParameters(parameters);
            }

            /*
              Important: Call startPreview() to start updating the preview surface. Preview must 
              be started before you can take a picture.
              */
            mCamera.startPreview();
        }

    }

输入图像描述

当手机被旋转时,你会发现乐高人变得更高更瘦:

如何确保我的相机视图的纵横比是正确的?


@scientific,你能帮我吗?我应该在哪里编写给定的方法?我也遇到了同样的问题。 - user3233280
我似乎遇到了类似的问题,但解决方法是在我的相机SurfaceView的surfaceCreated()方法中调用以下代码:mCamera.setDisplayOrientation(90)。 - Greg
15个回答

166

我正在使用基于API Demos的方法来获取我的预览大小:

private Camera.Size getOptimalPreviewSize(List<Camera.Size> sizes, int w, int h) {
        final double ASPECT_TOLERANCE = 0.1;
        double targetRatio=(double)h / w;

        if (sizes == null) return null;

        Camera.Size optimalSize = null;
        double minDiff = Double.MAX_VALUE;

        int targetHeight = h;

        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 - targetHeight) < minDiff) {
                optimalSize = size;
                minDiff = Math.abs(size.height - targetHeight);
            }
        }

        if (optimalSize == null) {
            minDiff = Double.MAX_VALUE;
            for (Camera.Size size : sizes) {
                if (Math.abs(size.height - targetHeight) < minDiff) {
                    optimalSize = size;
                    minDiff = Math.abs(size.height - targetHeight);
                }
            }
        }
        return optimalSize;
    }

正如您所见,您需要输入屏幕的宽度和高度。此方法将基于这些值计算屏幕比例,然后从支持的预览大小列表中选择最佳的可用大小。通过使用 Camera 对象非空的位置获取支持的预览大小列表。

mSupportedPreviewSizes = mCamera.getParameters().getSupportedPreviewSizes();

在onMeasure中,您可以像这样获取最佳的previewSize:

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        final int width = resolveSize(getSuggestedMinimumWidth(), widthMeasureSpec);
        final int height = resolveSize(getSuggestedMinimumHeight(), heightMeasureSpec);
        setMeasuredDimension(width, height);

        if (mSupportedPreviewSizes != null) {
           mPreviewSize = getOptimalPreviewSize(mSupportedPreviewSizes, width, height);
        }
    }

然后(在我的代码中的surfaceChanged方法中,就像我所说的,我正在使用CameraActivity代码的API Demos结构,在Eclipse中可以生成它):

Camera.Parameters parameters = mCamera.getParameters();
parameters.setPreviewSize(mPreviewSize.width, mPreviewSize.height);
mCamera.setParameters(parameters);
mCamera.startPreview();

我有一个提示给你,因为我几乎做了和你一样的应用程序。相机活动的好练习是隐藏状态栏,像 Instagram 这样的应用程序就是这样做的。它可以减少屏幕高度值并更改比例值。在某些设备上可能会得到奇怪的预览尺寸(您的 SurfaceView 会被切割一点)。


而要回答你的问题,如何检查预览比例是否正确?那么获取您在以下参数中设置的高度和宽度:

mCamera.setParameters(parameters);

您设置的比例应该等于高度/宽度。如果您希望相机在屏幕上显示良好,则您设置给相机的参数的高度/宽度比必须与您屏幕的高度(减去状态栏)/宽度比相同。


4
只有一个问题:我看到你使用了两个不同的计算比率的方式:double targetRatio=(double)h / wdouble ratio = (double) size.width / size.height。第一个是高度除以宽度,另一个是相反的,宽度除以高度。它们不应该进行相同的计算吗? - phyzalis
不要紧,因为尺寸列表考虑到了每种可能性,例如:你会发现像480x680这样的东西,但迟早会出现横向的680x480,所以你只需要查看该列表并找到符合您需求的尺寸即可。 你迟早会找到它。 - F1sher
2
@F1sher,我总是遇到空指针异常parameters.setPreviewSize(mPreviewSize.width, mPreviewSize.height)。找不到任何解决方案。 - FIXI
3
我同意你的观点。实际上,我必须进行那个更正以使代码按预期工作。 - fernio
你好,我也遇到了同样的问题,你的回答很清晰。但是我的SurfaceView在FrameLayout中,所以我创建了一个自定义视图来扩展FrameLayout。我这样做是为了重写onMeasure()方法。但是在这个方法中,你写了一个方法resolveSize(getSuggestedMinimumWidth(), widthMeasureSpec)。你能否向我展示一下这个方法的功能。@F1sher - Wubbalubbadubdub
显示剩余12条评论

156

F1Sher的解决方案很好,但有时不起作用,特别是当你的surfaceView没有覆盖整个屏幕时。在这种情况下,您需要重写onMeasure()方法。 我已经将我的代码复制在此供您参考。

由于我根据宽度测量了surfaceView,因此屏幕末端有一些白色间隙,我通过设计来填充它。如果您保持高度并将宽度乘以比例增加,则可以解决此问题。但是,这会稍微压缩surfaceView。

public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback {

    private static final String TAG = "CameraPreview";

    private Context mContext;
    private SurfaceHolder mHolder;
    private Camera mCamera;
    private List<Camera.Size> mSupportedPreviewSizes;
    private Camera.Size mPreviewSize;

    public CameraPreview(Context context, Camera camera) {
        super(context);
        mContext = context;
        mCamera = camera;

        // supported preview sizes
        mSupportedPreviewSizes = mCamera.getParameters().getSupportedPreviewSizes();
        for(Camera.Size str: mSupportedPreviewSizes)
                Log.e(TAG, str.width + "/" + str.height);

        // Install a SurfaceHolder.Callback so we get notified when the
        // underlying surface is created and destroyed.
        mHolder = getHolder();
        mHolder.addCallback(this);
        // deprecated setting, but required on Android versions prior to 3.0
        mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
    }

    public void surfaceCreated(SurfaceHolder holder) {
        // empty. surfaceChanged will take care of stuff
    }

    public void surfaceDestroyed(SurfaceHolder holder) {
        // empty. Take care of releasing the Camera preview in your activity.
    }

    public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
        Log.e(TAG, "surfaceChanged => w=" + w + ", h=" + h);
        // If your preview can change or rotate, take care of those events here.
        // Make sure to stop the preview before resizing or reformatting it.
        if (mHolder.getSurface() == null){
            // preview surface does not exist
            return;
        }

        // stop preview before making changes
        try {
            mCamera.stopPreview();
        } catch (Exception e){
            // ignore: tried to stop a non-existent preview
        }

        // set preview size and make any resize, rotate or reformatting changes here
        // start preview with new settings
        try {
            Camera.Parameters parameters = mCamera.getParameters();
            parameters.setPreviewSize(mPreviewSize.width, mPreviewSize.height);
            mCamera.setParameters(parameters);
            mCamera.setDisplayOrientation(90);
            mCamera.setPreviewDisplay(mHolder);
            mCamera.startPreview();

        } catch (Exception e){
            Log.d(TAG, "Error starting camera preview: " + e.getMessage());
        }
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        final int width = resolveSize(getSuggestedMinimumWidth(), widthMeasureSpec);
        final int height = resolveSize(getSuggestedMinimumHeight(), heightMeasureSpec);

        if (mSupportedPreviewSizes != null) {
            mPreviewSize = getOptimalPreviewSize(mSupportedPreviewSizes, width, height);
        }

        if (mPreviewSize!=null) {
            float ratio;
            if(mPreviewSize.height >= mPreviewSize.width)
                ratio = (float) mPreviewSize.height / (float) mPreviewSize.width;
            else
                ratio = (float) mPreviewSize.width / (float) mPreviewSize.height;

            // One of these methods should be used, second method squishes preview slightly
            setMeasuredDimension(width, (int) (width * ratio));
  //        setMeasuredDimension((int) (width * ratio), height);
        }
    }

    private Camera.Size getOptimalPreviewSize(List<Camera.Size> sizes, int w, int h) {
        final double ASPECT_TOLERANCE = 0.1;
        double targetRatio = (double) h / w;

        if (sizes == null)
            return null;

        Camera.Size optimalSize = null;
        double minDiff = Double.MAX_VALUE;

        int targetHeight = h;

        for (Camera.Size size : sizes) {
            double ratio = (double) size.height / size.width;
            if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE)
                continue;

            if (Math.abs(size.height - targetHeight) < minDiff) {
                optimalSize = size;
                minDiff = Math.abs(size.height - targetHeight);
            }
        }

        if (optimalSize == null) {
            minDiff = Double.MAX_VALUE;
            for (Camera.Size size : sizes) {
                if (Math.abs(size.height - targetHeight) < minDiff) {
                    optimalSize = size;
                    minDiff = Math.abs(size.height - targetHeight);
                }
            }
        }

        return optimalSize;
    }
}

13
这应该是正确的答案,它可以处理相机预览未覆盖整个屏幕的情况。+1。 - Houcine
1
当我在我的活动中膨胀时,应用程序关闭了<com.app.camera.CameraPreview             android:id =“@+id/camera_preview”             android:layout_width =“match_parent”             android:layout_height =“match_parent”             /> - fish40
2
嗨@adnan9011,这个教程可能会有所帮助。https://www.airpair.com/android/android-camera-surface-view-fragment - Hesam
@Hesam Kamalan,我写了一个Activity但是在SurfaceView上没有显示任何东西。有一件事情是错的。 - Adnan Abdollah Zaki
4
为了使这个方法对我起作用,需要在“getOptimalPreviewSize”中交换高度和宽度。这是因为显示方向设置为90吗?否则,它的比例计算结果与目标值1.7相差甚远,摄像头的比例小于1。 - ono
显示剩余7条评论

24

注意:我的解决方案是对Hesam的解决方案的延续:https://dev59.com/DGIk5IYBdhLWcg3wG63b#22758359

我要解决的问题是:Hesam说在一些手机上可能会出现一些小白边,像这样:

注意:虽然纵横比正确,但相机没有填满整个屏幕。

Hesam提出了第二种解决方案,但那会压缩预览图像,而且在一些设备上会严重扭曲。

那么如何解决这个问题呢?很简单...通过将纵横比乘以它,直到填满整个屏幕。我注意到,几个受欢迎的应用程序如Snapchat、WhatsApp等也是这样工作的。

你只需要将以下代码添加到onMeasure方法中即可:

float camHeight = (int) (width * ratio);
    float newCamHeight;
    float newHeightRatio;

    if (camHeight < height) {
        newHeightRatio = (float) height / (float) mPreviewSize.height;
        newCamHeight = (newHeightRatio * camHeight);
        Log.e(TAG, camHeight + " " + height + " " + mPreviewSize.height + " " + newHeightRatio + " " + newCamHeight);
        setMeasuredDimension((int) (width * newHeightRatio), (int) newCamHeight);
        Log.e(TAG, mPreviewSize.width + " | " + mPreviewSize.height + " | ratio - " + ratio + " | H_ratio - " + newHeightRatio + " | A_width - " + (width * newHeightRatio) + " | A_height - " + newCamHeight);
    } else {
        newCamHeight = camHeight;
        setMeasuredDimension(width, (int) newCamHeight);
        Log.e(TAG, mPreviewSize.width + " | " + mPreviewSize.height + " | ratio - " + ratio + " | A_width - " + (width) + " | A_height - " + newCamHeight);
    }

这会计算屏幕高度,并获取屏幕高度与mPreviewSize高度的比率。然后将相机的宽度和高度乘以新的高度比例,并相应地设置测量尺寸。

输入图像描述

接下来你会得到这个:D

输入图像描述

这对前置摄像头也很有效。我认为这是最好的方法。现在我的应用程序只剩下在单击“捕获”时保存预览本身了,但是这就是全部。


这个很不错!!但在横向模式下无法正常工作 :/ - Matan Dahan
你的代码解决了我两个月的空白问题 :) - karthik kolanji
1
它能够工作,但在我的情况下...我在顶部有操作栏,因此白色条更小,但是在添加您的代码后,我的相机看起来缩放了40-50%。当切换到前置摄像头时,我基本上无法直视看到自己的脸(只能看到一点头发),因为缩放太高了。 - Makalele
@Yoosuf,我按照你的代码操作后出现了白屏。在onMeasure(int widthMeasureSpec, int heightMeasureSpec)方法中,我总是得到宽度为0和高度为0的结果。 - Kush Patel

11

好的,所以我认为针对一般相机预览拉伸问题没有足够的答案。或者至少我没有找到一个。我的应用程序也遭受了这种拉伸综合症,花了我一段时间从所有用户在此门户网站和互联网上的答案中拼凑出一个解决方案。

我尝试过@Hesam的解决方案,但它并没有起作用,留下了我的相机预览严重失真。

首先,我展示我的解决方法的代码(代码的重要部分),然后解释为什么我采取了这些步骤。还有改进性能的空间。

主活动xml布局:

<RelativeLayout 
    android:id="@+id/main_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal" >

    <FrameLayout
        android:id="@+id/camera_preview"
        android:layout_centerInParent="true"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
    />
</RelativeLayout>

摄像头预览:

public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback {

private SurfaceHolder prHolder;
private Camera prCamera;
public List<Camera.Size> prSupportedPreviewSizes;
private Camera.Size prPreviewSize;

@SuppressWarnings("deprecation")
public YoCameraPreview(Context context, Camera camera) {
    super(context);
    prCamera = camera;

    prSupportedPreviewSizes = prCamera.getParameters().getSupportedPreviewSizes();

    prHolder = getHolder();
    prHolder.addCallback(this);
    prHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
}

public void surfaceCreated(SurfaceHolder holder) {
    try {
        prCamera.setPreviewDisplay(holder);
        prCamera.startPreview();
    } catch (IOException e) {
        Log.d("Yologram", "Error setting camera preview: " + e.getMessage());
    }
}

public void surfaceDestroyed(SurfaceHolder holder) {
}

public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
    if (prHolder.getSurface() == null){
      return;
    }

    try {
        prCamera.stopPreview();
    } catch (Exception e){
    }

    try {
        Camera.Parameters parameters = prCamera.getParameters();
        List<String> focusModes = parameters.getSupportedFocusModes();
        if (focusModes.contains(Camera.Parameters.FOCUS_MODE_AUTO)) {
            parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
        }
        parameters.setPreviewSize(prPreviewSize.width, prPreviewSize.height);

        prCamera.setParameters(parameters);
        prCamera.setPreviewDisplay(prHolder);
        prCamera.startPreview();

    } catch (Exception e){
        Log.d("Yologram", "Error starting camera preview: " + e.getMessage());
    }
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

    final int width = resolveSize(getSuggestedMinimumWidth(), widthMeasureSpec);
    final int height = resolveSize(getSuggestedMinimumHeight(), heightMeasureSpec);

    setMeasuredDimension(width, height);

    if (prSupportedPreviewSizes != null) {
        prPreviewSize = 
            getOptimalPreviewSize(prSupportedPreviewSizes, width, height);
    }    
}

public Camera.Size getOptimalPreviewSize(List<Camera.Size> sizes, int w, int h) {

    final double ASPECT_TOLERANCE = 0.1;
    double targetRatio = (double) h / w;

    if (sizes == null)
        return null;

    Camera.Size optimalSize = null;
    double minDiff = Double.MAX_VALUE;

    int targetHeight = h;

    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 - targetHeight) < minDiff) {
            optimalSize = size;
            minDiff = Math.abs(size.height - targetHeight);
        }
    }

    if (optimalSize == null) {
        minDiff = Double.MAX_VALUE;
        for (Camera.Size size : sizes) {
            if (Math.abs(size.height - targetHeight) < minDiff) {
                optimalSize = size;
                minDiff = Math.abs(size.height - targetHeight);
            }
        }
    }

    return optimalSize;
}
}

主要活动:

public class MainActivity extends Activity {

...

@SuppressLint("NewApi")
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);     
    setContentView(R.layout.activity_main);

        maCamera = getCameraInstance();

        maLayoutPreview = (FrameLayout) findViewById(R.id.camera_preview);

        maPreview = new CameraPreview(this, maCamera);

        Point displayDim = getDisplayWH();
        Point layoutPreviewDim = calcCamPrevDimensions(displayDim, 
                maPreview.getOptimalPreviewSize(maPreview.prSupportedPreviewSizes, 
                    displayDim.x, displayDim.y));
        if (layoutPreviewDim != null) {
            RelativeLayout.LayoutParams layoutPreviewParams = 
                (RelativeLayout.LayoutParams) maLayoutPreview.getLayoutParams();
            layoutPreviewParams.width = layoutPreviewDim.x;
            layoutPreviewParams.height = layoutPreviewDim.y;
            layoutPreviewParams.addRule(RelativeLayout.CENTER_IN_PARENT);
            maLayoutPreview.setLayoutParams(layoutPreviewParams);
        }
        maLayoutPreview.addView(maPreview);
}

@SuppressLint("NewApi")
@SuppressWarnings("deprecation")
private Point getDisplayWH() {

    Display display = this.getWindowManager().getDefaultDisplay();
    Point displayWH = new Point();

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) {
        display.getSize(displayWH);
        return displayWH;
    }
    displayWH.set(display.getWidth(), display.getHeight());
    return displayWH;
}

private Point calcCamPrevDimensions(Point disDim, Camera.Size camDim) {

    Point displayDim = disDim;
    Camera.Size cameraDim = camDim;

    double widthRatio = (double) displayDim.x / cameraDim.width;
    double heightRatio = (double) displayDim.y / cameraDim.height;

    // use ">" to zoom preview full screen
    if (widthRatio < heightRatio) {
        Point calcDimensions = new Point();
        calcDimensions.x = displayDim.x;
        calcDimensions.y = (displayDim.x * cameraDim.height) / cameraDim.width;
        return calcDimensions;
    }
    // use "<" to zoom preview full screen
    if (widthRatio > heightRatio) { 
        Point calcDimensions = new Point();
        calcDimensions.x = (displayDim.y * cameraDim.width) / cameraDim.height;
        calcDimensions.y = displayDim.y;
        return calcDimensions;
    }
    return null;
}   
}

我的评论:

这一切的关键在于,尽管您可以在getOptimalPreviewSize()中计算出最佳相机尺寸,但是您只选择最接近屏幕比例的比例。因此,除非比例完全相同,否则预览将会被拉伸。

为什么会被拉伸?因为您在layout.xml中设置了FrameLayout相机预览的宽度和高度匹配父容器。因此,预览将被拉伸到全屏。

需要做的是将相机预览布局的宽度和高度设置为与所选相机大小比例相匹配,以便预览保持其纵横比,不会失真。

我试图使用CameraPreview类来执行所有计算和布局更改,但我无法理解。我试图应用此解决方案,但是SurfaceView不认识getChildCount ()getChildAt(int index)。我想,最终通过引用maLayoutPreview解决了问题,但出现了问题,将设置的比例应用于整个应用程序,并在拍摄第一张照片后才这样做。因此,我放弃了它,并将布局修改移至MainActivity

CameraPreview中,我将prSupportedPreviewSizesgetOptimalPreviewSize()更改为public,以便在MainActivity中使用。然后,我需要显示维度(如果有导航/状态栏,则减去其尺寸)和选择的最佳相机尺寸。我试图获取RelativeLayout(或FrameLayout)大小而不是显示大小,但它返回零值。此解决方案对我不起作用。布局在onWindowFocusChanged之后获得了其值(在日志中检查)。

因此,我有了计算布局尺寸以匹配所选相机尺寸纵横比的方法。现在,您只需要设置相机预览布局的LayoutParams。更改宽度,高度并将其居中于父容器。

有两种选择如何计算预览尺寸。要么您希望它适合具有黑色边条(如果windowBackground设置为null)的屏幕边缘或顶部/底部。要么您希望预览缩放到全屏。我在calcCamPrevDimensions()中留下了更多信息的注释。


6

你好,这里的getOptimalPreview()对我没有用,所以我想分享我的版本:

private Camera.Size getOptimalPreviewSize(List<Camera.Size> sizes, int w, int h) {

    if (sizes==null) return null;

    Camera.Size optimalSize = null;
    double ratio = (double)h/w;
    double minDiff = Double.MAX_VALUE;
    double newDiff;
    for (Camera.Size size : sizes) {
        newDiff = Math.abs((double)size.width/size.height - ratio);
        if (newDiff < minDiff) {
            optimalSize = size;
            minDiff = newDiff;
        }
    }
    return optimalSize;
}

它将摄像头转换为正方形,给定的输入是(1920x1080),经过宽度和高度的优化后,它变成了(1088x1088),我的测试设备是三星S6。请问这是什么原因?如果您对此问题有任何想法,请分享。 - MohanRaj S
可能的原因是,您传递了相同的宽度和高度。 - Vijay

2

为了让这个帖子更加完整,我补充我的回答:

我的目标是:Surface View不应该被拉伸,并且应该覆盖整个屏幕。此外,在我的应用程序中只有横向模式。

解决方案:

解决方案是对F1sher的解决方案进行极小的扩展:

=> 第一步是集成F1sher的解决方案。

=> 现在,在F1sher的解决方案中可能会出现Surface View没有覆盖整个屏幕的情况。解决方法是使Surface View大于屏幕尺寸,以便它覆盖整个屏幕,具体方法如下:

    size = getOptimalPreviewSize(mCamera.getParameters().getSupportedPreviewSizes(), screenWidth, screenHeight);

    Camera.Parameters parameters = mCamera.getParameters();
    parameters.setPreviewSize(size.width, size.height);


    mCamera.setParameters(parameters);      

    double screenRatio = (double) screenHeight / screenWidth;
    double previewRatio = (double) size.height / size.width;

    if (previewRatio > screenRatio)     /*if preview ratio is greater than screen ratio then we will have to recalculate the surface height while keeping the surface width equal to the screen width*/
    {
        RelativeLayout.LayoutParams params1 = new RelativeLayout.LayoutParams(screenWidth, (int) (screenWidth * previewRatio));
        params1.addRule(RelativeLayout.CENTER_IN_PARENT);
        flPreview.setLayoutParams(params1);

        flPreview.setClipChildren(false);

        LayoutParams surfaceParams = new LayoutParams(screenWidth, (int) (screenWidth * previewRatio));
        surfaceParams.gravity = Gravity.CENTER;
        mPreview.setLayoutParams(surfaceParams);        
    }
    else     /*if preview ratio is smaller than screen ratio then we will have to recalculate the surface width while keeping the surface height equal to the screen height*/
    {
        RelativeLayout.LayoutParams params1 = new RelativeLayout.LayoutParams((int) ((double) screenHeight / previewRatio), screenHeight);
        params1.addRule(RelativeLayout.CENTER_IN_PARENT);
        flPreview.setLayoutParams(params1);
        flPreview.setClipChildren(false);

        LayoutParams surfaceParams = new LayoutParams((int) ((double) screenHeight / previewRatio), screenHeight);
        surfaceParams.gravity = Gravity.CENTER;
        mPreview.setLayoutParams(surfaceParams);

    }       

    flPreview.addView(mPreview); 

  /*  The TopMost layout used is the RelativeLayout, flPreview is the FrameLayout in which Surface View is added, mPreview is an instance of a class which extends SurfaceView  */

2
@Hesam的答案是正确的,CameraPreview将在所有竖屏设备上工作,但如果设备处于横向模式或多窗口模式,则此代码可以正常工作,只需替换onMeasure()即可。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    int width = resolveSize(getSuggestedMinimumWidth(), widthMeasureSpec);
    int height = resolveSize(getSuggestedMinimumHeight(), heightMeasureSpec);
    setMeasuredDimension(width, height);

    int rotation = ((Activity) mContext).getWindowManager().getDefaultDisplay().getRotation();
    if ((Surface.ROTATION_0 == rotation || Surface.ROTATION_180 == rotation)) {//portrait
        mPreviewSize = getOptimalPreviewSize(mSupportedPreviewSizes, height, width);
    } else
        mPreviewSize = getOptimalPreviewSize(mSupportedPreviewSizes, width, height);//landscape

    if (mPreviewSize == null) return;
    float ratio;
    if (mPreviewSize.height >= mPreviewSize.width) {
        ratio = (float) mPreviewSize.height / (float) mPreviewSize.width;
    } else ratio = (float) mPreviewSize.width / (float) mPreviewSize.height;


    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && ((Activity) mContext).isInMultiWindowMode()) {
        if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT ||
                !(Surface.ROTATION_0 == rotation || Surface.ROTATION_180 == rotation)) {
            setMeasuredDimension(width, (int) (width / ratio));
        } else {
            setMeasuredDimension((int) (height / ratio), height);
        }
    } else {
        if ((Surface.ROTATION_0 == rotation || Surface.ROTATION_180 == rotation)) {
            Log.e("---", "onMeasure: " + height + " - " + width * ratio);
            //2264 - 2400.0 pix c -- yes
            //2240 - 2560.0 samsung -- yes
            //1582 - 1440.0 pix 2 -- no
            //1864 - 2048.0 sam tab -- yes
            //848 - 789.4737 iball -- no
            //1640 - 1600.0 nexus 7 -- no
            //1093 - 1066.6667 lenovo -- no
            //if width * ratio is > height, need to minus toolbar height
           if ((width * ratio) < height)
                setMeasuredDimension(width, (int) (width * ratio));
            else
                setMeasuredDimension(width, (int) (width * ratio) - toolbarHeight);
        } else {
            setMeasuredDimension((int) (height * ratio), height);
        }
    }
    requestLayout();
}

1
我找出了问题所在 - 它与方向更改有关。如果您将相机方向更改为90度或270度,那么您需要交换支持的尺寸的宽度和高度,所有问题都会解决。
此外,表面视图应位于框架布局中,并具有居中重力。
以下是C#(Xamarin)的示例:
public void SurfaceChanged(ISurfaceHolder holder, Android.Graphics.Format format, int width, int height)
{
    _camera.StopPreview();

    // find best supported preview size

    var parameters = _camera.GetParameters();
    var supportedSizes = parameters.SupportedPreviewSizes;
    var bestPreviewSize = supportedSizes
        .Select(x => new { Width = x.Height, Height = x.Width, Original = x }) // HACK swap height and width because of changed orientation to 90 degrees
        .OrderBy(x => Math.Pow(Math.Abs(x.Width - width), 3) + Math.Pow(Math.Abs(x.Height - height), 2))
        .First();

    if (height == bestPreviewSize.Height && width == bestPreviewSize.Width)
    {
        // start preview if best supported preview size equals current surface view size

        parameters.SetPreviewSize(bestPreviewSize.Original.Width, bestPreviewSize.Original.Height);
        _camera.SetParameters(parameters);
        _camera.StartPreview();
    }
    else
    {
        // if not than change surface view size to best supported (SurfaceChanged will be called once again)

        var layoutParameters = _surfaceView.LayoutParameters;
        layoutParameters.Width = bestPreviewSize.Width;
        layoutParameters.Height = bestPreviewSize.Height;
        _surfaceView.LayoutParameters = layoutParameters;
    }
}

请注意,相机参数应设置为原始大小(而非交换),表面视图大小应进行交换。

1

我尝试了以上所有解决方案,但它们都对我不起作用。最终我自己解决了问题,并发现实际上很容易。有两点需要注意。

parameters.setPreviewSize(cameraResolution.x, cameraResolution.y);

此预览大小必须是相机支持的分辨率之一,可以按以下方式获取:

List<Camera.Size> rawSupportedSizes = parameters.getSupportedPreviewSizes(); 

通常其中一个原始支持的尺寸等于设备分辨率。
其次,在FrameLayout中放置您的SurfaceView,并在上述surfaceChanged方法中设置表面布局的高度和宽度。
FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) surfaceView.getLayoutParams();
layoutParams.height = cameraResolution.x;
layoutParams.width = cameraResolution.y;

好的,事情完成了,希望这能对你有所帮助。

1
非常重要的一点是,需要理解SurfaceView的大小必须与相机参数的大小相同,这意味着它们具有相同的纵横比,然后Stretch效果就会关闭。
您需要使用params.getSupportedPreviewSizes()获取正确支持的相机预览大小之一,然后将您的SurfaceView及其持有者更改为此大小。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接