在API Level 30(Android 11)中获取屏幕宽度:getDefaultDisplay()和getMetrics()现已被弃用。我们应该使用什么替代方法?

58

我目前是这样计算屏幕宽度的:

public static int getScreenWidth(@NonNull Context context) {
    DisplayMetrics displayMetrics = new DisplayMetrics();
    ((Activity) context).getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
    return displayMetrics.widthPixels;
}

由于这两个函数(getDefaultDisplay()getMetrics())现在已被弃用,我们应该使用什么替代品呢?


你要计算的确切尺寸是多少?它应该包括状态栏、导航栏、切口吗?我一直在尝试文档中建议的替代方法,但未能找到任何组合选项可以产生与此代码相同的高度,这表明这段代码可能并没有计算出特别有用的指标。 - Ryan M
context.getDisplay()是context.getWindowManager().getDefaultDisplay()的替代方案。 - Jashanpreet singh Chakkal
@JashanChakkal 但是 getDisplay() 的 getMetrics() 仍然已被弃用。 - chitgoks
9个回答

70

用于计算屏幕宽度减去任何系统栏的方法:

public static int getScreenWidth(@NonNull Activity activity) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
        WindowMetrics windowMetrics = activity.getWindowManager().getCurrentWindowMetrics();
        Insets insets = windowMetrics.getWindowInsets()
                .getInsetsIgnoringVisibility(WindowInsets.Type.systemBars());
        return windowMetrics.getBounds().width() - insets.left - insets.right;
    } else {
        DisplayMetrics displayMetrics = new DisplayMetrics();
        activity.getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
        return displayMetrics.widthPixels;
    }
}

请注意,这并不完全相同:测试高度会产生不同的结果,并且我无法通过新的API复制旧的API功能(部分原因是旧的API行为有点棘手,不总是您想要的,因此被弃用)。但在实践中,对于许多事情来说,它应该已经足够作为通用屏幕宽度。


2
我正在阅读文档,我也认为应该这样做。你是正确的。 - Greelings
您的新方法和弃用方法在 Pixel 4 横向方向上返回不同的结果。 - user924
3
我猜我们也应该使用 window.decorView.rootWindowInsets?.displayCutout (safeInsetLeftsafeInsetRight),这些不全是 0,有些大于 0 - user924
1
使用windowMetrics.getWindowInsets()时存在问题,当屏幕从纵向强制旋转到横向时,Insets.left和insets.right始终返回0。 - CodingBruceLee
3
R+的密度是多少? - user924

14

在2022年遇到了同样的问题,现在有一个较新的Jetpack库可用于处理各种API版本之间的兼容性。

build.gradle

dependencies {
    implementation 'androidx.window:window:1.0.0'

}

import androidx.window.layout.WindowMetrics;
import androidx.window.layout.WindowMetricsCalculator;


WindowMetrics windowMetrics = WindowMetricsCalculator.getOrCreate().computeCurrentWindowMetrics(activity);
final int height = windowMetrics.getBounds().height();
final int width = windowMetrics.getBounds().width();

我遇到的一个小问题是,包含 androidx.window 会导致引入数万个库方法,使得我超出了 DEX 64k 方法限制,因此我需要使用 R8/proguard 设置来优化这些方法,但这是另一个问题。


12
@RequiresApi(20)
inline val Fragment.windowHeight: Int
    get() {
        return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
            val metrics = requireActivity().windowManager.currentWindowMetrics
            val insets = metrics.windowInsets.getInsets(WindowInsets.Type.systemBars())
            metrics.bounds.height() - insets.bottom - insets.top
        } else {
            val view = requireActivity().window.decorView
            val insets = WindowInsetsCompat.toWindowInsetsCompat(view.rootWindowInsets, view).getInsets(WindowInsetsCompat.Type.systemBars())
            resources.displayMetrics.heightPixels - insets.bottom - insets.top
        }
    }

@RequiresApi(20)
inline val Fragment.windowWidth: Int
    get() {
        return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
            val metrics = requireActivity().windowManager.currentWindowMetrics
            val insets = metrics.windowInsets.getInsets(WindowInsets.Type.systemBars())
            metrics.bounds.width() - insets.left - insets.right
        } else {
            val view = requireActivity().window.decorView
            val insets = WindowInsetsCompat.toWindowInsetsCompat(view.rootWindowInsets, view).getInsets(WindowInsetsCompat.Type.systemBars())
            resources.displayMetrics.widthPixels - insets.left - insets.right
        }
    }

这需要 androidx.core 版本为 1.5.x


