How to set String key in recyclerview-selection in Android? - android

I am implementing RecyclerView and want to get details of selected items.
I am following a tutorial given here. But I want to get some string key instead of item position.
For example, if i do like this:
for (selection in selectionTracker?.selection!!)
Log.e("tag", selection.toString())
I get indexes as output, but I want to get string keys here.
But I don't know what further changes should be made if I do like this:
fun getItemDetails() = object: ItemDetailsLookup.ItemDetails<Long>() {
override fun getSelectionKey() = itemId
override fun getPosition() = adapterPosition
}
and change it into:
fun getItemDetails() = object: ItemDetailsLookup.ItemDetails<Long>() {
override fun getSelectionKey() = myData[adapterPosition].myKey // a string
override fun getPosition() = adapterPosition
}
----EDIT----
These are my codes:
The adapter class:
class AdapterSubjectsIndexRV (
private val indexList: ArrayList<ModelSubjectsIndex>,
val itemClick: (String) -> Unit
): RecyclerView.Adapter<AdapterSubjectsIndexRV.MyViewHolder>() {
// every recycler view item has unique id
init {
setHasStableIds(true)
}
// Provide a reference to the views for each data item
// Complex data items may need more than one view per item, and
// you provide access to all the views for a data item in a view holder.
inner class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val courseValue = itemView.findViewById<TextView>(R.id.course_value)!!
val courseCode = itemView.findViewById<TextView>(R.id.course_code)!!
val courseName = itemView.findViewById<TextView>(R.id.course_name)!!
val progressValue = itemView.findViewById<TextView>(R.id.progress_value)!!
val progressBar = itemView.findViewById<ProgressBar>(R.id.progress_bar)!!
val selectedOverlay = itemView.findViewById<View>(R.id.subjects_card_view_selected)!!
// set click events
init {
itemView.setOnClickListener {
itemClick(courseCode.text.toString())
}
}
// get item details
fun getItemDetails() = object: ItemDetailsLookup.ItemDetails<String>() {
// override fun getSelectionKey() = itemId
override fun getSelectionKey() = indexList[adapterPosition].courseCode
override fun getPosition() = adapterPosition
}
}
// lookup class
class MyItemDetailsLookup(private val rv: RecyclerView): ItemDetailsLookup<String>() {
override fun getItemDetails(e: MotionEvent): ItemDetails<String>? {
val v = rv.findChildViewUnder(e.x, e.y)
return if (v != null) {
(rv.getChildViewHolder(v) as MyViewHolder).getItemDetails()
} else null
}
}
// set tracker
var tracker: SelectionTracker<Long>? = null
// Create new views (invoked by the layout manager)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AdapterSubjectsIndexRV.MyViewHolder {
// create a new view
val itemView = LayoutInflater.from(parent.context).inflate(R.layout.subjects_card, parent, false)
return MyViewHolder(itemView)
}
// Replace the contents of a view (invoked by the layout manager)
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
holder.courseValue.text = indexList[position].courseValue
holder.courseCode.text = indexList[position].courseCode
holder.courseName.text = indexList[position].courseName
holder.progressValue.text = "${indexList[position].progress}%"
holder.progressBar.progress = indexList[position].progress
// Highlight the item if it's selected
holder.selectedOverlay.visibility = if (tracker!!.isSelected(position.toLong())) View.VISIBLE else View.INVISIBLE
}
// Return the size of your data set (invoked by the layout manager)
override fun getItemCount() = indexList.size
override fun getItemId(position: Int): Long {
return position.toLong()
}
}
And this is how I am setting my fragment:
val rvAdapter = AdapterSubjectsIndexRV(getSubjectsIndexList(subjectsList)) {
// if not in selection mode
if (selectionTracker?.selection?.size() == 0) {
listener.showSubjectDetails(it)
}
}
// set recycler view
val recyclerView = theView.findViewById<RecyclerView>(R.id.subjects_recycler_view)
recyclerView.apply {
setHasFixedSize(true)
layoutManager = LinearLayoutManager(activity)
adapter = rvAdapter
}
// set selection tracker
selectionTracker = SelectionTracker
.Builder<Long>(
"subjects_index_selection",
recyclerView,
StableIdKeyProvider(recyclerView),
AdapterSubjectsIndexRV.MyItemDetailsLookup(recyclerView),
StorageStrategy.createLongStorage()
).withSelectionPredicate(
SelectionPredicates.createSelectAnything()
).build()
rvAdapter.tracker = selectionTracker
if(savedInstanceState != null)
selectionTracker?.onRestoreInstanceState(savedInstanceState)
return theView

