日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網(wǎng)為廣大站長提供免費收錄網(wǎng)站服務(wù),提交前請做好本站友鏈:【 網(wǎng)站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(wù)(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網(wǎng)站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

回顧過去

說起獲取屏幕高度,不知道你是如何理解這個高度范圍的?是以應(yīng)用顯示區(qū)域高度作為屏幕高度還是手機屏幕的高度。

那么我們先看一下平時使用獲取高度的方法:

public static int getScreenHeight(Context context) {
    WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
    Display display = wm.getDefaultDisplay();
    DisplayMetrics dm = new DisplayMetrics();
    display.getMetrics(dm);
    return dm.heightPixels;
}

//或
public static int getScreenHeight(Context context) {
    WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
    Point point = new Point();
    wm.getDefaultDisplay().getSize(point);
    return point.y;
}

// 或
public static int getScreenHeight(Context context) {
    return context.getResources().getDisplayMetrics().heightPixels;
}
// 貌似還有更多的方法

以上三種效果一致,只是寫法略有不同。

當(dāng)然你或許使用的是這種:

public static int getScreenHeight(Context context) {
    WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
    Display display = wm.getDefaultDisplay();
    DisplayMetrics dm = new DisplayMetrics();
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
        display.getRealMetrics(dm);
    } else {
        display.getMetrics(dm);
    }
    return dm.heightPixels;
}
// 其他幾種寫法大同小異
...

這個方法判斷了系統(tǒng)大于等于Android 4.2時,使用getRealMetrics(getRealSize)來獲取屏幕高度。那么這里發(fā)生了什么,為什么會這樣?

其實在Andoird 4.0時,引入了虛擬導(dǎo)航鍵,如果你繼續(xù)使用getMetrics之類的方式,獲取的高度是去除了導(dǎo)航欄的高度的。

當(dāng)時因為在4.0和4.2之間還沒有的getRealMetrics這個方法,所以甚至需要添加下面的適配代碼:

try {
     heightPixels = (Integer) Display.class.getMethod("getRawHeight").invoke(display);
} catch (Exception e) {
}

現(xiàn)在不會還有人適配4.4甚至5.0一下的機子了吧,不會吧不會吧。。。所以歷史的包袱可以去掉了。

資深架構(gòu)師:深入聊聊獲取屏幕高度這件事

 

上面方法名都是getScreenHeight,可是這個高度范圍到底和你需要的是否一致。這個需要開發(fā)時注意,我的習(xí)慣是ScreenHeight對應(yīng)用顯示的高度,不包括導(dǎo)航欄(非全屏下),RealHeight來指包含導(dǎo)航欄和狀態(tài)欄的高度(getRealMetrics)。

PS:以前也使用過AndroidUtilCode這個工具庫,里面將前者方法名定義為getAppScreenHeight,后者為getScreenHeight。也是很直觀的方法。

下文中我會以自己的習(xí)慣,使用ScreenHeight和RealHeight來代表兩者。

我印象中華為手機很早就使用了虛擬導(dǎo)航鍵,如下圖(圖片來源):

資深架構(gòu)師:深入聊聊獲取屏幕高度這件事

 

比較特別的是,當(dāng)時華為的導(dǎo)航欄還可以顯示隱藏,注意圖中左下角的箭頭。點擊可以隱藏,上滑可以顯示。即使這樣,使用getScreenHeight也可以準(zhǔn)確獲取高度,隱藏了ScreenHeight就等于RealHeight。

上述的這一切在“全面屏”時代沒有到來之前,沒有什么問題。

立足當(dāng)下

 

小米MIX的發(fā)布開啟了全面屏?xí)r代(16年底),以前的手機都是16:9的,記得雷布斯在發(fā)布會上說過,他們費了很大的力氣說服了谷歌去除了16:9的限制(從Android 7.0開始)

資深架構(gòu)師:深入聊聊獲取屏幕高度這件事

 


資深架構(gòu)師:深入聊聊獲取屏幕高度這件事

 

全面屏手機是真的香,不過隨之也帶來適配問題。首當(dāng)其沖的就是劉海屏,各家有各自的獲取劉海區(qū)域大小的方法。主要原因還是國內(nèi)競爭的激烈,各家為了搶占市場,先于谷歌定制了自己的方案。這一點讓人想起了萬惡的動態(tài)權(quán)限適配。。。

其實在劉海屏之下,還隱藏一個導(dǎo)航欄的顯示問題,也就是本篇的重點。全面屏追求更多的顯示區(qū)域,隨之帶來了手勢操作。在手勢操作模式下,導(dǎo)航欄是隱藏狀態(tài)。

本想著可以和上面提到的華為一樣,隱藏獲取的就是RealHeight,顯示就是減去導(dǎo)航欄高度的ScreenHeight。然而現(xiàn)實并不是這樣,下表是我收集的一些全面屏手機各高度的數(shù)據(jù)。

資深架構(gòu)師:深入聊聊獲取屏幕高度這件事

 

ScreenHeight一欄中括號內(nèi)表示顯示導(dǎo)航欄時獲取的屏幕高度。

大致的規(guī)律總結(jié)如下:

  • 在有劉海的手機上,ScreenHeight不包含狀態(tài)欄高度。
  • 小米手機在隱藏顯示導(dǎo)航欄時,ScreenHeight不變,且不包含導(dǎo)航欄高度。

其中vivo手機,屏幕高度加狀態(tài)欄高度大于真實高度(2201 + 84 > 2280)。本以為差值79是劉海高度,但查看vivo文檔后發(fā)現(xiàn),vivo劉海固定27dp(81px),也還是對不上。。。

一加6最奇怪,有三種設(shè)置模式。使用側(cè)邊全屏手勢時,手勢底部有一個小條,NavigationBar高度變?yōu)?2。(2159 + 42 = 2075 + 126 = 2201)也就是說這種模式也屬于有導(dǎo)航欄的情況。

資深架構(gòu)師:深入聊聊獲取屏幕高度這件事

 

這時如果你需要獲取準(zhǔn)確的ScreenHeight,只有通過RealHeight - NavigationBar來實現(xiàn)了。

所以首先需要判斷當(dāng)前導(dǎo)航欄是否顯示,再來決定是否減去NavigationBar高度。

先看看老牌的判斷方法如下:

public boolean isNavigationBarShow(){
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
        Display display = getWindowManager().getDefaultDisplay();
        Point size = new Point();
        Point realSize = new Point();
        display.getSize(size);
        display.getRealSize(realSize);
        return realSize.y!=size.y;
    } else {
        boolean menu = ViewConfiguration.get(this).hasPermanentMenuKey();
        boolean back = KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_BACK);
        if(menu || back) {
            return false;
        }else {
            return true;
        }
    }
}

此方法通過比較ScreenHeight和RealHeight是否相等來判斷。如果對比上面表中的數(shù)據(jù),那只有OPPO Find X可以判斷成功。也有一些方法通過ScreenHeight和RealHeight差值來計算導(dǎo)航欄高度。顯然這些方法已無法再使用。

所以搜索了一下相關(guān)信息,得到了下面的代碼:

    /**
     * 是否隱藏了導(dǎo)航鍵
     *
     * @param context
     * @return
     */
    public static boolean isNavBarHide(Context context) {
        try {
            String brand = Build.BRAND;
            // 這里做判斷主要是不同的廠商注冊的表不一樣
            if (!StringUtils.isNullData(brand) && (Rom.isVivo() || Rom.isOppo())) {
                return Settings.Secure.getInt(context.getContentResolver(), getDeviceForceName(), 0) != 0;
            } else if (!StringUtils.isNullData(brand) && Rom.isNokia()) {
                //甚至 nokia 不同版本注冊的表不一樣, key 還不一樣。。。
                return Settings.Secure.getInt(context.getContentResolver(), "swipe_up_to_switch_apps_enabled", 0) == 1
                        || Settings.System.getInt(context.getContentResolver(), "navigation_bar_can_hiden", 0) != 0;
            } else
                return Settings.Global.getInt(context.getContentResolver(), getDeviceForceName(), 0) != 0;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }

    /**
     * 各個手機廠商注冊導(dǎo)航鍵相關(guān)的 key
     *
     * @return
     */
    public static String getDeviceForceName() {
        String brand = Build.BRAND;
        if (StringUtils.isNullData(brand))
            return "navigationbar_is_min";
        if (brand.equalsIgnoreCase("HUAWEI") || "HONOR".equals(brand)) {
            return "navigationbar_is_min";
        } else if (Rom.isMiui()||Rom.check("XIAOMI")) {
            return "force_fsg_nav_bar";
        } else if (Rom.isVivo()) {
            return "navigation_gesture_on";
        } else if (Rom.isOppo()) {
            return "hide_navigationbar_enable";
        } else if (Rom.check("samsung")) {
            return "navigationbar_hide_bar_enabled";
        } else if (brand.equalsIgnoreCase("Nokia")) {
            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
                return "navigation_bar_can_hiden";
            } else {
                return "swipe_up_to_switch_apps_enabled";
            }
        } else {
            return "navigationbar_is_min";
        }
    }

可以看到包含了華為、小米、vivo、oppo 、三星甚至諾基亞的判斷。這就是適配的現(xiàn)實狀況,不要妄想尋找什么通用方法,老老實實一個個判斷吧。畢竟幺蛾子就是這些廠家搞出來的,廠家魔改教你做人。

這種方法在上面的測試機中都親測準(zhǔn)確有效。

不過這個判斷方法不夠嚴(yán)謹(jǐn),比如其他品牌手機使用此方法,那么結(jié)果都是false。用這樣的結(jié)果來計算高度顯得不夠嚴(yán)謹(jǐn)。

根據(jù)前面提到問題發(fā)生的原因是全面屏帶來的(7.0及以上)。所以我們可以先判斷是否是全面屏手機(屏幕長寬比例超過1.86以上),然后判斷是否顯示導(dǎo)航欄,對于不確定的機型,我們還是使用原先的ScreenHeight。盡量控制影響范圍。

我整理的代碼如下(補充了一加、錘子手機判斷):

/**
 * @author weilu
 **/
public class ScreenUtils {

    private static final String BRAND = Build.BRAND.toLowerCase();

    public static boolean isXiaomi() {
        return Build.MANUFACTURER.toLowerCase().equals("xiaomi");
    }

    public static boolean isVivo() {
        return BRAND.contains("vivo");
    }

    public static boolean isOppo() {
        return BRAND.contains("oppo") || BRAND.contains("realme");
    }

    public static boolean isHuawei() {
        return BRAND.contains("huawei") || BRAND.contains("honor");
    }

        public static boolean isOneplus(){
        return BRAND.contains("oneplus");
    }

    public static boolean isSamsung(){
        return BRAND.contains("samsung");
    }

    public static boolean isSmartisan(){
        return BRAND.contains("smartisan");
    }

    public static boolean isNokia() {
        return BRAND.contains("nokia");
    }

        public static boolean isgoogle(){
        return BRAND.contains("google");
    }

    public static int getRealScreenHeight(Context context) {
        WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        Display display = wm.getDefaultDisplay();
        DisplayMetrics dm = new DisplayMetrics();
        display.getRealMetrics(dm);
        return dm.heightPixels;
    }

    public static int getRealScreenWidth(Context context) {
        WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        Display display = wm.getDefaultDisplay();
        DisplayMetrics dm = new DisplayMetrics();
        display.getRealMetrics(dm);
        return dm.widthPixels;
    }

    public static int getScreenHeight(Context context) {
        WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        Display display = wm.getDefaultDisplay();
        DisplayMetrics dm = new DisplayMetrics();
        display.getMetrics(dm);
        return dm.heightPixels;
    }

    /**
     * 判斷設(shè)備是否顯示NavigationBar
     *
     * @return 其他值 不顯示 0顯示 -1 未知
     */
    public static int isNavBarHide(Context context) {
        // 有虛擬鍵,判斷是否顯示
        if (isVivo()) {
            return vivoNavigationEnabled(context);
        }
        if (isOppo()) {
            return oppoNavigationEnabled(context);
        }
        if (isXiaomi()) {
            return xiaomiNavigationEnabled(context);
        }
        if (isHuawei()) {
            return huaWeiNavigationEnabled(context);
        }
                if (isOneplus()) {
            return oneplusNavigationEnabled(context);
        }
        if (isSamsung()) {
            return samsungNavigationEnabled(context);
        }
        if (isSmartisan()) {
            return smartisanNavigationEnabled(context);
        }
        if (isNokia()) {
            return nokiaNavigationEnabled(context);
        }
            if (isGoogle()) {
            // navigation_mode 三種模式均有導(dǎo)航欄,只是高度不同。
            return 0;
        }
        return -1;
    }

    /**
     * 判斷當(dāng)前系統(tǒng)是使用導(dǎo)航鍵還是手勢導(dǎo)航操作
     *
     * @param context
     * @return 0 表示使用的是虛擬導(dǎo)航鍵,1 表示使用的是手勢導(dǎo)航,默認(rèn)是0
     */
    public static int vivoNavigationEnabled(Context context) {
        return Settings.Secure.getInt(context.getContentResolver(), "navigation_gesture_on", 0);
    }

    public static int oppoNavigationEnabled(Context context) {
        return Settings.Secure.getInt(context.getContentResolver(), "hide_navigationbar_enable", 0);
    }

    public static int xiaomiNavigationEnabled(Context context) {
        return Settings.Global.getInt(context.getContentResolver(), "force_fsg_nav_bar", 0);
    }

    private static int huaWeiNavigationEnabled(Context context) {
        return Settings.Global.getInt(context.getContentResolver(), "navigationbar_is_min", 0);
    }

    /**
     * @param context
     * @return 0虛擬導(dǎo)航鍵  2為手勢導(dǎo)航
     */
    private static int oneplusNavigationEnabled(Context context) {
        int result = Settings.Secure.getInt(context.getContentResolver(), "navigation_mode", 0);
        if (result == 2) {
            // 兩種手勢 0有按鈕, 1沒有按鈕
            if (Settings.System.getInt(context.getContentResolver(), "buttons_show_on_screen_navkeys", 0) != 0) {
                return 0;
            }
        }
        return result;
    }

    public static int samsungNavigationEnabled(Context context) {
        return Settings.Global.getInt(context.getContentResolver(), "navigationbar_hide_bar_enabled", 0);
    }

    public static int smartisanNavigationEnabled(Context context) {
        return Settings.Global.getInt(context.getContentResolver(), "navigationbar_trigger_mode", 0);
    }

    public static int nokiaNavigationEnabled(Context context) {
        boolean result = Settings.Secure.getInt(context.getContentResolver(), "swipe_up_to_switch_apps_enabled", 0) != 0
                || Settings.System.getInt(context.getContentResolver(), "navigation_bar_can_hiden", 0) != 0;

        if (result) {
            return 1;
        } else {
            return 0;
        }
    }

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

    private static boolean isAllScreenDevice(Context context) {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
            // 7.0放開限制,7.0以下都不為全面屏
            return false;
        } else {
            int realWidth = getRealScreenWidth(context);
            int realHeight = getRealScreenHeight(context);

            float width;
            float height;
            if (realWidth < realHeight) {
                width = realWidth;
                height = realHeight;
            } else {
                width = realHeight;
                height = realWidth;
            }
            // Android中默認(rèn)的最大屏幕縱橫比為1.86
            return height / width >= 1.86f;
        }
    }

    /**
     * 獲取去除導(dǎo)航欄高度的剩余高度(含狀態(tài)欄)
     * @param context
     * @return
     */
    public static int getScreenContentHeight(Context context) {

        if (isAllScreenDevice(context)) {

            int result = isNavBarHide(context);

            if (result == 0) {
                return getRealScreenHeight(context) - getNavigationBarHeight(context);
            } else if (result == -1){
                // 未知
                return getScreenHeight(context);
            } else {
                return getRealScreenHeight(context);
            }
        } else {
            return getScreenHeight(context);
        }

    }
}

有人會問,這些key都是哪里來的?畢竟我在廠商文檔也沒有翻到。

我能想到的辦法是查看SettingsProvider,它是提供設(shè)置數(shù)據(jù)的Provider,分有Global、System、Secure三種類型,上面代碼中可以看到不同品牌存放在的類型都不同。我們可以通過adb命令查看所有數(shù)據(jù),根據(jù)navigation等關(guān)鍵字去尋找。比如查看Secure的數(shù)據(jù):

    adb shell settings list secure

或者:

    ContentResolver cr = context.getContentResolver();
    Uri uri = Uri.parse("content://settings/secure/");
    Cursor cursor = cr.query(uri, null, null, null, null);
    while (cursor.moveToNext()) {
        String name = cursor.getString(cursor.getColumnIndex("name"));
        String value = cursor.getString(cursor.getColumnIndex("value"));
        Log.d("settings:", name + "=" + value);
    }
    cursor.close();     

這樣如果有上面兼容不到的機型,可以使用這個方法適配。也歡迎你的補充反饋。

費了這么大的勁獲取到了準(zhǔn)確的高度,可能你會說,還不如直接獲取ContentView的高度:

    public static int getContentViewHeight(Activity activity) {
        View contentView = activity.getWindow().getDecorView().findViewById(android.R.id.content);
        return contentView.getHeight();
    }

這個結(jié)果和上述計算的高度一致,唯一的限制是需要在onWindowFocusChanged之后調(diào)用,否則高度為0。這個我們可以根據(jù)實際情況自行選用。

已知問題

  • 網(wǎng)上有許多同類代碼,發(fā)現(xiàn)會將vivo和oppo都使用navigation_gesture_on這一個key。我在oppo Find x中發(fā)現(xiàn)此key并不存在,不知是否和系統(tǒng)版本有關(guān)。如果是的話,又需要判斷oppo的系統(tǒng)版本了。
  • 上面提到的獲取導(dǎo)航欄高度的方法在部分手機中無效,無效的原因是因為導(dǎo)航欄隱藏時,獲取高度就為0。所以判斷是否顯示導(dǎo)航欄是關(guān)鍵。
  • 劉海的出現(xiàn),很多人會吐槽丑,所以廠家想到了隱藏劉海的方式(掩耳盜鈴),比如下面是Redmi K30的設(shè)置頁面:
資深架構(gòu)師:深入聊聊獲取屏幕高度這件事

設(shè)置劉海顯示頁

第二種沒啥特別,就是狀態(tài)欄強制為黑色。這里我懷疑因為這個設(shè)置,導(dǎo)致在有劉海的手機上,ScreenHeight不包含狀態(tài)欄高度。

最糟糕的是第三種,隱藏后狀態(tài)欄在劉海外。例如Redmi K30在開啟后,ScreenHeight 為2174,RealHeight為2304,而關(guān)閉時為2175 和 2400。這下連萬年不變的RealHeight也變化了,這太不real了,大家自行體會。不過目前發(fā)現(xiàn)未影響適配方案,不知其他手機如何。

對于是否隱藏劉海,其實也是有各家的判斷的,比如小米:

    // 0:顯示劉海,1:隱藏劉海
    Settings.Global.getInt(context.getContentResolver(), "force_black", 0);
  • 有些App會使用修改density的屏幕適配方案,這會影響獲取導(dǎo)航欄高度的方法。比如130px的導(dǎo)航欄適配后獲取到的是136px。所以這里需要使用getSystem中的density轉(zhuǎn)換回去:
    public static int getNavigationBarHeight(Context context){
        int resourceId = context.getResources().getIdentifier("navigation_bar_height", "dimen", "android");
        if (resourceId > 0) {
            int height = context.getResources().getDimensionPixelSize(resourceId);
            // 兼容屏幕適配導(dǎo)致density修改
            float density = context.getResources().getDisplayMetrics().density;
            if (DENSITY != density) {
                return dpToPx(px2dp(context, height));
            }
            return height;
        }
        return 0;
    }

    public static final float DENSITY = Resources.getSystem().getDisplayMetrics().density;

    public static int dpToPx(int dpValue) {
        return (int) (dpValue * DENSITY + 0.5f);
    }

    public static int px2dp(Context context, int px) {
        return (int) (px / context.getResources().getDisplayMetrics().density + 0.5);
    }

getSystem源碼如下:

    /**
     * Return a global shared Resources object that provides access to only
     * system resources (no application resources), is not configured for the
     * current screen (can not use dimension units, does not change based on
     * orientation, etc), and is not affected by Runtime Resource Overlay.
     */
    public static Resources getSystem() {
        synchronized (sSync) {
            Resources ret = mSystem;
            if (ret == null) {
                ret = new Resources();
                mSystem = ret;
            }
            return ret;
        }
    }

它不受資源覆蓋的影響,我們可以通過它將值轉(zhuǎn)換回來。

展望未來

本篇看似聊的獲取高度這件事,其實伴隨導(dǎo)航欄的發(fā)展演進(jìn),核心是是如何判斷導(dǎo)航欄是否顯示。

通過上面的介紹,總結(jié)一下就是在“全面屏?xí)r代”,如果你想獲取屏幕高度,就不要使用ScreenHeight了。否則會出現(xiàn)UI展示上的問題。而且這種問題,線上也不會崩潰,難以發(fā)現(xiàn)。以前在支付寶中就發(fā)現(xiàn)過 PopupWindow彈出高度不正確的問題,過了好久才修復(fù)了。

至于屏幕寬度,也不清楚隨著折疊屏、環(huán)繞屏的到來會不會造成影響。但愿不要吧,碎片化越來越嚴(yán)重了。。。

最后,如果本文對你有啟發(fā)有幫助,點個贊可好?

分享到:
標(biāo)簽:獲取 屏幕 高度
用戶無頭像

網(wǎng)友整理

注冊時間:

網(wǎng)站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網(wǎng)站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網(wǎng)站吧!
最新入駐小程序

數(shù)獨大挑戰(zhàn)2018-06-03

數(shù)獨一種數(shù)學(xué)游戲,玩家需要根據(jù)9

答題星2018-06-03

您可以通過答題星輕松地創(chuàng)建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學(xué)四六

運動步數(shù)有氧達(dá)人2018-06-03

記錄運動步數(shù),積累氧氣值。還可偷

每日養(yǎng)生app2018-06-03

每日養(yǎng)生,天天健康

體育訓(xùn)練成績評定2018-06-03

通用課目體育訓(xùn)練成績評定