1

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!

Community
  • 1
  • 1
Tobias Wicker
  • 617
  • 8
  • 12
  • What are the dimensions of the circle drawable? –  Jun 05 '19 at 15:54
  • Since your fragment's `layout_width` is set to `match_parent`, it will expand to fill the available horizontal space in landscape mode. Your `ImageButton` will in turn fill the available space, and since it's ratio is set to 1:1, you're getting the undesired result. My question is.. why does the fragment have to be in a `ScrollView` if you want the `ImageButton` constrained to the available vertical space? –  Jun 05 '19 at 16:20
  • @glucaio I've added the drawable. I guess 120dp x 120dp is the answer you were looking for, right? – Tobias Wicker Jun 05 '19 at 16:24
  • @glucaio The fragment is in a `ScrollView` because the `ScrollView` and its parents are what defines my `MainActivity`. I replace the `fragment_main` with other fragments when the user taps another item from the menu / `NavigationView`. That item might not fit the screen. So instead of placing a `ScrollView` where needed, I'd like to use it for all fragments. Is that a bad idea? Do you have a solution that works without the `ScrollView`? – Tobias Wicker Jun 05 '19 at 16:32
  • In that case, try using a `FrameLayout` in place of this `ScrollView`, and if any of the other fragments needs scrolling, you can implement a `ScrollView` (or some other mechanism like a `RecyclerView`) in those specific layouts. –  Jun 05 '19 at 16:36
  • Actually, I believe in this situation, you can remove the `ScrollView` entirely, I'm just too tired to make sense of all this right now. If you're going to be swapping fragments in and out, you may want to take a look at [Navigation](https://developer.android.com/guide/navigation) –  Jun 05 '19 at 16:37
  • @glucaio While replacing `ScrollView` with `FrameLayout` gives me the desired layout (thanks!) I'm still eager to find a solution where I can keep the `ScrollView`. The previously mentioned inner `ConstraintLayout`s `layout_width="match_parent"` can be subject to change, but changing it to `wrap_content` seems to be of no help. Regarding the height of that `ConstraintLayout`: I'm aware that `layout_height="match_parent"` would be a bad idea for a child of a `ScollView`. I guess it's sad that there's no `layout_height="match_viewport"` or something similar which would probably solve this. – Tobias Wicker Jun 05 '19 at 16:55

1 Answers1

0

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>
Tobias Wicker
  • 617
  • 8
  • 12