簡體   English   中英

如何以編程方式獲取 Android 導航欄的高度和寬度?

[英]How do I get the height and width of the Android Navigation Bar programmatically?

屏幕下方的黑色導航條在Android中是不容易移除的,它從3.0開始作為硬件按鈕的替代品已經是Android的一部分。 這是一張照片:

系統欄

如何獲取此 UI 元素的寬度和高度大小(以像素為單位)?

試試下面的代碼:

Resources resources = context.getResources();
int resourceId = resources.getIdentifier("navigation_bar_height", "dimen", "android");
if (resourceId > 0) {
    return resources.getDimensionPixelSize(resourceId);
}
return 0;

我通過將應用程序可用的屏幕尺寸與實際屏幕尺寸進行比較來獲得導航欄尺寸。 我假設當應用程序可用的屏幕尺寸小於實際屏幕尺寸時,導航欄會出現。 然后我計算導航欄的大小。 此方法適用於 API 14 及更高版本。

public static Point getNavigationBarSize(Context context) {
    Point appUsableSize = getAppUsableScreenSize(context);
    Point realScreenSize = getRealScreenSize(context);

    // navigation bar on the side
    if (appUsableSize.x < realScreenSize.x) {
        return new Point(realScreenSize.x - appUsableSize.x, appUsableSize.y);
    }

    // navigation bar at the bottom
    if (appUsableSize.y < realScreenSize.y) {
        return new Point(appUsableSize.x, realScreenSize.y - appUsableSize.y);
    }

    // navigation bar is not present
    return new Point();
}

public static Point getAppUsableScreenSize(Context context) {
    WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
    Display display = windowManager.getDefaultDisplay();
    Point size = new Point();
    display.getSize(size);
    return size;
}

public static Point getRealScreenSize(Context context) {
    WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
    Display display = windowManager.getDefaultDisplay();
    Point size = new Point();

    if (Build.VERSION.SDK_INT >= 17) {
        display.getRealSize(size);
    } else if (Build.VERSION.SDK_INT >= 14) {
        try {
            size.x = (Integer) Display.class.getMethod("getRawWidth").invoke(display);
            size.y = (Integer) Display.class.getMethod("getRawHeight").invoke(display);
        } catch (IllegalAccessException e) {} catch (InvocationTargetException e) {} catch (NoSuchMethodException e) {}
    }

    return size;
}

更新

有關考慮顯示切口的解決方案,請查看John 的答案

NavigationBar 高度因某些設備而異,但對於某些方向也是如此。 首先,您必須檢查設備是否具有導航欄,然后檢查設備是平板電腦還是非平板電腦(手機),最后您必須查看設備的方向以獲得正確的高度。

public int getNavBarHeight(Context c) {
         int result = 0;
         boolean hasMenuKey = ViewConfiguration.get(c).hasPermanentMenuKey();
         boolean hasBackKey = KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_BACK);

         if(!hasMenuKey && !hasBackKey) {
             //The device has a navigation bar
             Resources resources = c.getResources();

             int orientation = resources.getConfiguration().orientation;
             int resourceId;
             if (isTablet(c)){
                 resourceId = resources.getIdentifier(orientation == Configuration.ORIENTATION_PORTRAIT ? "navigation_bar_height" : "navigation_bar_height_landscape", "dimen", "android");
             }  else {
                 resourceId = resources.getIdentifier(orientation == Configuration.ORIENTATION_PORTRAIT ? "navigation_bar_height" : "navigation_bar_width", "dimen", "android");     
             }

             if (resourceId > 0) {
                 return resources.getDimensionPixelSize(resourceId);
             }
         }
         return result;
} 


private boolean isTablet(Context c) {
    return (c.getResources().getConfiguration().screenLayout
            & Configuration.SCREENLAYOUT_SIZE_MASK)
            >= Configuration.SCREENLAYOUT_SIZE_LARGE;
}

實際上平板電腦(至少 Nexus 7)上的導航欄在縱向和橫向上有不同的大小,所以這個功能應該是這樣的:

private int getNavigationBarHeight(Context context, int orientation) {
    Resources resources = context.getResources();

    int id = resources.getIdentifier(
            orientation == Configuration.ORIENTATION_PORTRAIT ? "navigation_bar_height" : "navigation_bar_height_landscape",
            "dimen", "android");
    if (id > 0) {
        return resources.getDimensionPixelSize(id);
    }
    return 0;
}

