以下のスライドを記事に書き起こしたものです。
結論
画面幅をpxの単位で欲しいときは、Configuration.screenWidthDpをpxに変換すると正確な値が取れません。
BoxWithConstraintsScope.maxWidthにtoPxをかけたもの、またはResources.getSystem().displayMetrics.widthを使いましょう。
前提知識
dp、px、dpiの知識がある方がより理解しやすいので、以下の記事などをみてみるといいかもしれません。
https://qiita.com/nein37/items/0a92556a80c6c14503b2
screenWidthDpについて
dp単位で表示可能な画面領域の幅のことです。(WindowInsetを除いた値)
取得方法は以下の二つです。
Resources.getSystem().configuration.screenWidthDp
LocalConfiguration.current.screenWidthDp
何が落とし穴?
screenWidhDpはFloatではなく、Intで保存されている点に気を付ける必要があります。
(BoxWithConstraintsScope.maxWidthはFloat)
Intなので、不動小数点のない数値である必要があります。ここでDPを求める式をみてみると、画面幅(px) * 160 / dpiになっていますが、割り切れなさそうですよね、、??
実際、Pixel6では
横幅 → 1080px
dpi → 420
で、先ほどの計算式に当てはめてみると
1080 * 160 / 420 = 411.4285
となり、割り切れておらず、小数点以下にも数字が漏れていることがわかります。
ですがScreenWidthDpはIntのため、端数が切り落とされ411で保存されてしまいます。
screenWidthDpをもとにpxを作ると?
では実際にscreenWidthDpをもとにpxを作ってみましょう。
取得元によって、方法は分かれます。LocalConfigurationから取得している場合は、Composableのスコープの中なのでtoPX()が使えるので、以下のようにすると取得できます。
LocalConfiguration.current.screenWidthDp.dp.toPx()
Resourcesから取得している場合は、公式Documentに掲載されている公式を変形させて以下のように計算すると取得できます。
Resources.getSystem().configuration.screenWidthDp
*
resources.displayMetrics.densityDpi / 160
結果は1078.875となり、本来の1080とずれてしまっていることがわかります。
1/1000 px程度の微量な差分ではあり、ユーザーに対して大きな影響を与えるものではありませんが、正確な方法ではないことは確かです。
改めて
px単位で正確な画面横幅を知りたいときは、
Resources.getSystem().displayMetrics.widthPixels
BoxWithConstraintsScope.maxWidthをpxに変換
を使うと良さそうです。