Tablets and foldables offer plenty of screen real estate, especially when compared to mid-sized smartphones. A lot of proven user interface design patterns help you arrange the content of your app on large devices, for example Master Detail. But what if your app doesn't need all that space? This article explores how to deal with too much room.
To get an idea what I am talking about, take a look at the following screenshot.
Time Calculator can add and subtract hours, minutes, and seconds. Nothing more. So, naturally, there is not much that can take advantage of a big screen. In one screen mode, the user interface looks fine. Here's how the app looks on two screens.
The hinge of the device obstructs parts of some user interface elements. But that's not the main issue here. What I am referring to is: the app tries to respect the screen size, but this results in ridiculously large buttons.
This begs the question: Should we do this?
With such a huge virtual dial pad, entering the times to add or subtract is an unpleasant experience, because the fingers need to travel (relatively) large distances. Smaller keys would reduce the movement, presumably resulting in a more natural, fluent input.
Let's try to draw some inspiration from well known Android apps. The following screenshot was taken from a simulated 10 inch device running Android 12L. It shows the Clock app in Timer mode.
The virtual keys are somewhat big, too. But the pad as a whole appears almost centred. Let's see if this works for Time Calculator.
Not bad.
You can easily centre your existing user interface with BoxWithConstraints()
:
BoxWithConstraints(
modifier = Modifier
.fillMaxSize()
.background(color = MaterialTheme.colorScheme.tertiaryContainer),
contentAlignment = Alignment.Center
) {
Column(
modifier = Modifier
.width(min(maxWidth, 600.dp))
.background(color = MaterialTheme.colorScheme.surface)
) {
…
This way you can set a maximum width, in my example 600 density independent pixels.
So, what's our verdict? Should we go for this? While the coloured boxes to the left and to the right are not particularly pleasing, the user interface looks less inflated. And directing the attention to the centre of the screen is not a bad thing either, right?
Well, on a device with a hinge that area may be obstructed. As you can see in the screenshot, the hinge divides the main area of the app. Not pleasing at all.
Let's refine our recipe.
- On ordinary devices, do nothing
- On large screens without a hinge that obstructs content, consider centring your user interface horizontally
- On devices with an obstructing or blocking hinge, show your user interface on one side of the hinge
With Jetpack WindowManager this is quite easy. First, add an implementation dependency to your module-level build.gradle file:
implementation "androidx.window:window:1.0.0-rc01"
When the Activity is created, we need to check window bounds and some hinge-related information. This is done like this:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
lifecycleScope.launchWhenResumed {
setContent {
val li by WindowInfoTracker.getOrCreate(this@TimeCalculatorActivity)
.windowLayoutInfo(this@TimeCalculatorActivity).collectAsState(
initial = null
)
val wm = WindowMetricsCalculator.getOrCreate()
.computeCurrentWindowMetrics(this@TimeCalculatorActivity)
…
Content(li, wm)
…
Here's what Content()
does:
@Composable
fun Content(layoutInfo: WindowLayoutInfo?,
windowMetrics: WindowMetrics) {
…
val hingeDef = createHingeDef(layoutInfo, windowMetrics)
val largeScreen = windowWidthDp(windowMetrics) >= 600.dp
BoxWithConstraints(
modifier = Modifier
.fillMaxSize()
.background(color =
MaterialTheme.colorScheme.tertiaryContainer),
contentAlignment = if (hingeDef.hasGap and largeScreen)
Alignment.TopStart
else
Alignment.Center
) {
val width = min(
maxWidth, if (hingeDef.hasGap)
hingeDef.sizeLeft
else
600.dp
)
Column(
modifier = Modifier
.width(width)
.background(color = MaterialTheme.colorScheme.surface)
) {
Column(
…
As you can see, my bullet point list can be nicely implemented with a few if
s. createHingeDef()
and windowWidthDp()
are small helper functions. You can find them here. The following screenshot shows the result:
I think this looks much better. But it is not ideal yet. Because showing nothing is not very inviting, right?
Adding content
Here's how the Phone app on the Surface Duo Emulator looks like on two screens:
While it currently does a poor job in respecting the hinge, it shows us what we can do with the additional space: display helpful content. This may be:
- actions (something the user can do)
- tips or tricks
- a brief set of instructions
If your app has a settings page, you might want to show it there. But please respect established screen flows and interaction patterns. Also, refrain from diverting the user, for example by showing unrelated information and content. Under no circumstances should you show or present something your user would not expect.
How do we implement this? Fortunately, my HingeDef
class can help here, too, because it also contains the size of the gap and the size of the area to its right. Let's look at the implementation in Time Calculator.
BoxWithConstraints(
modifier = Modifier
.fillMaxSize()
.background(color = MaterialTheme.colorScheme.tertiaryContainer),
contentAlignment = if (hingeDef.hasGap and largeScreen)
Alignment.TopStart
else
Alignment.Center
) {
val width = min(
maxWidth, if (hingeDef.hasGap)
hingeDef.sizeLeft
else
600.dp
)
Row(
modifier = Modifier
.fillMaxSize()
.background(color = MaterialTheme.colorScheme.surface)
) {
Column(
modifier = Modifier
.width(width)
) {
Column(…) {
…
}
Spacer(modifier = Modifier.height(16.dp))
Column(…) {
…
}
}
Spacer(modifier = Modifier.width(hingeDef.widthGap))
Column(
modifier = Modifier
.width(hingeDef.sizeRight)
.fillMaxHeight()
.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(text = stringResource(id = R.string.info1))
Spacer(modifier = Modifier.height(16.dp))
Text(text = stringResource(id = R.string.info2))
Spacer(modifier = Modifier.height(16.dp))
Text(text = stringResource(id = R.string.info3))
Spacer(modifier = Modifier.height(16.dp))
Text(text = stringResource(id = R.string.info4))
}
}
}
So, all we do is adding three composables to a Row()
.
Conclusion
Small utilities may not have enough content to really fill large screens. You can enhance the experience by adding worthwhile additional information. However, you should never show content unrelated to the core idea of your app. And you must refrain from inventing new interaction patterns just to populate the display. Have you written small tools and faced such issues? Please share your thoughts in the comments.
Top comments (0)