ฉันจะรับความสูงและความกว้างของ Android Navigation Bar โดยทางโปรแกรมได้อย่างไร


122

แถบนำทางสีดำที่ด้านล่างของหน้าจอไม่สามารถถอดออกได้อย่างง่ายดายใน Android เป็นส่วนหนึ่งของ Android ตั้งแต่ 3.0 แทนปุ่มฮาร์ดแวร์ นี่คือภาพ:

แถบระบบ

ฉันจะรับขนาดของความกว้างและความสูงขององค์ประกอบ UI นี้เป็นพิกเซลได้อย่างไร


เพิ่มทางออกที่ดีที่สุดสำหรับปัญหานี้แล้วที่นี่ เราสามารถระบุได้ว่ามีแถบการนำทางอยู่ในอุปกรณ์หรือไม่โดยเปรียบเทียบเมตริกที่แสดงกับเมตริกจริง ดูคำตอบของฉันฉันเพิ่มรหัสเต็มเพื่อค้นหาขนาดแถบนำทางจริงสำหรับอุปกรณ์ 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;

7
ขอบคุณ +1 คุณรู้ไหมว่าแนวทางนี้มั่นคงแค่ไหน? ตัวระบุทรัพยากรจะมีการเปลี่ยนแปลงในแพลตฟอร์มเวอร์ชันต่างๆหรือไม่
Ben Pearson

59
โปรดทราบว่าส่วนของรหัสนี้ไม่ส่งคืน 0 บนอุปกรณ์ที่ไม่มีแถบนำทาง ทดสอบกับ Samsung S2 และ S3 ฉันได้ 72 และ 96
Egis

4
@Egidijus ดูคำตอบของฉันมันจะส่งกลับ 0 สำหรับอุปกรณ์ที่มีการนำทางทางกายภาพstackoverflow.com/a/29938139/1683141
Mdlc

1
galaxy s6 edge ยังไม่คืนค่า 0 แม้ว่าจะไม่มีแถบนำทางที่ด้านล่าง ผลตอบแทน 192.
agamov

5
รหัสนี้แสดงขนาดเริ่มต้นของแถบนำทาง (ลองnavigation_bar_height_landscapeใช้ความสูงของแถบนำทางในโหมดแนวนอนและnavigation_bar_widthสำหรับความกว้างของแถบนำทางแนวตั้ง) คุณต้องหาข้อมูลแยกกันว่าแถบนำทางแสดงอยู่ที่ไหนจริงหรือไม่เช่นโดยการทดสอบว่ามีปุ่มเมนูจริงหรือไม่ บางทีคุณอาจจะหาวิธีอื่นใน Android souce code ได้ที่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;
}

UPDATE

สำหรับการแก้ปัญหาที่คำนึงถึงการแสดงผลพิลึกกรุณาตรวจสอบคำตอบของจอห์น


2
นี่เป็นทางออกเดียวที่ใช้ได้ผลกับฉันในอุปกรณ์ต่าง ๆ ทั้งหมดที่ฉันได้รับการทดสอบ ความต้องการของฉันคือการตรวจสอบว่า navBar อยู่ที่ด้านล่างของหน้าจอโดยคำนึงถึงการวางแนวหรือไม่ ฉันได้ปรับรหัสเล็กน้อยในคำตอบเพื่อส่งคืนขนาดของแถบนำทางที่ด้านล่างของหน้าจอ 0 แสดงว่าไม่มีอยู่ การทดสอบของฉันแสดงให้เห็นว่า Nexus5 = แนวตั้ง: 144, แนวนอน: 0 | Nexus7 = แนวตั้ง: 96, แนวนอน: 96 | Samsung Note II = แนวตั้ง: 0, แนวนอน: 0 | Samsung S10 = แนวตั้ง: 0, แนวนอน: 0
se22as

2
ฉันยังสามารถยืนยันว่าโซลูชันของ Danylo ใช้ได้กับบางรุ่นเท่านั้น โซลูชันนี้โดย Egidijus เป็นโซลูชันที่ทำงานได้ดีขึ้นและใช้ได้กับทุกรุ่นที่ทดสอบแล้ว ขอบคุณสำหรับสิ่งนี้.
Bisclavret

