Home > Software design >  Can I access a button in a TableLayout(10x10) without having to give each button an id?
Can I access a button in a TableLayout(10x10) without having to give each button an id?

Time:11-11

I have crated a TableLayout which is populated with buttons.

I want the buttons to change color once clicked. But there are going to be 100 buttons so I do not think creating an id and a onClickListener() is the correct way of going about it for each button.

Is there a way I can tell which button is clicked without delegating id's to each button?

<TableRow
    android:layout_height="0dp"
    android:layout_weight="1"
    android:id="@ id/row1"
    >
    <Button
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:text="Button 1"
        />
    <Button
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:text="Button 2"
        />
    <Button
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:text="Button 3"
        />
</TableRow>
<TableRow
    android:layout_height="0dp"
    android:layout_weight="1"
    >
    <Button
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:text="Button 4"
        />
    <Button
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:text="Button 5"
        />
    <Button
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:text="Button 6"
        />
</TableRow>
<TableRow
    android:layout_height="0dp"
    android:layout_weight="1"
    >
    <Button
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:text="Button 7"
        />
    <Button
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:text="Button 8"
        />
    <Button
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:text="Button 9"
        />
</TableRow>

I have seen a few similar question's but when I implement them nothing seems to happen. This is in my Main Activity and my attempt of logging a button click but nothing is logged.

  val table : TableLayout = findViewById(R.id.table1)
    val row : TableRow = findViewById(R.id.row1)

    row.setOnClickListener(object : View.OnClickListener{
        override fun onClick(p0: View?) {
            val tr = p0?.getParent() as TableRow

            val rowIndex: Int = table.indexOfChild(p0)
            Log.i("TAG!!!!!!!!!!!!!!!", "onClick: "   tr)
        }

    })

CodePudding user response:

your TableRow constains some clickable = true items (Buttons), so when you press any then it swallows touch event and doesn't pass it to parent, on which you are setting listener. in fact your current listener never fire as there is no possibility of TableRow click - it is fulfilled completely by clickable items

best approach would be to use custom ids for all buttons, but there is another way - just set same OnClickListener for all childrens of this view/row

val onClick = object : View.OnClickListener{
    override fun onClick(p0: View?) { // p0 will be a button
        val tr = p0?.getParent() as TableRow // child of TableRow for shure
        val rowIndex = tr.indexOfChild(p0) // index of clicked child in its parent (row)
        val tableIndex = table.indexOfChild(tr) // index of this row in whole table
        Log.i("TAG!!!!!!!!!!!!!!!",
            "onClick rowIndex:"   rowIndex   " tableIndex:"   tableIndex)
    }
}

val row : TableRow = findViewById(R.id.row1)
for (i until 0..row.childCount) {
    row.getChildAt(i).setOnClickListener(onClick)
}

CodePudding user response:

You have a few ways of doing it - it depends what you want to do, but you could create a View lookup (so can associate each button with some data) or you could programmatically assign some data to the button itself (like with setTag(Object)).

Here's a simple "give each button an index" approach:

    lateinit var buttons: List<Button>

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        
        // basically get each table row, and turn them into a stream of buttons.
        // flatMap means that instead of mapping each row to a List<Button>,
        // so we get a list of lists, they're unpacked into a single list of all the contents
        buttons = view.findViewById<TableLayout>(R.id.table1)
            .children.filterIsInstance<TableRow>()
            .flatMap { row -> row.children.filterIsInstance<Button>() }
            .onEach { it.setOnClickListener(::onButtonClick) } // set this here why not
            .toList()
    }

    private fun onButtonClick(view: View) {
        val button = view as Button
        Toast.makeText(requireContext(), "Button ${buttons.indexOf(button)   1} clicked!", Toast.LENGTH_SHORT).show()
    }

Here's how you could nest your iterations so you can keep track of which row and column a button is in, and you can set that info on the button itself using its tag:

    data class TableButton(val row: Int, val column: Int)

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        // could be neater but you get the idea!
        view.findViewById<TableLayout>(R.id.table1)
            .children.filterIsInstance<TableRow>()
            .mapIndexed { rowNum, row ->
                row.children.filterIsInstance<Button>()
                    .forEachIndexed { colNum, button ->
                        button.tag = TableButton(rowNum, colNum)
                        button.setOnClickListener(::onButtonClick)
                    }
            }
    }

    private fun onButtonClick(view: View) {
        val position = view.tag as TableButton
        Toast.makeText(requireContext(), "Row ${position.row}, column: ${position.column}", Toast.LENGTH_SHORT).show()
    }

I made the onButtonClick function match the click listener signature (takes a View parameter) so you can just pass it as a function reference (::onButtonClick) - so the cast to Button happens in there. You could also create a single click listener function and do it in there instead:

        val clickListener = { v: View -> onButtonClick(v as Button) }
        buttons.forEach { it.setOnClickListener(clickListener) }
    }
    
    private fun onButtonClick(button: Button) {
        Toast.makeText(requireContext(), "Button ${buttons.indexOf(button)   1} clicked!", Toast.LENGTH_SHORT).show()
    }

which keeps onButtonClick a bit "cleaner" since it only takes Buttons now. Or you could just remove the separate function entirely and do it all in the lambda function - these are just ways to avoid creating 100 click listener functions, and just reuse the same one to handle it all!

  • Related