简体   繁体   中英

Android - Handling Screen Orientation with SurfaceView and Camera2 API

I have a question about how the screen orientation in Android is handled when we use the Camera2 API in combination with SurfaceView. I was playing with the official HdrViewfinder google sample code at https://github.com/googlesamples/android-HdrViewfinder a little bit. In that project, they use a class called FixedAspectSurfaceView which is an extension of SurfaceView . But that project displays the camera preview correctly only when the screenOrientation of the activity (AndroidManifest) is in landscape mode, not in portrait mode. Setting the attribute to portrait swaps the preview in a weird way. How could I modify that code to be also able to see the camera preview correctly in portrait mode ?

So, the FixedAspectSurfaceView.java class looks like this:

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

I changed the screenOrientation attribute in the AndroidManifest file to portrait . I changed also the activity_main.xml layout file:

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

When I leave the screenOrientation attribute in the manifest file in landscape , the camera preview is fine but the application opens of course "in landscape" mode. When I set the screenOrientation to portrait, then the camera preview "swaps the view to left". I did not know how to describe it but it is definitely weird. Why is the preview when I switch to portrait ? And how could I modify the project so that it also can be used in portrait mode?

For FixedAspectSurfaceView, you should just be able to set its aspect ratio to the inverse of the aspect ratio you use in landscape. So if in landscape you set it to 4/3, set it to 3/4 for portrait layout.

The camera-to-SurfaceView path should handle all the rotations for you, you just need to keep the shape of the SurfaceView correct.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM