首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Android 獲取螢幕/視圖寬高完整方案:DisplayMetrics、WindowManager、View 生命週期與最佳實踐

     編輯:Java 相關知識

注意:原文章標題為「Java 反射靜態調用」,但內容實為 Android 獲取螢幕/視圖尺寸。本文依實際內容重寫並補充現代 API (WindowMetrics、ViewBinding) 與最佳實踐。

在 Android 開發中,「獲取寬高」看似簡單,實則暗藏 生命週期時機、API 版本碎片化、多窗口/折疊屏適配、單位換算 等陷阱。本文系統梳理螢幕尺寸、視圖尺寸、DP/PX 轉換的完整解決方案。

一、螢幕尺寸 (設備物理/邏輯像素)

現代 API (API 30+ / Android 11+)

// Activity/Context 內
WindowMetrics metrics = getWindowManager().getCurrentWindowMetrics();
Rect bounds = metrics.getBounds(); // 包含狀態欄、導航欄、裝飾區
int width = bounds.width();
int height = bounds.height();

// 獲取可用區域 (扣除系統欄)
WindowInsets insets = metrics.getWindowInsets();
Insets systemInsets = insets.getInsetsIgnoringVisibility(WindowInsets.Type.systemBars());
int usableWidth = width - systemInsets.left - systemInsets.right;
int usableHeight = height - systemInsets.top - systemInsets.bottom;

傳統 API (API 1-29,兼容模式)

// 1. DisplayMetrics (最常用)
DisplayMetrics dm = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(dm);
int widthPx = dm.widthPixels;
int heightPx = dm.heightPixels;
float density = dm.density; // dp = px / density

// 2. 獲取真實物理尺寸 (含狀態欄/導航欄) - API 17+
DisplayMetrics realDm = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getRealMetrics(realDm);
int realWidth = realDm.widthPixels;
int realHeight = realDm.heightPixels;

折疊屏/多窗口適配關鍵

  • 不要緩存螢幕尺寸:折疊屏展開/摺疊、分屏模式下尺寸動態變化。
  • 監聽配置變更:onConfigurationChanged (Activity) 或 WindowSizeClassifier (Jetpack WindowManager)。
  • 使用 WindowManager 庫:WindowMetricsCalculator.getOrCreate(context).computeCurrentWindowMetrics(activity) 統一跨版本 API。

二、視圖尺寸

核心原則:必須在 Layout 完成後獲取

最佳實踐代碼

Java - 自定義 View 內部

@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    super.onSizeChanged(w, h, oldw, oldh);
    // w, h 即為最終測量寬高,在此初始化繪製參數、動畫等
    initDrawing(w, h);
}

Java - Activity/Fragment 中獲取任意 View 尺寸

view.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
    @Override
    public void onGlobalLayout() {
        // 重要:移除監聽器防止重複觸發 (API 16+)
        view.getViewTreeObserver().removeOnGlobalLayoutListener(this);
        int width = view.getWidth();
        int height = view.getHeight();
        // 使用尺寸...
    }
});

Kotlin - KTX 擴展函數 (最簡潔)

// 在 onViewCreated / onResume 等生命週期中
view.doOnNextLayout { v ->
    val width = v.width
    val height = v.height
    // 使用尺寸...
}

// 或針對 RecyclerView item 等動態 View
view.doOnLayout { v -> ... } // 每次 layout 都觸發

三、DP / PX / SP 單位轉換

// Context 擴展函數 (Kotlin)
fun Context.dp2px(dp: Float) = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, resources.displayMetrics).toInt()
fun Context.px2dp(px: Float) = px / resources.displayMetrics.density
fun Context.sp2px(sp: Float) = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp, resources.displayMetrics).toInt()

// Java 工具類
public static int dp2px(Context ctx, float dp) {
    return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, ctx.getResources().getDisplayMetrics());
}
public static int px2dp(Context ctx, float px) {
    return (int) (px / ctx.getResources().getDisplayMetrics().density);
}

四、常見陷阱總結表

時機方法可靠性適用場景
onCreate/onStart/onResumeview.getWidth()❌ 總是 0不可用
onWindowFocusChanged(true)view.getWidth()✅ 可靠傳統方案,但可能多次觸發
ViewTreeObserver.OnGlobalLayoutListenerview.getWidth()✅ 可靠**推薦通用方案**
View.onSizeChanged(w,h,oldw,oldh)參數 w,h✅ 最準確**自定義 View 內部首選**
View.doOnNextLayout / doOnLayout (KTX)lambda 中獲取✅ 簡潔**Kotlin 專案首選**
陷阱錯誤做法正確做法
onCreate 獲取 View 寬高view.getWidth()ViewTreeObserver / onSizeChanged / doOnNextLayout
硬編碼螢幕尺寸1080x1920動態獲取 + 多窗口/折疊屏監聽
忽略狀態欄/導航欄getMetrics(dm) 直接用WindowInsets / getRealMetrics / WindowMetrics 區分
DP/PX 手動乘除 densitypx = (int)(dp * density)TypedValue.applyDimension (處理縮放、四捨五入)
跨進程/系統服務獲取WindowManagerService 內部 API公開 API: WindowManager / DisplayMetrics / WindowMetrics
假設寬高固定靜態常量ConfigurationChanged / WindowSizeClass 適配

五、Jetpack WindowManager 庫 (現代標準)

dependencies {
    implementation "androidx.window:window:1.2.0"
    implementation "androidx.window:window-java:1.2.0" // Java 專用 API
}

// 獲取當前窗口指標
WindowMetricsCalculator calculator = WindowMetricsCalculator.getOrCreate(context);
WindowMetrics metrics = calculator.computeCurrentWindowMetrics(activity);
Rect bounds = metrics.getBounds();
// 獲取折疊屏狀態
WindowLayoutInfo layoutInfo = calculator.computeMaximumWindowMetrics(activity);
// 監聽變更
lifecycleScope.launch {
    lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
        WindowInfoTracker.getOrCreate(context)
            .windowLayoutInfo(activity)
            .collect { info -> /* 適配折疊/鉸鏈 */ }
    }
}

總結

  1. 螢幕尺寸:API 30+ 用 WindowMetrics,舊版用 DisplayMetrics,區分「含系統欄」與「可用區域」。
  2. 視圖尺寸:絕不在 onCreate 獲取。自定義 View 用 onSizeChanged,外部觀察用 ViewTreeObserver 或 KTX doOnNextLayout。
  3. 單位轉換:統一用 TypedValue.applyDimension。
  4. 折疊屏/多窗口:引入 androidx.window 庫,監聽 WindowLayoutInfo 動態適配。

尺寸獲取的本質是「在正確的生命週期時機,用正確的 API,處理正確的邊界情況」。建立團隊統一的尺寸工具類,封裝版本兼容與生命週期邏輯,是杜絕「寬高為 0」Bug 的根本之道。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved