相机预览布局仅在短暂延迟后才正确

时间:2011-08-22 17:08:45

标签: android android-layout

我有一个使用自定义CameraPreview ViewGroup实现的自定义相机Activity。在3.0之前的android一切正常,但从3.0相机预览开始看起来如图1所示。如果我添加Thread.sleep或做一些调试和延迟onLayout方法一点点然后布局按预期显示(图2)。我在这里有点不知所措......

CameraPreview类:

public class CameraPreview extends ViewGroup implements SurfaceHolder.Callback {
    private final String TAG = "CameraPreview";

    private boolean mPreviewRunning = false;

    private SurfaceView mSurfaceView;
    private SurfaceHolder mHolder;
    private Size mPreviewSize;
    private List<Size> mSupportedPreviewSizes;
    private Camera mCamera;

    public boolean IsPreviewRunning() {
        return mPreviewRunning;
    }

    public CameraPreview(Context context) {
        this(context, null, 0);
    }

    public CameraPreview(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public CameraPreview(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);

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

    @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 && mCamera != null) {
            mSupportedPreviewSizes = mCamera.getParameters().getSupportedPreviewSizes();
        }
        if (mSupportedPreviewSizes != null) {
            mPreviewSize = getOptimalPreviewSize(mSupportedPreviewSizes, width, height);            
        }
    }

    @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;
            }
            if (previewWidth == 0) {
                previewWidth = 1;
            }
            if (previewHeight == 0) {
                previewHeight = 1;
            }

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

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

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        // Surface will be destroyed when we return, so stop the preview.
        stop();
    }

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

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
        if (mCamera != null) {
            // Now that the size is known, set up the camera parameters and begin the preview.
            Camera.Parameters parameters = mCamera.getParameters();
            if (mPreviewSize != null) {
                parameters.setPreviewSize(mPreviewSize.width, mPreviewSize.height);
            }
            requestLayout();

            mCamera.setParameters(parameters);
            mCamera.startPreview();
            mPreviewRunning = true;
        }
    }

    public void stop() {
        if (mCamera != null) {
            mCamera.stopPreview();
            mPreviewRunning = false;
            mCamera = null;
        }
    }
}

使用此类的布局:

<LinearLayout
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="fill_parent"
  android:layout_height="fill_parent"
  android:orientation="horizontal"
  >
  <FrameLayout android:layout_width="fill_parent"
  android:layout_height="fill_parent"
  android:layout_weight="2"
  android:padding="5dip"  
  >
      <com.commonlib.controls.CameraPreview
      android:layout_width="fill_parent"
      android:layout_height="fill_parent"
      android:layout_weight="1"
      android:layout_gravity="center"
      android:id="@+id/surface_camera"
      />
      <ImageView android:id="@+id/target"
        android:layout_gravity="center"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/target"
        >
        </ImageView>
  </FrameLayout>
  <LinearLayout android:layout_width="wrap_content"
  android:layout_height="fill_parent"
  android:paddingLeft="7dip"
  android:orientation="vertical">
    <ImageButton
      android:layout_width="50dip"
      android:layout_height="0dip"
      android:layout_weight="1"
      android:id="@+id/cancel"
      android:src="@android:drawable/ic_menu_revert">
      </ImageButton>
    <ImageButton
      android:layout_width="50dip"
      android:layout_height="0dip"
      android:layout_weight="1"
      android:id="@+id/ok"
      android:src="@android:drawable/ic_menu_camera">
      </ImageButton>      
  </LinearLayout>
</LinearLayout>

Picture 1 Picture 2

更新 我发现只有在调用Activity处于纵向模式时才会发生这种情况。

更新2 看起来这种行为是由屏幕旋转动画引起的。相机活动使用横向模式。如果调用活动处于纵向模式 - 它会导致屏幕旋转动画,这会以某种方式混淆相机预览控制布局。我正在研究如何在这种情况下禁用屏幕旋转动画或如何重新布局相机活动。

1 个答案:

答案 0 :(得分:0)

所以怀疑,这一切都是由旋转动画引起的。在旋转时,测量宽度和高度测量值交换了它们的位置(因为从纵向到横向),因此始终考虑最小的余量。我这样解决了:

@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.
    int width = resolveSize(getSuggestedMinimumWidth(), widthMeasureSpec);
    int height = resolveSize(getSuggestedMinimumHeight(), heightMeasureSpec);

    // we only allow landscape, if height > width - it means that we are in rotation animation. Swap width with height
    if (height > width) {
        width += height;
        height = width - height;
        width = width - height;         
    }

    setMeasuredDimension(width, height);
    Log.v("Camera", width + "x" + height);

    if (mSupportedPreviewSizes == null && mCamera != null) {
        mSupportedPreviewSizes = mCamera.getParameters().getSupportedPreviewSizes();
    }
    if (mSupportedPreviewSizes != null) {
        mPreviewSize = getOptimalPreviewSize(mSupportedPreviewSizes, width, height);            
    }
}