Сделайте SurfaceView больше, чем экран (установка предварительного просмотра камеры на SurfaceView больше, чем на дисплее)

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

По какой-то причине я не могу сделать SurfaceView больше размера экрана.

Что я пробовал до сих пор:

  • изменить размер предварительного просмотра камеры в surfaceChanged

  • изменить предварительный просмотр камеры в режиме onMeasure

  • измените размер в onLayout .
  • добавление FLAG_LAYOUT_NO_LIMITS в действие - информация
  • добавление android: clipChildren для поверхностного вида - информация
  • ширина установки в xml: android:layout_width="852px"
  • getWindow().setLayout(852, 1280); в действии

но без каких-либо успехов - поведение одно и то же: он выглядит нормально в течение 1 секунды, а затем растягивается.

Вот код:

public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback {

    private static final int CAMERA_ROTATE_ANGLE = 90;

    private SurfaceHolder cameraHolder;
    private Camera androidHardCamera;
    private Context context;

    public CameraPreview(Context context) {
        super(context);
        this.context = context;

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

    public void setCamera(Camera camera) {
        this.androidHardCamera = camera;
        if (androidHardCamera != null) {
            requestLayout();
        }
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        // The Surface has been created, now tell the camera where to draw the preview.
        try {
            if (androidHardCamera != null) {
                androidHardCamera.stopPreview();//this is needed for devices with API level < 14 (from doc.: Starting
                // from API level 14, this method, aka setDisplayOrientation, can be called when preview is active.)

                androidHardCamera.setDisplayOrientation(CAMERA_ROTATE_ANGLE);//force the preview Display Orientation
                // to Portrait (rotate camera orientation/display to portrait)

                //holder.setFixedSize(852, 1280);
                androidHardCamera.setPreviewDisplay(holder);
                androidHardCamera.startPreview();
            }
        } catch (IOException e) {
        }
    }

    @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 (cameraHolder.getSurface() == null) {
            // preview surface does not exist
            return;
        }

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

        // set preview size and make any resize, rotate or
        // reformatting changes here
        FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) this.getLayoutParams();
        layoutParams.height = 1280;
        layoutParams.width = 852;
        this.setLayoutParams(layoutParams);

        //cameraHolder.setFixedSize(852, 1280);
        requestLayout();

        // start preview with new settings
        try {
            androidHardCamera.setPreviewDisplay(cameraHolder);
            androidHardCamera.startPreview();
        } catch (Exception e) {
        }
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
    }

//    @Override
//    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//       // super.onMeasure(widthMeasureSpec, heightMeasureSpec);    //To change body of overridden methods use File | Settings | File Templates.
//        //super.onMeasure(852, 1280);
//        setMeasuredDimension(852, 1280);
//    }
}

public class MyActivity extends Activity{

    private Camera camera;
    private CameraPreview previewCamera;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);

        setContentView(R.layout.camera_screen);

        previewCamera = new CameraPreview(this);
        FrameLayout preview = (FrameLayout) findViewById(R.id.camera_preview);
        preview.addView(previewCamera);

        //getWindow().setLayout(852, 1280);
    }

    @Override
    protected void onResume() {
        // Create an instance of Camera
        camera = getCameraInstance(1);
        if (camera == null) {
            Toast.makeText(this, "Camera in use!", Toast.LENGTH_LONG).show();
        } else {
            previewCamera.setCamera(camera);
            camera.stopPreview();

            Camera.Parameters p = camera.getParameters();
            p.setPreviewSize(176, 144);
            // p.setPreviewSize(480, 800);
            camera.setParameters(p);

            startPreviewCamera();
        }
        super.onResume();
    }

    @Override
    protected void onPause() {
        releaseCameraAndPreview();
        super.onPause();
    }

    public Camera getCameraInstance(int cameraInstance) {
        Camera c = null;
        try {
            c = Camera.open(cameraInstance);
        } catch (Exception e) {
            // Camera is not available (in use or does not exist)
            System.out.println("exception: " + e);
        }
        return c;
    }

    public void startPreviewCamera() {
        //Force the preview Display Orientation to Portrait (rotate camera orientation/display to portrait)
        camera.setDisplayOrientation(90);
        camera.startPreview();
    }

    public void releaseCameraAndPreview() {
        if (camera != null) {
            camera.stopPreview(); // updating the preview surface
            camera.setPreviewCallback(null);
            // camera.lock(); //if we don't lock the camera, release() will fail on some devices
            camera.release();
            camera = null;
        }
    }
}

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:orientation="vertical"
              android:layout_width="match_parent"
              android:layout_height="match_parent">

    <!-- This is the container for the camera preview screen -->
    <FrameLayout android:id="@+id/camera_preview"
                 android:layout_width="match_parent"
                 android:layout_height="match_parent"
                 android:clipChildren="false"
                 android:layout_weight="1"/>
</LinearLayout>

Вот весь проект: https://www.dropbox.com/sh/96jih9kw5zmmnzy/z7VX16T30M

Я тестирую устройство S3. На устройстве S2, похоже, все нормально... Я просто не знаю, что делать больше, чтобы решить эту проблему...

ОБНОВЛЕНИЕ 1

Например, Sony Xperia имеет экран 480/854. Один из размеров предварительного просмотра, который я могу использовать, - 176/144.

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

Код ниже не работает... предварительный просмотр камеры растянут/удлинен.

import android.app.Activity;
import android.graphics.Point;
import android.hardware.Camera;
import android.os.Bundle;
import android.view.Display;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.widget.FrameLayout;
import android.widget.LinearLayout;

public class CameraPreview extends Activity implements Preview.PreviewListener {

    private Preview mPreview;
    private Camera mCamera;

    FrameLayout preview;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);

        setContentView(R.layout.main);

        // Create our Preview view and set it as the content of our activity.
        mPreview = new Preview(this);
        preview = (FrameLayout) findViewById(R.id.surface_camera);
        preview.addView(mPreview);

        Display display = getWindowManager().getDefaultDisplay();
        getDisplaySize(display);
    }

    private static Point getDisplaySize(final Display display) {
        final Point point = new Point();
        try {
            display.getSize(point);
        } catch (java.lang.NoSuchMethodError ignore) {
            point.x = display.getWidth();
            point.y = display.getHeight();
        }
        System.out.println("============: Screen " + point.x + "/" + point.y);
        return point;
    }

    @Override
    protected void onResume() {
        super.onResume();
        mCamera = Camera.open(1);
        mPreview.setCamera(mCamera, this);
    }

    @Override
    protected void onPause() {
        super.onPause();
        if (mCamera != null) {
            mPreview.setCamera(null, null);
            mCamera.release();
            mCamera = null;
        }
    }


    @Override
    public void onSurfaceChanged() {
        LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) preview.getLayoutParams();
        params.setMargins(0, -218, 0, 0);
        preview.setLayoutParams(new FrameLayout.LayoutParams(480, 854));
        preview.setLayoutParams(params);

        preview.setVisibility(View.VISIBLE);
    }
}

import android.content.Context;
import android.hardware.Camera;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

import java.io.IOException;
import java.util.List;

class Preview extends SurfaceView implements SurfaceHolder.Callback {

    private SurfaceHolder mHolder;
    private Camera mCamera;

    private PreviewListener listener;

    public static interface PreviewListener {
        void onSurfaceChanged();
    }

