Android 相机预览拉伸

我一直致力于在 Android 上制作我自定义的摄像机活动,但是当旋转摄像机时,表面视图的高宽比就变得一团糟。

在活动的 oncreate 中,我设置了帧布局,其中包含显示摄像机参数的表面视图。

//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();
}


}

enter image description here

当手机旋转时,你可以看到这个乐高玩具男人变得越来越高,越来越瘦:

如何确保我的相机视图的高宽比是正确的?

136511 次浏览

您必须根据所需的长宽比设置 cameraView.getLayoutParams () . height 和 cameraView.getLayoutParams () . width。

我使用这个方法-> 基于 API 演示获得我的预览大小:

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;
}

正如你所看到的,你必须输入屏幕的宽度和高度。这个方法将根据这些值计算屏幕比率,然后从受支持的 PreviewSize 列表中选择最适合您的可用值。在 Camera 对象不为 null 的地方获取受支持的 PreviewSize 列表

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

然后,你就可以得到这样的最佳预览大小:

@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);
}
}

然后(在我的 Surface Changed 方法中的代码中,就像我说的,我使用的是 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);

你的设定比例等于高度/宽度。如果你想让相机在你的屏幕上看起来不错,那么你设置给相机的参数的高度/宽度比必须与你屏幕的高度(减去状态栏)/宽度比相同。

F1Sher 的解决方案很好,但有时不工作。特别是当你的 faceView 不能覆盖整个屏幕的时候。在这种情况下,您需要重写 on拳头()方法。 我在这里复制了我的代码供你参考。

因为我测量的表面视图的宽度,然后我有一点白色差距在我的屏幕结束,我填补了它的设计。你能够解决这个问题,如果你保持高度和增加宽度乘以它的比率。然而,它会压扁表面观点轻微。

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;
}
}

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

我试了@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()你只选择适合你的屏幕的 最接近的比率。因此,除非比率正好是 一样预览将拉伸。

为什么会拉长?因为您的 FrameLayout 相机预览设置在 XmlMatch _ father的宽度和高度。所以这就是为什么预览将延伸到全屏。

需要做的是 设置摄像机预览布局的宽度和高度匹配选定的 照相机尺寸比率,所以预览保持其长宽比,不会扭曲。

我尝试使用 CameraPreview类来完成所有的计算和布局更改,但是我不能弄明白。我试图应用 这个解决方案,但是 SurfaceView不能识别 getChildCount ()或者 getChildAt (int index)。我认为,我得到了它的工作,最终与 maLayoutPreview的参考,但它是错误的,并适用于我的整个应用程序的设置比率,它这样做后,第一张照片被采取。所以我让它去和移动的布局修改到 MainActivity

CameraPreview中,我把 prSupportedPreviewSizesgetOptimalPreviewSize()改成了 公众人士,所以我可以在 MainActivity中使用它。然后我需要 显示尺寸(如果有的话,减去导航/状态栏)并选择最佳的 相机尺寸。我尝试获取 RelativeLayout (或 FrameLayout)大小而不是显示大小,但它返回的值为零。这个解决方案不适合我。布局在 onWindowFocusChanged之后得到它的值(在日志中检查)。

因此,我有我的方法来计算布局尺寸,以匹配所选择的照相机尺寸的长宽比。现在您只需要设置相机预览布局的 LayoutParams。更改宽度、高度,并在父级中将其居中。

如何计算预览尺寸有两种选择。要么你希望它适合屏幕的 黑条(如果 windows 背景设置为空)的侧面或顶部/底部。或者你想要预览 放大到全屏。我在 calcCamPrevDimensions()中留下了带有更多信息的评论。

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

我想要的是: 表面视图不应该被拉伸,它应该覆盖整个屏幕,而且,在我的应用程序中只有一个横向模式。

解决方案:

这个解决方案是 F1sher 解决方案的一个极小的扩展:

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

现在,F1sher 的解决方案中可能会出现一种情况,当表面视图不覆盖整个屏幕时,解决方案是使表面视图大于屏幕尺寸,以便覆盖整个屏幕,为此:

    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  */

注意: 我的解决方案是 HESAM 解决方案的延续: https://stackoverflow.com/a/22758359/1718734

我要说的是: Hesam 说有些手机上可能会出现一小块空白,像这样:

NOTE: Although the aspect ratio is correct, the camera does not fill in the whole screen.

Hesam 提出了第二种解决方案,但这压缩了预览效果,而且在某些设备上,它严重扭曲了预览效果。

那么我们如何解决这个问题。这很简单... 通过乘以纵横比,直到它填满屏幕。我注意到,一些流行的应用程序,如 Snapchat,WhatsApp 等工作原理相同。

您所需要做的就是将其添加到 on拳头方法中:

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 高度的比值。然后将摄像机的宽度和高度乘以新的高度比,并相应地设置测量尺寸。