Change the generic type from <Long> to <String>, then you will be able to return a string key.
fun getItemDetails() = object: ItemDetailsLookup.ItemDetails<String>() {
override fun getSelectionKey() = myData[adapterPosition].myKey
// ...
}

I am a little bit confused by your code, but the best way to get the details is to do it on the `onBindViewHolder method. Call your on click method and then within that do your method to get the string.

Related

How to use select / deselect all items in Recyclerview Android

In my application I have one fragment and in this fragment I have one recyclerview and 2 button.
Button 1 is Select all and button 2 is Deselect all.
I write below codes for recyclerview:
`
private lateinit var binding: ItemDayMyBinding
private lateinit var context: Context
private val TYPE_HEADER = 0
private val TYPE_DAY = 1
private var firstDayDayOfWeek = 0
private var totalDays = 0
private var todayPosition = -1
private val addedDateList = mutableListOf<PersianDate>()
private val removedDateList = mutableListOf<PersianDate>()
private val selectedDays = mutableListOf<DayEntity>()
init {
firstDayDayOfWeek = items[0].dayOfWeek
totalDays = items.size
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
binding = ItemDayMyBinding.inflate(LayoutInflater.from(parent.context), parent, false)
context = parent.context
return ViewHolder()
}
override fun getItemCount() = 7 * 7
override fun getItemViewType(position: Int): Int {
return if (isPositionHeader(position)) {
TYPE_HEADER
} else {
TYPE_DAY
}
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(position)
holder.setIsRecyclable(false)
}
inner class ViewHolder : RecyclerView.ViewHolder(binding.root) {
//Bind data
fun bind(pos: Int) {
binding.apply {
var position = pos
position += 6 - (position % 7) * 2
//Check
if (totalDays < position - 6 - firstDayDayOfWeek) {
return
} else {
//Day
if (!isPositionHeader(position)) {
if (position - 7 - firstDayDayOfWeek >= 0) {
val day = items[position - 7 - firstDayDayOfWeek]
dayTxt.isVisible = true
//Day text options
dayTxt.text = day.num
dayTxt.setTextColor(ContextCompat.getColor(context, R.color.black))
//Holiday
if (day.isHoliday) {
dayTxt.setTextColor(ContextCompat.getColor(context, R.color.mystic))
} else {
dayTxt.setTextColor(ContextCompat.getColor(context, R.color.black))
//Today
if (day.isToday) {
todayPosition = position
}
//Previous of today
if (position < todayPosition) {
dayTxt.setTextColor(ContextCompat.getColor(context, R.color.spanishGray))
} else {
dayTxt.setTextColor(ContextCompat.getColor(context, R.color.black))
}
}
} else {
dayTxt.isVisible = false
}
} else {
//Header
dayTxt.text = NAME_OF_DAYS_OF_WEEK_NAME[position]
dayTxt.setTextColor(ContextCompat.getColor(context, R.color.weldonBlue))
}
}
}
}
}
private fun isPositionHeader(position: Int) = position < 7
I want when click on select all button, select all today and next days.
Not previous days!
Searched in stackoverflow and find this:
`
ArrayList<Item> selected = new ArrayList<Item>();
ArrayList<Item> items = new ArrayList<Item>();
public void selecteAll() {
selected.clear();
selected.addAll(items);
notifyDataSetChanged();
}
public void clearAll() {
selected.clear();
notifyDataSetChanged();
}
public void bindView() {
Item item = items.get(position);
if(selected.contains(item) {
// Do selected action
} else {
// Non selecetd ctions
}
}
`
In this solution just select all of items, but I want just select today and next days!
How can I it?
Not getting exactly what you have in Item and other data but yes below code can give you hint what you can do
I want when click on select all button, select all today and next days.
As per your requirement , you first need to determine that you need to perform selection operation or not and from where
So first you declare one boolean in your adapter class
private boolean needToSelectDays = false
after declaring it in adapter , you can make change in selectAll function like this
public void selecteAll() {
needToSelectDays = true
notifyDataSetChanged();
}
public void clearAll() {
needToSelectDays = false
notifyDataSetChanged();
}
in above code we are managing boolean variable only .. now it is time of use it and as per your code , you are binding data in ViewHolder class so you can add this code within your existing code
fun bind(pos: Int) {
// your existing code
if(needToSelectDays && dayGettingFromItem >= currentDay){
// you can write here logic of selecting date
}else{
// you can write here logic of unselecting dat
}
}
hope it will help you
Jacson
When you create adapter for recycler view then you pass list of model of items
so just create one variable like isSelected = false
on select all button click change all item model to selected is true and for deselect all button click for all item is selected is false
and if you need select only today and next days then set is selected true for only today and next day
after that notify dataset chenge so resule as you want

