What should I add in my code so that every time I check one of the recycler view's items it will display his number of calories? I wanna make a "checkListener" so that every time I check the checkbox corresponding to the selected item of the recyclerview ... his calories number will be stored in a variable and if I check for example 2 of these items the variable will store their sum
AdapterItem:
class AdapterItem( val userList: List<MyDataItem>): RecyclerView.Adapter<AdapterItem.ViewHolder>() {
class ViewHolder(itemView: View):RecyclerView.ViewHolder(itemView){
var numeLista: TextView
var caloriiLista: TextView = itemView.findViewById(R.id.caloriiLista)
init{
numeLista=itemView.numeLista
caloriiLista=itemView.caloriiLista
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
var itemView = LayoutInflater.from(parent.context).inflate(R.layout.row_items, parent, false)
return ViewHolder(itemView)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.numeLista.text=userList[position].foodItems.get(0).foodName
holder.caloriiLista.text=userList[position].foodItems.get(0).calories.toString()
}
override fun getItemCount(): Int {
return userList.size
}
}
**The fragment where the Recycle is displayed:**
const val BASE_URL= "https://raw.githubusercontent.com/terrenjpeterson/caloriecounter/master/src/data/"
class InformationFragment : Fragment() {
private var binding: FragmentInformationBinding? = null
private val sharedViewModel: SharedViewModel by activityViewModels()
lateinit var adapterItem: AdapterItem
lateinit var linearLayoutManager: LinearLayoutManager
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
getMyData()
val fragmentBinding = FragmentInformationBinding.inflate(inflater, container, false)
binding = fragmentBinding
return fragmentBinding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
linearLayoutManager = LinearLayoutManager(this.context)
recyclerview_lista.setHasFixedSize(true)
recyclerview_lista.layoutManager = linearLayoutManager
binding?.apply {
viewModel = sharedViewModel
informationFragment = this@InformationFragment
lifecycleOwner = viewLifecycleOwner
}
}
private fun getMyData() {
val retrofitBuilder = Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
.baseUrl(BASE_URL)
.build()
.create(ApiInterface::class.java)
val retrofitData = retrofitBuilder.getData()
retrofitData.enqueue(object : Callback<List<MyDataItem>?> {
override fun onResponse(
call: Call<List<MyDataItem>?>,
response: Response<List<MyDataItem>?>
) {
val responseBody = response.body()!!
adapterItem = AdapterItem(responseBody)
adapterItem.notifyDataSetChanged()
recyclerview_lista.adapter=adapterItem
}
override fun onFailure(call: Call<List<MyDataItem>?>, t: Throwable) {
d("informationFragment", "onFailure: " t.message)
}
})
}
}
**Fragment XML**
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".InformationFragment"
>
<androidx.recyclerview.widget.RecyclerView
android:id="@ id/recyclerview_lista"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="40dp"/>
<TextView
android:id="@ id/activitate_txt"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:padding="8dp"
android:text="@{@string/ramase viewModel.ideal.toString()}"
android:textSize="18dp"
app:layout_constraintTop_toTopOf="@id/recyclerview_lista"
tools:ignore="MissingConstraints"
android:layout_marginTop="-40dp"/>
</androidx.constraintlayout.widget.ConstraintLayout>
**row_items XML:**
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@ id/numeLista"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="10sp"
android:text="Nume"
android:textSize="20sp">
</TextView>
<TextView
android:id="@ id/caloriiLista"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="10sp"
android:text="calorii"
android:textSize="20sp">
</TextView>
<CheckBox
android:id="@ id/checkbox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
</LinearLayout>
</androidx.cardview.widget.CardView>
CodePudding user response:
Android: checkbox listener First you need to add the checkBox to your ViewHolder
Then you apply a checkbox listener specified in the link above. when isChecked is true, then you add the sum of its calorie to an integer, if not you remove it:
var calorieSum : Int = 0
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val food = userList[position].foodItems.first()!!
holder.numeLista.text= food.foodName
holder.caloriiLista.text=food.calories.toString()
holder.checkBox.setOnCheckedChangeListener(object : CompoundButton.OnCheckedChangeListener {
override fun onCheckedChanged(buttonView : CompoundButton, isChecked : Boolean) {
if (isChecked){
calorieSum = food.calories
}else{
calorieSum -= food.calories
}
}
});
}
CodePudding user response:
The simplest way but not the most efficient one may be this:
consider a sum variable in the adapter:
class AdapterItem( val userList: List<MyDataItem>): RecyclerView.Adapter<AdapterItem.ViewHolder>() {
var sum = 0
and set a listener to your items:
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.numeLista.text=userList[position].foodItems.get(0).foodName
holder.checkBox.setOnCheckedChangeListener(object : CompoundButton.OnCheckedChangeListener {
override fun onCheckedChanged(buttonView : CompoundButton, isChecked : Boolean) {
if (isChecked){
calorieSum = food.calories
}else{
calorieSum -= food.calories
}
}
});
...
}
This is just a Pseudocode to explain my idea and may need to change a bit
CodePudding user response:
If you're using check boxes in a RecyclerView
, you need to keep track of which ones are checked anyway - the ViewHolder
s get reused as you scroll, and you need to be able to check or uncheck the box in each ViewHolder
depending on whether the item it's displaying is supposed to be checked. If you don't, the checkbox will just "stick" as you scroll, showing a check for all the other items that happen to display in that VH, because you're never updating it.
So you need to do something like this:
class AdapterItem( val userList: List<MyDataItem>): RecyclerView.Adapter<AdapterItem.ViewHolder>() {
// a collection of all the item positions that are checked
private val checkedItems = mutableSetOf<Int>()
// a function to handle storing the checked state for a particular item
private fun setCheckedState(position: Int, checked: Boolean) {
if (checked) checkedItems.add(position)
else checkedItems.remove(position)
}
// needs to be an -inner- class so it can access the setCheckedState function
// on its parent class
inner class ViewHolder(itemView: View):RecyclerView.ViewHolder(itemView) {
// store a reference to this VH's checkbox
val checkBox = itemView.findViewById<CheckBox>(R.id.checkBox)
init {
// a click listener is easier than a change listener, since the latter
// will fire if you update the checked state yourself, but a click
// listener won't
checkBox.setOnClickListener {
// store the checked state for the item this VH is displaying
setCheckedState(bindingAdapterPosition, checkBox.isChecked)
}
}
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
// Set the checkbox state depending on whether this item is selected
holder.checkBox.isChecked = position in checkedItems
}
}
Now you can scroll and onBindViewHolder
can set each item's checked state properly. (You can also save and restore checkedItems
to maintain the checked state during activity recreation etc.)
So now you're keeping track of your checked items, it's easy enough to update a total - just recalculate it and display it whenever setCheckedState
is called!
// your internal total if you need one
var total: Int = 0
private fun setCheckedState(position: Int, checked: Boolean) {
if (checked) checkedItems.add(position)
else checkedItems.remove(position)
// recalculate the total - here's one way to do it
total = checkedItems.sumOf { index -> userItems[index].calories }
// or a safer way, if you can't guarantee the checked items are
// still in the user list for some reason:
total = checkedItems.sumBy { index -> userItems.getOrNull(index])?.calories ?: 0 }
}
Or, arguably a better way to do it:
val total: Int get() = checkedItems.sumOf { index -> userItems[index].calories }
This way total
always reflects the current state of the checked items - you don't need to update the checked items and then also update the total, this way it's sort of automatic. The tradeoff is that it gets recalculated every time the value is read, but you shouldn't need to read it often anyway in this situation, probably the same number of times you'd update it manually (since you're pushing updates when they occur, you're not actively polling it watching for changes).
Now you also need to push the update to whatever needs to display that total - lots of ways to do this too, doing it through a ViewModel
would be best if you're already using those, but we can do a listener function too:
class AdapterItem(
val userList: List<MyDataItem>,
private val calorieListener: (Int) -> Unit)
) : RecyclerView.Adapter<AdapterItem.ViewHolder>() {
Here we're passing in a function that we can call with the current calorie total whenever it updates. So in your Activity
or whatever, you can do this:
myRecyclerView.adapter = AdapterItem(users) { calorieTotal ->
someTextView.text = "Total calories: $calorieTotal"
}
And now the adapter has a function that will update a TextView
with the new total. So you just need to call it whenever checkedItems
updates:
private fun setCheckedState(position: Int, checked: Boolean) {
...
// push the new total to the listener function
calorieListener(total)
}
And that's basically it!