Welcome to the third part of my small series about screen orientation in Jetpack Compose. So far we have an app that in portrait mode features a traditional master-detail display where either master (for example a list of modules) or detail (the contents of one module) cover the whole screen. In landscape mode the screen is divided into two parts.
The width of the list is smaller than the module. This makes sense because
- the details should get as much screen real estate as possible
- the list itself has not that much to show, because its texts are usually just parts of a sentence
At least on smartphones and traditional tablets.
Now let's see how the app looks like on an opened foldable device.
Well, that's not what we want, right?
Right.
Please remember, currently the decision whether to show portrait or landscape mode looks like this:
isTwoColumnMode = (LocalConfiguration.current.orientation
== Configuration.ORIENTATION_LANDSCAPE)
if (isTwoColumnMode)
Landscape(module)
else
Portrait(module)
Although the foldable device is open, it still is held in portrait mode.
One way to make the decision smarter is to look at the available screen width. So we might argue that if there are at least 600 density-independent pixels this is enough to show two columns. That's how alternative layout resources have been working for quite a long time.
isTwoColumnMode = LocalConfiguration.current.screenWidthDp >= 600
But what if the foldable has two screens with a noticeable gap between them?
Depending on the content an app wants to present it may make sense to treat the screen real estate as two equally sized blocks. Please recall how my example organizes its composables.
@Composable
fun Landscape(module: MutableState<Module?>) {
Row(modifier = Modifier.fillMaxSize()) {
ModuleSelection(
module = module,
modifier = Modifier.weight(weight = 0.3f)
)
module.value?.let {
Module(
module = it,
modifier = Modifier.weight(0.7f)
)
}
}
}
As you can see, Modifier.weight()
is passed 0.3f
for the module selection and 0.7f
for the module. Instead of hardcoding the values we can easily pass them as parameters.
@Composable
fun Landscape(
module: MutableState<Module?>,
weightModuleSelection: Float,
weightModule: Float
) {
Row(modifier = Modifier.fillMaxSize()) {
ModuleSelection(
module = module,
modifier = Modifier.weight(weight = weightModuleSelection)
)
module.value?.let {
Module(
module = it,
modifier = Modifier.weight(weightModule)
)
}
}
}
But how do we determine the ratio? Or, to put it another way, when do we pass 0.5F
twice?
The Jetpack Window Manager component aims to
help application developers support new device form factors
and provide a common API surface for different
Window Manager features on both old and new platform
versions.
To use it we need to add the following line to our dependencies section in the build.gradle file of the app module:
implementation "androidx.window:window:1.0.0-alpha05"
androidx.window.WindowManager
is the main interaction point with the library.
private var weightModuleSelection by mutableStateOf(0.3F)
private var weightModule by mutableStateOf(0.7F)
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val wm = WindowManager(this)
wm.registerLayoutChangeCallback(mainExecutor, {
it.displayFeatures.forEach { feature ->
(feature as FoldingFeature).run {
if (isSeparating) {
weightModuleSelection = 0.5F
weightModule = 0.5F
}
}
}
})
setContent {
ScreenSizeDemo()
}
}
}
After obtaining an instance of WindowManager
we register a callback for layout changes of the window of the current visual Context
. The callback receives WindowLayoutInfo
objects, which in turn contain a list of DisplayFeature
s. According to the doc, a...
display feature is a distinctive physical attribute located
within the display panel of the device. It can intrude into
the application window space and create a visual
distortion, visual or touch discontinuity, make some area
invisible or create a logical divider or separation in the
screen space.
FoldingFeature
implements this interface. It
describes a fold in the flexible display or a hinge
between two physical display panels.
isSeparating
returns true
if...
a
FoldingFeature
should be thought of as splitting the
window into multiple physical areas that can be seen by
users as logically separate. Display panels connected by
a hinge are always separated. Folds on flexible screens
should be treated as separating when they are not
STATE_FLAT
.
On the Surface Duo Emulator the example looks as follows:
On the stock Android Emulator simulating a foldable the app looks like this:
While both results look quite pretty we have a few thing to do. Can you guess what? Please stay tuned for a follow-up.
Top comments (0)