安卓:平板支持横屏和竖屏,但手机只支持竖屏?

221

我希望平板电脑能够以纵向和横向(sw600dp或更大)的方式显示,但手机仅限于纵向。我找不到任何有条件地选择方向的方法。有什么建议吗?


一种方法是不为手机设计横向布局,即不在res文件夹中使用layout-land - Ghost
13
这只会导致横向布局在横屏模式下显示。它实际上无法防止手机旋转到横屏模式。 - radley
这里有一个仅使用XML的hack解决方案,如果需要更改方向,则不会像setRequestedOrientation一样重新创建Activity:https://dev59.com/questions/A4Xca4cB1Zd3GeqPGlKN#27015879 - guness
15个回答

484
Here's a good way using 资源尺寸限定符
将此bool资源放在res/values中,例如bools.xml或其他名称(文件名不重要):
    <?xml version="1.0" encoding="utf-8"?>
    <resources>
        <bool name="portrait_only">true</bool>
    </resources>

把这个放到res/values-sw600dp和res/values-xlarge中:

    <?xml version="1.0" encoding="utf-8"?>
    <resources>
        <bool name="portrait_only">false</bool>
    </resources>

请参考这个补充答案,以获取在Android Studio中添加这些目录和文件的帮助。
然后,在您的Activities的onCreate方法中,您可以执行以下操作:
    if(getResources().getBoolean(R.bool.portrait_only)){
        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
    }

最小宽度方向超过600dp的设备,或者在Android 3.2之前的大屏幕设备(基本上是平板电脑),将像正常情况一样表现出来,根据传感器和用户锁定的旋转等。其他所有设备(基本上是手机)只能竖屏显示。


2
我需要同时使用xlargesw600dp吗?或者只使用sw600dp就可以了吗?现在可能没有运行版本低于3.2的平板电脑了。 - theblang
7
在横屏启动时,这可能会重新启动活动,请参见http://developer.android.com/reference/android/app/Activity.html#setRequestedOrientation%28int%29。 - Bondax
1
@Bondax 那个评论只适用于“如果活动当前在前台或以其他方式影响屏幕方向”,但这不可能是情况,因为我们此时在onCreate中。 - Brian Christensen
14
如果我在横屏模式下启动应用程序,它仍然会短暂地显示横屏模式。 - 最白目
3
@dan,我通过在清单文件的每个活动中添加“android:screenOrientation =“behind””来解决了这个问题。请在此处查看我的完整答案:https://dev59.com/DlgQ5IYBdhLWcg3wWCc0#46565259 - Huby
显示剩余18条评论

20

已接受答案的补充

在Android Studio中,您可以执行以下步骤来添加具有其bools.xml文件的res/values-sw600dpres/values-large目录。

values-sw600dp

首先,在导航器中从项目选项卡中选择项目(而不是Android)过滤器。

enter image description here

请右键点击 app/src/main/res 目录。选择 新建 > Android 资源目录
选择 最小屏幕宽度,然后按下 >> 按钮。

enter image description here

在最小屏幕宽度处输入600。目录名称将自动生成。点击“确定”。

enter image description here

然后右键点击新创建的values-sw600dp文件。选择新建 > 值资源文件。将名称输入为bools

values-large

添加values-large目录仅在支持Android 3.2之前(API级别13)时才是必需的。否则可以跳过此步骤。values-large目录对应于values-sw600dp。(values-xlarge对应于values-sw720dp。)

要创建values-large目录,请按照上述步骤操作,但在此情况下选择大小而非最小屏幕宽度。选择。目录名称将自动生成。

enter image description here

右键单击目录,如前所述创建bools.xml文件。

10

跟随 Ginny 的回答,我认为最可靠的方法是:

按照这里所述,在资源文件夹sw600dp中设置一个布尔值。它必须具有前缀sw,否则它将无法正常工作:

在res/values-sw600dp/dimens.xml文件中。

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <bool name="isTablet">true</bool>
</resources>

在 res/values/dimens.xml 中

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <bool name="isTablet">false</bool>
</resources>

然后创建一个方法来检索该布尔值:

public class ViewUtils {
    public static boolean isTablet(Context context){
        return context.getResources().getBoolean(R.bool.isTablet);
    }
}

同时还需要一个基础活动来扩展那些你想要具有此行为的活动:

public abstract class BaseActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        if (!ViewUtils.isTablet(this)) {
            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
        }
    }
}

所以每个活动都应该扩展BaseActivity:

public class LoginActivity extends BaseActivity //....

重要提示:即使您从BaseActivity扩展,也必须在您的AndroidManifest.xml文件中为每个Activity添加行android:configChanges="orientation|screenSize"

    <activity
        android:name=".login.LoginActivity"
        android:configChanges="orientation|screenSize">
    </activity>

9
这里是我如何实现的(灵感来源于http://androidblogger.blogspot.com/2011/08/orientation-for-both-phones-and-tablets.html):
在AndroidManifest.xml中,对于每个您想要在纵向和横向之间切换的活动(确保添加screenSize-您以前不需要这样做!),您不需要在此处设置屏幕方向。
android:configChanges="keyboardHidden|orientation|screenSize"

在每个Activity中添加方法的步骤:

public static boolean isXLargeScreen(Context context) {
    return (context.getResources().getConfiguration().screenLayout
    & Configuration.SCREENLAYOUT_SIZE_MASK)
    >= Configuration.SCREENLAYOUT_SIZE_XLARGE;
} 

and: (如果您不覆盖此方法,应用程序在更改方向时将调用onCreate())

@Override
public void onConfigurationChanged (Configuration newConfig)
{       
    super.onConfigurationChanged(newConfig);

    if (!isXLargeScreen(getApplicationContext()) ) {            
        return; //keep in portrait mode if a phone      
    }

    //I set background images for landscape and portrait here
}

在每个Activity的onCreate()方法中:
if (!isXLargeScreen(getApplicationContext())) { //set phones to portrait; 
   setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);            
}
else {
  //I set background images here depending on portrait or landscape orientation 
}

唯一我似乎无法弄清楚的是如何在从横向切换到纵向或反之后让应用程序更改布局文件。 我认为答案类似于上面链接所做的事情,但我无法使其对我起作用 - 它删除了所有我的数据。但是,如果您拥有一个足够简单的应用程序,同时具有相同的纵向和横向布局文件,则应该可以使用此方法。


2
将横向布局放入您的layout-land文件夹中。 - radley
如果您在 onConfigurationChanged 方法中没有调用 super 方法,将会抛出异常。 - RominaV

8

我遇到了提供的解决方案的问题,最终这是对我有用的:

  1. AndroidManifest.xml 中将 Activity 的方向设置为 "locked" :
<activity
    android:name="com.whatever.YourActivity"
    android:screenOrientation="locked"
    ... />

在您的Activity的OnCreate(...)方法中,使用以下代码:
@SuppressLint("SourceLockedOrientationActivity")
override fun onCreate(savedInstanceState: Bundle?) {
    if (isTablet(resources)) {
        requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR;
    } else if (resources.configuration.orientation != Configuration.ORIENTATION_PORTRAIT) {
        requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
    }
    super.onCreate(savedInstanceState)
}

请注意,您不需要在资源中使用布尔值的hacky解决方案,您可以使用DeviceProperties类中的isTablet(resources)方法,该方法除其他事项外还检查smallestScreenWidthDp >= 600

5
我最近遇到了这个需求,但不想使用任何这些解决方案。所有这些解决方案都有一个共同点,就是在程序中以编程方式调用setRequestedOrientation。问题在于它设置的方向太晚了,会导致轻微的UI故障。如果您的设备处于横向状态,当您启动应用程序时,它会加载横向屏幕然后旋转。如果您使用主题来创建闪屏效果,则会注意到背景图像的方向错误,这尤其明显。这也可能会影响最近使用的应用程序中如何显示您的应用程序,并且还会引起配置更改方面的问题,正如其他答案评论中所指出的那样。
结论:正确的方向需要在清单文件中设置,这样系统就知道了方向而无需启动您的应用程序。
以下是如何实现这一点的解决方案(需要付出相当大的努力,但您会睡得更好):
首先,在清单文件中将方向设置为占位符。
<activity
      android:screenOrientation="${screenOrientation}"
     ...
</activity>

