Pratinjau Kamera Android Membentang

139

Saya telah bekerja untuk membuat aktivitas kamera khusus saya di Android, tetapi ketika memutar kamera, rasio aspek tampilan permukaan menjadi kacau.

Di oncreate saya untuk aktivitas, saya mengatur framelayout yang menahan tampilan permukaan yang menampilkan parameter kamera.

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

Kemudian, dalam tampilan Permukaan saya mengatur parameter kamera yang akan ditampilkan

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

    }

masukkan deskripsi gambar di sini

Anda dapat melihat bahwa pria LEGO tumbuh lebih tinggi dan lebih kurus ketika telepon diputar:

Bagaimana saya bisa memastikan bahwa rasio aspek untuk tampilan kamera saya sudah benar?

scientiffic
sumber
@ Ilmiah bisakah u tolong bantu saya di mana saya harus menulis metode yang diberikan im juga menghadapi masalah yang sama?
pengguna3233280
Saya sepertinya mendapatkan masalah serupa, tetapi yang memperbaikinya adalah memanggil yang berikut: mCamera.setDisplayOrientation (90) dalam metode surfaceCreated () pada SurfaceView untuk kamera
Greg

Jawaban:

164

Saya menggunakan metode ini -> berdasarkan Demo API untuk mendapatkan Ukuran Pratinjau saya:

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

Seperti yang Anda lihat, Anda harus memasukkan lebar dan tinggi layar Anda. Metode ini akan menghitung rasio layar berdasarkan nilai-nilai tersebut dan kemudian dari daftar yang didukungPreviewSizes akan memilih yang terbaik untuk Anda dari yang tersedia. Dapatkan daftar supportPreviewSize Anda di tempat di mana objek Kamera tidak null dengan menggunakan

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

Dan kemudian di onMeasure Anda bisa mendapatkan previewSize optimal Anda seperti itu:

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

Dan kemudian (dalam kode saya dalam metode surfaceChanged, seperti yang saya katakan, saya menggunakan struktur API Demo dari kode CameraActivity, Anda dapat membuatnya di Eclipse):

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

Dan satu petunjuk untuk Anda, karena saya melakukan aplikasi yang hampir sama seperti Anda. Praktik yang baik untuk Aktivitas Kamera adalah menyembunyikan StatusBar. Aplikasi seperti Instagram sedang melakukannya. Ini mengurangi nilai tinggi layar Anda dan mengubah nilai rasio Anda. Dimungkinkan untuk mendapatkan Ukuran Pratinjau yang aneh di beberapa perangkat (SurfaceView Anda akan dipotong sedikit)


Dan untuk menjawab pertanyaan Anda, bagaimana cara memeriksa apakah rasio pratinjau Anda sudah benar? Kemudian dapatkan parameter tinggi dan lebar yang Anda tetapkan:

mCamera.setParameters(parameters);

rasio set Anda sama dengan tinggi / lebar. Jika Anda ingin kamera terlihat bagus di layar Anda, maka rasio tinggi / lebar parameter yang Anda atur ke kamera harus sama dengan rasio tinggi (minus status bar) / lebar layar Anda.

F1sher
sumber
3
Hanya satu pertanyaan: Saya melihat Anda menggunakan 2 rasio komputasi yang berbeda: double targetRatio = (double) h / w dan double ratio = (double) size.width / size.height . Yang pertama adalah tinggi dibagi dengan lebar dan yang lainnya adalah kebalikannya, lebar dibagi tinggi. Bukankah seharusnya komputasi yang sama?
phyzalis
Tidak masalah karena daftar ukuran mempertimbangkan setiap kemungkinan, misalnya: Anda akan menemukan sesuatu seperti 480x680, tetapi cepat atau lambat akan ada 680x480 (untuk lanskap), jadi Anda hanya perlu memeriksa daftar itu dan menemukan yang sesuai dengan kebutuhan Anda. Anda akan menemukannya cepat atau lambat.
F1sher
Selanjutnya, jika Anda ingin gambar Anda berukuran ini juga (sebagaimana mestinya), Anda telah mengatur ukuran gambar dengan parameters.setPictureSize(mPreviewSize.width, mPreviewSize.height).
Matt Logan
2
@ F1sher, saya selalu mendapatkan pengecualian pointer null parameter.setPreviewSize (mPreviewSize.width, mPreviewSize.height); Tidak dapat menemukan solusi apa pun.
FIXI
3
@phyzalis Saya setuju dengan Anda. Sebenarnya, saya harus membuat koreksi agar kode berfungsi seperti yang diharapkan.
Fernio
151

