Android 自定义 SurfaceView 方向无法从纵向旋转到横向

Android custom SurfaceView orientation can't rotate from portrait to landscape

我是一名新 android 应用工程师。我正在尝试制作一个 vulkan 渲染后端演示,它使用 SurfaceView 作为 android Java 代码上的视图。我也使用 GLSurfaceView 制作了 gles 渲染后端演示。在应用程序代码中,我使用 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE) API 将 activity 从默认纵向设置为横向。但它在 SurfaceView 上不起作用,而在 GLSurfaceView 上起作用。

我用renderdoc抓取渲染结果,图片是横向布局的(和gles后端一样的布局)。我怀疑 activity 或 window 上的某些设置有问题,但无法找出根本原因。有人可以帮助解决问题吗?

这是 Java 源代码。

package com.example.graphicsdebugger;

import androidx.appcompat.app.AppCompatActivity;

import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.opengl.GLSurfaceView;
import android.os.Bundle;
import android.view.GestureDetector;
import android.view.MotionEvent;

//////////////////////////////////////////////Jerome///////////////////////////////////////////////
import android.view.SurfaceHolder;
import android.view.View;
import android.view.WindowManager;
import android.util.Log;
import android.widget.CheckBox;
import android.widget.SeekBar;
import android.widget.TextView;

///////////////////////////////////////////////////////////////////////////////////////////////////

/**
 * An example full-screen activity that shows and hides the system UI (i.e.
 * status bar and navigation/system bar) with user interaction.
 */
public class DemoFullscreenActivity extends AppCompatActivity /*implements Serializable */{
    //////////////////////////////////////////////Jerome///////////////////////////////////////////
    private static final String TAG = "9527";
    private static final int UNKNOWN_RENDERER = -1;
    private static final int FORWARD_RENDERER = 0;
    private static final int DEFERRED_RENDERER = 1;

    private static final int SET_UNKNOWN = -1;
    private static final int SET_MODEL = 0;
    private static final int SET_SSAO = 1;
    private static final int SET_SSSSS = 2;
    private static final int SET_SSAO_RADIUS = 3;
    private static final int SET_SSAO_BIAS = 4;
    private static final int SET_SSAO_COEFFICIENT = 5;
    private static final int SET_SSAO_KERNEL_SIZE = 6;
    private static final int SET_BACKEND = 7;

