如何使用 Android API 将相机预览大小设置为全屏?

Posted

技术标签:

【中文标题】如何使用 Android API 将相机预览大小设置为全屏?【英文标题】:How can I set the camera preview size to fullscreen with Android API? 【发布时间】:2011-11-29 14:04:50 【问题描述】:

我对 android 开发非常陌生,我正在尝试设置一个简单的相机应用程序。到目前为止,我有一个可以正常工作的相机应用程序,它在菜单中有“切换相机”和“拍照”按钮,它们工作正常。

我遇到的唯一问题是我想弄清楚如何让显示器全屏显示。目前,摄像头只出现在屏幕的正中间,仅占屏幕的 1/4 左右。

MainActivity 代码

package assist.core;

import android.app.Activity;
import android.app.AlertDialog;
import android.hardware.Camera;
import android.hardware.Camera.PictureCallback;
import android.hardware.Camera.ShutterCallback;
import android.hardware.Camera.CameraInfo;
import android.os.Bundle;
import android.view.Window;
import android.view.WindowManager;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.util.Log;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

public class MainActivity extends Activity 

    private final String TAG = "MainActivity";
    private Preview mPreview;
    Camera mCamera;
    int numberOfCameras;
    int cameraCurrentlyLocked;

    //The first rear facing camera
    int defaultCameraId;

    @Override
    protected void onCreate(Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);

        //Hide the window title.
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);

        //Create a RelativeLayout container that will hold a SurfaceView,
        //and set it as the content of our activity.
        mPreview = new Preview(this);
        setContentView(mPreview);

        //Find the total number of cameras available
        numberOfCameras = Camera.getNumberOfCameras();

        //Find the ID of the default camera
        CameraInfo cameraInfo = new CameraInfo();
        for (int i = 0; i < numberOfCameras; i++) 
            Camera.getCameraInfo(i, cameraInfo);
            if(cameraInfo.facing == CameraInfo.CAMERA_FACING_BACK) 
                defaultCameraId = i;
            
        
    

    @Override
    protected void onResume() 
        super.onResume();

        //Open the default i.e. the first rear facing camera.
        mCamera = Camera.open();
        cameraCurrentlyLocked = defaultCameraId;
        mPreview.setCamera(mCamera);
    

    @Override
    protected void onPause() 
        super.onPause();

        //Because the Camera object is a shared resource, it's very
        //Important to release it when the activity is paused.
        if (mCamera != null) 
            mPreview.setCamera(null);
            mCamera.release();
            mCamera = null;
        
    

    @Override
    public boolean onCreateOptionsMenu(Menu menu) 
        //Inflate our menu which can gather user input for switching camera
        MenuInflater inflater = getMenuInflater();
        inflater.inflate(R.menu.camera_menu, menu);
        return true;
    

    @Override
    public boolean onOptionsItemSelected(MenuItem item) 
        //Handle item selection
        switch (item.getItemId()) 
            case R.id.switchCam:
                //Check for availability of multiple cameras
                if (numberOfCameras == 1) 
                    AlertDialog.Builder builder = new AlertDialog.Builder(this);
                    builder.setMessage(this.getString(R.string.camera_alert)).setNeutralButton("Close", null);
                    AlertDialog alert = builder.create();
                    alert.show();
                    return true;
                

                //OK, we have multiple cameras.
                //Release this camera -> cameraCurrentlyLocked
                if (mCamera != null) 
                    mCamera.stopPreview();
                    mPreview.setCamera(null);
                    mCamera.release();
                    mCamera = null;
                

                //Acquire the next camera and request Preview to reconfigure parameters.
                mCamera = Camera.open((cameraCurrentlyLocked + 1) % numberOfCameras);
                cameraCurrentlyLocked = (cameraCurrentlyLocked + 1) % numberOfCameras;
                mPreview.switchCamera(mCamera);

                //Start the preview
                mCamera.startPreview();
                return true;

            case R.id.takePicture:
                mCamera.takePicture(shutterCallback, rawCallback, jpegCallback);
                return true;

            default:
                return super.onOptionsItemSelected(item);
        
    

    /**
     * Called when shutter is opened
     */
    ShutterCallback shutterCallback = new ShutterCallback()  
        public void onShutter() 
        
    ;

    /**
     * Handles data for raw picture when the picture is taken
     */
    PictureCallback rawCallback = new PictureCallback()  
        public void onPictureTaken(byte[] data, Camera camera) 
        
    ;

    /**
     * Handles data for jpeg picture when the picture is taken
     */
    PictureCallback jpegCallback = new PictureCallback()  
        public void onPictureTaken(byte[] data, Camera camera) 
            FileOutputStream outStream = null;
            try 
                // Write to SD Card
                outStream = new FileOutputStream(String.format("/sdcard/%d.jpg",
                System.currentTimeMillis()));
                outStream.write(data);
                outStream.close();
             
            catch (FileNotFoundException e)  
                Log.e(TAG, "IOException caused by PictureCallback()", e);
             
            catch (IOException e) 
                Log.e(TAG, "IOException caused by PictureCallback()", e);
             
        
    ;

预览课程代码

package assist.core;

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

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

/**
 *
 * @author cmetrolis
 */
class Preview extends ViewGroup implements SurfaceHolder.Callback 

    private final String TAG = "Preview";
    SurfaceView mSurfaceView;
    SurfaceHolder mHolder;
    Size mPreviewSize;
    List<Size> mSupportedPreviewSizes;
    Camera mCamera;

    Preview(Context context) 
        super(context);

        mSurfaceView = new SurfaceView(context);
        addView(mSurfaceView);

        //Install a SurfaceHolder.Callback so we get notified when the
        //underlying surface is created and destroyed.
        mHolder = mSurfaceView.getHolder(); 
        mHolder.addCallback(this); 
        mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
    

    public void setCamera(Camera camera) 
        mCamera = camera;
        if(mCamera != null) 
            mSupportedPreviewSizes = mCamera.getParameters().getSupportedPreviewSizes();
            requestLayout();
        
    

    public void switchCamera(Camera camera) 
       setCamera(camera);
       try 
           camera.setPreviewDisplay(mHolder);
        
       catch (IOException exception) 
           Log.e(TAG, "IOException caused by setPreviewDisplay()", exception);
       
       Camera.Parameters parameters = camera.getParameters();
       parameters.setPreviewSize(mPreviewSize.width, mPreviewSize.height);
       requestLayout();

       camera.setParameters(parameters);
    

    /**
     * Called to determine the size requirements for this view and all of its children.
     * @param widthMeasureSpec
     * @param heightMeasureSpec 
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) 
        //We purposely disregard child measurements because act as a
        //Wrapper to a SurfaceView that centers the camera preview instead of stretching it.
        final int width = resolveSize(getSuggestedMinimumWidth(), widthMeasureSpec);
        final int height = resolveSize(getSuggestedMinimumHeight(), heightMeasureSpec);
        setMeasuredDimension(width, height);

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

    /**
     * Called when this view should assign a size and position to all of its children.
     * @param changed
     * @param l
     * @param t
     * @param r
     * @param b 
     */
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) 
        if(changed && getChildCount() > 0) 
            final View child = getChildAt(0);

            final int width = r - l;
            final int height = b - t;

            int previewWidth = width;
            int previewHeight = height;
            if(mPreviewSize != null) 
                previewWidth = mPreviewSize.width;
                previewHeight = mPreviewSize.height;
            

            // Center the child SurfaceView within the parent.
            if(width * previewHeight > height * previewWidth) 
                final int scaledChildWidth = previewWidth * height / previewHeight;
                child.layout((width - scaledChildWidth) / 2, 0, (width + scaledChildWidth) / 2, height);
             
            else 
                final int scaledChildHeight = previewHeight * width / previewWidth;
                child.layout(0, (height - scaledChildHeight) / 2, width, (height + scaledChildHeight) / 2);
            
        
     

    /**
     * This is called immediately after the surface is first created
     * @param holder 
     */
    public void surfaceCreated(SurfaceHolder holder)  
        try 
            if(mCamera != null) 
                mCamera.setPreviewDisplay(holder); 
            
         
        catch (IOException e) 
            Log.e(TAG, "IOException caused by setPreviewDisplay()", e);
        
    

    /**
     * This is called immediately before a surface is being destroyed
     * @param holder 
     */
    public void surfaceDestroyed(SurfaceHolder holder) 
        //Surface will be destroyed when we return, so stop the preview.
        if(mCamera != null) 
            mCamera.stopPreview();
            mCamera.release();
        
    

    /**
     * This is called immediately after any structural changes (format or size) have been made to the surface
     * @param holder
     * @param format
     * @param w
     * @param h 
     */
    public void surfaceChanged(SurfaceHolder holder, int format, int w, int h)  
        Camera.Parameters parameters = mCamera.getParameters();
        parameters.setPreviewSize(mPreviewSize.width, mPreviewSize.height);
        mCamera.setDisplayOrientation(90);
        requestLayout();

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

    /**
     * Returns the best preview size
     * @param sizes
     * @param w
     * @param h
     * @return Size
     */
    private Size getOptimalPreviewSize(List<Size> sizes, int w, int h) 
        final double ASPECT_TOLERANCE = 0.1;
        double targetRatio = (double) w / h;
        if (sizes == null) return null;

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

        int targetHeight = h;

        // Try to find an size match aspect ratio and size
        for(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);
            
        

        // Cannot find the one match the aspect ratio, ignore the requirement
        if(optimalSize == null) 
            minDiff = Double.MAX_VALUE;
            for (Size size : sizes) 
                if(Math.abs(size.height - targetHeight) < minDiff) 
                    optimalSize = size;
                    minDiff = Math.abs(size.height - targetHeight);
                
            
        

        return optimalSize;
    

