Search code examples
androidxmlandroid-constraintlayoutandroid-scrollviewandroid-imagebutton

Center align ImageButton in landscape mode using ConstraintLayout


TL;DR

I want to center align an ImageButton in a ScrollView and I'm quite happy with the result in portrait mode. However, I can't say the same for landscape mode and any help would be greatly appreciated.

But let me explain

I'm using two layout files here; the first one is the one I use for my MainActivity class, called activity_main.xml. The second one (called fragment_main.xml) gets nested into into the activity_main.xml when the MainActivity loads the MainFragment.

The preview for fragment_main.xml looks quite promising for portrait and landscape mode: fragment_main.xml in landscape fragment_main.xml in portrait

And the rendering of activity_main.xml in portrait mode looks just how I want it to look: activity_main.xml in portrait

However, activity_main.xml in landscape mode looks odd and I can't figure out why: activity_main.xml in landscape

Here is the XML of activity_main.xml with fragment_main.xml merged into it as a child of the ScollView:

<?xml version="1.0" encoding="utf-8"?>
<androidx.drawerlayout.widget.DrawerLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/drawer_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:id="@+id/content_frame"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <androidx.appcompat.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:background="?attr/colorPrimary"
            android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
            app:titleTextAppearance="@style/Toolbar.TitleText"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toEndOf="parent"/>

        <ScrollView
            android:id="@+id/fragment_container"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_marginTop="?attr/actionBarSize"
            app:layout_constraintTop_toBottomOf="@id/toolbar"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintBottom_toBottomOf="parent">

            <!-- Content of fragment_main.xml -->
            <androidx.constraintlayout.widget.ConstraintLayout
                xmlns:tools="http://schemas.android.com/tools"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_gravity="center"
                android:paddingStart="50dp"
                android:paddingEnd="50dp"
                tools:context=".MainFragment">


                <ImageButton
                    android:id="@+id/overlay_button"
                    android:layout_width="0dp"
                    android:layout_height="0dp"
                    android:background="@null"
                    android:contentDescription="@string/start_speedometer"
                    android:scaleType="fitCenter"
                    android:src="@drawable/btn_circle_green"
                    app:layout_constraintDimensionRatio="1:1"
                    app:layout_constraintTop_toTopOf="parent"
                    app:layout_constraintBottom_toBottomOf="parent"
                    app:layout_constraintStart_toStartOf="parent"
                    app:layout_constraintEnd_toEndOf="parent"/>

                <TextView
                    android:id="@+id/overlay_button_text"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:clickable="false"
                    android:text="@string/start_speedometer"
                    android:textColor="@color/white"
                    android:textSize="40sp"
                    app:layout_constraintTop_toTopOf="parent"
                    app:layout_constraintBottom_toBottomOf="parent"
                    app:layout_constraintStart_toStartOf="parent"
                    app:layout_constraintEnd_toEndOf="parent"/>

            </androidx.constraintlayout.widget.ConstraintLayout>
            
        </ScrollView>

    </androidx.constraintlayout.widget.ConstraintLayout>

    <com.google.android.material.navigation.NavigationView
        android:id="@+id/nav_view"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_gravity="start"
        android:fitsSystemWindows="false"
        app:menu="@menu/nav_drawer_view"
        app:headerLayout="@layout/nav_drawer_header"
        app:itemIconTint="@drawable/nav_drawer_item_icon_color"
        app:itemTextColor="@drawable/nav_drawer_item_text_color" />

</androidx.drawerlayout.widget.DrawerLayout>

I'm looking for a solution where I only have to make changes to fragment_main.xml (that is, everything inside the ScollView). The solution should be involving XML only.

What I've tried

I've tried fiddling with app:layout_constraintDimensionRatio (W,1:1, H,1:1), app:layout_constrainedHeight, app:layout_constraintHeight_max, layout_constraintHeight_min, many other constraints and even tried nesting the content of fragment_main.xml in other layouts, moved from ConstraintLayout to RelativeLayout, LinearLayout and FrameLayout but none of them give me the desired result and ConstraintLayout is the one that came the closest.

Possible solution

The only solution that worked so far was using app:layout_constraintWidth_default="percent" and app:layout_constraintWidth_percent="0.5" resulting in desired look of activity_main.xml in landscape

While this might look great on a 16:9 phone it might result in the same issues as before on phones with different aspect ratio. So I'm looking for a more generic approach.

Additional details

This is the content of the drawable btn_circle_green.xml I'm using for the button:

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_pressed="true" android:drawable="@drawable/ic_circle_green_pressed" />
    <item android:state_focused="true" android:drawable="@drawable/ic_circle_green_focused" />
    <item android:state_selected="true" android:drawable="@drawable/ic_circle_green_focused" />
    <item android:drawable="@drawable/ic_circle_green_default" />