我認為更好的答案在這里,因為它也可以讓您獲得均勻的切口高度。

獲取您的根視圖,並添加 setOnApplyWindowInsetsListener (或者您可以從中覆蓋 onApplyWindowInsets),並從中獲取插圖。

在我的相機活動中,我將等於 systemBars.bottom 的填充添加到我的底部布局中。 最后,它修復了剪切問題。

相機活動插圖

使用 appcompat 是這樣的

ViewCompat.setOnApplyWindowInsetsListener(binding.root) { v, insets ->
    val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
    binding.takePictureLayout.apply {
        setPaddingRelative(paddingStart, paddingTop, paddingEnd, systemBars.bottom)
    }
    return@setOnApplyWindowInsetsListener insets
}

沒有 appcompat,這個:

mCameraSourcePreview.setOnApplyWindowInsetsListener((v, insets) -> { ... })

我希望這可以幫助你

public int getStatusBarHeight() {
    int result = 0;
    int resourceId = getResources().getIdentifier("status_bar_height", "dimen", "android");
    if (resourceId > 0) {
        result = getResources().getDimensionPixelSize(resourceId);
    }
    return result;
}

public int getNavigationBarHeight()
{
    boolean hasMenuKey = ViewConfiguration.get(context).hasPermanentMenuKey();
    int resourceId = getResources().getIdentifier("navigation_bar_height", "dimen", "android");
    if (resourceId > 0 && !hasMenuKey)
    {
        return getResources().getDimensionPixelSize(resourceId);
    }
    return 0;
}

這是我將 paddingRight 和 paddingBottom 添加到 View 以避開導航欄的代碼。 我在這里結合了一些答案,並與isInMultiWindowMode一起為橫向制作了一個特殊的子句。 關鍵是要閱讀navigation_bar_height ,還要檢查config_showNavigationBar以確保我們應該實際使用高度。

以前的解決方案都不適合我。 從 Android 7.0 開始,您必須考慮多窗口模式。 這打破了將display.realSizedisplay.size進行比較的實現,因為realSize為您提供了整個屏幕的尺寸(兩個拆分窗口),而size只為您提供了 App 窗口的尺寸。 將填充設置為這種差異將使您的整個視圖處於填充狀態。

/** Adds padding to a view to dodge the navigation bar.

    Unfortunately something like this needs to be done since there
    are no attr or dimens value available to get the navigation bar
    height (as of December 2016). */
public static void addNavigationBarPadding(Activity context, View v) {
    Resources resources = context.getResources();
    if (hasNavigationBar(resources)) {
        int orientation = resources.getConfiguration().orientation;
        int size = getNavigationBarSize(resources);
        switch (orientation) {
        case Configuration.ORIENTATION_LANDSCAPE:
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N &&
                context.isInMultiWindowMode()) { break; }
            v.setPadding(v.getPaddingLeft(), v.getPaddingTop(),
                         v.getPaddingRight() + size, v.getPaddingBottom());
            break;
        case Configuration.ORIENTATION_PORTRAIT:
            v.setPadding(v.getPaddingLeft(), v.getPaddingTop(),
                         v.getPaddingRight(), v.getPaddingBottom() + size);
            break;
        }
    }
}

private static int getNavigationBarSize(Resources resources) {
    int resourceId = resources.getIdentifier("navigation_bar_height",
                                             "dimen", "android");
    return resourceId > 0 ? resources.getDimensionPixelSize(resourceId) : 0;
}

private static boolean hasNavigationBar(Resources resources) {
    int hasNavBarId = resources.getIdentifier("config_showNavigationBar",
                                              "bool", "android");
    return hasNavBarId > 0 && resources.getBoolean(hasNavBarId);
}

2021年的新答案來拯救


靈感來自Egis 的回答:

context.navigationBarHeight

擴展吸氣劑在哪里

