I have an application with a news feed. I am using a recycler view to populate my news feed. I have 10 variations of viewholders for the recycler view depending on what type of content received from the server.
That all works fine.
My problem is when the user clicks on the item in the recycler view the user is brought to a new fragment, with the content and all the comments below.
My issue is this is a new fragment, so I'm outside of the recycler view. In this case I have to inflate the layout of the item clicked on in the recyclerview and that's fine. However, it should have all the functionality the item in the feed as, so for example, play media (video, audio), navigate to new fragments.
My question, is it possible to someway encapsulate the functionality of the viewholder in the recyclerview? Otherwise the same functionality will be repeated.
Any help is appreciated.
I've attached a drawing to help illustrate.
class VideoViewHolder(
private binding: VideoItemBinding
) : RecyclerView.ViewHolder(binding.root){
companion object {
fun create(
inflater: LayoutInflater,
viewGroup: ViewGroup
): VideoViewHolder {
val binding = DataBindingUtil.inflate<VideoItemBinding>(
inflater, R.layout.item_video, viewGroup, false
)
return VideoViewHolder(binding)
}
}
fun bind(video: Video){
// bind view to video model
// set click listeners
// fire network request on click events and on response check if the viewholder is binded to same model else its has been reused.
}
}
From Video Adapter
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return VideoViewHolder.create(inflater, parent)
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (holder) {
is VideoViewHolder -> {
holder.bind(item as Video, doubtsRepo)
}
}
}
To reuse in activity/fragment/ container layout without adapter manually create viewholder and call its bind function
val vh = VideoViewHolder.create(inflater, parent)
// get view from vh.binding.root and manually add it to view container and then bind the model to the view to reuse view holder functionality without any changes.
vh.bind(video)
Related
I simply made an adapter for my recyclerview.like this :
class CustomerAdapter(mCtx:Context,val customers:ArrayList<Customer>):RecyclerView.Adapter<CustomerAdapter.ViewHolder>(){
and i have a xml layout file to inflate recycleview items.
But the problem is in onCreateViewHolder ... I can not access inflated elements ...
codes writed in my onCreateViewHolder method :
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CustomerAdapter.ViewHolder {
val v = LayoutInflater.from(parent.getContext()).inflate(R.layout.lo_customers, parent, false);
var txt = v.id
}
i can not access view elements by id .
Even if you capture the View, you cannot directly access the children's in the View. In-order to get hold of children's you need to use
findViewById(R.id.my_textView_id) on the View object.
Hence replace your v.txt by v.findViewById(R.id.my_text_view_id).
This is completely the wrong way to do it . onCreateViewHolder the function name itself says that it is meant only for inflating the view and doing nothing else. It should only be used for creating the ViewHolder . Even if you inflate your view and assign view from there , it is going to crash in the future . You have to inflate the view there and pass the parent view to another class which should extend RecyclerView.ViewHolder() .
So do it the following way :
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CustomerAdapter.ViewHolder {
val v = LayoutInflater.from(parent.getContext()).inflate(R.layout.lo_customers, parent, false);
}
Create an ViewHolder Class extending RecyclerView.ViewHolder :
class CustomViewHolder(val view: View) :
RecyclerView.ViewHolder(view) {
fun bind(model : Model) {
//Now here you can access all the components present in your layout in the following way and do the stuff you want with them:
val text = view.findViewById(R.id.viewid)
}
}
At gradle app
plugins {
id 'kotlin-android-extensions'
}
Or
apply plugin: 'kotlin-android-extensions'
Then your Activity Import this
import kotlinx.android.synthetic.main.activity_main.*
Try not to access from android.R, rather try like this:
CategoryAdapterHolder(LayoutInflater.from(parent.context).inflate(com.example.secondhand.R.layout.category_list, parent, false))
I have a RecyclerView which has an EditText in its items.
For each EditText I have added a textChangeListener.
Whenever the user types a character I want to do an input validation and so I can change the item, for example show red border in case of wrong input etc...
After I do the validation I call:
adapter.notifyItemChanged(itemPosition)
to update the view.
But the problem is that the focus is lost from the EditText and I have to click again inside the box in order to continue typing.
Is there a solution for this case? How can I continue typing while the view is updated after calling notifyItemChanged ?
ADAPTER
class ItemAdapter() : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val inflater = LayoutInflater.from(parent.context)
val binding = LayoutItemBinding.inflate(inflater, parent, false)
binding.editText.addTextChangedListener {
// Do layout changes....
if (isInvalidInput) {
item.setError = true
....
...
}
adapter.notifyItemChanged(itemPosition)
}
return ItemViewHolder(binding)
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
holder.bind(items[position])
}
inner class ItemViewHolder(val binding: LayoutItemBinding)
: RecyclerView.ViewHolder(binding.root) {
fun bind(model: ItemModel) {
// Setup view for item
}
}
}
Try to move your error setting code in the bind function of the ViewHolder and update the error status outside of the TextWatcher:
class ItemAdapter() : RecyclerView.Adapter<RecyclerView.ViewHolder>()
{
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val inflater = LayoutInflater.from(parent.context)
val binding = LayoutItemBinding.inflate(inflater, parent, false)
return ItemViewHolder(binding)
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
holder.bind(items[position], position)
}
inner class ItemViewHolder(val binding: LayoutItemBinding) : RecyclerView.ViewHolder(binding.root) {
fun bind(model: ItemModel, position: Int) {
binding.editText.addTextChangedListener {
// Update the ItemModel on text change
adapter.notifyItemChanged(position)
}
// Update the view error status everytime it is updated
val isInvalidInput = ... // Add your logic
if (isInvalidInput) {
model.setError = true
....
....
}
}
}
}
First, Please post your code.
If you called notifyDatasetChanged(), all views of recyclerview will refreshed (it means that the views could be recreated.).
So if you want remain focus in edittext, you not call notifyDatasetChanged() or set focus to your edittext after notifyDataChanged().
But setFocus is bit more difficult.
So I suggest method that update views of recyclerview without call notifyDatasetChanged().
I think you can directly update view of recyclerview without call notifyDataChanged().
You should save the id of item that contains the EditText that is editing. And in the bind() method of ViewHolder, you will check if the item is currently being edit, you will call requestFocus() method on EditText.
And please take note that when you call notifyDataChanged(), the RecycleView maybe check the data of each item to know whether it was changed or not. If the data of an item was changed, RecycleView will redraw that item.
So I have a Recyclerview & multiple viewholders. Now one of my viewholder contains an item that has few animations. Now if a user clicks on the item activity opens and I have no control over the activity, the only way I got to know is via the callback in my fragment i.e onPause & onResume. I want to call animation.onResume() once my fragment onResume() is invoked.
Interface class
interface CustomTabClosedListener {
fun onCustomTabClosed()
}
Adapter Relevant code:
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
lateinit var viewViewHolder: RecyclerView.ViewHolder
val layoutInflater = LayoutInflater.from(context)
val view = layoutInflater!!.inflate(R.layout.feed_coverage, parent, false)
viewViewHolder = CoverageViewHolder(view)
listener = viewViewHolder
}
where listener is private var listener: CustomTabClosedListener?=null
ViewHolder
class CoverageViewHolder(val view: View) :CustomTabClosedListener {
override fun onCustomTabClosed() {
animation?.resume()
Now my fragment calls this adapter method in onResume -
fun customTabClosed() {
listener?.onCustomTabClosed()
}
But here my listener is getting null and I don't know the reason why. How can i invoke my method in viewholder via my adapter/fragment? Is there any other better approach?
I guess the reason why you get null is because you should have only a single call back in the Adapter viewHolder, and all the animation should be managed by the view. Is better and clean if the adapter manage some really elementar business logic, leaving what should happen to the view into the view itself.
Let's say you have an xml file with your animation
you could have from the view the control to notify the adapter and start the animation
fun IamACoolCallBack(){
AnimationUtils.loadLayoutAnimation(context, R.anim.layout_animation_fall_down);
with(recyclerView){
setLayoutAnimation(controller);
getAdapter().notifyDataSetChanged();
scheduleLayoutAnimation();
}
}
EDIT
I found this tutorial it looks pretty neat to me
I am researching on how to add an onClick event on my recyclerview properly,
currently I am using the interface inside my customAdapter
class CategoryAdapter(val categoryList : List<CategoryObject>, val context: Context, val mItemClickListener: MainInterface) : RecyclerView.Adapter<CategoryAdapter.ViewHolder>() {
interface MainInterface {
fun onCategoryItemClick(categoryKey: Int)
}
override fun getItemCount(): Int {
return categoryList.size
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(LayoutInflater.from(context).inflate(R.layout.listview_category, parent, false))
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder?.categoryName?.text = categoryList[position].categoryName
// holder?.categoryName.setOnClickListener{
// mItemClickListener.onCategoryItemClick(position)
// }
// holder?.categoryName?.setOnClickListener { listener(categoryList[position]) }
}
inner class ViewHolder (view: View) : RecyclerView.ViewHolder(view) {
val categoryName = view.lv_txt_category_name
init {
view.setOnClickListener {
mItemClickListener.onCategoryItemClick(adapterPosition)
}
}
}
}
viewHolder's onClick does not register on my activity ovveride function.
but putting the onClick inside onBindViewHolder works perfectly,
I'm not sure which is more efficient, if the onBindViewHolder onClick is the right answer, then I'll stick with it, but if the viewHolder is the right one, why it does not work properly?
Thanks in advance!
Update
This is the stackoverflow post I'm using to research things
RecyclerView itemClickListener in Kotlin
tried android:clickable="true" in your xml?
As the documentation suggest for:
RecyclerView.Adapter.html#onBindViewHolder
Called by RecyclerView to display the data at the specified position.
This method should update the contents of the itemView to reflect the
item at the given position.
And for the RecyclerView.Adapter.html#onCreateViewHolder
Called when RecyclerView needs a new RecyclerView.ViewHolder of the
given type to represent an item.
This new ViewHolder should be constructed with a new View that can
represent the items of the given type. You can either create a new
View manually or inflate it from an XML layout file.
As onCreateViewHolder is a method where we return the type of ViewHolder only while in onBindViewHolder we update or initialise the data to display.
It means no data is binded in onCreateViewHolder and binding anything in this method will have no effect.
Write next method for stop redrawing my view multiple times in instantiateItem.
override fun instantiateItem(container: ViewGroup, position: Int): Any {
val view = map.getOrPut(position) {
AuthorizationHeaderView(context = context)
}
updateViewContent(view, data[position])
timeUpdateListeners.add(view as TimeUpdateListener)
container.addView(view, 0)
return view
}
Now want to add next code map.remove(position) in my destroyItem().
But he also called multiple times.
How i can make remove from map and don t redraw my view?