How to set the background color of all ListView Items

I have got the following for setting the background color for a single item when clicking on it:
// Get the listView on the Home Fragment
val contactList = findViewById<ListView>(R.id.listViewContacts)
// Create IDs for each element
val id : Int = R.id.txtListElement
// Fill the ListView with the custom contact_list_template
arrayAdapter = ArrayAdapter(this,
R.layout.contact_list_template, id, contactSimple)
contactList.adapter = arrayAdapter
contactList.onItemClickListener = AdapterView.OnItemClickListener { adapterView, view,
position, _ ->
val selectedItem = adapterView.getItemAtPosition(position) as String
val itemIdAtPos = adapterView.getItemIdAtPosition(position)
view.setBackgroundColor(ContextCompat.getColor(this, R.color.purple_200))
...
}
But how can I set the color for every object inside the ListView?
I tried
contactList.setBackgroundColor(ContextCompat.getColor(this, R.color.white)) but that does not seem to work.
Try to define your custom BaseAdapter for the ListView. (I assume that the contact_list_template XML contains a TextView with id title).
class ListViewContactAdapter(
private val contactList: List<String>
) : BaseAdapter() {
private val lastClicked = ""
override fun getCount(): Int = contactList.size
override fun getItem(position: Int): String = contactList[position]
override fun getItemId(position: Int): Long = position.toLong()
override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View {
val view = convertView ?: LayoutInflater.from(parent.context).inflate(R.layout.contact_list_template, parent, false)
val holder = ViewHolderContact(view)
val contact = getItem(position)
holder.title.text = contact
if (contact == lastClicked) {
// Selected background
view.setBackgroundColor(ContextCompat.getColor(this, ...))
} else {
// Unselected background
view.setBackgroundColor(ContextCompat.getColor(this, ...))
}
view.setOnClickListener {
lastClicked = contact
notifyDataSetChanged()
}
return view
}
class ViewHolderContact(itemView: View) {
val title = itemView.findViewById<TextView>(R.id.title)
}
}
You can then set the adapter of the list with:
val contactList = findViewById<ListView>(R.id.listViewContacts)
contactList.adapter = ListViewContactAdapter(contactSimple)

RecyclerView showing two highlighted item

I have two scenario to highlight the item in recyclerview first one is when user click on any item so that clicked item should be highlighted and and second scenario is when last or recent item added into the list that time that last added item should be highlighted.
So my first scenario(onClick) is working fine but problem is in my second scenario so whenever I'm adding item into the list so two items are highlighted. Please suggest me where I am wrong. and one more thing that recent or last item should be perform auto click also. so How can I achieve it.
class CarItemAdapter(
private val activity: MainActivity,
private val carList: ArrayList<CarEntity>,
private val carViewModel: CarViewModel,
private val settings: Settings
) :
RecyclerView.Adapter<RecyclerView.ViewHolder>() {
var listener: OnItemsClickListener? = null
var rowIndex = 100
var listItemCount = 0
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
return when (viewType) {
VIEW_TYPE_ADDITEM -> ViewHolderAddCarItem(SettingsCarAddItemTileBinding.inflate(layoutInflater, parent, false))
VIEW_TYPE_ITEM -> ViewHolderCarItem(SettingsCarItemTileBinding.inflate(layoutInflater, parent, false))
else -> throw IllegalArgumentException("Invalid view type")
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (holder) {
is ViewHolderCarItem -> holder.bind(carList[position -1], position-1)
is ViewHolderAddCarItem -> holder.bind()
}
}
inner class ViewHolderAddCarItem internal constructor(private var binding: SettingsCarAddItemTileBinding) : RecyclerView.ViewHolder(binding.root) {
fun bind() {
binding.lifecycleOwner = activity
binding.handler = EventHandler()
binding.userSpecificCarSubtitle.setText(R.string.user_specific_car_subtitle)
}
inner class EventHandler {
fun onItemClicked() {
activity.showSubPage(Car360AddVehiclePage(activity), true)
}
}
}
inner class ViewHolderCarItem internal constructor(private var binding: SettingsCarItemTileBinding) : RecyclerView.ViewHolder(binding.root) {
fun bind(carEntity: CarEntity, position: Int) {
Timber.d("Data store position %s %s", position, carList.size)
listItemCount = itemCount
binding.lifecycleOwner = activity
val bmp = BitmapFactory.decodeByteArray(carEntity.image, 0, Objects.requireNonNull<ByteArray>(carEntity.image).size)
binding.carImage.setImageBitmap(bmp)
binding.carName.text = carEntity.label
binding.layoutCar.setOnClickListener {
listItemCount = 100
listener?.onItemClick(carEntity)
settings.finNumber = carEntity.finNumber
settings.selectedCarType = activity.getString(R.string.settings_category_view_sub_title_for_user_specific_car)
settings.isCarSelected = true
rowIndex = position
notifyDataSetChanged()
}
binding.deleteButton.setOnClickListener {
GlobalScope.launch(Dispatchers.IO) { carViewModel.deleteByFinNumber(carEntity.finNumber) }
carList.removeAt(position)
notifyDataSetChanged()
}
if(position == listItemCount-2){
binding.layoutCar.setBackgroundColor(activity.getColor(R.color.petrol))
binding.carName.setTextColor(activity.getColor(R.color.white))
}else{
binding.layoutCar.setBackgroundColor(activity.getColor(R.color.coolGray))
binding.carName.setTextColor(activity.getColor(R.color.coolGray_80k))
}
if (rowIndex == position) {
binding.layoutCar.setBackgroundColor(activity.getColor(R.color.petrol))
binding.carName.setTextColor(activity.getColor(R.color.white))
}
}
}
override fun getItemViewType(position: Int): Int {
return if (position == 0 && position <= carList.size) {
VIEW_TYPE_ADDITEM
} else VIEW_TYPE_ITEM
}
override fun getItemCount(): Int {
return carList.size + 1
}
interface OnItemsClickListener {
fun onItemClick(carEntity: CarEntity)
}
fun setWhenClickListener(listener: OnItemsClickListener) {
this.listener = listener
}
companion object {
const val VIEW_TYPE_ITEM = 0
const val VIEW_TYPE_ADDITEM = 1
}
}
MyFragmentSubPage : where I'm adding data into the list
if (isDialogVisible) {
Dialog imageDownloadCheckDialog = Dialog.createProgressBarDialog(getContext(), R.string.image_progressbar_title);
imageDownloadCheckDialog.setCancelable(false);
imageDownloadCheckDialog.show();
carViewModel.getGetDismissDialog().observe(getActivity(), isDismissDialog -> {
if (isDismissDialog) {
imageDownloadCheckDialog.dismiss();
carViewModel.dismissDialog(false);
new Thread(() -> {
CarEntity carEntity = carViewModel.getImageFor340Degree(vinNumber, "340");
// Here I am adding data into the list
recyclerImageList.add(carEntity);
}).start();
}
});
}
RecyclerView recyclerView = binding.getRoot().findViewById(R.id.list);
if (recyclerView != null) {
layoutManager = new GridLayoutManager(getContext(), 2);
recyclerView.setLayoutManager(layoutManager);
}
carItemAdapter = new CarItemAdapter(getActivity(), recyclerImageList, eventHandler, carViewModel, settings);
assert recyclerView != null;
recyclerView.setAdapter(carItemAdapter);
recyclerView.getRecycledViewPool().setMaxRecycledViews(carItemAdapter.VIEW_TYPE_ITEM, 50);
First make a CarEntity var inside your adapter called selectedCar.
Make a function inside your adapter called submitItem
fun submitItem(car: CarEntity) {
selectedCar = car //set the selected car to the new car
notifyItemChanged(rowIndex)//rebind old selected car
carList.add(car) //add new car to list
rowIndex = carList.size() - 1 //set current selected index
notifyItemInserted(rowIndex)//notify the change at selected index
}
And your dialog
new Thread(() -> {
CarEntity carEntity = carViewModel.getImageFor340Degree(vinNumber, "340");
// Here I am adding data into the list
requireActivity().runOnUiThread{
carItemAdapter.submitItem(carEntity);
}
}).start();
and your button
binding.layoutCar.setOnClickListener {
listItemCount = 100
listener?.onItemClick(carEntity)
settings.finNumber = carEntity.finNumber
settings.selectedCarType = activity.getString(R.string.settings_category_view_sub_title_for_user_specific_car)
selectedCar = carEntity //set the select car on click
notifyItemChanged(rowIndex) //notify old row has changed
notifyItemChanged(position) //notify new row has changed
rowIndex = position // set current selected row
}
and on bind to set selected view stuff
if(carEntity == selectedCar){
//highlight row
}