3
你的 RequiresApi 写错了,它需要 API 等级 23。 - Xam

5

我猜我成功地实现了等效的方法(改进了@RyanM的方法),以代替已过时的方法。

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

    // Deprecated older method for comparison.
    DisplayMetrics outMetrics = new DisplayMetrics();
    getDisplay().getMetrics(outMetrics);
    Log.d("Upto API-29", String.format(
            "(width, height) = (%d, %d)", outMetrics.widthPixels, outMetrics.heightPixels
    ));

    // Newer methods.
    Log.d("API-30+", String.format(
            "(width, height) = (%d, %d)", getScreenWidth(this), getScreenHeight(this)
    ));
}

public static int getScreenWidth(@NonNull Activity activity) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
        WindowMetrics windowMetrics = activity.getWindowManager().getCurrentWindowMetrics();
        Rect bounds = windowMetrics.getBounds();
        Insets insets = windowMetrics.getWindowInsets().getInsetsIgnoringVisibility(
                WindowInsets.Type.systemBars()
        );

        if (activity.getResources().getConfiguration().orientation
                == Configuration.ORIENTATION_LANDSCAPE
                && activity.getResources().getConfiguration().smallestScreenWidthDp < 600
        ) { // landscape and phone
            int navigationBarSize = insets.right + insets.left;
            return bounds.width() - navigationBarSize;
        } else { // portrait or tablet
            return bounds.width();
        }
    } else {
        DisplayMetrics outMetrics = new DisplayMetrics();
        activity.getWindowManager().getDefaultDisplay().getMetrics(outMetrics);
        return outMetrics.widthPixels;
    }
}

public static int getScreenHeight(@NonNull Activity activity) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
        WindowMetrics windowMetrics = activity.getWindowManager().getCurrentWindowMetrics();
        Rect bounds = windowMetrics.getBounds();
        Insets insets = windowMetrics.getWindowInsets().getInsetsIgnoringVisibility(
                WindowInsets.Type.systemBars()
        );

        if (activity.getResources().getConfiguration().orientation
                == Configuration.ORIENTATION_LANDSCAPE
                && activity.getResources().getConfiguration().smallestScreenWidthDp < 600
        ) { // landscape and phone
            return bounds.height();
        } else { // portrait or tablet
            int navigationBarSize = insets.bottom;
            return bounds.height() - navigationBarSize;
        }
    } else {
        DisplayMetrics outMetrics = new DisplayMetrics();
        activity.getWindowManager().getDefaultDisplay().getMetrics(outMetrics);
        return outMetrics.heightPixels;
    }
}

要点如下:

  1. 系统栏的高度不包含在窗口边界的高度中。我们只应该排除导航栏的高度(除非手机设备横屏模式下)。
  2. 在手机设备横屏模式下,我们应该将导航栏的大小从窗口边界的宽度中排除。

测试

1. 在我的真实手机上(API-30)

纵向模式:

2021-12-14 22:17:28.231 31660-31660/com.stackoverflow.windowmetrics D/Upto API-29: (width, height) = (1080, 2016)
2021-12-14 22:17:28.237 31660-31660/com.stackoverflow.windowmetrics D/API-30+: (width, height) = (1080, 2016)

横屏:

2021-12-14 22:17:35.858 31660-31660/com.stackoverflow.windowmetrics D/Upto API-29: (width, height) = (2016, 1080)
2021-12-14 22:17:35.887 31660-31660/com.stackoverflow.windowmetrics D/API-30+: (width, height) = (2016, 1080)

2. 在模拟的 Nexus10 上 (API-31)

纵向:

2021-12-14 22:19:33.379 1416-1416/com.stackoverflow.windowmetrics D/Upto API-29: (width, height) = (1600, 2464)
2021-12-14 22:19:33.382 1416-1416/com.stackoverflow.windowmetrics D/API-30+: (width, height) = (1600, 2464)

横向布局:

2021-12-14 22:18:44.809 1416-1416/com.stackoverflow.windowmetrics D/Upto API-29: (width, height) = (2560, 1504)
2021-12-14 22:18:44.814 1416-1416/com.stackoverflow.windowmetrics D/API-30+: (width, height) = (2560, 1504)

2. 在模拟器上的Nexus7 (API-31)

竖屏模式:

2021-12-14 22:21:21.606 3108-3108/com.stackoverflow.windowmetrics D/Upto API-29: (width, height) = (800, 1216)
2021-12-14 22:21:21.610 3108-3108/com.stackoverflow.windowmetrics D/API-30+: (width, height) = (800, 1216)