val Context.navigationBarHeight: Int
get() {
    val windowManager = getSystemService(Context.WINDOW_SERVICE) as WindowManager

    return if (Build.VERSION.SDK_INT >= 30) {
        windowManager
                .currentWindowMetrics
                .windowInsets
                .getInsets(WindowInsets.Type.navigationBars())
                .bottom

    } else {
        val currentDisplay = try {
            display
        } catch (e: NoSuchMethodError) {
            windowManager.defaultDisplay
        }

        val appUsableSize = Point()
        val realScreenSize = Point()
        currentDisplay?.apply {
            getSize(appUsableSize)
            getRealSize(realScreenSize)
        }

        // navigation bar on the side
        if (appUsableSize.x < realScreenSize.x) {
            return realScreenSize.x - appUsableSize.x
        }

        // navigation bar at the bottom
        return if (appUsableSize.y < realScreenSize.y) {
            realScreenSize.y - appUsableSize.y
        } else 0
    }
}

測試:

  • 帶導航欄的模擬器
    • 像素 3a (api 30)
    • 像素 2 (api 28)
    • 像素 3 (api 25)
    • 像素 2 (api 21)
  • 小米 Poco f2 pro 帶和不帶導航欄(全屏)

Egidijus 提出的解決方案適用於 Build.VERSION.SDK_INT >= 17

但是在我的設備上使用 Build.VERSION.SDK_INT < 17 執行以下語句時,我得到了“NoSuchMethodException”:

Display.class.getMethod("getRawHeight").invoke(display);

對於這種情況,我已經修改了 getRealScreenSize() 方法:

else if(Build.VERSION.SDK_INT >= 14) 
{
    View decorView = getActivity().getWindow().getDecorView();
    size.x = decorView.getWidth();
    size.y = decorView.getHeight();
}

我為所有設備(包括 Nexus 5、Samsung Galaxy Nexus 6 edge+、Samsung S10、Samsung Note II 等)解決了這個問題。 我認為這將幫助您處理與設備相關的問題。

在這里,我添加了兩種類型的代碼,

Java 代碼(適用於原生 Android):

import android.content.Context;
import android.content.res.Resources;
import android.os.Build;
import android.util.DisplayMetrics;
import android.view.Display;
import android.view.ViewConfiguration;
import android.view.WindowManager;

public class DeviceSpec {

    private int resourceID = -1;
    private Display display = null;
    private DisplayMetrics displayMetrics = null;
    private DisplayMetrics realDisplayMetrics = null;
    private Resources resources = null;
    private WindowManager windowManager = null;

    public double GetNavigationBarHeight(Context context) {
        try {
            windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
            display = windowManager.getDefaultDisplay();
            displayMetrics = new DisplayMetrics();
            if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
                realDisplayMetrics = new DisplayMetrics();
                display.getMetrics(displayMetrics);
                display.getRealMetrics(realDisplayMetrics);
                if(displayMetrics.heightPixels != realDisplayMetrics.heightPixels) {
                    resources = context.getResources();
                    return GetNavigationBarSize(context);
                }
            }
            else {
                resources = context.getResources();
                resourceID = resources.getIdentifier("config_showNavigationBar", "bool", "android");
                if (resourceID > 0 && resources.getBoolean(resourceID))
                    return GetNavigationBarSize(context);
            }
        }
        catch (Exception e){
            e.printStackTrace();
        }
        return 0;
    }

    private double GetNavigationBarSize(Context context) {
        resourceID = resources.getIdentifier("navigation_bar_height", "dimen", "android");
        if (resourceID > 0 && ViewConfiguration.get(context).hasPermanentMenuKey())
           return (resources.getDimensionPixelSize(resourceID) / displayMetrics.density);
        return 0;
    }
}

和 C# 代碼(用於 Xamarin Forms/Android)

int resourceId = -1;
        IWindowManager windowManager = null;
        Display defaultDisplay = null;
        DisplayMetrics displayMatrics = null;
        DisplayMetrics realMatrics = null;
        Resources resources = null;

        public double NavigationBarHeight
        {
            get
            {
                try
                {
                    windowManager = Forms.Context.GetSystemService(Context.WindowService).JavaCast<IWindowManager>();
                    defaultDisplay = windowManager.DefaultDisplay;
                    displayMatrics = new DisplayMetrics();
                    if (Build.VERSION.SdkInt >= BuildVersionCodes.JellyBeanMr2)
                    {
                        realMatrics = new DisplayMetrics();
                        defaultDisplay.GetMetrics(displayMatrics);
                        defaultDisplay.GetRealMetrics(realMatrics);
                        if (displayMatrics.HeightPixels != realMatrics.HeightPixels)
                        {
                            resources = Forms.Context.Resources;
                            return GetHeightOfNivigationBar();
                        }
                    }
                    else {
                        resources = Forms.Context.Resources;
                        resourceId = resources.GetIdentifier("config_showNavigationBar", "bool", "android");
                        if (resourceId > 0 && resources.GetBoolean(resourceId))
                            return GetHeightOfNivigationBar();
                    }
                }
                catch (Exception e) { }
                return 0;
            }
        }

        private double GetHeightOfNivigationBar()
        {
            resourceId = resources.GetIdentifier("navigation_bar_height", "dimen", "android");
            if (!ViewConfiguration.Get(Forms.Context).HasPermanentMenuKey && resourceId > 0)
            {
                return resources.GetDimensionPixelSize(resourceId) / displayMatrics.Density;
            }
            return 0;
        }

用於獲取導航欄高度(以像素為單位)的測試代碼:

public static int getNavBarHeight(Context c) {
    int resourceId = c.getResources()
                      .getIdentifier("navigation_bar_height", "dimen", "android");
    if (resourceId > 0) {
        return c.getResources().getDimensionPixelSize(resourceId);
    }
    return 0;
}

用於獲取狀態欄高度的測試代碼(以像素為單位):

public static int getStatusBarHeight(Context c) {
    int resourceId = c.getResources()
                      .getIdentifier("status_bar_height", "dimen", "android");
    if (resourceId > 0) {
        return c.getResources().getDimensionPixelSize(resourceId);
    }
    return 0;
}

將像素轉換為 dp:

public static int pxToDp(int px) {
    return (int) (px / Resources.getSystem().getDisplayMetrics().density);
}

如何獲取導航欄和狀態欄的高度。 此代碼適用於某些華為設備和三星設備 Egis 上面的解決方案很好,但是在某些設備上仍然不正確。 所以,我改進了它。

這是獲取狀態欄高度的代碼

private fun getStatusBarHeight(resources: Resources): Int {
        var result = 0
        val resourceId = resources.getIdentifier("status_bar_height", "dimen", "android")
        if (resourceId > 0) {
            result = resources.getDimensionPixelSize(resourceId)
        }
        return result
    }

即使隱藏導航欄,此方法也始終返回導航欄的高度。

private fun getNavigationBarHeight(resources: Resources): Int {
    val resourceId = resources.getIdentifier("navigation_bar_height", "dimen", "android")
    return if (resourceId > 0) {
        resources.getDimensionPixelSize(resourceId)
    } else 0
}

注意:在三星 A70 上,此方法返回狀態欄高度 + 導航欄高度。 在其他設備(華為)上,它只返回導航欄的高度,隱藏導航欄時返回0。

private fun getNavigationBarHeight(): Int {
        val display = activity?.windowManager?.defaultDisplay
        return if (display == null) {
            0
        } else {
            val realMetrics = DisplayMetrics()
            display.getRealMetrics(realMetrics)
            val metrics = DisplayMetrics()
            display.getMetrics(metrics)
            realMetrics.heightPixels - metrics.heightPixels
        }
    }

這是獲取導航欄和狀態欄高度的代碼

val metrics = DisplayMetrics()
        activity?.windowManager?.defaultDisplay?.getRealMetrics(metrics)

        //resources is got from activity

        //NOTE: on SamSung A70, this height = height of status bar + height of Navigation bar
        //On other devices (Huawei), this height = height of Navigation bar
        val navigationBarHeightOrNavigationBarPlusStatusBarHeight = getNavigationBarHeight()

        val statusBarHeight = getStatusBarHeight(resources)
        //The method will always return the height of navigation bar even when the navigation bar was hidden.
        val realNavigationBarHeight = getNavigationBarHeight(resources)

        val realHeightOfStatusBarAndNavigationBar =
                if (navigationBarHeightOrNavigationBarPlusStatusBarHeight == 0 || navigationBarHeightOrNavigationBarPlusStatusBarHeight < statusBarHeight) {
                    //Huawei: navigation bar is hidden
                    statusBarHeight
                } else if (navigationBarHeightOrNavigationBarPlusStatusBarHeight == realNavigationBarHeight) {
                    //Huawei: navigation bar is visible
                    statusBarHeight + realNavigationBarHeight
                } else if (navigationBarHeightOrNavigationBarPlusStatusBarHeight < realNavigationBarHeight) {
                    //SamSung A70: navigation bar is still visible but it only displays as a under line
                    //navigationBarHeightOrNavigationBarPlusStatusBarHeight = navigationBarHeight'(under line) + statusBarHeight
                    navigationBarHeightOrNavigationBarPlusStatusBarHeight
                } else {
                    //SamSung A70: navigation bar is visible
                    //navigationBarHeightOrNavigationBarPlusStatusBarHeight == statusBarHeight + realNavigationBarHeight
                    navigationBarHeightOrNavigationBarPlusStatusBarHeight
                }

我已經做到了,它適用於我測試的每台設備,甚至在模擬器上:

// Return the NavigationBar height in pixels if it is present, otherwise return 0
public static int getNavigationBarHeight(Activity activity) {
    Rect rectangle = new Rect();
    DisplayMetrics displayMetrics = new DisplayMetrics();
    activity.getWindow().getDecorView().getWindowVisibleDisplayFrame(rectangle);
    activity.getWindowManager().getDefaultDisplay().getRealMetrics(displayMetrics);
    return displayMetrics.heightPixels - (rectangle.top + rectangle.height());
}

底部導航欄的高度為 48dp(縱向和橫向模式),垂直放置時為 42dp。

結合@egis 和其他人的答案 - 這適用於各種設備,在 Pixel EMU、Samsung S6、Sony Z3、Nexus 4 上進行了測試。此代碼使用顯示尺寸來測試導航欄的可用性,然后使用實際系統導航欄大小(如果存在)。

 /** * Calculates the system navigation bar size. */ public final class NavigationBarSize { private final int systemNavBarHeight; @NonNull private final Point navBarSize; public NavigationBarSize(@NonNull Context context) { Resources resources = context.getResources(); int displayOrientation = resources.getConfiguration().orientation; final String name; switch (displayOrientation) { case Configuration.ORIENTATION_PORTRAIT: name = "navigation_bar_height"; break; default: name = "navigation_bar_height_landscape"; } int id = resources.getIdentifier(name, "dimen", "android"); systemNavBarHeight = id > 0 ? resources.getDimensionPixelSize(id) : 0; navBarSize = getNavigationBarSize(context); } public void adjustBottomPadding(@NonNull View view, @DimenRes int defaultHeight) { int height = 0; if (navBarSize.y > 0) { // the device has a nav bar, get the correct size from the system height = systemNavBarHeight; } if (height == 0) { // fallback to default height = view.getContext().getResources().getDimensionPixelSize(defaultHeight); } view.setPadding(0, 0, 0, height); } @NonNull private static Point getNavigationBarSize(@NonNull Context context) { Point appUsableSize = new Point(); Point realScreenSize = new Point(); WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); if (windowManager != null) { Display display = windowManager.getDefaultDisplay(); display.getSize(appUsableSize); display.getRealSize(realScreenSize); } return new Point(realScreenSize.x - appUsableSize.x, realScreenSize.y - appUsableSize.y); } }

簡單的一站式解決方案

如上述許多答案中所建議的,例如

僅僅獲得導航欄的高度可能還不夠。 我們需要考慮 1. 導航欄是否存在, 2. 是在底部,還是在右側或左側, 3. 應用程序是否以多窗口模式打開。

幸運的是,您只需在根布局中設置android:fitsSystemWindows="true"即可輕松繞過所有冗長的編碼。 Android 系統會自動為根布局添加必要的填充,以確保子視圖不會進入導航欄或狀態欄區域。

有一個簡單的單行解決方案

android:fitsSystemWindows="true"

或以編程方式

findViewById(R.id.your_root_view).setFitsSystemWindows(true);

您還可以通過以下方式獲得根視圖

findViewById(android.R.id.content).getRootView();
or
getWindow().getDecorView().findViewById(android.R.id.content)

有關獲取根視圖的更多詳細信息,請參閱 - https://stackoverflow.com/a/4488149/9640177

這是我解決這個問題的方法。 我制作了一個可隱藏的底部欄,它需要根據是否有導航欄(電容式、屏幕上或只是棒棒糖前)來填充。


看法

setPadding(0, 0, 0, Utils.hasNavBar(getContext()) ? 30 : 0);

實用程序.java

public static boolean hasNavBar(Context context) {
    // Kitkat and less shows container above nav bar
    if (android.os.Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {
        return false;
    }
    // Emulator
    if (Build.FINGERPRINT.startsWith("generic")) {
        return true;
    }
    boolean hasMenuKey = ViewConfiguration.get(context).hasPermanentMenuKey();
    boolean hasBackKey = KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_BACK);
    boolean hasNoCapacitiveKeys = !hasMenuKey && !hasBackKey;
    Resources resources = context.getResources();
    int id = resources.getIdentifier("config_showNavigationBar", "bool", "android");
    boolean hasOnScreenNavBar = id > 0 && resources.getBoolean(id);
    return hasOnScreenNavBar || hasNoCapacitiveKeys || getNavigationBarHeight(context, true) > 0;
}

public static int getNavigationBarHeight(Context context, boolean skipRequirement) {
    int resourceId = context.getResources().getIdentifier("navigation_bar_height", "dimen", "android");
    if (resourceId > 0 && (skipRequirement || hasNavBar(context))) {
        return context.getResources().getDimensionPixelSize(resourceId);
    }
    return 0;
}

就我而言,我想要這樣的東西:

截屏

我必須遵循@Mdlc建議的相同操作,但可能稍微簡單一些(針對 >= 21):

    //kotlin
    val windowManager = getSystemService(Context.WINDOW_SERVICE) as WindowManager
    val realSize = Point()
    windowManager.defaultDisplay.getRealSize(realSize);
    val usableRect = Rect()
    windowManager.defaultDisplay.getRectSize(usableRect)
    Toast.makeText(this, "Usable Screen: " + usableRect + " real:"+realSize, Toast.LENGTH_LONG).show()

    window.decorView.setPadding(usableRect.left, usableRect.top, realSize.x - usableRect.right, realSize.y - usableRect.bottom)

它也適用於景觀:

景觀

編輯上述解決方案在多窗口模式下無法正常工作,其中可用矩形不僅由於導航欄而且由於自定義窗口大小而變小。 我注意到的一件事是,在多窗口中,導航欄沒有懸停在應用程序上,因此即使沒有更改 DecorView 填充,我們也有正確的行為:

多窗口,裝飾視圖填充沒有變化沒有更改裝飾視圖填充的單窗口

請注意在這些場景中導航欄懸停在應用程序底部的方式之間的區別。 幸運的是,這很容易解決。 我們可以檢查應用程序是否是多窗口。 下面的代碼還包括計算和調整工具欄位置的部分(完整解決方案: https ://stackoverflow.com/a/14213035/477790)

    // kotlin
    // Let the window flow into where window decorations are
    window.addFlags(WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN)
    window.addFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS)

    // calculate where the bottom of the page should end up, considering the navigation bar (back buttons, ...)
    val windowManager = getSystemService(Context.WINDOW_SERVICE) as WindowManager
    val realSize = Point()
    windowManager.defaultDisplay.getRealSize(realSize);
    val usableRect = Rect()
    windowManager.defaultDisplay.getRectSize(usableRect)
    Toast.makeText(this, "Usable Screen: " + usableRect + " real:" + realSize, Toast.LENGTH_LONG).show()

    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N || !isInMultiWindowMode) {
        window.decorView.setPadding(usableRect.left, usableRect.top, realSize.x - usableRect.right, realSize.y - usableRect.bottom)
        // move toolbar/appbar further down to where it should be and not to overlap with status bar
        val layoutParams = ConstraintLayout.LayoutParams(appBarLayout.layoutParams as ConstraintLayout.LayoutParams)
        layoutParams.topMargin = getSystemSize(Constants.statusBarHeightKey)
        appBarLayout.layoutParams = layoutParams
    }

三星彈出模式的結果:

在此處輸入圖像描述

在三星 S8 的情況下,上述提供的方法都沒有提供適當的導航欄高度,所以我使用了 KeyboardHeightProvider鍵盤高度提供程序 android 它給了我負值的高度,對於我的布局定位,我在計算中調整了這個值。

這是KeyboardHeightProvider.java

import android.app.Activity;
import android.content.res.Configuration;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.ColorDrawable;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
import android.view.WindowManager.LayoutParams;
import android.widget.PopupWindow;


/**
 * The keyboard height provider, this class uses a PopupWindow
 * to calculate the window height when the floating keyboard is opened and closed. 
 */
public class KeyboardHeightProvider extends PopupWindow {

    /** The tag for logging purposes */
    private final static String TAG = "sample_KeyboardHeightProvider";

    /** The keyboard height observer */
    private KeyboardHeightObserver observer;

    /** The cached landscape height of the keyboard */
    private int keyboardLandscapeHeight;

    /** The cached portrait height of the keyboard */
    private int keyboardPortraitHeight;

    /** The view that is used to calculate the keyboard height */
    private View popupView;

    /** The parent view */
    private View parentView;

    /** The root activity that uses this KeyboardHeightProvider */
    private Activity activity;

    /** 
     * Construct a new KeyboardHeightProvider
     * 
     * @param activity The parent activity
     */
    public KeyboardHeightProvider(Activity activity) {
        super(activity);
        this.activity = activity;

        LayoutInflater inflator = (LayoutInflater) activity.getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
        this.popupView = inflator.inflate(R.layout.popupwindow, null, false);
        setContentView(popupView);

        setSoftInputMode(LayoutParams.SOFT_INPUT_ADJUST_RESIZE | LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
        setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED);

        parentView = activity.findViewById(android.R.id.content);

        setWidth(0);
        setHeight(LayoutParams.MATCH_PARENT);

        popupView.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {

                @Override
                public void onGlobalLayout() {
                    if (popupView != null) {
                        handleOnGlobalLayout();
                    }
                }
            });
    }

    /**
     * Start the KeyboardHeightProvider, this must be called after the onResume of the Activity.
     * PopupWindows are not allowed to be registered before the onResume has finished
     * of the Activity.
     */
    public void start() {

        if (!isShowing() && parentView.getWindowToken() != null) {
            setBackgroundDrawable(new ColorDrawable(0));
            showAtLocation(parentView, Gravity.NO_GRAVITY, 0, 0);
        }
    }

    /**
     * Close the keyboard height provider, 
     * this provider will not be used anymore.
     */
    public void close() {
        this.observer = null;
        dismiss();
    }

    /** 
     * Set the keyboard height observer to this provider. The 
     * observer will be notified when the keyboard height has changed. 
     * For example when the keyboard is opened or closed.
     * 
     * @param observer The observer to be added to this provider.
     */
    public void setKeyboardHeightObserver(KeyboardHeightObserver observer) {
        this.observer = observer;
    }

    /**
     * Get the screen orientation
     *
     * @return the screen orientation
     */
    private int getScreenOrientation() {
        return activity.getResources().getConfiguration().orientation;
    }

    /**
     * Popup window itself is as big as the window of the Activity. 
     * The keyboard can then be calculated by extracting the popup view bottom 
     * from the activity window height. 
     */
    private void handleOnGlobalLayout() {

        Point screenSize = new Point();
        activity.getWindowManager().getDefaultDisplay().getSize(screenSize);

        Rect rect = new Rect();
        popupView.getWindowVisibleDisplayFrame(rect);

        // REMIND, you may like to change this using the fullscreen size of the phone
        // and also using the status bar and navigation bar heights of the phone to calculate
        // the keyboard height. But this worked fine on a Nexus.
        int orientation = getScreenOrientation();
        int keyboardHeight = screenSize.y - rect.bottom;

        if (keyboardHeight == 0) {
            notifyKeyboardHeightChanged(0, orientation);
        }
        else if (orientation == Configuration.ORIENTATION_PORTRAIT) {
            this.keyboardPortraitHeight = keyboardHeight; 
            notifyKeyboardHeightChanged(keyboardPortraitHeight, orientation);
        } 
        else {
            this.keyboardLandscapeHeight = keyboardHeight; 
            notifyKeyboardHeightChanged(keyboardLandscapeHeight, orientation);
        }
    }

    /**
     *
     */
    private void notifyKeyboardHeightChanged(int height, int orientation) {
        if (observer != null) {
            observer.onKeyboardHeightChanged(height, orientation);
        }
    }

    public interface KeyboardHeightObserver {
        void onKeyboardHeightChanged(int height, int orientation);
    }
}

popupwindow.xml

<?xml version="1.0" encoding="utf-8"?>
<View
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/popuplayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/transparent"
    android:orientation="horizontal"/>

MainActivity中的用法

import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import kotlinx.android.synthetic.main.activity_main.*

/**
 * Created by nileshdeokar on 22/02/2018.
 */
class MainActivity : AppCompatActivity() , KeyboardHeightProvider.KeyboardHeightObserver  {

    private lateinit var keyboardHeightProvider : KeyboardHeightProvider


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        keyboardHeightProvider = KeyboardHeightProvider(this)
        parentActivityView.post { keyboardHeightProvider?.start() }
    }

    override fun onKeyboardHeightChanged(height: Int, orientation: Int) {
        // In case of 18:9 - e.g. Samsung S8
        // here you get the height of the navigation bar as negative value when keyboard is closed.
        // and some positive integer when keyboard is opened.
    }

    public override fun onPause() {
        super.onPause()
        keyboardHeightProvider?.setKeyboardHeightObserver(null)
    }

    public override fun onResume() {
        super.onResume()
        keyboardHeightProvider?.setKeyboardHeightObserver(this)
    }

    public override fun onDestroy() {
        super.onDestroy()
        keyboardHeightProvider?.close()
    }
}

如需進一步幫助,您可以在此處查看此高級用法。

我處理剪裁+導航欄的版本

fun View.getCutoutRect(): Rect {
    return when {
        isInEditMode -> {
            val cutout = context.dpToPx(16f).roundToInt()
            Rect(cutout, cutout, cutout, cutout)
        }
        Build.VERSION.SDK_INT >= Build.VERSION_CODES.M -> {
            val windowInsets = (context as? AppCompatActivity)?.window?.decorView?.rootWindowInsets ?: run {
                requestLayout()
                return Rect()
            }
            val cutout = WindowInsetsCompat.toWindowInsetsCompat(windowInsets).displayCutout
            val systemBars = WindowInsetsCompat.toWindowInsetsCompat(windowInsets).getInsets(WindowInsetsCompat.Type.systemBars())

            Rect(
                maxOf(cutout?.safeInsetLeft ?: 0, systemBars.left),
                maxOf(cutout?.safeInsetTop ?: 0, systemBars.top),
                maxOf(cutout?.safeInsetRight ?: 0, systemBars.right),
                maxOf(cutout?.safeInsetBottom ?: 0, systemBars.bottom),
            )
        }
        else -> {
            val savedRect = (this.getTag(R.id.view_insets_tag_id) as? Rect) ?: Rect()
            ViewCompat.setOnApplyWindowInsetsListener(this) { v, insets ->
                val cutout = insets.displayCutout
                val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
                val rect = Rect(
                    maxOf(cutout?.safeInsetLeft ?: 0, systemBars.left),
                    maxOf(cutout?.safeInsetTop ?: 0, systemBars.top),
                    maxOf(cutout?.safeInsetRight ?: 0, systemBars.right),
                    maxOf(cutout?.safeInsetBottom ?: 0, systemBars.bottom),
                )
                this.setTag(R.id.view_insets_tag_id, rect)
                if (savedRect != rect) {
                    requestLayout()
                }
                return@setOnApplyWindowInsetsListener insets
            }
            this.requestApplyInsets()
            savedRect
        }
    }
}

