首页 > 解决方案 > Android - 使用 SurfaceView 和 Camera2 API 处理屏幕方向

问题描述

我有一个关于当我们将 Camera2 API 与 SurfaceView 结合使用时如何处理 Android 中的屏幕方向的问题。我在https://github.com/googlesamples/android-HdrViewfinder上玩了一下官方的HdrViewfindergoogle 示例代码。在那个项目中,他们使用了一个名为的类,它是. 但是该项目仅在活动的 screenOrientation (AndroidManifest) 处于横向模式而不是纵向模式时才能正确显示相机预览。将属性设置为纵向会以一种奇怪的方式交换预览。我如何修改该代码才能在纵向模式下正确查看相机预览?FixedAspectSurfaceViewSurfaceView

所以,这个FixedAspectSurfaceView.java类看起来像这样:

public class FixedAspectSurfaceView extends SurfaceView {

    /**
     * Desired width/height ratio
     */
    private float mAspectRatio;

    private GestureDetector mGestureDetector;

    public FixedAspectSurfaceView(Context context, AttributeSet attrs) {
        super(context, attrs);

        // Get initial aspect ratio from custom attributes
        TypedArray a =
                context.getTheme().obtainStyledAttributes(attrs,
                        R.styleable.FixedAspectSurfaceView, 0, 0);
        setAspectRatio(a.getFloat(
                R.styleable.FixedAspectSurfaceView_aspectRatio, 1.f));
        a.recycle();
    }

    /**
     * Set the desired aspect ratio for this view.
     *
     * @param aspect the desired width/height ratio in the current UI orientation. Must be a
     *               positive value.
     */
    public void setAspectRatio(float aspect) {
        if (aspect <= 0) {
            throw new IllegalArgumentException("Aspect ratio must be positive");
        }
        mAspectRatio = aspect;
        requestLayout();
    }

    /**
     * Set a gesture listener to listen for touch events
     */
    public void setGestureListener(Context context, GestureDetector.OnGestureListener listener) {
        if (listener == null) {
            mGestureDetector = null;
        } else {
            mGestureDetector = new GestureDetector(context, listener);
        }
    }

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

        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int width = MeasureSpec.getSize(widthMeasureSpec);
        int height = MeasureSpec.getSize(heightMeasureSpec);

        // General goal: Adjust dimensions to maintain the requested aspect ratio as much
        // as possible. Depending on the measure specs handed down, this may not be possible

        // Only set one of these to true
        boolean scaleWidth = false;
        boolean scaleHeight = false;

        // Sort out which dimension to scale, if either can be. There are 9 combinations of
        // possible measure specs; a few cases below handle multiple combinations
        if (widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY) {
            // Can't adjust sizes at all, do nothing
        } else if (widthMode == MeasureSpec.EXACTLY) {
            // Width is fixed, heightMode either AT_MOST or UNSPECIFIED, so adjust height
            scaleHeight = true;
        } else if (heightMode == MeasureSpec.EXACTLY) {
            // Height is fixed, widthMode either AT_MOST or UNSPECIFIED, so adjust width
            scaleWidth = true;
        } else if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST) {
            // Need to fit into box <= [width, height] in size.
            // Maximize the View's area while maintaining aspect ratio
            // This means keeping one dimension as large as possible and shrinking the other
            float boxAspectRatio = width / (float) height;
            if (boxAspectRatio > mAspectRatio) {
                // Box is wider than requested aspect; pillarbox
                scaleWidth = true;
            } else {
                // Box is narrower than requested aspect; letterbox
                scaleHeight = true;
            }
        } else if (widthMode == MeasureSpec.AT_MOST) {
            // Maximize width, heightSpec is UNSPECIFIED
            scaleHeight = true;
        } else if (heightMode == MeasureSpec.AT_MOST) {
            // Maximize height, widthSpec is UNSPECIFIED
            scaleWidth = true;
        } else {
            // Both MeasureSpecs are UNSPECIFIED. This is probably a pathological layout,
            // with width == height == 0
            // but arbitrarily scale height anyway
            scaleHeight = true;
        }

        // Do the scaling
        if (scaleWidth) {
            width = (int) (height * mAspectRatio);
        } else if (scaleHeight) {
            height = (int) (width / mAspectRatio);
        }

        // Override width/height if needed for EXACTLY and AT_MOST specs
        width = View.resolveSizeAndState(width, widthMeasureSpec, 0);
        height = View.resolveSizeAndState(height, heightMeasureSpec, 0);

        // Finally set the calculated dimensions
        setMeasuredDimension(width, height);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (mGestureDetector != null) {
            return mGestureDetector.onTouchEvent(event);
        }
        return false;
    }
}

我将文件中的screenOrientation属性更改为. 我还更改了 activity_main.xml 布局文件:AndroidManifestportrait

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:custom="http://schemas.android.com/apk/res-auto">

    <com.celik.abdullah.project.utils.FixedAspectSurfaceView
        android:id="@+id/preview"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        custom:aspectRatio="0.75"/>

    <Button
        android:id="@+id/next_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom|center"
        android:text="next"/>

</FrameLayout>

当我将screenOrientation属性留在清单文件中时landscape,相机预览很好,但应用程序当然会以“横向”模式打开。当我将其设置screenOrientation为纵向时,相机预览“将视图向左交换”。我不知道如何描述它,但它绝对是奇怪的。为什么我切换到预览版portrait?我怎样才能修改项目以便它也可以在纵向模式下使用?

标签: androidmappingsurfaceviewlandscape-portraittextureview

解决方案


对于 FixedAspectSurfaceView,您应该能够将其纵横比设置为您在横向中使用的纵横比的倒数。因此,如果在横向中将其设置为 4/3,则将其设置为 3/4 以进行纵向布局。

camera-to-SurfaceView 路径应该为你处理所有的旋转,你只需要保持 SurfaceView 的形状正确。


推荐阅读