camer_menu.xml 代码

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

    <item android:id="@+id/switchCam"
          android:title="@string/switch_cam" />

    <item android:id="@+id/takePicture"
          android:title="@string/take_picture" 
          android:onClick="snapPicture" 
          android:layout_gravity="center" />
</menu>

更新

我尝试将 Preview 构造函数中的代码更改为以下代码。

ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);
this.setLayoutParams(lp);

mSurfaceView = new SurfaceView(context);
mSurfaceView.setLayoutParams(lp);
addView(mSurfaceView);

这没有崩溃,但也没有让相机全屏显示。

【问题讨论】:

嗨,我想知道你是否也有这个问题:***.com/questions/16727836/… ? 【参考方案1】:

我通过从 Preview 类中删除以下代码来修复它,

if(mPreviewSize != null) 
    previewWidth = mPreviewSize.width;
    previewHeight = mPreviewSize.height;

【讨论】:

【参考方案2】:

由于您将 setContentView 与派生自 ViewGroup 的自定义 Preview 类一起使用,只需传递一个 ViewGroup.LayoutParams 告诉它填充它的父级。

类似这样的:

ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);
Preview.setLayoutParams(lp);

【讨论】:

我的例子不应该是“this.setLayoutParams(lp)”吗? 是的,您可以在 Preview 类的构造函数内部进行,也可以在 MainActivity 的外部进行,在实例化它和调用 setContentView 之间进行 我尝试将其放入 Preview 构造函数并将“Preview”更改为“this”。它编译并运行而不会崩溃,但它仍然不是全屏。我想知道我是否需要对“getOptimalPreviewSize”函数做一些事情才能让它工作。 我刚刚再次查看了代码,您必须对 SurfaceView 执行相同的操作。 Preview 是容器(因为它派生自 ViewGroup),SurfaceView 是实际的预览。 这仍然没有解决它。我在这里用我尝试过的新代码更新了我的问题。

以上是关于如何使用 Android API 将相机预览大小设置为全屏?的主要内容,如果未能解决你的问题,请参考以下文章

android相机预览错误的纵横比

使用 OpenGL 2.0 API 在 GLSurfaceView 上将 Android 相机预览旋转 90 度

Android:如何通过回调显示相机预览?

Android 相机预览帧时间戳

Android Camera2 预览输出大小

android 相机 怎么改变相机预览界面大小