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 id
s 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!