Solusi F1Sher bagus tetapi terkadang tidak berhasil. Terutama, saat surfaceView Anda tidak menutupi seluruh layar. Dalam hal ini Anda perlu mengganti metode onMeasure (). Saya telah menyalin kode saya di sini untuk referensi Anda.

Karena saya mengukur surfaceView berdasarkan lebar, maka saya memiliki sedikit celah putih di ujung layar yang saya isi dengan desain. Anda dapat memperbaiki masalah ini jika Anda menjaga tinggi dan menambah lebar dengan mengalikannya dengan rasio. Namun, itu akan sedikit meremas 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
sumber
13
ini harus menjadi jawaban yang benar, ini menangani kasus ketika pratinjau kamera tidak menutupi seluruh layar. +1.
Houcine
1
ketika saya mengembangkan aplikasi aktivitas saya ditutup <com.app.camera.CameraPreview android: id = "@ + id / camera_preview" android: layout_width = "match_parent" android: layout_height = "match_parent" />
fish40
Anda perlu melakukan beberapa modifikasi tetapi saya menerima ini sebagai solusi. Dipilih.
JaydeepW
2
Hai @ adnan9011, tutorial ini mungkin bisa membantu. airpair.com/android/android-camera-surface-view-fragment
Hesam
4
tinggi dan lebar yang perlu diaktifkan getOptimalPreviewSizeagar ini bekerja untuk saya. apakah ini karena orientasi tampilan disetel ke 90? Jika tidak, perhitungan rasio tidak bahkan dekat (target adalah 1,7 dan rasio kamera kurang dari 1)
ono
24

CATATAN: SOLUSI SAYA ADALAH LANJUTAN DARI SOLUSI HESAM: https://stackoverflow.com/a/22758359/1718734

Yang saya bahas: Hesam mengatakan ada sedikit ruang putih yang mungkin muncul di beberapa ponsel, seperti ini:

CATATAN: Meskipun rasio aspeknya benar, kamera tidak memenuhi seluruh layar.

Hesam menyarankan solusi kedua, tetapi itu menghentikan pratinjau. Dan pada beberapa perangkat, itu sangat terdistorsi.

Jadi bagaimana kita memperbaiki masalah ini. Ini sederhana ... dengan mengalikan rasio aspek hingga memenuhi layar. Saya perhatikan, beberapa aplikasi populer seperti Snapchat, WhatsApp, dll bekerja dengan cara yang sama.

Yang harus Anda lakukan adalah menambahkan ini ke metode 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);
    }

Ini akan menghitung tinggi layar dan mendapatkan rasio tinggi layar dan tinggi mPreviewSize. Kemudian, lebar dan tinggi kamera dikalikan dengan rasio tinggi baru dan setel dimensi yang diukur sesuai.

masukkan deskripsi gambar di sini

Dan hal berikutnya yang Anda ketahui, Anda berakhir dengan ini: D

masukkan deskripsi gambar di sini

Ini juga bekerja dengan baik dengan kamera depan. Saya percaya ini adalah cara terbaik untuk melakukan ini. Sekarang satu-satunya hal yang tersisa untuk aplikasi saya adalah menyimpan pratinjau itu sendiri setelah mengklik "Tangkap." Tapi ya, ini dia.

Yoosuf
sumber
Ini bekerja dengan sangat baik !! tetapi tidak bekerja dengan baik di Landscape: /
Matan Dahan
kode Anda memecahkan masalah ruang hitam kosong 2 bulan saya :)
karthik kolanji
1
Ini berfungsi, tetapi dalam kasus saya .. Saya memiliki bilah tindakan di bagian atas sehingga bilah putih lebih kecil, namun setelah menambahkan kode Anda, kamera saya terlihat diperbesar 40-50%. Saat beralih ke kamera depan, saya pada dasarnya tidak bisa melihat wajah saya terlihat lurus (hanya sedikit rambut), karena zoom tinggi itu.
Makalele
@Yoosuf Saya mendapatkan layar putih ketika saya mengikuti kode Anda. Dalam onMeasure(int widthMeasureSpec, int heightMeasureSpec)metode saya selalu mendapatkan lebar = 0 dan tinggi = 0;
Kush Patel
10

