Как программно получить высоту и ширину панели навигации Android?

122

Черная панель навигации в нижней части экрана не снимается в Android. Он был частью Android с версии 3.0 в качестве замены аппаратных кнопок. Вот картинка:

Системная панель

Как я могу получить размер ширины и высоты этого элемента пользовательского интерфейса в пикселях?

Kevik
источник
Здесь добавлено лучшее решение этой проблемы. Мы можем определить, присутствует ли панель навигации на устройстве, сравнив показатели отображения и реальные показатели. Посмотрите мой ответ, я добавил полный код, чтобы узнать фактический размер панели навигации для всех устройств Android.
Sino Raj

Ответы:

178

Попробуйте код ниже:

Resources resources = context.getResources();
int resourceId = resources.getIdentifier("navigation_bar_height", "dimen", "android");
if (resourceId > 0) {
    return resources.getDimensionPixelSize(resourceId);
}
return 0;
Sanket
источник
7
Спасибо. +1. Вы знаете, насколько устойчив этот подход? Будут ли идентификаторы ресурсов изменяться в зависимости от версии платформы?
Бен Пирсон
59
Обратите внимание, что этот фрагмент кода не возвращает 0 на устройствах без панели навигации. Проверено на Samsung S2 и S3. У меня 72 и 96.
Эгис
4
@Egidijus Посмотрите на мой ответ, он вернет 0 для устройств с физической навигацией stackoverflow.com/a/29938139/1683141
Mdlc 01
1
galaxy s6 edge также не возвращает 0, хотя внизу нет панели навигации. возвращает 192.
agamov
5
Этот код показывает размер панели навигации по умолчанию (попробуйте также navigation_bar_height_landscapeвысоту панели навигации в альбомном режиме и navigation_bar_widthширину вертикальной панели навигации). Вы должны отдельно выяснить, действительно ли отображается панель навигации и где, например, путем проверки наличия физической кнопки меню. Возможно, вы сможете найти другой способ в исходном коде Android на android.googlesource.com/platform/frameworks/base/+/…
user149408
103

Я получаю размер панели навигации, сравнивая размер экрана, доступный для приложения, с реальным размером экрана. Я предполагаю, что панель навигации присутствует, когда размер экрана, доступного для использования приложением, меньше реального размера экрана. Затем я рассчитываю размер панели навигации. Этот метод работает с 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;
}

ОБНОВИТЬ

Чтобы узнать о решении, в котором учитываются вырезы на экране, проверьте ответ Джона .

эгида
источник
2
Это единственное решение, которое работает для меня на всех устройствах, которые я тестировал. Мое требование - определить, находится ли панель навигации внизу экрана с учетом ориентации. Я немного изменил код в ответе, чтобы просто вернуть мне размер панели навигации в нижней части экрана, 0 означает, что ее нет. Мое тестирование показывает, что Nexus5 = портрет: 144, Пейзаж: 0 | Nexus7 = портрет: 96, Пейзаж: 96 | Samsung Note II = портрет: 0, альбомный: 0 | Samsung S10 = портрет: 0,
горизонтальный
2
Я также могу подтвердить, что решение Danylo работает только на некоторых моделях. Это решение от Egidijus - лучшее рабочее решение, оно работало на всех протестированных моделях. Спасибо тебе за это.
Бисклаврет
2
Строка состояния является частью экрана, который можно использовать в приложении.
Эгис
1
Это работает, но не учитывает, скрыта ли существующая панель. Есть идеи, как это проверить?
X-HuMan
1
Он не работает для Android P, пока вместо панели навигации включен жест навигации. Не могли бы вы
Ник
36

