Android Camera Preview Stretched

Я работал над созданием своей пользовательской активности камеры на Android, но при повороте камеры соотношение сторон вида поверхности испортилось.

В моем режиме для действия я установил структуру кадра, которая содержит вид поверхности, который отображает параметры камеры.

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

    }

Вы можете видеть, что человек LEGO становится выше и стройнее, когда телефон поворачивается:

Как я могу убедиться, что соотношение сторон для моей камеры правильное?

15 ответов

Решение

Я использую этот метод -> на основе 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;
    }

Как видите, вы должны ввести ширину и высоту экрана. Этот метод будет рассчитывать соотношение экрана на основе этих значений, а затем из списка поддерживаемых размеров просмотра он выберет для вас лучшее из доступных. Получите ваш список SupportPreviewSize в месте, где объект Camera не равен NULL, используя

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, как я уже говорил, я использую структуру API Demos кода CameraActivity, вы можете сгенерировать его в Eclipse):

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

И один совет для вас, потому что я сделал почти то же приложение, что и вы. Хорошей практикой для Camera Activity является скрытие StatusBar. Такие приложения, как Instagram, делают это. Это уменьшает значение высоты экрана и меняет соотношение. На некоторых устройствах можно получить странные размеры предварительного просмотра (ваш SurfaceView будет немного обрезан)


И чтобы ответить на ваш вопрос, как проверить правильность вашего предварительного просмотра? Затем получите высоту и ширину параметров, которые вы установили:

mCamera.setParameters(parameters);

Ваше установленное соотношение равно высоте / ширине. Если вы хотите, чтобы камера хорошо выглядела на вашем экране, то соотношение высоты / ширины параметров, установленных вами для камеры, должно совпадать с отношением высоты (без строки состояния)/ ширины вашего экрана.

Решение 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;
    }
}

ПРИМЕЧАНИЕ: МОЕ РЕШЕНИЕ - ПРОДОЛЖЕНИЕ РЕШЕНИЯ HESAM: /questions/26673073/android-camera-preview-stretched/26673074#26673074

Что я адресую: Хесам сказал, что на некоторых телефонах может появиться небольшое пустое пространство, например:

ПРИМЕЧАНИЕ. Несмотря на правильное соотношение сторон, камера не заполняет весь экран

Хесам предложил второе решение, но оно сводит на нет предварительный просмотр. И на некоторых устройствах это сильно искажает.

Итак, как мы можем решить эту проблему. Это просто... путем умножения пропорций, пока он не заполнит экран. Я заметил, что несколько популярных приложений, таких как 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

Это также хорошо работает с фронтальной камерой. Я считаю, что это лучший способ сделать это. Теперь единственное, что осталось для моего приложения, - это сохранить сам предварительный просмотр после нажатия "Захват". Но да, это оно.

Хорошо, так что я думаю, что нет достаточного ответа на общую проблему растягивания предварительного просмотра камеры. Или, по крайней мере, я не нашел. Мое приложение также страдало этим синдромом растяжения, и мне потребовалось некоторое время, чтобы найти решение из всех ответов пользователей на этом портале и в Интернете.

Я попробовал решение @Hesam, но оно не сработало, и мой предварительный просмотр камеры был сильно искажен.

Сначала я показываю код моего решения (важные части кода), а затем объясняю, почему я предпринял эти шаги. Есть место для модификаций производительности.

Основная деятельность xml layout:

<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 в layout.xml задано значение match_parent по ширине и высоте. Вот почему предварительный просмотр растянется на весь экран.

Что нужно сделать, это установить ширину и высоту макета предварительного просмотра камеры в соответствии с выбранным соотношением размеров камеры, чтобы предварительный просмотр сохранял соотношение сторон и не искажал.

Я пытался использовать CameraPreview класс, чтобы сделать все вычисления и изменения макета, но я не мог понять это. Я пытался применить это решение, но SurfaceView не узнает getChildCount () или же getChildAt (int index), Я думаю, я получил его в конечном итоге со ссылкой на maLayoutPreview, но он плохо себя вел и применил установленное соотношение ко всему моему приложению, и это произошло после того, как была сделана первая фотография. Так что я отпустил его и перенес изменения макета в MainActivity,

