DEV Community

Cover image for The confusing world of Android adaptive layouts
Tristan Elliott
Tristan Elliott

Posted on

The confusing world of Android adaptive layouts

Table of contents

  1. The UI wireframe
  2. The confusing world of Android adaptive layouts
  3. Your UI System
  4. Landscape mode

The code

Introduction

  • I have embarked on my next app, a Twitch client app. This series will be all my notes and problems faced when creating this app.

The UI wireframe

  • So I want two configurations for my app. A portrait configuration and landscape configuration. Basically, it should look something like this:

portrait landscape layout

The confusing world of Android adaptive layouts

Your UI System

  • First and foremost, your adaptive layout depends on which UI system you are using. Are you using the traditional XML or are you use Jetpack Compose?. For my application, I am using both XML and Compose code. So my original UI code looks like this:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    tools:context=".presentation.stream.StreamFragment">

        <androidx.constraintlayout.widget.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent">

                <WebView
                    android:id="@+id/webView"
                    android:layout_width="0.dp"
                    android:layout_height="0.dp"
                    app:layout_constraintDimensionRatio="16:9"
                    app:layout_constraintEnd_toEndOf="parent"
                    app:layout_constraintStart_toStartOf="parent"
                    app:layout_constraintTop_toTopOf="parent" />

            <androidx.compose.ui.platform.ComposeView
                android:id="@+id/compose_view"
                android:layout_width="0dp"
                android:layout_height="0dp"

                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toBottomOf="@+id/webView" />
        </androidx.constraintlayout.widget.ConstraintLayout>

</FrameLayout>

Enter fullscreen mode Exit fullscreen mode
  • The main things I want to draw you attention to are this:

  • 1) I am not using any hard coded values. As you can image hard coded values become very unreliable as your app is used on devices on different sizes.

  • 2) ConstraintLayout, I am using a Constraint layout to take advantage of its ability to form adaptive layouts.

  • 3) width and heights, all width and heights are set to 0dp which is considered matching constraints. Basically we are telling the design system to allow the constraints to determine the widths and heights. You can read more about it HERE.

  • 4) constraintDimensionRatio, anytime you are dealing with videos or pictures you have to be aware of the aspect ratio. Basically, we don't want the video to look squished or stretched and we want it to remain in the 16:9 viewing ratio. app:layout_constraintDimensionRatio="16:9" tells the design system, to automatically calculate the width available and stretch the height until we have the desired height which gives us the 16:9 ratio.

  • 5) constraintTop_toBottomOf="@+id/webView , in the ComposeView we are restricting its height to the bottom of WebView

  • Since I have not hard coded any direct values and instead relied on constraints and aspect ratios to define my height and width. No matter what screen size uses our application, in portrait mode it will adapt

Landscape mode

  • Again I want to remind the reader that your design system will determine how you will handle landscape orientations. However, since I have both XML and Compose I will rely heaviliy on these two resources:

1) Alternative Resources for XML

2) Configuration classes for Compose

Alternative Resources for XML

  • These can be created through android studio like so:

Landscape orientation creation

  • Just make sure the name of the file is the exact same as portrait file and that the quantifiers are set to Landscape

Configuration classes for Compose

  • This is where things are a bit up to the individual because there are two main ways to do this:

1) Window Size classes the official recommended way

2) Configuration class hack the way I did it

  • So I would caution the user, that you should probably not follow my lead here and I will probably convert this code into the window class into the future. However, for the time being I am using this hack:
@Composable
fun StreamView(
    streamViewModel: StreamViewModel
){

    var orientation by remember { mutableStateOf(Configuration.ORIENTATION_PORTRAIT) }
    val configuration = LocalConfiguration.current

    LaunchedEffect(configuration) {
        // Save any changes to the orientation value on the configuration object
        snapshotFlow { configuration.orientation }
            .collect { orientation = it }
    }

    when (orientation) {
        Configuration.ORIENTATION_LANDSCAPE -> {

            Column(){
                Text("LANDSCAPE",fontSize=30.sp,color= Color.Red)

            }
        }
        else -> {
            Column(){
                Text("PORTRAIT",fontSize=30.sp,color= Color.Red)

            }
        }
    }

}

Enter fullscreen mode Exit fullscreen mode
  • Now if we combine this with the Landscape XML file:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    tools:context=".presentation.stream.StreamFragment">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <WebView
            android:id="@+id/webView"
            android:layout_width="0.dp"
            android:layout_height="0.dp"

            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"
            />

        <androidx.compose.ui.platform.ComposeView
            android:id="@+id/compose_view"
            android:layout_width="0dp"
            android:layout_height="0dp"

            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
             />
    </androidx.constraintlayout.widget.ConstraintLayout>

</FrameLayout>

Enter fullscreen mode Exit fullscreen mode
  • Notice the constraints, I have used the constraints to tell our design system to grow both the ComposeView and the WebView as large as the screen will allow them. This will overlap them and give the start of a stream overlay. (Now we get to rebuild of all Twitch's stream overlay, yay!)

  • Thankfully, through heavy use of the ConstraintLayout. We now have our desired UI

Conclusion

  • Thank you for taking the time out of your day to read this blog post of mine. If you have any questions or concerns please comment below or reach out to me on Twitter.

Top comments (0)