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?
Related
I am using a RecyclerView in a Fragment to display a list of elements, each containing a checkbox, as shown below.
Upon clicking the START button, I want to disable all of the elements in the RecyclerView.
Here is my code to do so:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
startButton = view.findViewById<Button>(R.id.start_button)
startButton.setOnClickListener {
// yes, recyclerView is defined
setViewGroupEnabled(recyclerView, false)
}
}
fun setViewGroupEnabled(view: ViewGroup, enabled: Boolean) {
Log.d(TAG, "Numkids: ${view.childCount}")
view.isEnabled = enabled
for (v in view.children) {
if (v is ViewGroup) setViewGroupEnabled(v, enabled)
else v.isEnabled = enabled
}
}
This code disables most of the elements in recyclerView, but for some reason it skips some, often multiple at a time. It also appears to skip subviews in a pattern that varies based on how far down the list I have scrolled.
Why is it behaving so strangely?
A RecyclerView has an Adapter. Its job is to handle the layout of each item of the RecyclerView. This includes disabling an item.
Add a class parameter to your adapter:
private var disabled = false
Add a method to your Adapter:
fun setDisabled(disabled: Boolean) {
this.disabled = disabled
notifyDatasetChanged()
}
In your onBindViewHolder method, check for the disabled parameter and disable the view as you want to:
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
if (this.disabled) {
//disable you view, by disabling whichever subview you want
} else {
// The normal non disabled flow (what you have now)
}
}
Now call setDisabled(true) on a button click:
startButton.setOnClickListener {
// yes, recyclerView is defined
adapter.setDisabled(true)
}
And call setDisabled(false) to enable the items back.
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.
There is a Recyclerview in my activity. it has always 10 items. Recyclerview's item has two buttons witch each one's click listener is defined in onBindViewHolder method of adapter class. after clicking any of the items's buttons it calls a callback method witch is implemented in activity's onCreate method. The callback method passes adapterPosition to activity an activity returns a long value witch I update button's text with that.
The problem is when I click the first item's button of the recyclerview, it updates the first and the last item's text. First I used position but then changed it to adapterPosition or layoutPosition, but it didn't worked. sometime the one before last item gets updated and sometimes the last one gets updated with the updating of the first item
activity's onCreate code
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_videocrop)
var segmentAdapter = SegmentAdapter(10)
var layoutManager = LinearLayoutManager(this, RecyclerView.VERTICAL, false)
videoCropRecyclerView.adapter = segmentAdapter
videoCropRecyclerView.layoutManager = layoutManager
segmentAdapter.onSegmentSelectedListener = object : OnSegmentSelectedListener {
override fun onSelectFrom(index: Int): Int? {
return 12345
}
override fun onSelectTo(index: Int): Int? {
return 54321
}
}
}
Adapter class code
class SegmentAdapter(var segments: Int) : RecyclerView.Adapter<SegmentAdapter.SegmentViewHolder>() {
var onSegmentSelectedListener: OnSegmentSelectedListener? = null
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SegmentViewHolder =
SegmentViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.videocrop_segment_item, parent, false))
override fun getItemCount(): Int = segments
override fun onBindViewHolder(holder: SegmentViewHolder, position: Int) {
holder.from!!.setOnClickListener {
holder.from!!.text = onSegmentSelectedListener!!.onSelectFrom(holder.adapterPosition)
}
holder.to!!.setOnClickListener {
holder.to!!.text = onSegmentSelectedListener!!.onSelectTo(holder.adapterPosition)
}
}
class SegmentViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var from: Button? = null
var to: Button? = null
init {
from = itemView.findViewById(R.id.videoCropSegmentItemFromButton)
to = itemView.findViewById(R.id.videoCropSegmentItemToButton)
}
}
}
Callback Interface Code
interface OnSegmentSelectedListener {
fun onSelectFrom(index:Int):Int?
fun onSelectTo(index:Int):Int?
}
when I click the first item's button and its text becomes "0"
then I scroll down to the last item and I see the last item is also updated
I also checked this question but it wasn't helpful.
I would be very thankful if somebody helps me
Your onBindViewHolder method is not handling recycled views. You need to set the text every time in onBindViewHolder based on the backing data, rather than just setting it in the click listener, because you may get an itemView re-used from a different item (in this case, the first item where you've changed the text).
I am trying to write a RecyclerView adapter class in Kotlin for android. I am trying to use the traditional way of creating a custom viewer class, for custom objects, and use a click listener in that. While I am able to do rest of the things like access the variables of inner class and show the RecyclerView, what I have not been able to do is add click listener to the var objects of inner class.
ie something like
var convertView : View? = itemView
convertView.setOnClickListener(this)
Following is my complete code of adapter class
public open class TestAdapter(val items: MutableList<Any>, val context: Activity) : RecyclerView.Adapter<TestAdapter.CustomViewHolder>() {
public var mItem: MutableList<Any> = items
public var mActivity: Activity = context
protected var clickListener: ExampleInterface? = null
public interface ExampleInterface {
fun click(pos: Int) {
}
}
open public fun setListener(mInterFaceListener: ExampleInterface) {
clickListener = mInterFaceListener
}
override fun onCreateViewHolder(p0: ViewGroup, p1: Int): CustomViewHolder {
var parentLayout: View = LayoutInflater.from(mActivity).inflate(R.layout.custom_view, p0, false)
return CustomViewHolder(parentLayout)
// return CustomViewHolder(LayoutInflater.from(mActivity).inflate(R.layout.custom_view, p0, false))
}
override fun getItemCount(): Int {
return mItem.size
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun onBindViewHolder(p0: CustomViewHolder, p1: Int) {
p0.dataView.text = mItem.get(p1).toString()
}
inner class CustomViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), View.OnClickListener {
var convertView: View? = itemView
var dataView: TextView = convertView!!.findViewById(R.id.data)
var mposition = adapterPosition
override fun onClick(p0: View?) {
if (clickListener != null) {
clickListener!!.click(mposition)
}
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
}
}
So if you see these two lines in CustomView class
var convertView: View? = itemView
var dataView: TextView = convertView!!.findViewById(R.id.data)
I cannot access these variables "convertView" and "dataView" so that I can set clicklistener to them. So how to achieve it ?
Thanks :)
I referred to this site
https://www.raywenderlich.com/367-android-recyclerview-tutorial-with-kotlin
Here I go to know my mistake , I need to use init in the class, there I am able to access it and initialize on click listener. Got it working
init {
convertView?.setOnClickListener(this)
}
Though the above answer could be acceptable as well, as I am new to Kotlin I cannot say which one is the better option, but my requirement is satisfied with the above mentioned site.
Thank you :)
In RecyclerView adapters, You could place the OnClickListeners in onCreateViewHolder in order to prevent them from being set each time onBindViewHolder is called (as onBindViewHolder is called multiple times when RecyclerView is scrolled). Use parentLayout in your onCreateViewHolder to access your views and set onClickListener to them. to determine current position in onCreateViewHolder you can do as below :
override fun onCreateViewHolder(p0: ViewGroup, p1: Int): CustomViewHolder {
val parentLayout: View = LayoutInflater.from(mActivity).inflate(R.layout.custom_view, p0, false)
val customViewHolder = CustomViewHolder(parentLayout)
parentLayout.myExampleTextView.setOnClickListener {
// Place onClick logic here.
// If you need position, do as bellow :
val adapterPosition = customViewHolder.adapterPosition
if(adapterPosition != RecyclerView.NO_POSITION) {
// Place your position dependent logic here
}
}
return customViewHolder
}
UPDATE:
I updated the code snippet above and added the RecyclerView.NO_POSITION (which is equal to -1) check. The reason for the position being returned as -1 is explained below.
From android docs:
The other set of position related methods are in the form of
AdapterPosition. (e.g. getAdapterPosition(), findViewHolderForAdapterPosition(int)) You should use these methods
when you need to work with up-to-date adapter positions even if they
may not have been reflected to layout yet. For example, if you want to
access the item in the adapter on a ViewHolder click, you should use
getAdapterPosition(). Beware that these methods may not be able to
calculate adapter positions if notifyDataSetChanged() has been called
and new layout has not yet been calculated. For this reasons, you
should carefully handle NO_POSITION or null results from these
methods.
you can access them in the onBindViewHolder using the p0 this way p0.dataView so there you can set listeners successfully
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)