Высота 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;
}
MDLC
источник
У меня это не работает, Nexus5 возвращает true для hasMenuKey и hasBackKey, поэтому думает, что navBar нет. (Nexus7 правильно возвращает false для hasMenuKey и hasBackKey)
se22as
5
Я тестировал этот код на Nexus 5, и он вернул false для обоих. Вы уверены, что не используете эмулятор или ром?
Mdlc
Да, я использовал эмулятор, извините, я должен был уточнить в своем заявлении ранее
se22as
не должно этого быть! hasMenuKey || ! hasBackKey вместо! hasMenuKey &&! hasBackKey? пожалуйста, подтвердите
yongsunCN 06
2
К сожалению, не работает на Sony Xperia Z3, hasBackKey = false!хотя это должно быть правдой. Вместо этого сработало следующее: boolean navBarExists = getResources().getBoolean(getResources().getIdentifier("config_showNavigationBar", "bool", "android"));
Hamzeh Soboh
27

На самом деле панель навигации на планшетах (по крайней мере, 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;
}
Даниил Волох
источник
6
Чтобы извлечь ориентацию из контекста, используйтеcontext.getResources().getConfiguration().orientation
Hugo Gresse
Здесь добавлено лучшее решение этой проблемы. Мы можем определить, присутствует ли панель навигации на устройстве, сравнив показатели отображения и реальные показатели. Посмотрите мой ответ, я добавил полный код, чтобы узнать фактический размер панели навигации для всех устройств Android.
Sino Raj
17

Я думаю, что лучший ответ здесь, потому что он позволяет также получить даже высоту выреза.

Возьмите свое корневое представление и добавьте setOnApplyWindowInsetsListener (или вы можете переопределить onApplyWindowInsets из него) и возьмите из него файл insert.getSystemWindowInsets.

В своей работе с камерой я добавляю отступ, равный systemWindowInsetBottom, в мой нижний макет. И, наконец, исправлена ​​проблема с вырезом.

Вставки активности камеры

с appcompat это так

ViewCompat.setOnApplyWindowInsetsListener(mCameraSourcePreview, (v, insets) -> {
    takePictureLayout.setPadding(0,0,0,insets.getSystemWindowInsetBottom());
    return insets.consumeSystemWindowInsets();
});

без appcompat это:

mCameraSourcePreview.setOnApplyWindowInsetsListener((v, insets) -> { ... })
Джон
источник
Можно ли setOnApplyWindowInsetsListenerвместо этого использовать ? Если да, то как? и почему вы отделили LOLLIPOP от остальных?
разработчик Android
да, это возможно, и это лучше. исправлю ответ. и дифференцировать тоже не нужно
Джон
1
Это лучший ответ. Я буквально люблю тебя.
Химаншу Рават
1
Это настоящий ответ.
nyconing
1
Когда я вызываю это из onCreate (), слушатель никогда не выполняется. Я что-то упускаю?
howettl
12

Я надеюсь, это поможет вам

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;
}
Франсиско Драмонд
источник
5

Это мой код для добавления paddingRight и paddingBottom в представление, чтобы избежать панели навигации. Я объединил здесь некоторые ответы и сделал специальное предложение для альбомной ориентации вместе с isInMultiWindowMode. Ключ должен прочитать navigation_bar_height , но также проверить config_showNavigationBar чтобы убедиться, что мы действительно должны использовать высоту.

Ни одно из предыдущих решений у меня не помогло. Начиная с Android 7.0 вы должны учитывать многооконный режим. Это нарушает реализацию сравнения display.realSize с display.size, поскольку realSize дает вам размеры всего экрана (оба разделенных окна), а size дает вам только размеры вашего окна приложения. Установка отступов на эту разницу оставит все ваше представление как отступы.

