I have differents screens that use recyclerview but they have a very similar item layout. The item layout is basically a textview and an image. The image is the same and never changes and the textview style is the same but the text changes because it comes from the api.
I started using the same item layout for each recyclerview because it was very similar but i don't know how to make it work and if it would take too much time
Below is the simplest way to change what to display. You can pass a boolean in the constructor of the adapter from the parent classes.
For example, If it is true, show some data. If it is false you can show some other data.
class RecyclerAdapter(var displayUserData: Boolean) : RecyclerView.Adapter<RecyclerAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {...}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = values[position]
if(displayUserData){
holder.textView.text = item.name
}else{
holder.textView .text = item.otherStuff
}
}
inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {...}
}
Then set the adapter with true or false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val adapter = RecyclerAdapter(true)
}
Related
I want to make a custom view with two Recyclerview data. First one is header titles, second one is content details. When I click on a title, I want different XML files to be shown.
After some research, I saw that I could do it using TabLayout and ViewPager, however, I don't want to use an extra fragment for each tab, instead I want to inflate an XML file. Because I want to use this view on other screens as a custom view.
How can I do that? What should I use as a solution? Thank you.
You can use ViewPager2 :
<androidx.viewpager2.widget.ViewPager2
android:id="#+id/topicViewPager"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
you can assign second recycler adapter to ViewPager2 for example:-
class DummyAdapter(var list:ArrayList<String>) :
RecyclerView.Adapter<DummyAdapter.ViewHolder>() {
lateinit var context: Context
var isFullScreen = false
var isDownload: Boolean = false
var doubleClick = false
inner class ViewHolder(val binding: DummyItemBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(
position: Int,
context: Context,
adapter: DummyAdapter,
) {
with(binding) {
// add data
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int):
ViewHolder {
context = parent.context
val inflater = LayoutInflater.from(context)
val binding = DummyItemBinding.inflate(inflater, parent, false)
return ViewHolder(binding)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) =
holder.bind(position, context, this, list[position])
override fun getItemCount(): Int {
return list.size
}
}
binding.viewPager.adapter = dummyAdapter
then on the click of TabLayout you can update the adapter of second recyclerView.
I try to make a small file manager, I want that when holding a file or folder I get a contextual menu, and I tried to use registerForContextMenu(newRecyclerView) but it does not work for me, nothing happens at all, instead if I do it with another element like a button or an ImageView, the menu comes out perfect, and I've been googling for hours, but most of the solutions I've found are in java, and I don't know how to implement them in kotlin, one thing to keep in mind is that when handling files , the recylerview is going to be constantly changing as we navigate through the directories, what I want is a menu where I can copy, cut the file and stuff, I know there are other solutions for this, but I want to implement them with a context menu , here is MyAdapter, I don't put the Main Activity,because it's a riot
import...
class MyAdapter(private val newsList:
ArrayList<News>):RecyclerView.Adapter<MyAdapter.MyViewHolder>()
{
private lateinit var mListener:OnItemClickListener
interface OnItemClickListener{
fun onItemClick(position: Int)
}
fun setOnItemClicKListener(listener:OnItemClickListener){
mListener = listener
}
override fun onCreateViewHolder(parent: ViewGroup, viewType:
Int): MyViewHolder {
val itemView =
LayoutInflater.from(parent.context).inflate(R.layout.list_items,
parent, false)
return MyViewHolder(itemView, mListener)
}
override fun onBindViewHolder(holder: MyViewHolder, position:
Int) {
val currentItem = newsList[position]
holder.titleImage.setImageResource(currentItem.titleImage)
holder.tvHeading.text = currentItem.heading
}
override fun getItemCount(): Int {
return newsList.size
}
class MyViewHolder(itemView: View, listener:
OnItemClickListener):RecyclerView.ViewHolder(itemView){
val titleImage: ShapeableImageView =
itemView.findViewById(R.id.title_image)
val tvHeading:TextView = itemView.findViewById(R.id.tvHeading)
init{
itemView.setOnClickListener {
listener.onItemClick(adapterPosition)
}
}
}
}
Instead of registering the entire RecyclerView with a context menu, you should register each child view. This is because you are holding the child view, not the RecyclerView itself.
I am building an Android app with Kotlin and decided to replace the calls to findViewById and use binding. It all works fine but specifically, when I change an Adapter for a RecyclerView it breaks the item layout.
Original code with findViewById:
class WeightListAdapter(val weights: List<WeightWithPictures>, val onWeightItemClickListener: OnWeightItemClickListener) : RecyclerView.Adapter<WeightListAdapter.WeightHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): WeightListAdapter.WeightHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.list_item_weight, parent, false)
return WeightHolder(view)
}
override fun onBindViewHolder(holder: WeightListAdapter.WeightHolder, position: Int) {
val weightWithPictures = weights[position]
holder.bind(weightWithPictures)
}
override fun getItemCount() = weights.size
inner class WeightHolder(itemView: View) : RecyclerView.ViewHolder(itemView), View.OnClickListener {
private lateinit var weight: Weight
private val weightValueView: TextView = this.itemView.findViewById(R.id.weightValue)
private val weightDateView: TextView = this.itemView.findViewById(R.id.weightDate)
private val weightImageView: ImageView = this.itemView.findViewById(R.id.weightImage) as ImageView
And this is the layout:
But then whenever I use binding:
class WeightListAdapter(val weights: List<WeightWithPictures>, val onWeightItemClickListener: OnWeightItemClickListener) : RecyclerView.Adapter<WeightListAdapter.WeightHolder>() {
private var _binding: ListItemWeightBinding? = null
private val binding get() = _binding!!
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): WeightListAdapter.WeightHolder {
_binding = ListItemWeightBinding.inflate(LayoutInflater.from(parent.context))
val view = binding.root
return WeightHolder(view)
}
override fun onBindViewHolder(holder: WeightListAdapter.WeightHolder, position: Int) {
val weightWithPictures = weights[position]
holder.bind(weightWithPictures)
}
override fun getItemCount() = weights.size
inner class WeightHolder(itemView: View) : RecyclerView.ViewHolder(itemView), View.OnClickListener {
private lateinit var weight: Weight
private val weightValueView: TextView = binding.weightValue
private val weightDateView: TextView = binding.weightDate
private val weightImageView: ImageView = binding.weightImage
The layout breaks:
Any ideas about what am I doing wrong here? Is it a bug?
P.S - For now, I am just adding the annotation to ignore bindings as documented here for the item view but I would really like to understand what's wrong.
Your binding needs to be inflated in the context of its parent so its root view's layout params will take effect:
binding = ListItemWeightBinding.inflate(LayoutInflater.from(parent.context), parent, false)
I think it will also give you problems that you are creating a binding property for the Adapter if you try to use it long term. Each ViewHolder holds a distinct view with a distinct binding instance. It's working now because you use it only for the ViewHolder instantiation immediately after setting each instance. But if that's all your intent is, you should just pass the binding to the constructor of your ViewHolder and omit the adapter's property.
By the way, this is the sort of pattern I use for ViewHolders. Less code. Note, it doesn't have to be an inner class.
class WeightHolder(binding: ListItemWeightBinding) : RecyclerView.ViewHolder(binding.root), View.OnClickListener {
fun bind(item: WeightWithPictures) {
with (binding) {
// set data for views here
}
}
}
I agree with #Tenfour04, using the same instance of binding is wrong but I believe the root cause of your issue is with the binding logic. with binding, the data is bound to bind with the view but not immediately. So your view gets inflated but since the binding happens at a later stage, scheduled to happen in near future, the item_view width is shrunk.
So try the following,
// oncreate view logic
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): WeightListAdapter.WeightHolder {
val binding = ListItemWeightBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return WeightHolder(binding)
}
// onBindViewHolder logic remains the same
// this remains same as suggested by #Tenfour04 but a change in the bind function
class WeightHolder(binding: ListItemWeightBinding) : RecyclerView.ViewHolder(binding.root), View.OnClickListener {
fun bind(item: WeightWithPictures) {
with (binding) {
// set data for views using databindig
customVariable = item
executePendingBindings() // this is important
}
}
}
// define the customvariable in your `item_list_view.xml`
<variable
name="customVariable"
type="packagename.WeightWithPictures" />
executePendingBindings() is the way we force the binding to happen right away and not to schedule it later
Edit:
This answer is from Databinding perspective and not ViewBinding
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.
I have a RecyclerView.Adapter like this:
internal class MyAdapter : RecyclerView.Adapter<MyAdapter.MyViewHolder>() {
private val data: List<MyModel> = SeedData().seed()
override fun onCreateViewHolder(v: ViewGroup, viewType: Int): MyViewHolder {
val binding = MyListitemBinding.inflate(LayoutInflater.from(v.context), v, false)
return MyViewHolder(binding)
}
override fun getItemCount() = data.size
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
holder.bind(data[position])
}
}
However, only the first item from the data is getting displayed (i.e. onCreateViewHolder & onBindViewHolder invoked only one time). How can I make it display all items from the data properly?
The commented answers above are correct. My list item (views) were full height of screen:
This means that RecyclerView would only update the ViewHolder once you scroll to the next element. The solution is to modify the height of these items.