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.
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:
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:
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:
Same as before. Getting 3 cells, text not centered, and not auto-resizing its font.
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?
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... :)
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 ?
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"
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".
User contributions licensed under CC BY-SA 3.0