Initializing on item selected listener (Custom Spinner Adapter)

I have created a custom spinner adapter which returns two views (item_new_area & item_area), item_new_area is static with an option Add New and item_area populated dynamic.
When Add new is selected,I want to launch the dialog for add new area but i keep on getting an error that lateinit property listener has not been initialized
Custom Spinner Adapter
class AreaSpinnerAdapter(private val inflater: LayoutInflater) : BaseAdapter() {
var areas: List<AreaEntity>? = null
lateinit var listener: OnItemSelectedListener
override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View {
return when (getItemViewType(position)) {
1 -> {
val binding: ItemNewAreaBinding = DataBindingUtil.inflate<ItemNewAreaBinding>(
inflater,
R.layout.item_new_area,
parent, false
).also {
it.root.tag = it
}
binding.addNew.setOnClickListener {
listener.onClick()
}
binding.root
}
else -> {
val binding: ItemAreaBinding = DataBindingUtil.inflate<ItemAreaBinding>(
inflater,
R.layout.item_area,
parent, false
).also {
it.root.tag = it
}
binding.area = getItem(position)
binding.root
}
}
}
override fun getItemViewType(position: Int): Int {
return if (position == 1) {
1
} else {
2
}
}
fun setOnItemClickListener(listener: OnItemSelectedListener) {
this.listener = listener
}
interface OnItemSelectedListener {
fun onClick()
}
}
I try to set the on Item Clicked listener in the onCreateView method of my fragment:
val spinnerAdapter = AreaSpinnerAdapter(layoutInflater)
spinnerAdapter.setOnItemClickListener(object :
AreaSpinnerAdapter.OnItemSelectedListener {
override fun onClick() {
addNewAreaDialog()
}
})
binding?.spinnerArea?.adapter = spinnerAdapter
Error Log
kotlin.UninitializedPropertyAccessException: lateinit property listener has not been initialized
at com.test.adapter.AreaSpinnerAdapter.getListener(AreaSpinnerAdapter.kt:15)
at com.test.adapter.AreaSpinnerAdapter$getView$1.onClick(AreaSpinnerAdapter.kt:50)
line 15 = lateinit var listener: OnItemSelectedListener
line 25 = listener.onClick()
I know that am supposed to initialize the listener before i call getView method but for some reasons am failing to initialize the listener, Any help on how to do it shall be be greatly appreciated. Thanks
You need to move the listener AreaSpinnerAdapter to the constructor.
class AreaSpinnerAdapter(
private val inflater: LayoutInflater,
private val listener: OnItemSelectedListener
) : BaseAdapter() {
var areas: List<AreaEntity>? = null
and then call it like
val spinnerAdapter = AreaSpinnerAdapter(layoutInflater,
object: AreaSpinnerAdapter.OnItemSelectedListener {
override fun onClick() {
addNewAreaDialog()
}
})

Add item to RecyclerView from another Activity (Kotlin)

I have the following:
Main Activity with RecyclerView which is filled from Array List
Adapter (of course)
Another Activity where a new item should be created, added to RecyclerView on Main Activity and then be closed
The main question is, how can I add an item to RecyclerView from another Activity?
MainActivity Code
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val items = arrayListOf<CustomData>()
items.add(CustomData("Name", R.drawable.icon, 50, "Weekly", "Monday", 1, 50))
button.setOnClickListener {
val intent = Intent(this, NewItem::class.java)
startActivity(intent)
}
itemsListView.apply {
layoutManager = LinearLayoutManager(this#MainActivity)
adapter = ItemsAdapter(items)
}
}
Adapter Code
class ItemsAdapter(private val items: ArrayList<CustomData>) :
RecyclerView.Adapter<ItemsAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.wish_row, parent, false)
val holder = ViewHolder(view)
view.setOnClickListener {
val intent = Intent(parent.context, ItemView::class.java)
intent.putExtra("Name", items[holder.adapterPosition].name)
intent.putExtra("Price", items[holder.adapterPosition].price)
intent.putExtra("Icon", items[holder.adapterPosition].image)
parent.context.startActivity(intent)
}
return holder
}
override fun getItemCount() = items.size
override fun onBindViewHolder(holder: ItemsAdapter.ViewHolder, position: Int) {
holder.name.text = items[position].name
holder.price.text = items[position].price.toString()
holder.image.setImageDrawable(holder.image.context.getDrawable(items[position].image))
}
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val image: ImageView = itemView.findViewById(R.id.imageView)
val name: TextView = itemView.findViewById(R.id.textView)
val price: TextView = itemView.findViewById(R.id.textView2)
}
}
Another Activity from which I want to add item to Recyclerview
class NewItem : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.new_item_activity)
val items = arrayListOf<CustomData>()
items.add(CustomData("Name2", R.drawable.icon, 50, "Weekly", "Monday", 2, 50))
newItemButton.setOnClickListener {
itemsListView.layoutManager = LinearLayoutManager(this)
itemsListView.adapter = itemsAdapter(items)
finish()
}
}
}
When adding new item from your NewItem activity, pass a bundle adding that item to your MainActivity.
Then in your MainActivity, get that bundle and set that new item to your RecyclerView.

Categories

Resources