    Preview(Context context) {
        super(context);

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

    public void setCamera(Camera camera, PreviewListener listener) {
        this.listener = listener;
        mCamera = camera;
        if (mCamera != null) {
            List<Camera.Size> mSupportedPreviewSizes = mCamera.getParameters().getSupportedPreviewSizes();
            for (Camera.Size s : mSupportedPreviewSizes) {
                System.out.println("============: " + s.width + "/" + s.height);
            }
            requestLayout();
        }
    }

    public void surfaceCreated(SurfaceHolder holder) {
        // The Surface has been created, acquire the camera and tell it where
        // to draw.
        try {
            if (mCamera != null) {
                mCamera.setPreviewDisplay(holder);
            }
        } catch (IOException exception) {
            Log.e("Error: ", "IOException caused by setPreviewDisplay()", exception);
        }
    }

    public void surfaceDestroyed(SurfaceHolder holder) {
        // Surface will be destroyed when we return, so stop the preview.
        if (mCamera != null) {
            mCamera.stopPreview();
        }
    }

    public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
        mCamera.stopPreview(); // pe Xpedia daca nu pui asta crapa la  setDisplayOrientation

        // Now that the size is known, set up the camera parameters and beginthe preview.
        Camera.Parameters parameters = mCamera.getParameters();

        mCamera.setDisplayOrientation(90);
        parameters.setPreviewSize(176, 144);

        requestLayout();

        mCamera.setParameters(parameters);
        mCamera.startPreview();
    }

//    @Override
//    protected void onSizeChanged(\int w, int h, int oldw, int oldh) {
//        super.onSizeChanged(w, h, oldw, oldh);
//        //setLayoutParams(new LayoutParams((int)RATIO * w, w));
//
//        FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) getLayoutParams();
//        setLayoutParams(new FrameLayout.LayoutParams(960, 1280));
//        params.setMargins(0, -120, 0,0);
//        setLayoutParams(params);
//
//        //preview.setVisibility(View.VISIBLE);
//    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);    //To change body of overridden methods use File | Settings | File Templates.

//        FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) getLayoutParams();
//       setLayoutParams(new FrameLayout.LayoutParams(698, 854));
//        params.setMargins(0, -218, 0,0);
//        setLayoutParams(params);
    }

    //https://stackoverflow.com/info/11853297/change-size-of-android-custom-surfaceview
    @Override
    public void onLayout(boolean changed, int left, int top, int right, int bottom) {
        if (changed) {
            //setLayoutParams();
                 listener.onSurfaceChanged();
            //(this).layout(0, 0, viewWidth , viewHeight);
        }
    }

}

Это тест класса, который вычисляет правильный размер поверхности в зависимости от размера экрана и размера предварительного просмотра камеры:

public class Test {

    /**
     * Determine proper width to be used for surface view in order to not stretch the camera live preview.
     */
    public static void main(String[] args) {
        // camera preview size:
        int surfaceViewWidth = 176;
        int surfaceViewHeight = 144;

        int holder;
        if (surfaceViewWidth > surfaceViewHeight) {
            holder = surfaceViewWidth;
            surfaceViewWidth = surfaceViewHeight;
            surfaceViewHeight = holder;
        }

        //device screen display sizes:
        int width = 480;
        int height = 854;

        double sc1 = (double) width / surfaceViewWidth;
        double sc2 = (double) height / surfaceViewHeight;
        double rez;
        if (sc1 > sc2) {
            rez = sc1;
        } else {
            rez = sc2;
        }

        System.out.println("Width/height: " + (int) (surfaceViewWidth * rez) + "/" + (int) (surfaceViewHeight * rez)); // size of the preview size we need to set
        System.out.println(((int) (surfaceViewWidth * rez))-width); // difference between preview size and device screen size = whit how much is bigger the preview size than screen size 
    }
}

Ответ 1

Сначала удалите источник сбоев: startPreviewCamera, вызванный onResume. Предварительный просмотр камеры запускается в методах SurfaceHolder.Callback.

Затем вы должны знать, что вы можете установить размер предварительного просмотра только по размерам, указанным в Camera.Parameters.getSupportedPreviewSizes. И эти размеры, скорее всего, будут меньше или равны размеру экрана устройства.

Затем вы просто вызываете

Camera.Parameters p = camera.getParameters();
p.setPreviewSize(w, h); // one of supported sizes
camera.setParameters(p);

Затем поверхность предварительного просмотра будет иметь этот размер (возможно, повернутый и w/h сменился). И эта поверхность будет перемасштабирована Android до размера вашего представления CameraPreview при рисовании, поэтому также важно, как вы задали размер вашего CameraPreview.

Вы можете установить фиксированный размер вашего CameraPreview, просто позвонив

previewCamera.setLayoutParams(new FrameLayout.LayoutParams(w, h));

Итак, вы задали требуемый размер предварительного просмотра в Camera.setParameters, и размер вашего предварительного просмотра по своему размеру, возможно, будет такого же размера, как и предварительный просмотр, как и ваше требование. Тогда ваше представление предварительного просмотра может быть равно размеру экрана или быть меньше (при условии, что камера не обеспечивает предварительный просмотр больше экрана). Если камера обеспечивает больший предварительный просмотр, чем экран, вы все равно можете вызвать preview.setX, preview.setY, чтобы переместить его.