Reputation: 7046
Is there any Orientation helpers class in Jetpack Compose, like Flutter has https://flutter.dev/docs/cookbook/design/orientation ?? I need to know when orientation have been changed to adjust my layout properly.
Upvotes: 49
Views: 37959
Reputation: 11477
We can use state in jectpack compose, so that a composable re-composes itself when the state changes.
An example of listening to the configuration change
using state
is follows:-
@Composable
fun ShowConfig(config: String) {
Text(text = "$config!")
}
Keep a config state
in activity:-
var orientation by mutableStateOf("Portrait")
Then listen to the config changes in the activity and on configuration just update the state by the value like:-
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
orientation = "Landscape" // this will automatically change the text to landscape
} else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT){
orientation = "Portrait" // this will automatically change the text to portrait
}
}
The Greeting composable observes the state of the config string whenever the state of the config string changes it re-composes.
Upvotes: 5
Reputation: 11
just a simplified clean method for compose:
@Composable
fun isLandscape(): Boolean = when (LocalConfiguration.current.orientation){
Configuration.ORIENTATION_LANDSCAPE -> true
else -> false
}
Upvotes: 0
Reputation: 11622
We can use LocalConfiguration
to determine what the current orientation is, but the following code does not help to listen for configuration changes:
@Composable
fun ConfigChangeExample() {
val configuration = LocalConfiguration.current
when (configuration.orientation) {
Configuration.ORIENTATION_LANDSCAPE -> {
Text("Landscape")
}
else -> {
Text("Portrait")
}
}
}
Upvotes: 95
Reputation: 21
You can change values dynamically with this method:
@Composable
fun <T> dynamicOrientationValue(portraitValue: T, landscapeValue: T): T =
when (LocalConfiguration.current.orientation) {
Configuration.ORIENTATION_LANDSCAPE -> landscapeValue
else -> portraitValue
}
Usage:
SpinBtn(
modifier = Modifier
.fillMaxWidth(dynamicOrientationValue(0.6f, 0.3f))
.align(dynamicOrientationValue(BottomCenter, CenterEnd))
.padding(32.dp), isSpinning = isSpinning
) { isSpinning = true }
Upvotes: 2
Reputation: 469
var orientation by remember { mutableStateOf(Configuration.ORIENTATION_PORTRAIT) }
LaunchedEffect(configuration) {
snapshotFlow { configuration.orientation }
.collect { orientation = it }
}
when (orientation) {
Configuration.ORIENTATION_LANDSCAPE -> {
LandScapeScreen(
isPortrait = false,
) {
activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
orientation = Configuration.ORIENTATION_PORTRAIT
}
}
else -> {
PortraitUI(
isPortrait = true,
onClick = {
activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
orientation = Configuration.ORIENTATION_LANDSCAPE
},
)
}
}
Upvotes: 0
Reputation: 4166
You can get it from BoxWithConstraints
and compare the width with the height.
Simple example:
@Composable
fun ShowScreenOrientation() {
BoxWithConstraints {
val mode = remember { mutableStateOf("") }
mode.value = if (maxWidth < maxHeight) "Portrait" else "Landscape"
Text(text = "Now it is in ${mode.value} mode")
}
}
Upvotes: 3
Reputation: 938
Adding an updated answer based on the new androidx.compose.material3.windowsizeclass
library.
Instead of detecting orientation directly, you can now use WindowSizeClass
to query the width and height classes. This library is specifically meant for building adaptive layouts and will automatically recompose the layout when your activity's size/orientation changes.
This API gives you the rough size of your width and height (COMPACT
, MEDIUM
, EXPANDING
). This makes it easy to handle devices like foldables and large screen displays. This also takes into account phones using split screen (orientation remains same) and other options which cannot be handled by a simple orientation check.
Here is a simple example I made:-
class MainActivity {
/* ...... */
setContent {
val windowSizeClass = calculateWindowSizeClass(this)
/* .... */
MyApp(windowWidthSizeClass = windowSizeClass.widthSizeClass, /* .... */ )
}
}
@Composable
fun MyApp(windowWidthSizeClass: WindowWidthSizeClass, /* ... */) {
when(windowWidthSizeClass) {
WindowWidthSizeClass.Expanded -> // orientation is landscape in most devices including foldables (width 840dp+)
WindowWidthSizeClass.Medium -> // Most tablets are in landscape, larger unfolded inner displays in portrait (width 600dp+)
WindowWidthSizeClass.Compact -> // Most phones in portrait
}
}
Here, I can set the layout to a landscape view when windowWidthSizeClass
is equal to WindowWidthSizeClass.Expanded
. I can also use the WindowWidthSizeClass.Medium
width to optimize my layout for larger devices like tablets and foldables.
Lastly, WindowWidthSizeClass.Compact
tells me that most mobiles are in portrait and I can update my UI accordingly.
These same enums are also available for the height of the activity but the documentation states -
Most apps can build a responsive UI by considering only the width window size class.
(So far, true for me)
Note that this library is still in alpha and explicitly marked as Experimental so it might change. I just wanted to add this here for anyone trying out Material 3 with Jetpack Compose.
Official Guide - https://developer.android.com/guide/topics/large-screens/support-different-screen-sizes#window_size_classes
Example implementation can be found in the JetNews sample app - https://github.com/android/compose-samples/tree/main/JetNews.
Release notes (released in 1.0.0-alpha10, current version - alpha13) - https://developer.android.com/jetpack/androidx/releases/compose-material3#1.0.0-alpha10
Upvotes: 2
Reputation: 1915
Here's a little helper for swapping a compose row into a column when turning to portrait-mode. You may want to tweak it a bit for your purposes.
@Composable
fun OrientationSwapper(
content1: @Composable (modifier: Modifier) -> Unit,
content2: @Composable (modifier: Modifier) -> Unit
) {
val portrait = LocalConfiguration.current.orientation == Configuration.ORIENTATION_PORTRAIT
if (portrait) {
Column(verticalArrangement = Arrangement.SpaceEvenly, modifier = Modifier.fillMaxSize()) {
content1(modifier = Modifier.weight(1f))
content2(modifier = Modifier.weight(1f))
}
} else {
Row(horizontalArrangement = Arrangement.SpaceEvenly, modifier = Modifier.fillMaxSize()) {
content1(modifier = Modifier.weight(1f))
content2(modifier = Modifier.weight(1f))
}
}
}
and use as:
OrientationSwapper(
content1 = { modifier ->
Text("foo", modifier = modifier) },
content2 = { modifier ->
Text("bar", modifier = modifier) })
Upvotes: 1
Reputation: 7804
To observe the orientation, we can create a snapshot flow to observe changes to the orientation which feeds into a state variable you can use directly.
var orientation by remember { mutableStateOf(Configuration.ORIENTATION_PORTRAIT) }
val configuration = LocalConfiguration.current
// If our configuration changes then this will launch a new coroutine scope for it
LaunchedEffect(configuration) {
// Save any changes to the orientation value on the configuration object
snapshotFlow { configuration.orientation }
.collect { orientation = it }
}
when (orientation) {
Configuration.ORIENTATION_LANDSCAPE -> {
LandscapeContent()
}
else -> {
PortraitContent()
}
}
Upvotes: 19