Hi I have a condition where I have to add different viewType with text
show more or loading...
at the bottom of the recyclerview
I have successfully added the footer view with show more and also I have added the listener on this footer item view for loading more data, here everything works fine. Since I have swipe refresh layout as a parent for recyclerview I show enable swiperefresh progress on loading and disable swiperefresh progress while loading is complete. Now what I need is whenever I click on the show more view type which is a footer attached to the recycler view I want the text to be changed with loading... and when the loading is completed the text should be changed to show more again.
Here is my adapter class
class MyTransactionAdapter(private val context: Context?,
private val transactionList: List<TransactionHistoryResponse>,
private val transactionListener: MyTransactionListener) : Filterable,
RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private var viewTypeList: Int = 0
private var viewTypeFooter: Int = 1
private var transactionListFiltered = transactionList
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return if (viewType == viewTypeList) {
val view: View = LayoutInflater.from(context).inflate(R.layout.row_my_transaction, parent, false)
ListViewHolder(view)
} else {
val view: View = LayoutInflater.from(context).inflate(R.layout.row_footer_so_more, parent, false)
FooterViewHolder(view)
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val myTransaction = transactionListFiltered[position]
if (holder is ListViewHolder) {
holder.bind(myTransaction, transactionListener)
}
}
override fun getItemCount(): Int {
return transactionListFiltered.size
}
override fun getItemViewType(position: Int): Int {
return if (position == transactionList.lastIndex) {
viewTypeFooter
} else {
viewTypeList
}
}
inner class FooterViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
init {
itemView.apply {
btnShowMore.setOnClickListener {
transactionListener.onLoadMoreClicked()
}
}
}
fun enableShowMore(enable: Boolean) {
if(enable){
itemView.btnShowMore.text = "show more"
}else{
itemView.btnShowMore.text = "loading..."
}
}
}
inner class ListViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private lateinit var mTransactionStatus: TransactionStatus
fun bind(myTransaction: TransactionHistoryResponse, transactionListener: MyTransactionListener){
//logic to populate data on views
}
}
}
And here is the fragment's function where I do the loading stuff
This function is being called for fetching data from remote
private fun getTransactionHistory() {
swipeContainer.enableDisableSwipeRefresh(true)
enableShowMore(false)
//logic to load web data and hide show swiperefresh
//when load is success call this function enableShowMore(true)
//when load is failed call this function enableShowMore(false)
}
#Synchronized
private fun enableShowMore(enable: Boolean) {
val viewHolder = recyclerView.findViewHolderForAdapterPosition(transactionList.size)
if (viewHolder is MyTransactionAdapter.FooterViewHolder) {
viewHolder.enableShowMore(enable)
}
}
//this is the implemented method when footer view is clicked
override fun onLoadMoreClicked() {
fetchTransactionListFromServer()
}
Is there anything I need to do with this approach because users are able to click the footer multiple at a time without letting the app to load the remote data.
Hope you have understood the situation if not let me know. Thanks in advance
Here is the screenshot what I have achieved
Can you try disabling the footer view button once the user clicks on it?
fun enableShowMore(enable: Boolean) {
if( enable ) {
itemView.btnShowMore.text = "show more"
} else {
itemView.btnShowMore.text = "loading..."
}
itemView.btnShowMore.isEnabled = enable
}
Update :
//In the adapter
mIsFooterEnabled = false
//In the View holder
fun enableShowMore() {
if( mIsFooterEnabled ) {
itemView.btnShowMore.text = "show more"
} else {
itemView.btnShowMore.text = "loading..."
}
itemView.btnShowMore.isEnabled = mIsFooterEnabled
}
//In your activity / fragment
private fun enableShowMore(enable: Boolean) {
adapter.mIsFooterEnabled = enable
adapter.notifyDataSetChanged()
}
Could you please try to put your function code inside enableShowMore() in onBindViewHolder() or set holder.bind for FooterViewHolder and put code inside binder function.
Let me know if it is useful or not.
Related
For a certain reason, I have to do one operation inside the adapter. In other words, this process should not be repeated when scrolling. The onCreate method in the activity works once when the activity is opened. Is there a method to do this inside the adapter?
UPDATE
My adapter class:
class EducationAdapter #Inject constructor(
var userManager: UserManager
) : ListAdapter<EducationModel, RecyclerView.ViewHolder>.
(EDUCATION_ITEM_DIFF_CALLBACK) {
companion object {
}
var callBack: EducationAdapterCallBack? = null
interface EducationAdapterCallBack {
fun onClickLayer(educationModel: EducationModel)
}
class EducationViewHolder(var binding: ItemEducationBinding) :
RecyclerView.ViewHolder(binding.root) {
fun setItem(item: EducationModel) {
binding.educationItem = item
}
}
#Nonnull
override fun onCreateViewHolder(
#Nonnull parent: ViewGroup,
viewType: Int
): RecyclerView.ViewHolder {
val binding: ItemEducationBinding = DataBindingUtil.inflate(
LayoutInflater.from(parent.context),
R.layout.item_education, parent, false
)
return EducationAdapter.EducationViewHolder(binding)
}
override fun onBindViewHolder(#Nonnull holder: RecyclerView.ViewHolder, position: Int) {
val item = getItem(position)
when (holder) {
is EducationAdapter.EducationViewHolder -> {
holder.setItem(item)
holder.binding.itemEducation.setOnClickListener {
callBack?.onClickLayer(item)
}
}
}
}
}
OncreateViewHolder and onBindViewHolder called when scrolling listview. But I want a single shot process is there any method that runs a single time?
UPDATE 2
I created a dummy operation (My original code was too long, I created summarized dummy operation). OnBind method should be like this:
override fun onBindViewHolder(#Nonnull holder: RecyclerView.ViewHolder, position: Int) {
val item = getItem(position)
when (holder) {
is EducationAdapter.EducationViewHolder -> {
holder.setItem(item)
holder.binding.itemEducation.setOnClickListener {
callBack?.onClickLayer(item)
}
var isExpandClicked = false
holder.binding.btnExpandProject.setOnClickListener {
if (isExpandClicked) {
isExpandClicked = false
val context = holder.binding.llTaskFaaliyet.context
holder.binding.btnExpandProject.setImageDrawable(
ContextCompat.getDrawable(
context,
R.drawable.ic_arrow_down
)
)
val headerText = TextView(context)
headerText.apply {
text = subTask.title
textSize = 16f
setTextColor(ContextCompat.getColor(context, R.color.marine))
gravity = Gravity.START
typeface = ResourcesCompat.getFont(context, R.font.ubuntu_bold)
setPadding(48, 16, 4, 0)
setBackgroundColor(ContextCompat.getColor(context, R.color.white))
}
holder.binding.llTask.add(headerText)
}else{
isExpandClicked = true
val context = holder.binding.llTask.context
holder.binding.btnExpandProject.setImageDrawable(
ContextCompat.getDrawable(
context,
R.drawable.ic_close
)
)
holder.binding.llTask.removeAllViews()
}
}
}
}
}
I am trying to create those views below:
Expanded View:
Shrinked View
Views should be expanded at the first opening. It should be single time when the view is created.
If you put the function on bind method, it should be triggered whenever the item is shown. You can make a simple way, use boolean on adapter scope that will be changed to false after the expand function is triggered. Then use this boolean to check whether value is true, so it should trigger the expand function.
I'm new in Kotlin, I'm using databinding library to set items in recyclerview which I'm getting from viewmodel and calling clicklistener inside activity class. I have defined an interface in my adapter class and implemented in activity but nothing happens when I click on the recyclerview item and also not getting any error. I'm unable to figure out the mistake
My adapter looks like this
class NewsAdapter (private var listener: OnNewsClickListener) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private val mCategoryList = ArrayList<Newslist>()
fun setAppList(categoryModel: ArrayList<Newslist>) {
mCategoryList.addAll(categoryModel)
//notifyItemRangeInserted(0, categoryModel.size)
notifyDataSetChanged()
}
override fun getItemCount(): Int {
Log.d("LIST_SIZE","" + mCategoryList.size)
return mCategoryList.size
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val appInfo = mCategoryList[position]
(holder as NewsAdapter.RecyclerHolderCatIcon).bind(appInfo, listener)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val applicationBinding = ItemNewsBinding.inflate(layoutInflater, parent, false)
return RecyclerHolderCatIcon(applicationBinding)
}
interface OnNewsClickListener {
fun onNewsClick(position: Int)
}
inner class RecyclerHolderCatIcon(private var applicationBinding:ItemNewsBinding) : RecyclerView.ViewHolder(applicationBinding.root) {
fun bind(appInfo:Newslist, listener: OnNewsClickListener?) {
applicationBinding.newsItem = appInfo
}
}
}
Because you have not set listener for for item layout.
You don't need to define OnNewsClickListener interface.
inner class RecyclerHolderCatIcon(private var applicationBinding:ItemNewsBinding) : RecyclerView.ViewHolder(applicationBinding.root) {
fun bind(appInfo:Newslist) {
applicationBinding.root.setOnClickListener{ /*handle click here*/ }
}
}
You need to set the click listener in bind like this.
Here is a sample code, you will need to modify it
fun bind(appInfo:Newslist, listener: OnNewsClickListener?) {
applicationBinding.newsItem = appInfo
itemView.setOnClickListener( view -> {
if (listener != null) {
listener.onNewsClick();
}
}
}
Actually I am using recycler view and adding a layout in the rows and I am using flip animation on cardviews(when clicked on it). The problem is when I add multiple items in the recycler the flip animation works only with the first item. I used toast to make sure that click function is working with other items or not, turns out it's working but flip animation is not working with any other items.Can any one help me out here
This is my code
override fun onCardClick(item: PacketModel, position: Int) {
val scale = this.resources.displayMetrics.density
frontCard.cameraDistance= 8000 * scale
backCard.cameraDistance = 8000 * scale
front_anim = AnimatorInflater.loadAnimator(context, R.animator.front_animator) as AnimatorSet
back_anim = AnimatorInflater.loadAnimator(context, R.animator.back_animator) as AnimatorSet
if (isFront){
front_anim.setTarget(frontCard)
back_anim.setTarget(backCard)
front_anim.start()
back_anim.start()
isFront = false
}else
{
front_anim.setTarget(backCard)
back_anim.setTarget(frontCard)
back_anim.start()
front_anim.start()
isFront = true
}
Toast.makeText(context, item.Name , Toast.LENGTH_SHORT).show()
}
}
This is the adapter Class
class PacketAdapter (val packetList: ArrayList<PacketModel> , var clickListener2: onPacketItemClickListener): RecyclerView.Adapter<PacketAdapter.ViewHolder>(){
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val a = LayoutInflater.from(parent?.context).inflate(R.layout.packet, parent, false)
return ViewHolder(a)
}
override fun getItemCount(): Int {
return packetList.size
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val packet : PacketModel = packetList[position]
holder.intialize(packet, clickListener2)
}
class ViewHolder(itemView : View) : RecyclerView.ViewHolder(itemView)
{
val packetTime = itemView.findViewById<TextView>(R.id.packetTime)
val timeMessage = itemView.findViewById<TextView>(R.id.timeMessage)
fun intialize(item: PacketModel, action: onPacketItemClickListener){
packetTime.text = item.Name
timeMessage.text = item.Age
itemView.setOnClickListener {
action.onCardClick(item, adapterPosition)
}
}
}
interface onPacketItemClickListener{
fun onCardClick (item: PacketModel, position: Int)
}
}
You should place your card flipping code inside your recyclerview adapter so that recyclerview can recycle it as it should be. You can place your card flipping code inside itemview onClicklistener:
itemView.setOnClickListener {
// Place your flipping code here
action.onCardClick(item, adapterPosition)
}
Remove flipping code from onCardClick callback. Let me know if it works fine.
I'm new to Android development (and Kotlin).
I'm trying to implement a RecyclerView (which works fine) and when I click on a specific row it opens a new activity (Intent).
However, whenever I've press/click on one of the rows, I'm only able to get the value "-1" returned.
I've tried a number of different approaches (you should see the number of tabs in my browser).
This seems like it should be a fairly straightforward occurrence for something as common as a RecyclerView, but for whatever reason I'm unable to get it working.
Here is my RecyclerView Adapter file:
class PNHLePlayerAdapter (val players : ArrayList<PNHLePlayer>, val context: Context) : RecyclerView.Adapter<ViewHolder>() {
var onItemClick: ((Int)->Unit) = {}
// Gets the number of items in the list
override fun getItemCount(): Int {
return players.size
}
// Inflates the item views
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val itemView = LayoutInflater.from(context).inflate(
R.layout.pnhle_list_item,
parent,
false
)
val viewHolder = ViewHolder(itemView)
itemView.setOnClickListener {
onItemClick(viewHolder.adapterPosition)
}
return ViewHolder(itemView)
}
// Binds each item in the ArrayList to a view
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.tvPlayerName?.text = players[position].Name
holder.tvPlayerRank?.text = position.toString()
holder.tvPNHLe?.text = players[position].PNHLe.toString()
holder.tvTeam?.text = players[position].Team
holder.ivLeague?.setImageResource(leagueImageID)
}
}
class ViewHolder (view: View) : RecyclerView.ViewHolder(view) {
val linLayout = view.hor1LinearLayout
val ivTeam = view.teamImageView
val tvPlayerName = view.playerNameTextView
val tvPlayerRank = view.rankNumTextView
val tvPNHLe = view.pnhleTextView
val tvTeam = view.teamTextView
val ivLeague = view.leagueImageView
}
As you can see, there is a class property "onItemClick" which uses a lambda as the click callback.
I setOnClickListener in the onCreateViewHolder method after the view is inflated.
Next, in my Activity I add the list to my Adapter and set the call back.
However, every time I 'Toast' the position it is displayed as '-1'.
val adapter = PNHLePlayerAdapter(list, this)
adapter.onItemClick = { position ->
Toast.makeText(this, position.toString(),Toast.LENGTH_SHORT).show()
var intent = Intent(this, PlayerCardActivity::class.java)
//startActivity(intent)
}
rv_player_list.adapter = adapter
Perhaps I'm not thinking about this properly, but shouldn't the position represent the row number of the item out of the RecyclerView???
Ideally, I need to use the position so that I can obtain the correct item from the 'list' (ArrayList) so that I can pass information to my next Activity using the Intent
I found the issue.
Change this line in onCreateViewHolder:
return ViewHolder(itemView)
to this one:
return viewHolder
I would reorganize the adapter like this:
class PNHLePlayerAdapter : androidx.recyclerview.widget.RecyclerView.Adapter<Adapter.ViewHolder>() {
interface AdapterListener {
fun onItemSelected(position: Int?)
}
var players: List<Player> = listOf()
set(value) {
field = value
this.notifyDataSetChanged()
}
var listener: AdapterListener? = null
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.item_car_selector, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(position)
}
override fun getItemCount(): Int {
return brands.size
}
inner class ViewHolder(view: View): androidx.recyclerview.widget.RecyclerView.ViewHolder(view) {
private var position: Int? = null
private val baseView: LinearLayout? = view.findViewById(R.id.baseView) as LinearLayout?
...
init {
baseView?.setOnClickListener {
listener?.onManufacturerSelected(position)
}
}
fun bind(position: Int) {
this.position = position
...
}
}
}
And from your activity/fragment set the listener as adapter.listener = this, and implement the onItemSelected(position: Int?)
override fun onItemSelected(position: Int?) {
...
}
I have a recyclerView adapter named ArticleAdapter
import kotlinx.android.synthetic.main.articlerecycler_item.view.*
class ArticleAdapter(private val controller: IController) : RecyclerView.Adapter<ArticleViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ArticleViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.articlerecycler_item, parent, false)
return ArticleViewHolder(view)
}
// How implement a button click for `btnSave`
/** Called when the user taps the Save button */
btnSave.setOnClickListener(){
(R.layout.articlerecycler_item)
AlertDialog.Builder(this)
.setMessage("Article Saved.")
.create()
.show()
// Do something in response to button click
}
override fun getItemCount(): Int {
return controller.articles.size
}
override fun onBindViewHolder(holder: ArticleViewHolder, position: Int) {
val article: Article = controller.articles[position]
if(article.title.length > 100) {
holder.itemView.titleTv.text = "${article.title.substring(0, 99)}..."
} else {
holder.itemView.titleTv.text = article.title
}
holder.itemView.authorTv.text = article.author
}
}
class ArticleViewHolder(view: View?) : RecyclerView.ViewHolder(view)
Then in my recyclerView layout I have a button
<Button
android:id="#+id/btnSave"
android:layout_width="70dp"
android:layout_height="40dp"
android:onClick="btnSave"
android:text="#string/save"
android:textAlignment="center" />
Do I handle the event inside onBindViewHolder? I'm not sure how to implement the listener within my recyclerView adapter
A good solution to handle the items click of an adapter is to pass them to the activity or fragment which has created them.
With kotlin you can use function definition instead of an interface for this simple work, so inside your adapter class add a var of a function type:
private var listener: ((item: DataClass) -> Unit)? = null
fun setOnItemClickListener(listener: (item: DataClass) -> Unit) {
this.listener = listener
}
And inside the ViewHolder class set the listener, something like below:
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
init {
itemView.btn_save.setOnClickListener { listener?.invoke(data[adapterPosition]) }
}
// ...
}
Finally inside your activity or fragment you can easily access the items by adding a new listener:
adapter.setOnItemClickListener { it -> TODO() }
Notice that setting listeners inside ViewHolder constructor(init) is more efficient as it is not necessary to set a listener for every data binding.
I suppose you should add an extension lister. Here's https://dzone.com/articles/click-listener-for-recyclerview-adapter.
Also, you must add the custom listener for the save button.
override fun onBindViewHolder(holder: ArticleViewHolder, position: Int) {
val article: Article = controller.articles[position]
if(article.title.length > 100) {
holder.itemView.titleTv.text = "${article.title.substring(0, 99)}..."
} else {
holder.itemView.titleTv.text = article.title
}
holder.itemView.authorTv.text = article.author
*holder.btnSave.setOnClickListener(){_ ->
**yourCustomListener**.OnClick(data/position) }*
}
You have to handle it in onBindViewHolder() but logic which is responsible for creating and showing AlertDialog shouldn't be located in RecyclerView. You need to create an interface, which will be responsible for passing onClick() event information from your adapter to activity/fragment, and there you should show AlertDialog.
class ArticleAdapter(private val controller: IController) : RecyclerView.Adapter<ArticleViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ArticleViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.articlerecycler_item, parent, false)
return ArticleViewHolder(view)
}
override fun getItemCount(): Int {
return controller.articles.size
}
override fun onBindViewHolder(holder: ArticleViewHolder, position: Int) {
val article: Article = controller.articles[position]
if(article.title.length > 100) {
holder.itemView.titleTv.text = "${article.title.substring(0, 99)}..."
} else {
holder.itemView.titleTv.text = article.title
}
btnSave.setOnClickListener {
listener?.onSaveButtonClick()
}
holder.itemView.authorTv.text = article.author
}
private var listener: OnClickListener? = null
fun setListener(listener: OnClickListener) {
this.listener = listener
}
}
interface OnClickListener {
fun onSaveButtonClick()
}
class ArticleViewHolder(view: View?) : RecyclerView.ViewHolder(view)
Now pass the instance of OnClickListener from your activity to your adapter by setListener() method.