横屏:

2021-12-14 22:22:23.283 3108-3108/com.stackoverflow.windowmetrics D/Upto API-29: (width, height) = (1280, 736)
2021-12-14 22:22:23.289 3108-3108/com.stackoverflow.windowmetrics D/API-30+: (width, height) = (1280, 736)

4
val windowMetrics = requireActivity().windowManager.currentWindowMetrics
val displayMetrics = resources.displayMetrics

val pxHeight = windowMetrics.bounds.height()
val pxWidth  = windowMetrics.bounds.width()

val density  = displayMetrics.density

val dpHeight = pxHeight/density
val dpWidth  = pxWidth/density

1

大多数答案中使用windowManager.getCurrentWindowMetrics()方法来获取屏幕大小。但是这不会给出实际(物理设备)屏幕大小。如果屏幕处于分割模式,应用程序将仅占用部分屏幕,并且它将给出该大小而不是全屏大小。

因此,建议使用windowManager.getMaximumWindowMetrics()方法来获取全屏大小。

WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
WindowMetrics metrics = windowManager.getMaximumWindowMetrics();
Rect bounds = metrics.getBounds();
int width = bounds.width();
int height = bounds.height();

最终出现了异常:没有接口方法getMaximumWindowMetrics()。 - Gaurav Singh
1
@GauravSingh 在 API Level 30(Android 11)中添加了 getMaximumWindowMetrics()。因此,如果在低于 API Level30 的设备上运行此函数,则会抛出异常。 - Chandrakanth

0
在之前的回答基础上,再加上显示屏的切口:
public static int getScreenWidth(@NonNull Activity activity) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
        WindowMetrics windowMetrics = activity.getWindowManager().getCurrentWindowMetrics();
        Rect bounds = windowMetrics.getBounds();

        if (activity.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE
                && activity.getResources().getConfiguration().smallestScreenWidthDp < 600) {
                // landscape and phone
                Insets insets = windowMetrics.getWindowInsets().getInsetsIgnoringVisibility(WindowInsets.Type.systemBars());
                Insets cutout = windowMetrics.getWindowInsets().getInsetsIgnoringVisibility(WindowInsets.Type.displayCutout());
                return bounds.width() - insets.right - insets.left - cutout.right - cutout.left;
        } else {
            // portrait or tablet
            return bounds.width();
        }
    } else {
        DisplayMetrics displayMetrics = new DisplayMetrics();
        activity.getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
        return displayMetrics.widthPixels;
    }
}

0
这是一个DisplayUtils类。你可以从中获取当前显示的高度和宽度。你可以为对话框或对话框片段获取宽度和高度,其中对话框的宽度应该是屏幕宽度的80%。这是通过'getDeviceDisplayWidth'函数完成的,该函数接受'reductionPercent'参数。
object DisplayUtils {

    data class DisplaySize(
        val height: Int,
        val width: Int,
    )

    fun Context.getDeviceDisplaySize(): DisplaySize {
        val height: Int
        val width: Int
        val wm = this.getSystemService(Context.WINDOW_SERVICE) as WindowManager

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
            val metrics = wm.currentWindowMetrics
            val insets = metrics.windowInsets.getInsets(WindowInsets.Type.systemBars())
            height = metrics.bounds.height() - insets.bottom - insets.top
            width = metrics.bounds.width() - insets.left - insets.right

        } else {
            val displayMetrics = DisplayMetrics()

            @Suppress("DEPRECATION")
            val display = wm.defaultDisplay // deprecated in API 30
            @Suppress("DEPRECATION")
            display.getMetrics(displayMetrics) // deprecated in API 30

            height = displayMetrics.heightPixels
            width = displayMetrics.widthPixels
        }
        return DisplaySize(height = height, width = width)
    }

    fun Context.getDeviceDisplayWidth(reductionPercent: Float = 0.16f): Int {
        val size = getDeviceDisplaySize()
        return (size.width - (reductionPercent * size.width)).toInt()
    }

    fun Context.getDeviceDisplayHeight(reductionPercent: Float = 0.16f): Int {
        val size = getDeviceDisplaySize()
        return (size.height - (reductionPercent * size.height)).toInt()
    }

}

-5

你可以使用

Context.getDisplay() 代替 getDefaultDisplay()

display.getRealMatrix(displayMetrics) 代替 display.getMetrics(displayMetrics)


1
display.getRealMatrix 返回您屏幕的完整尺寸(包括状态栏大小和导航栏大小) - user924

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