I've created a RecyclerView with Horizontal Scrolling and PageSnapHelper. Now I think to replace RecyclerView with ViewPager2.? Can I simply set RecyclerView Adapter I've created earlier for new ViewPager2.?
Adapter Class goes here
class QuoteAdapter(
val context: Context,
val list: ArrayList<ResultsItem>
) : RecyclerView.Adapter<QuoteAdapter.QuoteViewHolder>() {
var i = 0;
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int):
QuoteViewHolder {
val view = LayoutInflater.from(context)
.inflate(R.layout.item_quote, parent, false)
return QuoteViewHolder(view)
}
override fun getItemCount() = list.size
override fun onBindViewHolder(holder: QuoteViewHolder, position: Int) {
holder.quote.text = list[position].quoteText
holder.quote_by.text = "- ${list[position].quoteAuthor}"
ColorStore()
if (position == 0) {
holder.quote_bg.setBackgroundColor(ContextCompat.getColor(context, R.color.md_blue_400))
} else {
holder.quote_bg.setBackgroundColor(ContextCompat.getColor(context, colorList.random()))
}
holder.quote_bg.setOnClickListener {
holder.quote_bg.setBackgroundColor(ContextCompat.getColor(context, colorList.random()))
}
}
class QuoteViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val quote = itemView.quote_text
val quote_by = itemView.by_text
val quote_bg = itemView.quote_bg
}
}
I just set recyclerview adapter to viewpager2. It works.
Related
I am creating a GenericAdapter for handling single row|item layouts. Everything working fine only view-binding is not updating data..
I want to get RecyclerView.ViewHolder binding in callback ,I know I can bind it in adapter using BR.item and executePending
I want viewDataBinding context in a Callback
holder.binding.name.text = mutableList[pos]
Above line in TestActivity not working properly
GenericAdapter.kt
class GenericAdapter<T,VB:ViewDataBinding>(
var items:MutableList<T>,
#LayoutRes val resLayoutID:Int,
val onBind:(holder:GenericViewHolder<T,VB>,pos:Int) -> Unit
): RecyclerView.Adapter<GenericViewHolder<T,VB>>() {
lateinit var mItemBinding:VB
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): GenericViewHolder<T,VB> {
val layoutInflater = LayoutInflater.from(parent.context)
mItemBinding = DataBindingUtil.inflate(layoutInflater, resLayoutID, parent, false)
return GenericViewHolder(mItemBinding)
}
override fun onBindViewHolder(holder: GenericViewHolder<T,VB>, position: Int) {
onBind(holder,position)
}
override fun getItemCount(): Int = items.size
}
GenericViewHolder.kt
class GenericViewHolder<T,VB: ViewDataBinding>(val binding: VB)
:RecyclerView.ViewHolder(binding.root){
val mItemBinding:VB = binding
}
TestActivity.kt
class TestActivity:AppCompatActivity() {
lateinit var recyclerView: RecyclerView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_test)
recyclerView = findViewById(R.id.recyclerView)
recyclerView.layoutManager = LinearLayoutManager(this)
recyclerView.setHasFixedSize(true)
populateList()
}
private fun populateList(){
val mutableList = mutableListOf<String>("apple","mango","tutti fruti","apricot",
"apple","mango","tutti fruti","apricot",
"apple","mango","tutti fruti","apricot",
"apple","mango","tutti fruti","apricot")
val mAdapter = GenericAdapter<String,ItemCountryBinding>(mutableList,R.layout.item_country){ holder,pos ->
//val nameTv = holder.itemView.findViewById<TextView>(R.id.name)
//nameTv.text = mutableList[pos]
holder.binding.name.text = mutableList[pos]
}
recyclerView.adapter = mAdapter
}
}
Where as below code working fine
val nameTv = holder.itemView.findViewById<TextView>(R.id.name)
nameTv.text = mutableList[pos]
I would recommend do onBind inside view holder.
I don't see reason why you need this callback.
You already passed mutableList to the adapter.
For example here is my ViewHolder
class NewsViewHolder(private val binding: ListItemNewsBinding) : RecyclerView.ViewHolder(binding.root) {
fun bind(news: RedditPost, itemClick: (RedditPost, View) -> Unit) {
with(binding) {
root.transitionName = news.thumbnail
root.setOnClickListener { itemClick.invoke(news, binding.root) }
title.text = news.title
image.setImageUrl(news.thumbnail)
author.text = news.author
xHoursAgo.text = news.created_utc
numComments.text = news.numComments
}
}
}
And
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
if (isLoadingMore && position == (itemCount - 1)) return
(holder as? NewsViewHolder)?.bind(news[position], itemClick)
}
I am creating a RecyclerView which has an item with dynamic chips. I am looping through an array to set the text of the chip, this works fine but when I scroll the RecyclerView, more chips gets created
class Adapter(activity: Activity): RecyclerView.Adapter<Adapter.Holder>() {
val mActivity = activity
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder {
val itemView: View?
itemView = parent.inflate(R.layout.item, false)
return Holder(itemView)
}
override fun getItemCount(): Int {
return 7
}
override fun onBindViewHolder(holder: Holder, position: Int) {
holder.bindView()
}
inner class Holder(view: View): RecyclerView.ViewHolder(view){
private val binding: ItemBinding = ItemBinding.bind(view)
private val array: ArrayList<String> = ArrayList(5)
init {
}
fun bindView() {
binding.textView.text = ""
for (item in array){
val mChip: Chip = mActivity.layoutInflater.inflate(R.layout.item_chip,null,false) as Chip
mChip.text = item
val paddingDp = TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, 10f,
mActivity.resources.displayMetrics
).toInt()
mChip.setPadding(paddingDp, 0, paddingDp, 0)
mChip.setOnCloseIconClickListener { Toast.makeText(mActivity.applicationContext, absoluteAdapterPosition.toString()+ mChip.text,Toast.LENGTH_SHORT).show() }
binding.chips.addView(mChip)
}
}
}
}
I have one ParentAdapter that has multiple view holders and each view holder has its own RecyclerView and an adapter.
I am facing a problem when I need to notify an item of a child adapter is changed. I passed the position of the changed child view holder to the parent and I can use notifyItemChanged(position) but the whole child adapter is recreated.
What I need is to be able to notify a child item and keep the state of the child recyclerview as it is.
Here's the code of the Parent Adapter:
class ParentAdapter(): RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private lateinit var context: Context
private var items = listOf<ParentItem>()
companion object {
const val CHILD_ONE = 1
const val CHILD_TWO = 2
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
context = parent.context
val layoutInflater = LayoutInflater.from(context)
return when(viewType) {
CHILD_ONE -> {
ChildOneViewHolder(layoutInflater.inflate(R.layout.child_one, parent, false))
}
CHILD_TWO -> {
ChildTwoViewHolder(layoutInflater.inflate(R.layout.child_two, parent, false))
}
else -> {
BlankItemViewHolder(layoutInflater.inflate(R.layout.fragment_blank, parent, false))
}
}
}
fun update(items: List<Section>) {
this.items = items
notifyDataSetChanged()
}
override fun getItemViewType(position: Int): Int {
val item = items[position]
return when(item.type) {
1 -> CHILD_ONE
2 -> CHILD_TWO
else -> -1
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when(holder.itemViewType) {
CHILD_ONE -> {
val section = items[position]
val childOneAdapter = ChildOneAdapter(this)
if(!section.items.isNullOrEmpty()) {
holder.rvChildOne.apply {
layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
adapter = childOneAdapter
isNestedScrollingEnabled = true
}
childOneAdapter.update(section.items)
}
}
CHILD_TWO -> {
val section = items[position]
val childTwoAdapter = ChildTwoAdapter(this)
if(!section.items.isNullOrEmpty()) {
holder.rvChildTwo.apply {
layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
adapter = childTwoAdapter
isNestedScrollingEnabled = true
}
childTwoAdapter.update(section.items)
}
}
else ->
initBlankSection(holder as BlankItemViewHolder, position)
}
}
private fun initBlankSection(holder: BlankItemViewHolder, position: Int) {
}
private class ChildOneViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val rvChildOne: RecyclerView = itemView.findViewById(R.id.rv_child_one)
}
private class ChildTwoViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val rvChildTwo: RecyclerView = itemView.findViewById(R.id.rv_child_two)
}
private class BlankItemViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {}
override fun getItemCount(): Int = items.size
}
I have a nested recyclerview. Parent recyclerview adapter can add a data to child by a button, then child recyclerview adapter can remove a data in child by a button. The preview is like below.
Adding data seems fine, but when removing data i got index out of bound. I have called notifyDataSetChanged() in add or remove function.
My parent adapter code :
class FormPlanParentAdapter :
RecyclerView.Adapter<FormPlanParentAdapter.ViewHolder>() {
private val data: MutableList<MutableList<ItemPlan>> = mutableListOf()
private val viewPool = RecyclerView.RecycledViewPool()
fun setData(newData: List<MutableList<ItemPlan>>) {
data.clear()
data.addAll(newData)
notifyDataSetChanged()
}
fun getData(): String = Gson().toJson(data)
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): ViewHolder {
return ViewHolder(
LayoutInflater.from(parent.context).inflate(R.layout.item_plan_parent, parent, false)
)
}
override fun getItemCount(): Int = data.size
override fun onBindViewHolder(
holder: ViewHolder,
position: Int
) {
val listPlans = data[position]
val childLayoutManager = LinearLayoutManager(
holder.recyclerView.context,
LinearLayoutManager.VERTICAL,
false
)
val childAdapter = FormPlanChildAdapter(listPlans, object : PlanParentCallback {
override fun deletePlan(position: Int) {
listPlans.removeAt(position)
notifyDataSetChanged()
}
})
holder.recyclerView.apply {
layoutManager = childLayoutManager
adapter = childAdapter
setRecycledViewPool(viewPool)
}
holder.textView.text = "Hari ke ${position + 1}"
holder.imageButton.setOnClickListener {
listPlans.add(ItemPlan())
notifyDataSetChanged()
}
}
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val recyclerView: RecyclerView = itemView.rv_plan_child
val textView: TextView = itemView.tv_days_of
val imageButton: ImageButton = itemView.ib_add
}
interface PlanParentCallback {
fun deletePlan(position: Int)
}
}
My child adapter code :
class FormPlanChildAdapter(
private val listPlans: List<ItemPlan>,
private val callback: FormPlanParentAdapter.PlanParentCallback
) :
RecyclerView.Adapter<FormPlanChildAdapter.ViewHolder>() {
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): ViewHolder {
return ViewHolder(
LayoutInflater.from(parent.context).inflate(R.layout.item_plan_child, parent, false)
)
}
override fun getItemCount(): Int = listPlans.size
override fun onBindViewHolder(
holder: ViewHolder,
position: Int
) {
val itemPlan = listPlans[position]
holder.etPlan.setText(itemPlan.plan)
holder.etPlan.doAfterTextChanged {
listPlans[position].plan = it.toString()
}
if (position == 0) {
holder.ibDelete.invisible()
} else {
holder.ibDelete.visible()
}
holder.ibDelete.setOnClickListener {
if (listPlans.size > 1) {
callback.deletePlan(position)
}
}
}
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val etPlan: EditText = itemView.et_plan
val ibDelete: ImageButton = itemView.ib_delete
}
}
Any help appreciated, thank you.
Reason:
The old TextWatchers and adapter position mess up when the dataset changed.
notifyDatasetChanged triggers onBindViewHolder, and then EditText's TextWatcher is being notified because of this line
holder.etPlan.setText(itemPlan.plan)
Since etPlan has already an active TextWatcher from previous binding, it tries to notify it with the old position of the adapter.
Quick answer:
Keep the TextWatcher in your ViewHolder, remove it from EditText on onBindViewHolder, and set it to the EditText again after setting the EditText's value.
So, update your child adapter's onBindViewHolder as
override fun onBindViewHolder(
holder: ViewHolder,
position: Int
) {
val itemPlan = listPlans[position]
holder.etPlan.removeTextChangedListener(holder.textWatcher)
holder.etPlan.setText(itemPlan.plan)
holder.textWatcher = holder.etPlan.doAfterTextChanged {
listPlans[position].plan = it.toString()
}
...
}
and also update it's ViewHolder like:
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val etPlan: EditText = itemView.et_plan
val ibDelete: ImageButton = itemView.ib_delete
var textWatcher: TextWatcher? = null
}
But, it's not recommended setting listeners on onBindViewHolder due to performance concerns. Please check this and this.
Here is another workaround:
Move all the TextWatcher things to Adapter via a listener, and prevent notifying the TextWatcher if it's not necessary.
interface TextChangeListener {
fun onTextChange(text: String, position: Int)
}
class FormPlanChildAdapter(...) :
RecyclerView.Adapter<FormPlanChildAdapter.ViewHolder>(), TextChangeListener {
override fun onBindViewHolder(
holder: ViewHolder,
position: Int
) {
val itemPlan = listPlans[position]
holder.setPlan(itemPlan.plan)
...
}
override fun onTextChanged(text: String, position: Int) {
listPlans[position].plan = text
}
inner class ViewHolder(itemView: View, textChangeListener: TextChangeListener) :
RecyclerView.ViewHolder(itemView) {
private var disableTextChangeListener = false
private val etPlan: EditText = itemView.et_plan
val ibDelete: ImageButton = itemView.ib_delete
init {
etPlan.doAfterTextChanged {
if (!disableTextChangeListener) {
textChangeListener.onTextChanged(it.toString(), adapterPosition)
}
}
}
fun setPlan(plan: String, notifyTextChangeListener: Boolean = false) {
if (notifyTextChangeListener) {
etPlan.setText(plan)
} else {
disableTextChangeListener = true
etPlan.setText(plan)
disableTextChangeListener = false
}
}
}
}
Please try doing
listPlans.removeAt(position)
notifyItemRemoved(position)
instead of
listPlans.removeAt(position)
notifyDataSetChanged()
I have the following adapter:
class HomeAdapter(val list: List<Team>, val fragment: HomeFragment) : RecyclerView.Adapter<HomeAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(LayoutInflater.from(fragment.context).inflate(R.layout.list_team, parent, false))
}
override fun getItemCount(): Int {
return list.size
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.position?.text = list[position].position.toString()
holder.number?.text = list[position].number
holder.name?.text = list[position].name
holder.puntuation?.text = list[position].puntuation.toString()
}
class ViewHolder(v: View) : RecyclerView.ViewHolder(v) {
var view: View = v
var position: TextView? = view.findViewById(R.id.textViewPosition)
var number: TextView? = view.findViewById(R.id.textViewNumber)
var name: TextView? = view.findViewById(R.id.textViewName)
var puntuation: TextView? = view.findViewById(R.id.textViewPuntuation)
}
}
And I have the following call in my fragment:
adapter = HomeAdapter(teams, fragment!!)
if (adapter?.list!!.isNullOrEmpty()) {
image_empty?.visibility = View.VISIBLE
} else {
recycler?.adapter = adapter
}
But my adapter is not being called. What am I doing wrong?