Oke, jadi saya pikir tidak ada jawaban yang memadai untuk masalah peregangan pratinjau kamera umum. Atau setidaknya saya tidak menemukannya. Aplikasi saya juga mengalami sindrom peregangan ini dan saya butuh beberapa saat untuk mencari solusi dari semua jawaban pengguna di portal dan internet ini.

Saya mencoba solusi @ Hesam tetapi tidak berhasil dan membuat pratinjau kamera saya sangat terdistorsi.

Pertama saya menunjukkan kode solusi saya (bagian penting dari kode) dan kemudian saya menjelaskan mengapa saya mengambil langkah-langkah itu. Ada ruang untuk modifikasi performa.

Tata letak xml aktivitas utama:

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

Pratinjau Kamera:

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

Aktifitas utama:

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

Komentar saya:

Inti dari semua ini adalah, bahwa meskipun Anda menghitung ukuran kamera yang optimal dalam getOptimalPreviewSize()Anda hanya memilih rasio yang paling dekat untuk menyesuaikan layar Anda. Jadi, kecuali jika rasionya persis sama , pratinjau akan meregang.

Mengapa bisa meregang? Karena pratinjau kamera FrameLayout Anda disetel di layout.xml menjadi match_parent dalam lebar dan tinggi. Itulah sebabnya pratinjau akan meregang ke layar penuh.

Yang perlu dilakukan adalah menyetel lebar dan tinggi tata letak pratinjau kamera agar sesuai dengan rasio ukuran kamera yang dipilih , sehingga pratinjau mempertahankan rasio aspeknya dan tidak akan mendistorsi.

Saya mencoba menggunakan CameraPreviewkelas untuk melakukan semua kalkulasi dan perubahan tata letak, tetapi saya tidak bisa memahaminya. Saya mencoba menerapkan solusi ini , tetapi SurfaceViewtidak mengenali getChildCount ()atau getChildAt (int index). Saya pikir, akhirnya saya membuatnya berfungsi dengan referensi ke maLayoutPreview, tetapi itu berperilaku buruk dan menerapkan rasio yang ditetapkan ke seluruh aplikasi saya dan itu melakukannya setelah gambar pertama diambil. Jadi saya melepaskannya dan memindahkan modifikasi tata letak ke MainActivity.

Di CameraPreviewsaya berubah prSupportedPreviewSizesdan getOptimalPreviewSize()menjadi publik sehingga saya bisa menggunakannya di MainActivity. Kemudian saya membutuhkan dimensi tampilan (tanpa bilah navigasi / status jika ada) dan memilih ukuran kamera yang optimal . Saya mencoba mendapatkan ukuran RelativeLayout (atau FrameLayout) alih-alih ukuran tampilan, tetapi mengembalikan nilai nol. Solusi ini tidak berhasil untuk saya. Tata letak mendapatkan nilainya setelahnya onWindowFocusChanged(diperiksa di log).

Jadi saya memiliki metode untuk menghitung dimensi tata letak agar sesuai dengan rasio aspek ukuran kamera yang dipilih. Sekarang Anda hanya perlu mengatur LayoutParamstata letak pratinjau kamera Anda. Ubah lebar, tinggi, dan pusatkan di induk.

Ada dua pilihan cara menghitung dimensi pratinjau. Entah Anda ingin menyesuaikan layar dengan bilah hitam (jika windowBackground disetel ke null) di samping atau atas / bawah. Atau Anda ingin pratinjau diperbesar ke layar penuh . Saya meninggalkan komentar dengan informasi lebih lanjut di calcCamPrevDimensions().

Will Neithan
sumber
6

Hai getOptimalPreview () yang ada di sini tidak berfungsi untuk saya, jadi saya ingin membagikan versi saya:

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;
}
EstebanA
sumber
itu diubah menjadi kamera persegi, Diberikan input (1920x1080) setelah mengoptimalkan lebar dan tinggi yang diberikannya (1088x1088), perangkat pengujian saya adalah Samsung S6. Bolehkah saya tahu apa alasannya. tolong bagikan jika Anda punya ide tentang masalah ini.
MohanRaj S
2