/** 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);
}
Маркус
источник
1

Высота нижней панели навигации составляет 48 точек на дюйм (как в портретном, так и в альбомном режиме) и 42 точек на дюйм, когда панель расположена вертикально.

Аритра Рой
источник
4
Высота ОБЫЧНО 48dp. Но некоторые производители могут это изменить, некоторые пользовательские ПЗУ могут это изменить и так далее. Чтобы получить точный размер , лучше положиться на ответ Данило ( stackoverflow.com/a/26118045/1377145 )
Хьюго Гресс,
Это даже лучше. Спасибо.
Аритра Рой,
поставить вертикально? как в ландшафтном режиме?
Sudocoder
1

Решение, предложенное Egidijus, отлично работает для Build.VERSION.SDK_INT> = 17

Но я получил «NoSuchMethodException» во время выполнения следующего оператора с Build.VERSION.SDK_INT <17 на моем устройстве:

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();
}
Chebyr
источник
1

Я решил эту проблему для всех устройств (включая 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;
        }
Сино Радж
источник
Display.getRealMetrics()требуется уровень API 17
Weizhi
если (Build.VERSION.SdkInt> = BuildVersionCodes.JellyBeanMr2) уже отмечен.
Сино Радж
Код нужно исправить. Мой LG Nexus 5X получит 0.0 из - за: [1] Если по screenOrientationумолчанию, hasPermanentMenuKeyэто falseтолько если не вращаются. [2] Когда screenOrientationесть пейзаж, он попадает в displayMetrics.heightPixels != realDisplayMetrics.heightPixels)другое. [3] Когда screenOrientationпортрет, то hasPermanentMenuKeyесть false.
Fruit
он работает, но в 4.x если (hasPermanentMenuKey) - это трата времени, прокомментируйте это
djdance 08
Ваше состояние должно быть, если (Build.VERSION.SDK_INT> = Build.VERSION_CODES.JELLY_BEAN_MR1). API 17 требуется для выполнения общедоступных недействительных getRealMetrics (DisplayMetrics outMetrics). Смотрите документы: developer.android.com/reference/kotlin/android/util/...
portfoliobuilder
1

Объединяя ответ от @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);
	}

}

Meanman
источник
Если я хочу программно установить высоту TextView в нижнюю часть полезной высоты экрана, как мне использовать этот класс?
Навид Аббас
1

Как получить высоту панели навигации и строки состояния. Этот код работает у меня на некоторых устройствах Huawei и устройствах Samsung . Приведенное выше решение 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
}

ПРИМЕЧАНИЕ: на Samsung A70 этот метод возвращает высоту строки состояния + высоту панели навигации. На других устройствах (Huawei) он возвращает только высоту панели навигации и возвращает 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
                }
Ань Дуй
источник
1

Я сделал это, он работает на всех устройствах, которые я тестировал, и даже на эмуляторах:

// 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());
}
Matteo
источник
0

Вот как я это решил. Я сделал скрываемую нижнюю панель, которая требовала заполнения в зависимости от того, была ли панель навигации или нет (емкостная, экранная или просто леденец).


Посмотреть

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

Utils.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;
}
Йонас Борггрен
источник
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
    }

Результат в всплывающем режиме Samsung:

введите описание изображения здесь

Бакши
источник
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);
}
Сагар
источник
0

В случае Samsung 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()
    }
}

Для получения дополнительной помощи вы можете взглянуть на расширенное использование этого здесь .

Нилеш Деокар
источник
0

Простое однострочное решение

Как было предложено во многих из приведенных выше ответов, например

Просто получить высоту панели навигации может быть недостаточно. Нам нужно учитывать, 1. существует ли панель навигации, 2. находится ли она внизу, справа или слева, 3. открыто ли приложение в многооконном режиме.

К счастью, вы можете легко обойти все длинное кодирование, просто установив android:fitsSystemWindows="true"корневой макет. Система Android автоматически позаботится о добавлении необходимых отступов в корневой макет, чтобы дочерние представления не попали в область панели навигации или строки состояния.

Есть простое однострочное решение

android:fitsSystemWindows="true"

или программно

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

вы также можете получить root-доступ

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

Подробнее о получении root-view см. - https://stackoverflow.com/a/4488149/9640177

mayank1513
источник