How to make a GridLayout to change its children views size accordingly?

1

Background

I need to make a dialpad-like View, like on the Phone app.

I'm using a GridLayout of Views. Each cell is of the same size, and contains just a simple TextView that should change its font size if needed.

The problem

I've succeeded, but for some reason it doesn't work well according to the space that it is given.

If it has a lot of space, it works fine:

enter image description here

However, when it gets smaller (example: small screens, landscape, split-window...), only the top buttons of the grid become visible, and they didn't even change their font size, as if they all want to be of the biggest size they can:

enter image description here

What I've tried

I tried to modify various attributes of the views, but none helped.

I know though, that the dial-pad of the Phone app doesn't really change its font size. Up to some size, it gets shown normally, and if it's too small, it changes to a different layout. This is especially important for landscape and split-window modes.

Here's the code I've made (I change the value of "layout_constraintHeight_percent" to check the various sizes for the top area) :

gradle

...
implementation 'androidx.appcompat:appcompat:1.0.2'
implementation 'androidx.core:core-ktx:1.0.2'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'androidx.gridlayout:gridlayout:1.0.0'
...

QueryKeyboard.kt

class QueryKeyboard @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0) : GridLayout(context, attrs, defStyle) {
    init {
        orientation = HORIZONTAL
        clipChildren = false
        clipToPadding = false
        columnCount = 3
        rowCount = 4
        //workaround for a weird issue of seeing just 3 huge buttons, instead of all
        val runnable = Runnable {
            for (i in 1..9)
                addView(generateGridTextButton(i.toString()))
            addView(generateGridTextButton("*"))
            addView(generateGridTextButton("0"))
            addView(generateGridTextButton("+"))
        }
        if (isInEditMode)
            runnable.run()
        else
            this.doOnPreDraw { runnable.run() }
    }

    private fun generateGridTextButton(textToShowAndAddUponClick: CharSequence): TextView {
        val tv = LayoutInflater.from(context).inflate(R.layout.grid_text_button, this, false) as TextView
        tv.text = textToShowAndAddUponClick
        return tv
    }
}

grid_text_button.xml

<androidx.appcompat.widget.AppCompatTextView 
   xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:app="http://schemas.android.com/apk/res-auto"
   xmlns:tools="http://schemas.android.com/tools"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   android:background="?attr/selectableItemBackgroundBorderless"
   android:breakStrategy="balanced"
   android:clickable="true"
   android:focusable="true"
   android:focusableInTouchMode="false"
   android:gravity="center"
   android:textColor="#000"
   android:textSize="36dp"
   app:autoSizeMaxTextSize="36dp"
   app:autoSizeMinTextSize="12dp"
   app:layout_columnWeight="1"
   app:layout_gravity="fill"
   app:layout_rowWeight="1"
   tools:layout_gravity="center"
   tools:targetApi="m"
   tools:text="1" />

Usage in activity_main.xml :

<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
    android:layout_height="match_parent" android:orientation="vertical" tools:context=".MainActivity">

    <TextView
        android:id="@+id/textView" android:layout_width="match_parent" android:layout_height="0px"
        android:background="#33ff0000" android:gravity="center" android:text="some content"
        app:layout_constraintBottom_toTopOf="@id/queryKeyboard" app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHeight_percent="0.6" app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <com.sample.myapplication.QueryKeyboard
        android:id="@+id/queryKeyboard" android:layout_width="match_parent" android:layout_height="0px"
        app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/textView" />

</androidx.constraintlayout.widget.ConstraintLayout>

EDIT: I tried to wrap the TextView with FrameLayout, to show the size of each cell:

grid_text_button.xml

<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools" android:layout_width="wrap_content" android:layout_height="wrap_content"
    app:layout_columnWeight="1" app:layout_gravity="fill" app:layout_rowWeight="1">

    <androidx.appcompat.widget.AppCompatTextView
        android:id="@+id/textView" android:layout_width="wrap_content" android:layout_height="wrap_content"
        android:background="?attr/selectableItemBackgroundBorderless" android:breakStrategy="balanced"
        android:clickable="true" android:focusable="true" android:focusableInTouchMode="false" android:gravity="center"
        android:textColor="#000" android:textSize="36sp" app:autoSizeMaxTextSize="36sp" app:autoSizeMinTextSize="12sp"
        tools:layout_gravity="center" tools:targetApi="m" tools:text="1" />
</FrameLayout>

QueryKeyboard.kt

class QueryKeyboard @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0) : GridLayout(context, attrs, defStyle) {
    private var cellBackgroundColor = 0xffff0000.toInt()

    init {
        orientation = HORIZONTAL
        clipChildren = false
        clipToPadding = false
        columnCount = 3
        rowCount = 4
        //workaround for a weird issue of seeing just 3 huge buttons, instead of all
        val runnable = Runnable {
            for (i in 1..9) {
                addView(generateGridTextButton(i.toString()))
            }
            addView(generateGridTextButton("*"))
            addView(generateGridTextButton("0"))
            addView(generateGridTextButton("+"))
        }
        if (isInEditMode)
            runnable.run()
        else
            this.doOnPreDraw { runnable.run() }
    }

    private fun switchColor() {
        cellBackgroundColor = if (cellBackgroundColor == 0xffff0000.toInt()) 0xff00ff00.toInt() else 0xffff0000.toInt()
    }

    private fun generateGridTextButton(textToShowAndAddUponClick: CharSequence): View {
        val view = LayoutInflater.from(context).inflate(R.layout.grid_text_button, this, false)
        switchColor()
        view.setBackgroundColor(cellBackgroundColor)
        view.textView.text = textToShowAndAddUponClick
        return view
    }
}

And here are the 2 cases, of when it works fine, and when it doesn't:

enter image description here

enter image description here

Same as before. Getting 3 cells, text not centered, and not auto-resizing its font.

The questions

  1. Why don't the cells adjust their sizes, including the font size of each of them? How come I see just 3 cells when it's too small? How can I fix it?

  2. Is there a better alternative? I guess I could use LinearLayout of multiple LinearLayout instances, but that's just weird for this case... After all, how often do you use GridLayout... :)

  3. How can I detect when it's just too small, so that I switch to another layout, like on the Phone app, including all the various cases they used (even split-window)? Is it possible they just used qualifier for the layouts? If so, which is recommended for this case ?

android
androidx
android-gridlayout
asked on Stack Overflow Aug 22, 2019 by android developer • edited Aug 26, 2019 by android developer

2 Answers

0

Why don't the cells adjust their sizes, including the font size of each of them? How can I fix it?

A cell can dynamically adjust its size based on two main constraints: the weight of its parent and the value of its textsize or childview. Having a fixed textsize on cells can lead to inconsistency in design. To fix this issue you can either set a layout weight on parentview with cells width and height matching parents or create different dimensions of textsize for targeted devices.

Is there a better alternative? I guess I could use LinearLayout of multiple LinearLayout instances, but that's just weird for this case... After all, how often do you use GridLayout... :)

There is a better way and that is what you're currently implementing The GridLayout. Using indented LinearLayouts would limit you from lots of benefits and would make you write more code, take for example cases where you need to switch or animate cells, access the nth column of nth row, dynamically change cell span, etc. All these can be done via Gridlayout with a few lines of code. It is more powerful than you think.

How can I detect when it's just too small, so that I switch to another layout, like on the Phone app, including all the various cases they used (even split-window)? Is it possible they just used qualifier for the layouts? If so, which is recommended for this case ?

There's nothing for you to detect, just follow the guidelines and Android would do the detecting.

Here are a couple of ways you can manage your scenario based on Android Design guidelines

First: Create a layout landscape and portrait mode for your activity (layout/activity.xml and layout-land/activity.xml)

layout/activity.xml

 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android
 xmlns:app="http://schemas.android.com/apk/res-auto"
 xmlns:tools="http://schemas.android.com/tools" 
 android:layout_width="match_parent"
android:layout_height="match_parent" 
android:orientation="vertical"
 android:weightSum="2"
tools:context=".MainActivity">

<TextView
    android:id="@+id/textView" 
    android:layout_width="match_parent" 
    android:layout_height="0dp"
    android:background="#33ff0000"
    android:gravity="center" android:text="some content"
    android:layout_weight="1" />

 <com.sample.myapplication.QueryKeyboard
    android:id="@+id/queryKeyboard" 
   android:layout_width="match_parent" 
    android:layout_height="0dp"
    android:layout_weight="1" />
 </LinearLayout>

layout-land/activity.xml

  <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android
 xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" 
android:layout_width="match_parent"
android:layout_height="match_parent" 
android:orientation="horizontal"
 android:weightSum="2"
tools:context=".MainActivity">
<TextView
    android:id="@+id/textView" 
    android:layout_width="0dp" 
    android:layout_height="match_parent"
    android:background="#33ff0000"
    android:gravity="center" android:text="some content"
    android:layout_weight="1" />

 <com.sample.myapplication.QueryKeyboard
    android:id="@+id/queryKeyboard" 
   android:layout_width="0dp" 
    android:layout_height="match_parent"
    android:layout_weight="1" />
 </LinearLayout>

Furthermore You need to handle textsize for various screen size.

This textsize calculation can be handled automatically by adding Intuit android library to your dependency list in gradle

dependencies {
   implementation 'com.intuit.ssp:ssp-android:1.0.6'
 }

Then in your grid button textsize call

 android:textSize="@dimens/_30ssp"
answered on Stack Overflow Aug 23, 2019 by Giddy Naya • edited Aug 23, 2019 by Giddy Naya
0

OK I've changed the layout files a bit, to avoid the 3-cells issue. It still occurs, but on much smaller sizes. Sadly the font sizes issue remains the same, and even the very small this time.

If anyone finds out why this occurs, please let me know. For now I consider this as a bug, so I've reported here.

grid_text_button.xml

<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools" android:layout_width="wrap_content"
    android:layout_height="wrap_content" android:background="?attr/selectableItemBackgroundBorderless"
    android:clickable="true" android:focusable="true" android:focusableInTouchMode="false" app:layout_columnWeight="1"
    app:layout_gravity="fill" app:layout_rowWeight="1" tools:layout_gravity="center">

    <androidx.appcompat.widget.AppCompatTextView
        android:id="@+id/textView" android:layout_width="wrap_content" android:layout_height="wrap_content"
        android:layout_gravity="center" android:breakStrategy="balanced" android:gravity="center"
        android:textColor="#000" app:autoSizeMaxTextSize="36sp" app:autoSizeMinTextSize="12sp" tools:targetApi="m"
        tools:text="1" />
</FrameLayout>

QueryKeyboard.kt

class QueryKeyboard @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0) : GridLayout(context, attrs, defStyle) {
    //    private var cellBackgroundColor = 0xffff0000.toInt()
    init {
        orientation = HORIZONTAL
        clipChildren = false
        clipToPadding = false
        columnCount = 3
        rowCount = 4
        for (i in 1..9)
            addView(generateGridTextButton(i.toString()))
        addView(generateGridTextButton("*"))
        addView(generateGridTextButton("0"))
        addView(generateGridTextButton("+"))
    }

    //    private fun switchColor() {
    //        cellBackgroundColor = if (cellBackgroundColor == 0xffff0000.toInt()) 0xff00ff00.toInt() else 0xffff0000.toInt()
    //    }
    private fun generateGridTextButton(textToShowAndAddUponClick: CharSequence): View {
        val view = LayoutInflater.from(context).inflate(R.layout.grid_text_button, this, false)
        //        switchColor()
        //        view.setBackgroundColor(cellBackgroundColor)
        view.textView.text = textToShowAndAddUponClick
        return view
    }
}

An alternative to the GridLayout implementation, that doesn't have this issue, but it's still weird that I would use it, is as I wrote, a LinearLayout of LinearLayouts :

class QueryKeyboard2 @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : LinearLayout(context, attrs, defStyleAttr) {
    //private var cellBackgroundColor = 0xffff0000.toInt()

    init {
        orientation = VERTICAL
        clipChildren = false
        clipToPadding = false
        val columnCount = 3
        val rowCount = 4
        val cellsList = ArrayList<View>()
        for (i in 1..9)
            cellsList.add(generateGridTextButton(i.toString()))
        cellsList.add(generateGridTextButton("*"))
        cellsList.add(generateGridTextButton("0"))
        cellsList.add(generateGridTextButton("+"))
        for (i in 0 until rowCount) {
            val rowLayout = generateRowLayout(context)
            for (j in 0 until columnCount) {
                val cellView = cellsList[i * columnCount + j]
                val cellLayoutParams = LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.MATCH_PARENT)
                cellLayoutParams.weight = 1f
                rowLayout.addView(cellView, cellLayoutParams)
            }
            //            switchColor()
            //            rowLayout.setBackgroundColor(cellBackgroundColor)
            addView(rowLayout)
        }
    }

    private fun generateRowLayout(context: Context): LinearLayout {
        val result = LinearLayout(context)
        result.layoutParams = LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 0)
        (result.layoutParams as LayoutParams).weight = 1f
        result.orientation = HORIZONTAL
        return result
    }

    //private fun switchColor() {
    //    cellBackgroundColor = if (cellBackgroundColor == 0xffff0000.toInt()) 0xff00ff00.toInt() else 0xffff0000.toInt()
    //}

    private fun generateGridTextButton(textToShowAndAddUponClick: CharSequence): View {
        val view = LayoutInflater.from(context).inflate(R.layout.grid_text_button, this, false)
        //switchColor()
        //view.setBackgroundColor(cellBackgroundColor)
        view.textView.text = textToShowAndAddUponClick
        return view
    }
}

As for trying to make it shown only when there is enough space (height) , I've set the layout to use it on "res/layout-h400dp" (can be changed according to the needs), and a different one, where the dialpad is on the right, for the normal "res/layout".

answered on Stack Overflow Aug 26, 2019 by android developer • edited Aug 26, 2019 by android developer

User contributions licensed under CC BY-SA 3.0