</selector>

With each ic_circle_green_* being a vector asset like this with differing colors:

<vector
    android:viewportWidth="300"
    android:viewportHeight="300"
    android:height="120dp"
    android:width="120dp"
    xmlns:android="http://schemas.android.com/apk/res/android">
    <path android:fillColor="@color/green"
        android:pathData="M150,150m-140,0a140,140 0,1 1,280 0a140,140 0,1 1,-280 0"
        android:strokeWidth="5"
        android:strokeColor="@color/green_light"/>
</vector>

Many thanks in advance!


Solution

  • After I understood that the ScrollView is the element that's causing me headaches (thanks again, @glucaio!) I found this answer and was able to solve my problem.

    The ScrollView received android:fitsSystemWindows="true", the inner ConstraintLayout had its attributes for width and height switched (android:layout_width="wrap_content" and android:layout_height="match_parent"), received a paddingTop and paddingBottom (instead of paddingStart and paddingEnd) and was wrapped in a LinearLayout:

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:gravity="center" />
    

    This solution only works for landscape mode. So I'm using the one in this answer for landscape and the one from my question for portrait mode.

    This is how the final XML for landscape mode looks like:

    Rendered activity_main.xml

    And this is the actual XML:

    <?xml version="1.0" encoding="utf-8"?>
    <androidx.drawerlayout.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:id="@+id/drawer_layout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:fitsSystemWindows="true">
    
        <androidx.constraintlayout.widget.ConstraintLayout
            android:id="@+id/content_frame"
            android:layout_width="match_parent"
            android:layout_height="match_parent">
    
            <androidx.appcompat.widget.Toolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                android:background="?attr/colorPrimary"
                android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
                app:titleTextAppearance="@style/Toolbar.TitleText"
                app:layout_constraintTop_toTopOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintEnd_toEndOf="parent"/>
    
            <ScrollView
                android:id="@+id/fragment_container"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:layout_marginTop="?attr/actionBarSize"
                android:fillViewport="true"
                app:layout_constraintTop_toBottomOf="@id/toolbar"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintBottom_toBottomOf="parent">
    
                <!-- Content of fragment_main.xml -->
                <LinearLayout
                    xmlns:tools="http://schemas.android.com/tools"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:orientation="vertical"
                    android:gravity="center"
                    tools:context=".MainFragment">
    
                    <androidx.constraintlayout.widget.ConstraintLayout
                        android:layout_width="wrap_content"
                        android:layout_height="match_parent"
                        android:layout_gravity="center"
                        android:paddingTop="30dp"
                        android:paddingBottom="30dp">
    
    
                        <ImageButton
                            android:id="@+id/overlay_button"
                            android:layout_width="0dp"
                            android:layout_height="0dp"
                            android:background="@null"
                            android:contentDescription="@string/start_speedometer"
                            android:scaleType="fitCenter"
                            android:src="@drawable/btn_circle_green"
                            app:layout_constraintDimensionRatio="1:1"
                            app:layout_constraintTop_toTopOf="parent"
                            app:layout_constraintBottom_toBottomOf="parent"
                            app:layout_constraintStart_toStartOf="parent"
                            app:layout_constraintEnd_toEndOf="parent"/>
    
                        <TextView
                            android:id="@+id/overlay_button_text"
                            android:layout_width="wrap_content"
                            android:layout_height="wrap_content"
                            android:clickable="false"
                            android:text="@string/start_speedometer"
                            android:textColor="@color/white"
                            android:textSize="40sp"
                            app:layout_constraintTop_toTopOf="parent"
                            app:layout_constraintBottom_toBottomOf="parent"
                            app:layout_constraintStart_toStartOf="parent"
                            app:layout_constraintEnd_toEndOf="parent"/>
    
                    </androidx.constraintlayout.widget.ConstraintLayout>
    
                </LinearLayout>
    
            </ScrollView>
    
        </androidx.constraintlayout.widget.ConstraintLayout>
    
        <com.google.android.material.navigation.NavigationView
            android:id="@+id/nav_view"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:layout_gravity="start"
            android:fitsSystemWindows="false"
            app:menu="@menu/nav_drawer_view"
            app:headerLayout="@layout/nav_drawer_header"
            app:itemIconTint="@drawable/nav_drawer_item_icon_color"
            app:itemTextColor="@drawable/nav_drawer_item_text_color" />
    
    </androidx.drawerlayout.widget.DrawerLayout>