Home > OS >  Trying programmaticly to space a grid layout evenly
Trying programmaticly to space a grid layout evenly

Time:12-02

I have a GridLayout which should show 25 Buttons spaced evenly. To be able to set an onClickListener without calling each one them I want to do that programmatically. I made a layout resource file with the grid itself to bind it and being able to inflate it

activity.xml
<?xml version="1.0" encoding="utf-8"?>
<GridLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:grid="http://schemas.android.com/apk/res-auto"
    android:id="@ id/bingo_grid"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_centerHorizontal="true"
    android:columnCount="5"
    android:rowCount="5"
    tools:context=".BingoActivity" />

Now I'm creating the fields:

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val bingoField = (1).rangeTo(25).toSet().toIntArray()
        binding = BingoActivityBinding.inflate(layoutInflater)

        setContentView(binding.root)
        binding.bingoGrid.alignmentMode = GridLayout.ALIGN_BOUNDS

        val bingoFieldGrid = binding.bingoGrid
        bingoFieldGrid.alignmentMode = GridLayout.ALIGN_BOUNDS
        bingoField.forEach {
            val button = createButton(it.toString())
            val gridLayoutParams = GridLayout.LayoutParams().apply {
                rowSpec = spec(GridLayout.UNDEFINED, GridLayout.CENTER, 1f)
                columnSpec = spec(GridLayout.UNDEFINED, GridLayout.CENTER, 1f)
                height = GridLayout.LayoutParams.WRAP_CONTENT
                width = GridLayout.LayoutParams.WRAP_CONTENT
            }

            bingoFieldGrid.addView(button, gridLayoutParams)
        }
    @RequiresApi(Build.VERSION_CODES.M)
    private fun createButton(buttonText: String): Button {
        var isCompleted = false
        return Button(baseContext).apply {
            setBackgroundColor(getColor(R.color.red))
            gravity = Gravity.CENTER
            text = buttonText
            setOnClickListener {
                isCompleted = if (!isCompleted) {
                    setBackgroundColor(getColor(R.color.green))
                    true
                } else {
                    setBackgroundColor(getColor(R.color.red))
                    false
                }
            }
        }
    }

So, the fields are auto generated without problems, but the spacing is not right: enter image description here

I'm quite new to the old layouting, is there a way to easily achieve that?

CodePudding user response:

You're creating two different types of LayoutParams which doesn't make sense. LinearLayout shouldn't be involved at all.

The way they work is each child should get a set of LayoutParams that match the type of LayoutParams that its parent ViewGroup uses. So in this case the parent is GridLayout, so each child should be added using an instance of GridLayout.LayoutParams.

The way GridLayout.LayoutParams work is you define a row Spec and a column Spec that describe how a child should take up cells. We want them to take the single next cell, so we can leave the first parameter as UNDEFINED. We need to give them an equal weight more than 0 so they all share evenly in the leftover space. I'm using 1f for the weight.

I'm using FILL with a size of 0 for the buttons so they fill their cells. The margins put some gap between them.

I'm setting height and width to 0 to prevent them from being oversized. If the rows or columns become too big to fit the screen, the layout goes way too big.

You might want to use MaterialButton instead of a plain Button, so you can easily tint the background color without simply making it a static solid color rectangle.

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    binding = BingoBinding.inflate(layoutInflater)
    setContentView(binding.root)

    binding.bingoGrid.alignmentMode = GridLayout.ALIGN_BOUNDS

    for (num in 1..25) {
        val button = MaterialButton(this).apply {
            setBackgroundColor(resources.getColor(R.color.blue_500))
            gravity = Gravity.CENTER
            text = num.toString()
            setPadding(0)
        }
        val params = GridLayout.LayoutParams().apply {
            rowSpec = spec(GridLayout.UNDEFINED, GridLayout.FILL, 1f)
            columnSpec = spec(GridLayout.UNDEFINED, GridLayout.FILL, 1f)
            width = 0
            height = 0
            setMargins((4 * resources.displayMetrics.density).toInt())
        }
        binding.bingoGrid.addView(button, params)
    }

}

AndroidStudio was finnicky about importing the spec function. I had to manually add this at the top:

import android.widget.GridLayout.Spec.*

enter image description here

CodePudding user response:

You could consider Google ConstraintLayout Flows:

To set the number of elements use app:flow_maxElementsWrap="5"

layout:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@ id/root"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <androidx.constraintlayout.helper.widget.Flow
        android:id="@ id/flow"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:flow_horizontalGap="8dp"
        app:flow_maxElementsWrap="5"
        app:flow_verticalGap="8dp"
        app:flow_verticalStyle="packed"
        app:flow_wrapMode="chain" />

</androidx.constraintlayout.widget.ConstraintLayout>

Then add the buttons programmatically to the ConstraintLayout:

val root = findViewById<ViewGroup>(R.id.root)
val size = 25
val array = IntArray(size)
for (i in 0 until size) {
    array[i] = i   1
    val button = Button(this).apply {
        layoutParams = ViewGroup.LayoutParams(0, 0)
        id = i   1
        text = (i   1).toString()
    }
    root.addView(button)
}
val flow = findViewById<Flow>(R.id.flow)
flow.referencedIds = array

Hint: you could use WRAP_CONTENT for the button height to avoid stretching out the buttons height.

  • Related