enter image description here

接下来你知道的,你得到了这个: D

enter image description here

这也适用于他的前置摄像头。我相信这是最好的办法。现在我的应用程序唯一剩下的就是在点击“捕获”时保存预览本身但是,就是这样。

我发现了问题所在——是 定位的变化。如果你改变相机方向到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;
}
}

请注意,相机参数应设置为原始尺寸(不交换) ,表面视图尺寸应交换。

我放弃了计算,只是得到我想要显示相机预览的视图大小,并在我自定义的 SurfaceView 实现中设置相机预览大小相同(只是由于旋转而翻转宽度/高度) :

@Override // CameraPreview extends SurfaceView implements SurfaceHolder.Callback {
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {


Display display = ((WindowManager) getContext().getSystemService(
Context.WINDOW_SERVICE)).getDefaultDisplay();


if (display.getRotation() == Surface.ROTATION_0) {
final Camera.Parameters params = camera.getParameters();
// viewParams is from the view where the preview is displayed
params.setPreviewSize(viewParams.height, viewParams.width);
camera.setDisplayOrientation(90);
requestLayout();
camera.setParameters(params);
}
// I do not enable rotation, so this can otherwise stay as is
}

我尝试了以上所有的解决方案,但没有一个对我有效。最后我自己解出来了,发现其实很简单。有两点你需要小心。

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

这个预览大小必须是相机支持的分辨率之一,它可以得到如下:

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

通常一个 rawSupportdSize 等于设备分辨率。

其次,将 SurfaceView 放置在 FrameLayout 中,并在 Surface Changed 方法中设置表面布局的高度和宽度,如上所示

FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) surfaceView.getLayoutParams();
layoutParams.height = cameraResolution.x;
layoutParams.width = cameraResolution.y;

好了,事已至此,希望这能帮到你。

我的要求是相机预览需要全屏,并保持高宽比。 Hesam 和 Yoosuf 的解决方案是伟大的,但我确实看到了一个高变焦的问题,出于某种原因。

这个想法是一样的,让预览容器在父容器中居中,并根据长宽比增加宽度或高度,直到它能覆盖整个屏幕。

需要注意的一点是,预览的大小是横向的,因为我们设置了显示方向。

SetDisplayOrient (90) ;

我们将把 SurfaceView 视图添加到的容器:

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

将预览添加到活动中以父级为中心的容器中。

this.cameraPreview = new CameraPreview(this, camera);
cameraPreviewContainer.removeAllViews();
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
params.addRule(RelativeLayout.CENTER_IN_PARENT, RelativeLayout.TRUE);
cameraPreviewContainer.addView(cameraPreview, 0, params);

在 CameraPreview 类中:

@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
// 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 (holder.getSurface() == null) {
// preview surface does not exist
return;
}


stopPreview();


// set preview size and make any resize, rotate or
// reformatting changes here
try {
Camera.Size nativePictureSize = CameraUtils.getNativeCameraPictureSize(camera);
Camera.Parameters parameters = camera.getParameters();
parameters.setPreviewSize(optimalSize.width, optimalSize.height);
parameters.setPictureSize(nativePictureSize.width, nativePictureSize.height);
camera.setParameters(parameters);
camera.setDisplayOrientation(90);
camera.setPreviewDisplay(holder);
camera.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 (supportedPreviewSizes != null && optimalSize == null) {
optimalSize = CameraUtils.getOptimalSize(supportedPreviewSizes, width, height);
Log.i(TAG, "optimal size: " + optimalSize.width + "w, " + optimalSize.height + "h");
}


float previewRatio =  (float) optimalSize.height / (float) optimalSize.width;
// previewRatio is height/width because camera preview size are in landscape.
float measuredSizeRatio = (float) width / (float) height;


if (previewRatio >= measuredSizeRatio) {
measuredHeight = height;
measuredWidth = (int) ((float)height * previewRatio);
} else {
measuredWidth = width;
measuredHeight = (int) ((float)width / previewRatio);
}
Log.i(TAG, "Preview size: " + width + "w, " + height + "h");
Log.i(TAG, "Preview size calculated: " + measuredWidth + "w, " + measuredHeight + "h");


setMeasuredDimension(measuredWidth, measuredHeight);
}

嗨,这里的 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;
}

@ 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();
}

这里非常重要的一点要明白,表面视图的大小必须与相机参数的大小相同,这意味着他们有相同的长宽比,然后拉伸效果就会消失。

你必须使用 params.getSupportedPreviewSize ()获得正确的支持相机预览大小,选择其中一个,然后将 SurfaceView 及其持有者更改为这个大小。

对你来说有点晚,但希望不是每个人。

你可以用来确保一个特定的口粮是属性:

layout_constraintDimensionRatio="ration_a:ratio_b"

它解决了我的问题。