2
แถบสถานะเป็นส่วนหนึ่งของหน้าจอที่ใช้งานได้ของแอป
Egis

1
วิธีนี้ใช้ได้ผล แต่ไม่ได้พิจารณาว่าแถบที่มีอยู่ถูกซ่อนอยู่หรือไม่ มีความคิดอย่างไรในการตรวจสอบสิ่งนี้?
X-HuMan

1
มันไม่ทำงานกับ Android P ในขณะที่ท่าทางการนำทางเปิดอยู่แทนแถบนำทาง คุณช่วยแนะนำวิธีแก้ไขสถานการณ์นี้ให้ฉันได้ไหม
Nik

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;
}

สิ่งนี้ใช้ไม่ได้สำหรับฉัน Nexus5 คืนค่าจริงสำหรับ hasMenuKey และ hasBackKey ดังนั้นจึงคิดว่าไม่มี navBar (Nexus7 แสดงผลเท็จอย่างถูกต้องสำหรับ hasMenuKey และ hasBackKey)
se22as

5
ฉันได้ทดสอบรหัสนี้บน Nexus 5 แล้วและส่งคืนค่าเท็จสำหรับทั้งคู่ คุณแน่ใจหรือไม่ว่าคุณไม่ได้ใช้โปรแกรมจำลองหรือ ROM?
Mdlc

ใช่ฉันใช้อีมูเลเตอร์ขออภัยฉันควรชี้แจงในคำชี้แจงของฉันก่อนหน้านี้
se22as

ไม่ควรเป็นอย่างนี้! hasMenuKey || ! hasBackKey แทน! hasMenuKey &&! hasBackKey? โปรดตรวจสอบ
yongsunCN

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 จากมัน) และใช้ insets.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
นี่คือคำตอบที่ดีที่สุด ฉันรักคุณอย่างแท้จริง
Himanshu Rawat

1
นี่คือคำตอบที่แท้จริง
nyconing

1
Listener จะไม่ถูกดำเนินการเมื่อฉันเรียกสิ่งนี้จาก 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 ลงใน View เพื่อหลบแถบนำทาง ฉันรวมคำตอบบางส่วนไว้ที่นี่และสร้างประโยคพิเศษสำหรับการวางแนวนอนร่วมกับ isInMultiWindowMode กุญแจสำคัญคือการอ่านnavigation_bar_heightแต่ให้ตรวจสอบconfig_showNavigationBar ด้วยเพื่อให้แน่ใจว่าเราควรใช้ความสูงจริงๆ

ไม่มีวิธีแก้ไขปัญหาก่อนหน้านี้สำหรับฉัน สำหรับ Android 7.0 คุณต้องคำนึงถึงโหมดหลายหน้าต่าง นี่เป็นการแบ่งการใช้งานที่เปรียบเทียบdisplay.realSizeกับdisplay.sizeเนื่องจากrealSizeให้ขนาดของหน้าจอทั้งหมด (ทั้งสองหน้าต่างแยก) และขนาดจะให้ขนาดของหน้าต่างแอปของคุณเท่านั้น การตั้งค่าช่องว่างในความแตกต่างนี้จะทำให้การดูทั้งหมดของคุณเป็นช่องว่าง