Hanya untuk membuat utas ini lebih lengkap, saya menambahkan jawaban versi saya:

Yang ingin saya capai: Tampilan permukaan tidak boleh direntangkan, dan harus menutupi seluruh layar, Selain itu, hanya ada mode lanskap di aplikasi saya.

Larutan:

Solusinya adalah perluasan yang sangat kecil untuk solusi F1sher:

=> Langkah pertama adalah mengintegrasikan solusi F1sher.

=> Nah, mungkin akan timbul skenario pada solusi F1sher ketika surface view tidak mencakup seluruh layar, Solusinya adalah membuat surface view lebih besar dari dimensi layar sehingga mencakup keseluruhan layar, untuk itu:

    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  */
Sarthak Mittal
sumber
1

Saya menemukan apa masalahnya - dengan perubahan orientasi . Jika Anda mengubah orientasi kamera ke 90 atau 270 derajat daripada Anda perlu menukar lebar dan tinggi dari ukuran yang didukung dan semuanya akan baik-baik saja.

Juga tampilan permukaan harus terletak pada tata letak bingkai dan memiliki gravitasi pusat.

Berikut adalah contoh di 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;
    }
}

Perhatikan bahwa parameter kamera harus disetel sebagai ukuran asli (tidak ditukar), dan ukuran tampilan permukaan harus ditukar.

Alexander Danilov
sumber
1

saya mencoba semua solusi di atas tetapi tidak ada yang berhasil untuk saya. akhirnya saya memecahkannya sendiri, dan ternyata itu cukup mudah. Ada dua hal yang perlu Anda perhatikan.

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

previewSize ini harus menjadi salah satu resolusi yang didukung kamera, yang bisa didapatkan seperti di bawah ini:

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

biasanya salah satu rawSupportedSize sama dengan resolusi perangkat.

Kedua, tempatkan SurfaceView Anda di FrameLayout dan setel tinggi dan lebar tata letak permukaan dalam metode surfaceChanged seperti di atas

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

Oke, semuanya sudah selesai, semoga ini bisa membantu Anda.

SalutonMondo
sumber
1

@Hesam 's jawabannya benar, CameraPreviewakan bekerja di semua perangkat portrait, tetapi jika perangkat berada dalam modus lansekap atau dalam mode multi-window, kode ini sempurna bekerja, hanya menggantionMeasure()

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

Persyaratan saya adalah pratinjau kamera harus layar penuh dan menjaga rasio aspek. Solusi Hesam dan Yoosuf sangat bagus tetapi saya melihat masalah zoom tinggi karena suatu alasan.

Idenya sama, buatlah pusat penampung pratinjau sebagai induk dan tambah lebar atau tinggi tergantung pada rasio aspek hingga dapat menutupi seluruh layar.

Satu hal yang perlu diperhatikan adalah ukuran pratinjau dalam bentuk landscape karena kita mengatur orientasi tampilan.

camera.setDisplayOrientation (90);

Kontainer yang akan kita tambahkan tampilan SurfaceView:

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

Tambahkan pratinjau ke penampungnya dengan pusat di induk dalam aktivitas Anda.

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

Di dalam kelas 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);
}
Howard Lin
sumber
-1

Anda harus menyetel cameraView.getLayoutParams (). Height dan cameraView.getLayoutParams (). Lebar sesuai dengan rasio aspek yang Anda inginkan.

pengguna2919210
sumber
apakah ini berbeda dengan menyetel parameter tampilan kamera saya seperti ini (yang saat ini saya lakukan): parameter.setPreviewSize (optimalSize.width, optimalSize.height); mCamera.setParameters (parameter);
scientiffic
-2

Saya menyerah kalkulasi dan hanya mendapatkan ukuran tampilan di mana saya ingin pratinjau kamera ditampilkan dan mengatur ukuran pratinjau kamera yang sama (hanya membalik lebar / tinggi karena rotasi) dalam implementasi SurfaceView kustom saya:

@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
}
Martin Šuráb
sumber
Dokumen Android mengatakan: Saat menyetel ukuran pratinjau, Anda harus menggunakan nilai dari getSupportedPreviewSizes (). Jangan setel nilai arbitrer dalam metode setPreviewSize ().
G. Steigert