我建議使用兩個上下文擴展來獲取以 px 為單位的狀態欄高度和以 px 為單位的底部導航欄高度

狀態欄高度(以 px 為單位)

val Context.statusBarHeightInPx
    get() = run {
        val resourceId = this.resources.getIdentifier(
            "status_bar_height",
            "dimen",
            "android"
        )
        this.resources.getDimensionPixelSize(resourceId) / this.resources.displayMetrics.density
    }

底部導航欄高度(以 px 為單位)

val Context.navBarHeightInPx
    get() = run {
        val resourceId = this.resources.getIdentifier(
            "navigation_bar_height",
            "dimen",
            "android"
        )
        this.resources.getDimensionPixelSize(resourceId) / this.resources.displayMetrics.density
    }

從 Android R (SDK 30+),您可以使用此代碼獲取狀態欄和導航欄的大小

WindowInsets insets = activity.getWindowManager().getCurrentWindowMetrics().getWindowInsets();
int statusBarHeight = insets.getInsets(WindowInsetsCompat.Type.statusBars()).top; //in pixels
int navigationBarHeight = insets.getInsets(WindowInsetsCompat.Type.navigationBars()).bottom; //in pixels

要獲取布局 XML 本身的高度(當 clipToPadding 為 false 時,對於回收視圖中的最后一個元素很有用),您可以使用屬性actionBarSize

android:paddingBottom="?attr/actionBarSize"

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM