DeX: Dimensions.getモンキーパッチ + transform scaleで低密度ディスプレイ対応

- Dimensions.get('window')を1/1.3に縮小パッチ
- useWindowDimensions()も自動的に小さい値を返す
- DensityScaleWrapperで1.3倍transform scaleで拡大
- コンポーネントは872x531dpとしてレイアウト→実画面1133x690に拡大
This commit is contained in:
harukin-expo-dev-env
2026-03-29 23:31:26 +00:00
parent ec53d4fa2a
commit 393bcc4df3
2 changed files with 107 additions and 6 deletions

46
App.tsx
View File

@@ -1,5 +1,10 @@
import React, { useEffect } from "react";
import { Linking, Platform, UIManager } from "react-native";
import {
IS_LOW_DENSITY,
DEX_SCALE,
useRealWindowDimensions,
} from "./utils/dexDimensionOverride";
import { Linking, Platform, UIManager, View } from "react-native";
import { GestureHandlerRootView } from "react-native-gesture-handler";
import "./utils/disableFontScaling"; // グローバルなフォントスケーリング無効化
import { AppContainer } from "./Apps";
@@ -137,13 +142,42 @@ export default function App() {
<DeviceOrientationChangeProvider>
<SafeAreaProvider>
<StatusbarDetect />
<GestureHandlerRootView style={{ flex: 1 }}>
<ProviderTree>
<AppContainer />
</ProviderTree>
</GestureHandlerRootView>
<DensityScaleWrapper>
<GestureHandlerRootView style={{ flex: 1 }}>
<ProviderTree>
<AppContainer />
</ProviderTree>
</GestureHandlerRootView>
</DensityScaleWrapper>
</SafeAreaProvider>
</DeviceOrientationChangeProvider>
</AppThemeProvider>
);
}
/**
* 低密度ディスプレイDeX等で全体を transform scale で拡大。
* Dimensions.get をパッチ済みなので、コンポーネントは内部サイズに合わせてレイアウトする。
*/
function DensityScaleWrapper({ children }: { children: React.ReactNode }) {
const { width, height } = useRealWindowDimensions();
if (!IS_LOW_DENSITY) {
return <>{children}</>;
}
return (
<View style={{ width, height, overflow: "hidden" }}>
<View
style={{
width: width / DEX_SCALE,
height: height / DEX_SCALE,
transform: [{ scale: DEX_SCALE }],
transformOrigin: "top left",
}}
>
{children}
</View>
</View>
);
}

View File

@@ -0,0 +1,67 @@
/**
* Samsung DeX 等の低密度ディスプレイで、Dimensions を仮想的に縮小する。
* コンポーネントが小さい画面だと思ってレイアウトし、
* App.tsx の DensityScaleWrapper が transform scale で実画面に拡大する。
*
* 必ず App.tsx の最初にインポートすること。
*/
import { Dimensions, PixelRatio } from "react-native";
import { useState, useEffect } from "react";
const pr = PixelRatio.get();
export const IS_LOW_DENSITY = pr < 1.5;
export const DEX_SCALE = IS_LOW_DENSITY ? Math.min(1.3, 1.5 / pr) : 1;
// オリジナル関数の参照を保存
const originalGet = Dimensions.get.bind(Dimensions);
const originalAddEventListener =
Dimensions.addEventListener.bind(Dimensions);
if (IS_LOW_DENSITY) {
// Dimensions.get('window') をスケーリング
(Dimensions as any).get = (type: "window" | "screen") => {
const real = originalGet(type);
if (type === "window") {
return {
...real,
width: real.width / DEX_SCALE,
height: real.height / DEX_SCALE,
};
}
return real;
};
// change イベントもスケーリング
(Dimensions as any).addEventListener = (type: string, handler: any) => {
const wrappedHandler = (event: any) => {
if (event?.window) {
handler({
...event,
window: {
...event.window,
width: event.window.width / DEX_SCALE,
height: event.window.height / DEX_SCALE,
},
});
} else {
handler(event);
}
};
return originalAddEventListener(type, wrappedHandler);
};
}
/** パッチ前の実画面サイズを取得するhookDensityScaleWrapper用 */
export function useRealWindowDimensions() {
const [dims, setDims] = useState(() => {
const d = originalGet("window");
return { width: d.width, height: d.height };
});
useEffect(() => {
const sub = originalAddEventListener("change", ({ window }: any) => {
setDims({ width: window.width, height: window.height });
});
return () => sub?.remove();
}, []);
return dims;
}