/** 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

ความสูงของแถบนำทางด้านล่างคือ 48dp (ทั้งในโหมดแนวตั้งและแนวนอน) และ 42dp เมื่อวางแถบในแนวตั้ง


4
ความสูงปกติ 48dp แต่ผู้ผลิตบางรายสามารถเปลี่ยนสิ่งนั้นได้ ROM ที่กำหนดเองบางตัวสามารถเปลี่ยนแปลงได้และอื่น ๆ ควรพึ่งพาคำตอบของ Danylo ( stackoverflow.com/a/26118045/1377145 ) เพื่อให้มีขนาดที่แน่นอน
Hugo Gresse

ที่ดียิ่งขึ้น ขอบคุณ
Aritra Roy

วางในแนวตั้ง? เช่นเมื่ออยู่ในโหมดแนวนอน?
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();
}

1

ฉันแก้ไขปัญหานี้สำหรับอุปกรณ์ทั้งหมด (รวมถึง Nexus 5, Samsung Galaxy Nexus 6 edge +, Samsung S10, Samsung Note II เป็นต้น) ฉันคิดว่าสิ่งนี้จะช่วยคุณจัดการปัญหาที่ขึ้นกับอุปกรณ์ได้

ที่นี่ฉันกำลังเพิ่มรหัสสองประเภท

Java Code (สำหรับ Native 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) แล้ว
Sino Raj

รหัสต้องแก้ไข LG Nexus 5X ของฉันจะได้รับ 0.0 เนื่องจาก: [1] เมื่อscreenOrientationใดเป็นค่าเริ่มต้น hasPermanentMenuKeyจะเป็นfalseเฉพาะในกรณีที่ไม่หมุน [2] เมื่อscreenOrientationใดภูมิก็ตกไปอยู่displayMetrics.heightPixels != realDisplayMetrics.heightPixels)ที่อื่น [3] เมื่อscreenOrientationเป็นแนวตั้งคือhasPermanentMenuKey false
ผลไม้

มันใช้งานได้ แต่ใน 4.x ถ้า (hasPermanentMenuKey) เสียแสดงความคิดเห็น
djdance

เงื่อนไขของคุณควรเป็น if (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);
	}

}


หากฉันต้องการตั้งค่าความสูงของ TextView โดยทางโปรแกรมให้อยู่ด้านล่างของความสูงที่ใช้งานได้ของหน้าจอฉันจะใช้คลาสนี้ได้อย่างไร
Naveed Abbas

1

วิธีรับความสูงของแถบนำทางและแถบสถานะ รหัสนี้ทำงานสำหรับฉันในบางอุปกรณ์ Huawei และอุปกรณ์ของซัมซุง วิธีแก้ปัญหาของ 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());
}

0

นี่คือวิธีที่ฉันแก้ไขปัญหานี้ ฉันสร้างแถบด้านล่างที่ซ่อนได้ซึ่งจำเป็นต้องมีช่องว่างภายในขึ้นอยู่กับว่ามีแถบนำทางหรือไม่ (capacitive, บนหน้าจอหรือแค่อมยิ้มก่อน)


ดู

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 padding เราก็มีพฤติกรรมที่ถูกต้อง:

หลายหน้าต่างโดยไม่มีการเปลี่ยนแปลงช่องว่างการตกแต่งมุมมอง หน้าต่างเดียวที่ไม่มีการเปลี่ยนแปลงช่องว่างการตกแต่งมุมมอง

สังเกตความแตกต่างระหว่างวิธีที่แถบนำทางวางเมาส์เหนือด้านล่างของแอปในสถานการณ์เหล่านี้ โชคดีที่นี่เป็นเรื่องง่ายที่จะแก้ไข ตรวจสอบได้ว่าแอพเป็นหลายหน้าต่างหรือไม่ โค้ดด้านล่างยังรวมถึงส่วนในการคำนวณและปรับตำแหน่งของแถบเครื่องมือ (โซลูชันแบบเต็ม: 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 ไม่มีวิธีการใดที่ระบุไว้ข้างต้นได้ให้ความสูงของแถบนำทางที่เหมาะสมดังนั้นฉันจึงใช้Android ผู้ให้บริการความสูงของแป้นพิมพ์ KeyboardHeightProviderแป้นพิมพ์ผู้ให้บริการมีความสูงหุ่นยนต์และมันทำให้ฉันมีความสูงเป็นค่าลบและสำหรับการวางตำแหน่งเลย์เอาต์ฉันปรับค่านั้นในการคำนวณ

นี่คือ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);

คุณอาจได้รับมุมมองรูทโดย

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

สำหรับรายละเอียดเพิ่มเติมเกี่ยวกับการดูการอ้างอิงรูท - https://stackoverflow.com/a/4488149/9640177

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.