В CameraPreview Я изменился prSupportedPreviewSizes а также getOptimalPreviewSize() для публики, чтобы я мог использовать его в MainActivity, Затем мне понадобились размеры дисплея (без навигационной панели / строки состояния, если она есть) и выбор оптимального размера камеры. Я пытался получить размер RelativeLayout (или FrameLayout) вместо размера дисплея, но он возвращал нулевое значение. Это решение не сработало для меня. Макет получил свое значение после onWindowFocusChanged (проверено в журнале).

Таким образом, у меня есть свои методы для расчета размеров макета, чтобы соответствовать соотношению сторон выбранного размера камеры. Теперь вам просто нужно установить LayoutParams вашего макета предварительного просмотра камеры. Измените ширину, высоту и отцентрируйте его в родительском.

Есть два варианта расчета размеров предварительного просмотра. Либо вы хотите, чтобы он соответствовал экрану с черными полосами (если для windowBackground установлено значение null) по бокам или сверху / снизу. Или вы хотите, чтобы предварительный просмотр увеличен до полного экрана. Я оставил комментарий с дополнительной информацией в calcCamPrevDimensions(),

Привет 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();
}

Просто чтобы сделать эту тему более полной, я добавляю свою версию ответа:

Чего я хотел добиться: вид поверхности не должен быть растянут, и он должен охватывать весь экран. Более того, в моем приложении был только ландшафтный режим.

Решение:

Решение является чрезвычайно небольшим расширением решения 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  */

Здесь очень важно понять, что размер SurfaceView должен быть таким же, как и размер параметров камеры, это означает, что они имеют одинаковое соотношение сторон, тогда эффект Stretch отключится.

Вы должны получить правильный поддерживаемый размер предварительного просмотра камеры, используя params.getSupportedPreviewSizes(), выберите один из них, а затем измените свой SurfaceView и его держатели на этот размер.

Я попробовал все решения выше, но ни один из них не работает для меня. наконец, я решил это сам, и на самом деле это довольно легко. Есть два момента, которые вы должны быть осторожны.

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

Этот previewSize должен быть одним из поддерживаемых разрешением камеры, которое можно получить, как показано ниже:

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

обычно один из rawSupportedSize равен разрешению устройства.

Во-вторых, поместите SurfaceView в FrameLayout и установите высоту и ширину макета поверхности в методе SurfaceChanged, как указано выше.

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

Хорошо, все готово, надеюсь, это поможет вам.

Я разобрался в чем проблема - это с изменением ориентации. Если вы измените ориентацию камеры на 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;
    }
}

Обратите внимание, что параметры камеры должны быть установлены как исходный размер (не поменялся местами), а размер вида поверхности должен поменяться местами.

Мои требования: предварительный просмотр камеры должен быть полноэкранным и сохранять соотношение сторон. Решение Hesam и Yoosuf было великолепным, но я почему-то вижу проблему с большим зумом.

Идея та же: иметь центр контейнера предварительного просмотра в родительском элементе и увеличивать ширину или высоту в зависимости от пропорций, пока он не сможет охватить весь экран.

Следует отметить, что размер предварительного просмотра находится в альбомной ориентации, поскольку мы устанавливаем ориентацию отображения.

camera.setDisplayOrientation (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);
}

Ниже приведено обновление ответа @Hesam , в котором рассматриваются следующие вопросы:

  1. Окончательный вид искажен на нескольких экранах

  2. Предварительный просмотр располагается в самом верху экрана, а не централизованно, что выглядит очень странно.


Вам просто нужно обновить onMeasure таким образом, все остальное остается прежним.

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


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

    //centralize preview
    FrameLayout.LayoutParams surfaceParams = new FrameLayout.LayoutParams(width, (int) height);
    surfaceParams.gravity = Gravity.CENTER;
    this.setLayoutParams(surfaceParams);

    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;

        setMeasuredDimension(width, (int) (width * ratio));


        //fix distortion, based on this answer https://stackoverflow.com/a/30634009/6688493
        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);
        }
    }
}

немного поздно для вас, но, надеюсь, не для всех.

То, что вы можете использовать для обеспечения определенного рациона, это атрибут:

      layout_constraintDimensionRatio="ration_a:ratio_b"

Это решило мои проблемы.

Вы должны установить cameraView.getLayoutParams(). Height и cameraView.getLayoutParams(). Width в соответствии с желаемым соотношением сторон.

Я отказался от расчетов и просто получил размер вида, в котором я хочу отобразить предварительный просмотр камеры, и установил такой же размер предварительного просмотра камеры (только ширина / высота при повороте) в моей пользовательской реализации 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
}
Другие вопросы по тегам