    private boolean mBackend = false;
    private GLSurfaceView mGLSurfaceView;
    private VKSurfaceView mVKSurfaceView;
    protected static final float FLIP_DISTANCE = 50;
    GestureDetector mDetector;
    DemoRenderer mRender;
    ///////////////////////////////////////////////////////////////////////////////////////////////
    private void setSSAORadiusSeekBar() {
        SeekBar seekBar = (SeekBar) findViewById(R.id.seekBarRadius);
        // set default value
        seekBar.setProgress(5);
        TextView textView = (TextView) findViewById(R.id.textRadius);
        seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
            @Override
            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
                textView.setText("Radius:" + Integer.toString(progress));
                mRender.SetInt(SET_SSAO_RADIUS, progress);
            }

            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {}
            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {}
        });
    }

    private void setSSAOBiasSeekBar() {
        SeekBar seekBar = (SeekBar) findViewById(R.id.seekBarBias);
        // set default value
        seekBar.setProgress(5);
        TextView textView = (TextView) findViewById(R.id.textBias);
        seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
            @Override
            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
                textView.setText("Bias:" + Integer.toString(progress));
                mRender.SetInt(SET_SSAO_BIAS, progress);
            }

            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {}
            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {}
        });
    }

    private void setSSAOCoefficientSeekBar() {
        SeekBar seekBar = (SeekBar) findViewById(R.id.seekBarCoefficient);
        // set default value
        seekBar.setProgress(150);
        TextView textView = (TextView) findViewById(R.id.textCoefficient);
        seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
            @Override
            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
                textView.setText("Coefficient:" + Integer.toString(progress));
                mRender.SetInt(SET_SSAO_COEFFICIENT, progress);
            }

            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {}
            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {}
        });
    }

    private void setSSAOKernelSizeSeekBar() {
        SeekBar seekBar = (SeekBar) findViewById(R.id.seekBarKernelSize);
        // set default value
        seekBar.setProgress(2);
        TextView textView = (TextView) findViewById(R.id.textKernelSize);
        seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
            @Override
            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
                textView.setText("Kernel Size:" + Integer.toString(progress * 8));
                mRender.SetInt(SET_SSAO_KERNEL_SIZE, progress);
            }

            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {}
            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {}
        });
    }

    private void setSSAOCheckBox() {
        CheckBox ssao = (CheckBox) findViewById(R.id.ssao_check_box);
        ssao.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                boolean checked = ((CheckBox)v).isChecked();
                mRender.SetSSAO(checked);
            }
        });
    }

    private void setSSSSSCheckBox() {
        CheckBox sssss = (CheckBox) findViewById(R.id.sssss_check_box);
        sssss.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                boolean checked = ((CheckBox)v).isChecked();
                mRender.SetSSSSS(checked);
            }
        });
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_demo_fullscreen);

        //////////////////////////////////////////////Jerome///////////////////////////////////////
        // 设置横屏
        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
        // 隐藏导航栏
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
                WindowManager.LayoutParams.FLAG_FULLSCREEN);
        // 隐藏应用标题栏
        getSupportActionBar().hide();

        setSSAOCheckBox();
        setSSSSSCheckBox();
        setSSAORadiusSeekBar();
        setSSAOBiasSeekBar();
        setSSAOCoefficientSeekBar();
        setSSAOKernelSizeSeekBar();
        rendererDraw();
        ///////////////////////////////////////////////////////////////////////////////////////////
    }

    @Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
        setIntent(intent);
    }

    //////////////////////////////////////////////Jerome///////////////////////////////////////////
    private void rendererDraw() {

        Bundle bundle = getIntent().getExtras();
        if (bundle == null) return;

        if (bundle.containsKey("Backend")) {
            mBackend = bundle.getBoolean("Backend");
        }

        if (mBackend) { // Vulkan
            Log.d("9527", "init VKSurfaceView");
            mVKSurfaceView = (VKSurfaceView)findViewById(R.id.vk_surface_view);
            SurfaceHolder holder = mVKSurfaceView.getHolder();
            mRender = new DemoRenderer(this, holder.getSurface());
            mVKSurfaceView.setRenderer(mRender);
        } else { // GLES
            Log.d("9527", "init GLSurfaceView");
            mGLSurfaceView = (GLSurfaceView)findViewById(R.id.gl_surface_view);
            mGLSurfaceView.setEGLContextClientVersion(2);
            SurfaceHolder holder = mGLSurfaceView.getHolder();
            mRender = new DemoRenderer(this, holder.getSurface());
            mGLSurfaceView.setRenderer(mRender);
            mGLSurfaceView.getAlpha();
        }

        mRender.SetInt(SET_SSAO_RADIUS, 5);
        mRender.SetInt(SET_SSAO_BIAS, 5);
        mRender.SetInt(SET_SSAO_COEFFICIENT, 150);
        mRender.SetInt(SET_SSAO_KERNEL_SIZE, 2);

        int renderer = UNKNOWN_RENDERER;
        if (bundle.containsKey("Renderer")) {
            renderer = bundle.getInt("Renderer");
            mRender.SetRenderer(renderer);
        }

        if (bundle.containsKey("Backend")) {
            mRender.SetInt(SET_BACKEND, mBackend ? 1 : 0);
        }

        if (renderer != DEFERRED_RENDERER) {
            CheckBox ssao = (CheckBox) findViewById(R.id.ssao_check_box);
            ssao.setEnabled(false);
        } else if (bundle.containsKey("SSAO")) {
            boolean isChecked = bundle.getBoolean("SSAO");
            mRender.SetSSAO(isChecked);
            CheckBox ssao = (CheckBox) findViewById(R.id.ssao_check_box);
            ssao.setChecked(isChecked);
        }

        if (renderer != DEFERRED_RENDERER) {
            CheckBox sssss = (CheckBox) findViewById(R.id.sssss_check_box);
            sssss.setEnabled(false);
        } else if (bundle.containsKey("SSSSS")) {
            mRender.SetSSSSS(bundle.getBoolean("SSSSS"));
        }
        if (bundle.containsKey("Model")) {
            mRender.SetModel(bundle.getInt("Model"));
        }

        mDetector = new GestureDetector(this, new GestureDetector.OnGestureListener() {

            @Override
            public boolean onSingleTapUp(MotionEvent e) {
                // TODO Auto-generated method stub
                return false;
            }

            @Override
            public void onShowPress(MotionEvent e) {
                // TODO Auto-generated method stub
            }

            @Override
            public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
                // TODO Auto-generated method stub
                return false;
            }

            @Override
            public void onLongPress(MotionEvent e) {
                // TODO Auto-generated method stub
            }

            /**
             *
             * e1 The first down motion event that started the fling. e2 The
             * move motion event that triggered the current onFling.
             */
            @Override
            public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
                if (e1.getX() - e2.getX() > FLIP_DISTANCE) {
                    Log.d(TAG, "Slide left...");
                    return true;
                }
                if (e2.getX() - e1.getX() > FLIP_DISTANCE) {
                    Log.d(TAG, "Slide right...");
                    return true;
                }
                if (e1.getY() - e2.getY() > FLIP_DISTANCE) {
                    Log.d(TAG, "Slide up...");
                    return true;
                }
                if (e2.getY() - e1.getY() > FLIP_DISTANCE) {
                    Log.d(TAG, "Slide down...");
                    return true;
                }

                Log.d(TAG, e2.getX() + " " + e2.getY());

                return false;
            }

            @Override
            public boolean onDown(MotionEvent e) {
                // TODO Auto-generated method stub
                return false;
            }
        });
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        //Log.d(TAG, "===> MainActivity call dispatchTouchEvent()");
        //Log.d(TAG, "===> super.dispatchTouchEvent() default return true");
        //Log.d(TAG, "--------------------------------------------------");
        return super.dispatchTouchEvent(ev);
    }

    @Override
    public void onUserInteraction() {
        //Log.d(TAG, "===> MainActivity call onUserInteraction()");
        //Log.d(TAG, "--------------------------------------------------");
        super.onUserInteraction();
    }

    float Finger_0_DownX = 0;
    float Finger_0_DownY = 0;
    float Finger_1_DownX = 0;
    float Finger_1_DownY = 0;
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                int mActivePointerId =0;
                int idx = event.findPointerIndex(mActivePointerId);
                if (idx >=0 ) {
                    Finger_0_DownX = event.getX(idx);
                    Finger_0_DownY = event.getY(idx);
                    mRender.setFingerDown(0, Finger_0_DownX, Finger_0_DownY);
                }
                break;
            case MotionEvent.ACTION_UP:
                Finger_0_DownX = 0.0f;
                Finger_0_DownY = 0.0f;
                mRender.setFingerUp(0);
                break;
            default:
                break;
        }

        switch(event.getActionMasked()) {
            case MotionEvent.ACTION_POINTER_DOWN:
                int mActivePointerId = 1;
                int idx = event.findPointerIndex(mActivePointerId);
                if (idx >=0 ) {
                    Finger_1_DownX = event.getX(idx);
                    Finger_1_DownY = event.getY(idx);
                    mRender.setFingerDown(1, Finger_1_DownX, Finger_1_DownY);
                }
                break;
            case MotionEvent.ACTION_POINTER_UP:
                Finger_1_DownX = 0.0f;
                Finger_1_DownY = 0.0f;
                mRender.setFingerUp(1);
                break;
            default:
                break;
        }

        if (Finger_0_DownX != 0.0f && Finger_0_DownY != 0.0f) {
            int mActivePointerId =0;
            int idx = event.findPointerIndex(mActivePointerId);
            if (idx >=0 ) {
                mRender.setFingerPosition(0, event.getX(idx), event.getY(idx));
            }
        }

        if (Finger_1_DownX != 0.0f && Finger_1_DownY != 0.0f) {
            int mActivePointerId = 1;
            int idx = event.findPointerIndex(mActivePointerId);
            if (idx >=0 ) {
                mRender.setFingerPosition(1, event.getX(idx), event.getY(idx));
            }
        }
        return mDetector.onTouchEvent(event);
    }
    ///////////////////////////////////////////////////////////////////////////////////////////////
}