然后我们需要添加普通/平板电脑版本,在app/build.gradle中设置该值。(如果您已经使用风格,则可以使用新的构建类型来实现)

android {
    ...
    productFlavors {
        normal {
            manifestPlaceholders = [screenOrientation: "portrait"]
        }
        tablet {
            manifestPlaceholders = [screenOrientation: "unspecified"]
        }
    }
}

现在我们需要告诉平板电脑构建版本仅适用于大型设备。这可以通过添加一个仅适用于平板电脑的清单与默认清单合并来实现。在下面的路径添加一个新的清单文件->

app
 src
  tablet
   AndroidManifest.xml

以下是此清单所需的全部内容,因为它与默认清单合并。
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="{package}">

    <supports-screens
        android:smallScreens="false"
        android:normalScreens="false"
        android:largeScreens="true"
        android:xlargeScreens="true"
        android:requiresSmallestWidthDp="600"
        />

</manifest>

最后一个技巧是我们需要通过版本代码来区分构建版本,因为Playstore不能上传具有相同代码的两个应用。我们希望确保平板电脑应用(较为严格的构建版本)具有更高的版本代码。一种简单的方法是采用基本代码,然后对于平板电脑的构建版本,版本代码为基本代码*2,普通构建版本则为基本代码*2 - 1。我使用CI来执行此操作,其中基本代码为构建号码,但很容易在您的不同口味中进行硬编码。
现在生成这两种不同口味的构建版本。
 app-normal.apk/aab (v1.0.0, version code 1)
 app-tablet.apk/aab (v1.0.0, version code 2)

如果要在平板上下载,请将它们作为多个apk/aab上传到Playstore,然后Playstore会向他们提供可以旋转的apk。如果在手机上下载,则会提供仅支持竖屏的apk。

注意:仅适用于通过Google Playstore / Amazon Kindle进行分发

进一步阅读:https://developer.android.com/google/play/publishing/multiple-apks https://developer.amazon.com/docs/fire-tablets/ft-screen-layout-and-resolution.html


产品口味去哪里了?你错过了一些上下文。 - wesley franks
1
@wesleyfranks 添加了更多背景。你可能需要阅读一下 "flavours"(口味)https://developer.android.com/studio/build/build-variants,因为答案假设你对它们有一些经验,依赖于你的应用程序当前设置可能需要进行调整。该概念是使用 gradle 在构建时配置清单,而不是在运行时配置的另一个答案。 - Stuart Campbell
这个解决方案对我有用: https://dev59.com/CbTma4cB1Zd3GeqP4EvJ#60381441 - David Elmasllari
这意味着在使用screenOrientation:'nosensor'时,您将拥有2个不同的APK,而在单个APK中执行相同操作。https://dev59.com/Ymkw5IYBdhLWcg3wzd3Z#73893655 - TwiXter
这将会锁定平板电脑为横屏模式,这可能是你想要的,但我希望在平板电脑上可以使用任何方向。问题标题:“Android:允许平板电脑使用纵向和横向模式”。 - Stuart Campbell

4

按照已接受的答案,我正在添加包含解决方案的Kotlin文件,希望能帮到某些人。

将此布尔资源放入 res / values 中,命名为bools.xml或其他名称(文件名称在此无关紧要):

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <bool name="portrait_only">true</bool>
</resources>

将此文件放置于res/values-sw600dpres/values-sw600dp-land文件夹中:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <bool name="portrait_only">false</bool>
</resources>

然后,在您的活动或片段中添加以下行:
class MyActivity : Activity() {

    @SuppressLint("SourceLockedOrientationActivity")
    override fun onCreate(savedInstanceState: Bundle?) {
        if (resources.getBoolean(R.bool.portrait_only)) {
            requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
        }
        super.onCreate(savedInstanceState)
    }

    @SuppressLint("SourceLockedOrientationActivity")
    override fun onConfigurationChanged(newConfig: Configuration) {
        if (resources.getBoolean(R.bool.portrait_only)) {
            requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
        }
        super.onConfigurationChanged(newConfig)
    }
}

1

developer.android.com中,您应该在清单文件中使用值“nosensor”声明屏幕方向:

方向不是根据物理方向传感器确定的。忽略传感器,因此显示屏不会根据用户移动设备而旋转。

旋转将是设备的“自然”行为(对于手机:竖屏/对于平板电脑:横屏)。

优点是可以在每个活动中混合不同的旋转方式,例如,如果您有一个显示数据库记录的活动,则可以将此活动方向设置为“fullUser”,而其他活动则使用“nosensor”。 “fullUser”活动将根据用户选择旋转(启用旋转:使用传感器,否则使用用户首选项)。

<activity android:name=".scanner.ScannerPropertiesActivity" 
android:configChanges="keyboardHidden|orientation|screenSize"
android:label="@string/title_activity_scanner_properties"
android:screenOrientation="${screenOrientation}"/>

${screenOrientation} 是一个 ManifestPlaceHolder,因此我可以使用它轻松地在所有方向行为之间切换我想要测试的所有活动:

//module's build.gradle
defaultConfig {
//...
    manifestPlaceholders = [applicationName:appName,screenOrientation:'nosensor']
//...
}

如果您需要更复杂的功能(例如:仅在平板电脑上允许屏幕旋转并将手机方向固定为纵向),那么您应该使用locked

android:screenOrientation="locked" 我注意到,如果您强制将方向设置为portrait,而传感器发送的是手机横向旋转,则您的Activity将以横向模式启动,然后在执行代码后旋转为纵向模式,几乎可以适用于任何其他方向。 lock会保留先前的旋转状态,因此您可能已经被锁定在先前的纵向模式中。然后,您必须知道设备是平板电脑还是手机:为此,我建议您使用以下内容:

Detect tablet using resource overload

//normal tablet_detection file content:
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <item name="isTablet" type="bool">false</item>
</resources>

//sw600dp tablet_detection file content:
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <item name="isTablet" type="bool">true</item>
</resources>

//Now in your code you can detect if it's a tablet or not by calling following function in your Activity's **onCreate** and apply screen orientation by code:
protected  void updateScreenOrientation(){
boolean isTablet =context.getResources().getBoolean(R.bool.isTablet);
//And select the orientation you wish to set:
int defaultOrientation=isTablet? ActivityInfo.SCREEN_ORIENTATION_FULL_USER:ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
        if(getRequestedOrientation()== ActivityInfo.SCREEN_ORIENTATION_LOCKED) {
            //Apply this only on Activities where you set the orientation to 'locked' in the manifest
            this.setRequestedOrientation(defaultOrientation);
        }
}

1

其他解决方案对我没有用。我仍然遇到了一些奇怪的对话框方向问题和重建问题。我的解决方案是扩展Activity,在清单文件中将其强制设为纵向。

示例:

public class MainActivityPhone extends MainActivity {}

manifest.xml:

        <activity
        android:screenOrientation="portrait"
        android:name=".MainActivityPhone"
        android:theme="@style/AppTheme.NoActionBar" />

在启动屏幕活动中:
    Intent i = null;
    boolean isTablet = getResources().getBoolean(R.bool.is_tablet);
    if (!isTablet)
        i = new Intent(this, MainActivityPhone.class);
    else
        i = new Intent(this, MainActivity.class);
    startActivity(i);

0

建议的方法是不要为任何窗口尺寸锁定方向,但您可以按照此处的指南来实现。

步骤上,首先要做的事情是在清单上解锁方向。

<activity
    android:name=".MyActivity"
    android:screenOrientation="fullUser">

接下来,您可以使用Jetpack Window Manager来确定应用程序在屏幕上占用了多少空间:
/** Determines whether the device has a compact screen. **/
fun compactScreen(): Boolean {
    val screenMetrics = WindowMetricsCalculator
                        .getOrCreate()
                        .computeMaximumWindowMetrics(this)
    val shortSide = min(screenMetrics.bounds.width(),
                        screenMetrics.bounds.height())
    return shortSide / resources.displayMetrics.density < 600
}

最后,当空间较小时(通常在手机上),您可以锁定方向:

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    requestedOrientation = if (compactScreen())
        ActivityInfo.SCREEN_ORIENTATION_PORTRAIT else
        ActivityInfo.SCREEN_ORIENTATION_FULL_USER
    ...
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接