I'm currently working on a Wordle clone with Kotlin. So far I've finished building the keyboard with RecyclerView, but there are some issues with the onClick event listener, it's not very responsive. I have to click the key multiple times to make it work, sometimes it just doesn't respond to click at all. I wonder what I did wrong here.
Here's my code for MainActivity
class MainActivity : AppCompatActivity() {
private var layoutManager: RecyclerView.LayoutManager? = null;
private var adapter: RecyclerView.Adapter<RecyclerAdapter.ViewHolder>? = null;
private var row1KeyList = listOf<String>("Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P");
private var row2KeyList = listOf<String>("A", "S", "D", "F", "G", "H", "J", "K", "L");
private var row3KeyList = listOf<String>("Z", "X", "C", "V", "B", "N", "M");
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val recyclerView = findViewById<RecyclerView>(R.id.recyclerView)
val recyclerView2 = findViewById<RecyclerView>(R.id.recyclerView2)
val recyclerView3 = findViewById<RecyclerView>(R.id.recyclerView3)
recyclerView.layoutManager = LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false)
recyclerView2.layoutManager = LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false)
recyclerView3.layoutManager = LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false)
var rowAdapter1 = RecyclerAdapter(row1KeyList)
recyclerView.adapter = rowAdapter1
rowAdapter1.setOnItemClickListener(object : RecyclerAdapter.onItemClickListener{
override fun onItemClick(position: Int){
// Toast.makeText(this@MainActivity, row1KeyList[position], Toast.LENGTH_SHORT).show()
Log.d("letter: ", row1KeyList[position])
Log.d("position", position.toString())
val tile = findViewById<TextView>(R.id.tile1)
tile.text = row1KeyList[position]
}
})
recyclerView2.adapter= RecyclerAdapter(row2KeyList)
recyclerView3.adapter= RecyclerAdapter(row3KeyList)
}
}
Here's the code of RecyclerAdapter
class RecyclerAdapter(val keyList: List<String>): RecyclerView.Adapter <RecyclerAdapter.ViewHolder>() {
// private var keyList = listOf<String>("Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P");
private var mListener: onItemClickListener? = null
interface onItemClickListener{
fun onItemClick(position: Int)
}
fun setOnItemClickListener(listener: onItemClickListener){
mListener = listener
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val v = LayoutInflater.from(parent.context).inflate(R.layout.key_list, parent, false)
return ViewHolder(v, mListener)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.itemKey.text = keyList[position]
}
override fun getItemCount(): Int {
return keyList.size;
}
inner class ViewHolder(itemView: View, listener: onItemClickListener?): RecyclerView.ViewHolder(itemView) {
var itemKey: AppCompatButton
init{
itemView.setOnClickListener {
listener?.onItemClick(position)
}
itemKey = itemView.findViewById(R.id.letterKey)
}
}
}
In case, I also attached the code of the layout file of the key list and the main layout
// key_list.xml defined how each key looks like
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
>
<androidx.appcompat.widget.AppCompatButton
android:id="@ id/letterKey"
android:layout_width="24dp"
android:layout_height="36dp"
android:background="#D3D6DA"
android:layout_marginRight="6dp"
android:textColor="@android:color/black"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
here's the layout for main activity
<?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"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.recyclerview.widget.RecyclerView
android:id="@ id/recyclerView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.5"
/>
<androidx.recyclerview.widget.RecyclerView
android:id="@ id/recyclerView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.6"
/>
<androidx.recyclerview.widget.RecyclerView
android:id="@ id/recyclerView3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.7"
/>
<TextView
android:id="@ id/tile1"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:textSize="10pt"
android:gravity="center"
android:background="@drawable/tile_border"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintVertical_bias="0.1"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
CodePudding user response:
TRY THIS:
adapter:
class RecyclerAdapter(val keyList: List<String>, val mListener : ClickListener ) : RecyclerView.Adapter<RecyclerAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val v = LayoutInflater.from(parent.context).inflate(R.layout.key_list, parent, false)
return ViewHolder(v, mListener)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.itemKey.text = keyList[position]
holder.itemKey.setOnClickListener{
mListener.click(position)
}
}
// return the number of the items in the list
override fun getItemCount(): Int {
return keyList.size
}
// Holds the views for adding it to image and text
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val itemKey = itemView.findViewById(R.id.letterKey)
}
interface ClickListener {
public fun click(position : Int)
}
}
Your new MainActivity:
class MainActivity : AppCompatActivity() : ClickListener{
private var layoutManager: RecyclerView.LayoutManager? = null;
private var adapter: RecyclerView.Adapter<RecyclerAdapter.ViewHolder>? = null;
private var row1KeyList = listOf<String>("Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P");
private var row2KeyList = listOf<String>("A", "S", "D", "F", "G", "H", "J", "K", "L");
private var row3KeyList = listOf<String>("Z", "X", "C", "V", "B", "N", "M");
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val recyclerView = findViewById<RecyclerView>(R.id.recyclerView)
val recyclerView2 = findViewById<RecyclerView>(R.id.recyclerView2)
val recyclerView3 = findViewById<RecyclerView>(R.id.recyclerView3)
recyclerView.layoutManager = LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false)
recyclerView2.layoutManager = LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false)
recyclerView3.layoutManager = LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false)
recyclerView.adapter = RecyclerAdapter(row1KeyList, this)
recyclerView2.adapter= RecyclerAdapter(row2KeyList)
recyclerView3.adapter= RecyclerAdapter(row3KeyList)
}
override fun click(position : Int){
Log.d("letter: ", row1KeyList[position])
Log.d("position", position.toString())
val tile = findViewById<TextView>(R.id.tile1)
tile.text = row1KeyList[position]
}
}
CodePudding user response:
I have figured out a solution inspired by Jon's answer. Basically, I just create a listener in the main activity and pass it to the adapter
Here's the updated main activity code:
class MainActivity : AppCompatActivity() {
private val row1KeyList = listOf<String>("Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P");
private var row2KeyList = listOf<String>("A", "S", "D", "F", "G", "H", "J", "K", "L");
private var row3KeyList = listOf<String>("Z", "X", "C", "V", "B", "N", "M");
interface ItemClickListener {
fun onItemClick(position : Int, list: List<String>)
}
val itemClickListener = object : ItemClickListener {
override fun onItemClick(position: Int, list: List<String>) {
Log.d("letter: ", list[position])
Log.d("position", position.toString())
val tile = findViewById<TextView>(R.id.tile1)
tile.text = list[position]
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val recyclerView = findViewById<RecyclerView>(R.id.recyclerView)
val recyclerView2 = findViewById<RecyclerView>(R.id.recyclerView2)
val recyclerView3 = findViewById<RecyclerView>(R.id.recyclerView3)
recyclerView.layoutManager = LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false)
recyclerView2.layoutManager = LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false)
recyclerView3.layoutManager = LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false)
recyclerView.adapter = RecyclerAdapter(row1KeyList, itemClickListener)
recyclerView2.adapter= RecyclerAdapter(row2KeyList, itemClickListener)
recyclerView3.adapter= RecyclerAdapter(row3KeyList, itemClickListener)
}
}
here's the updated RecyclerAdapter code:
class RecyclerAdapter(val keyList: List<String>, val mListener : MainActivity.ItemClickListener): RecyclerView.Adapter <RecyclerAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val v = LayoutInflater.from(parent.context).inflate(R.layout.key_list, parent, false)
return ViewHolder(v)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.itemKey.text = keyList[position]
holder.itemKey.setOnClickListener{
mListener.onItemClick(position, keyList)
}
}
override fun getItemCount(): Int {
return keyList.size;
}
inner class ViewHolder(itemView: View): RecyclerView.ViewHolder(itemView) {
var itemKey: AppCompatButton
init{
itemKey = itemView.findViewById(R.id.letterKey)
}
}
}
CodePudding user response:
You haven't posted your click listener code for the other two rows in the question, so we don't know what's happening there and if there's a problem - but your solution you posted is treating every click listener value as coming from row 1. You need to do something like this:
// Making it a list of lists instead of separate variables will make lookups easier!
// You could keep them as variables, but then you'll have to translate row numbers
// to the correct variable
val rows = listOf(
listOf("Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P"),
listOf("A", "S", "D", "F", "G", "H", "J", "K", "L"),
listOf("Z", "X", "C", "V", "B", "N", "M")
)
// we'll look this up once in onCreate and store it here
lateinit var tile: TextView
// this is the function your click listeners will call
fun handleKeyClick(row: Int, position: Int) {
// look up the key, nice and easy when it's nested lists!
tile.text = rows[row][position]
}
override fun onCreate(savedInstanceState: Bundle?) {
...
// it's better to find this once and keep a reference to it!
tile = findViewById<TextView>(R.id.tile1)
...
// pass each RecyclerView the appropriate row of keys, and also a function
// that takes a position Int. We're using that to call our handler function,
// and also passing the appropriate row number - it's different in each function
// for each RecyclerView!
recyclerView.adapter = RecyclerAdapter(rows[0]) { position ->
handleKeyClick(row = 0, position)
}
recyclerView.adapter = RecyclerAdapter(rows[1]) { position ->
handleKeyClick(row = 1, position)
}
recyclerView.adapter = RecyclerAdapter(rows[2]) { position ->
handleKeyClick(row = 2, position)
}
...
}
And then your Adapter needs to handle that function we're passing in:
// the second parameter is different - it's a type that represents a function with
// one Int parameter (the position) and it returns nothing. That's what we're
// passing in when we're creating the adapters in onCreate up there
class RecyclerAdapter(
val keyList: List<String>,
val positionClickListener : (Int) -> Unit
) : RecyclerView.Adapter <RecyclerAdapter.ViewHolder>() {
...
// no need to pass a listener in, it's an -inner- class so it can see
// positionClickListener in the parent class
inner class ViewHolder(itemView: View): RecyclerView.ViewHolder(itemView) {
init {
itemView.setOnClickListener {
// call the listener with the current position this is displaying
positionClickListener(bindingAdapterPosition)
}
}
}
You could set up a click listener in onBindViewHolder
instead, but it's more efficient to set it up once per ViewHolder
so you're not constantly creating listener objects (in general, doesn't matter here with your fixed layout!)
That should make what you're doing work - your other option is to just pass the key character itself, i.e. make the listener function a (String) -> Unit
(you should be working with Char
s really) and then the listener can just use the character instead of having to look it up by position.
The other thing I'd say is this isn't really something you should use a RecyclerView
for - it's meant for scrolling lists of items, and it reuses ViewHolder
s to make that efficient instead of creating one for every single item. You have everything displayed on the screen at once, you're not getting any benefit for the extra work it involves!
There are lots of ways to do what you're doing - the simplest would just be a ConstraintLayout
with all the key TextView
s (they should be Button
s really, especially for accessibility reasons) added to it in rows, using a packed
chain to squish them all together in the centre. They could all use the same OnClickListener
, which passes the View
that was clicked as a parameter, so you can grab the text contents (or a tag
) and work out which key was pressed. Just an idea! You've already done the work here so no need to reinvent it