这是 activity 布局。

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/frameLayout3"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="?attr/fullscreenBackgroundColor"
    android:theme="@style/ThemeOverlay.GraphicsDebugger.FullscreenContainer"
    tools:context=".DemoFullscreenActivity">

    <android.opengl.GLSurfaceView
        android:id="@+id/gl_surface_view"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.0"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.0" />

    <com.example.graphicsdebugger.VKSurfaceView
        android:id="@+id/vk_surface_view"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.0"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.0" />

    <CheckBox
        android:id="@+id/ssao_check_box"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="24dp"
        android:text="SSAO"
        app:layout_constraintBottom_toTopOf="@+id/sssss_check_box"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.019"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.728" />

    <CheckBox
        android:id="@+id/sssss_check_box"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="272dp"
        android:text="SSSSS"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.019"
        app:layout_constraintStart_toStartOf="parent" />

    <TextView
        android:id="@+id/textRadius"
        android:layout_width="72dp"
        android:layout_height="35dp"
        android:text="Radius:5"
        app:layout_constraintBottom_toBottomOf="@+id/gl_surface_view"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.175"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.101" />

    <TextView
        android:id="@+id/textCoefficient"
        android:layout_width="111dp"
        android:layout_height="35dp"
        android:text="Coefficient:150"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.419"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.101" />

    <TextView
        android:id="@+id/textBias"
        android:layout_width="72dp"
        android:layout_height="35dp"
        android:text="Bias:5"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.68"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.101" />

    <TextView
        android:id="@+id/textKernelSize"
        android:layout_width="111dp"
        android:layout_height="35dp"
        android:text="Kernel Size:16"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.925"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.101" />

    <SeekBar
        android:id="@+id/seekBarKernelSize"
        android:layout_width="146dp"
        android:layout_height="28dp"
        android:max="8"
        android:min="1"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.935"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.043" />

    <SeekBar
        android:id="@+id/seekBarCoefficient"
        android:layout_width="146dp"
        android:layout_height="28dp"
        android:max="300"
        android:min="100"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.408"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.041" />

    <SeekBar
        android:id="@+id/seekBarRadius"
        android:layout_width="146dp"
        android:layout_height="28dp"
        android:max="100"
        android:min="0"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="@+id/gl_surface_view"
        app:layout_constraintHorizontal_bias="0.141"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.04" />

    <SeekBar
        android:id="@+id/seekBarBias"
        android:layout_width="146dp"
        android:layout_height="28dp"
        android:max="30"
        android:min="0"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="@+id/gl_surface_view"
        app:layout_constraintHorizontal_bias="0.675"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.041" />

</androidx.constraintlayout.widget.ConstraintLayout>

这是AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.graphicsdebugger">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.GraphicsDebugger">
        <activity android:name=".RendererSettingsActivity"></activity>
        <activity
            android:name=".DemoFullscreenActivity"
            android:configChanges="orientation|keyboardHidden|screenSize"
            android:label="@string/title_activity_demo_fullscreen"
            android:theme="@style/Theme.GraphicsDebugger.Fullscreen"
            android:launchMode="singleInstance"/>
        <activity android:name=".RenderingEngineActivity" />
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

在 AndroidManifest.xml 的 <activity> 标签中,添加行 android:screenOrientation="portrait".

参考https://www.geeksforgeeks.org/designing-the-landscape-and-portrait-mode-of-application-in-android/添加横向布局。布局文件将位于 res/layout-land/ 目录。布局 xml 文件与 res/layout 目录中的文件同名。该文件名将在 setContentView(R.layout.activity_layout_name) 中使用。 activity 的 landsacpe/portrait 布局选择在 AndroidManifest.xml 参数 android:screenOrientation="value" 中设置,作为实验值,而值要么是横向的,要么是纵向的。当我们在AndroidManifest.xml中选择纵向模式时,对应的activity会在res/layout目录下找到布局文件,并设置ContentView。选择景观时,它将在res/layout-land目录中找到布局文件。所以在res/layout和res/layout-land目录下,layout文件名应该是一样的,因为我们在activity里调用了setContentViewAPI,参数应该都适合res/layout 和 res/layout-land 目录中的文件。

我需要在横向布局中设置我的 activity,因此在 AndroidManifest.xml 对应的 activity 中,我设置 android:screenOrientation="横向"。

然后 Activity 代码将 setContentView 使用 res/layout-land 目录中的布局文件。在横向布局 xml 文件中,我有两个视图,一个是 GLSurfaceView,另一个是 VKSurfaceView。