I have a recyclerView inside a fragment, this fragment is located in a viewpager2.
I read data from server and convert them to my ModelClass, them create an ArrayList and pas this array list to recyclerView.
here is my recyclerView item layout:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/root"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:background="#drawable/main_icon_background"
android:elevation="5dp"
android:layoutDirection="rtl"
android:orientation="horizontal"
android:padding="10dp">
<ImageView
android:id="#+id/image"
android:layout_width="120dp"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:src="#color/black" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="0.6"
android:orientation="vertical">
<TextView
android:id="#+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:layout_marginEnd="10dp"
android:ellipsize="end"
android:maxLines="1"
android:textAppearance="#style/vazir_font"
android:textColor="#color/black" />
<TextView
android:id="#+id/description"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:ellipsize="end"
android:maxLines="3"
android:textAppearance="#style/vazir_font"
android:textColor="#color/gray"
android:textSize="13sp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="1.5dp"
android:layout_marginStart="20dp"
android:layout_marginTop="5dp"
android:layout_marginEnd="20dp"
android:layout_marginBottom="8dp"
android:alpha="0.5"
android:background="#color/gray" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:id="#+id/source"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:layout_marginEnd="10dp"
android:layout_weight="0.8"
android:ellipsize="end"
android:maxLines="1"
android:textAppearance="#style/vazir_font"
android:textColor="#color/colorPrimaryDark"
android:textSize="13sp" />
<TextView
android:id="#+id/date"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:gravity="end"
android:textAppearance="#style/vazir_font"
android:textColor="#color/colorAccent"
android:textSize="13sp" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
and here is my recyclerView adapter:
class NewsRecyclerViewAdapter : RecyclerView.Adapter<NewsRecyclerViewAdapter.ViewHolder>() {
private val items = ArrayList<NewsAdapterModel>()
lateinit var onClick: NewsTutorialClickListener
fun add(list: ArrayList<NewsAdapterModel>) {
items.clear()
items.addAll(list)
notifyDataSetChanged()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view =
LayoutInflater.from(parent.context).inflate(R.layout.news_recycler_model, parent, false)
return ViewHolder(view)
}
override fun getItemCount(): Int = items.size
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.binding(items[position])
}
override fun getItemId(position: Int): Long {
return items[position].id.hashCode().toLong()
}
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
fun binding(model: NewsAdapterModel) {
itemView.setOnClickListener {
onClick.onClick(model.id)
}
itemView.title.text = model.title
itemView.description.text = model.description
itemView.date.text = Arrange().persianConverter(model.date)
itemView.source.text = model.source
Glide.with(itemView.context)
.load(model.image)
.placeholder(R.mipmap.logo)
.into(itemView.image)
}
}
}
I used this recycler view in my fragment just like this:
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
adapter.onClick = this
binding.recyclerView.layoutManager = LinearLayoutManager(context)
binding.recyclerView.adapter = adapter
binding.recyclerView.setHasFixedSize(true)
binding.recyclerView.setItemViewCacheSize(20)
scope.launch {
withContext(Dispatchers.Default) {
fetchNews()
}
}
}
my problem is : when I scroll recycler view for first time it is not smooth and it seems some frames are jumped, but when I reach end of recyclerView and scroll to up then scroll down it is very smooth and find. how can I solve this issue?
I also should mention that Items of recyclerView are not much, it's around 10 items.
remove
recyclerView.setHasFixedSize(true)
actually it can cause no harm and indeed improves performance, because recyclerView won't bother calculating size of viewHolders evrytime inside onBindViewHolder. But you have all your TextViews with wrap_content height which will most certainly cause some broken UI in future.
remove
recyclerView.setItemViewCacheSize(20)
This function forces RecyclerView to prematurely inflate and bind 20 extra ViewHolders, so that it can use them straight away without calling OncreateViewHolder and OnBindViewHolder. You should'nt play around with this and just go with the defaults. Although 20 extra viewholders shouldn't have been a big deal, i guess maybe RecyclerView got confused because you forced it create extra 20 on top of existing 10, while you list only contains 10 items. It was taking time to create those extra viewHolders blocking ui thread, but after they were created it seemed smooth.
You don't need to add these lines
binding.recyclerView.setHasFixedSize(true)
binding.recyclerView.setItemViewCacheSize(20)
Please remove these lines
Related
I created the image slider using smarteist image slider github library and it's working fine. I wanted to show 3 items at a time but Item occupies whole space and visible only 1 item. I also tried recyclerview to achieve this thing but can't get same feel as image slider. So guide me how to achieve it.
Expected
To show 3 items at a time before slide.
what I had done
val partner_recyclerView: SliderView = findViewById(R.id.partnership_slider)
var partnerAdapter: PartnerAdapter = PartnerAdapter(partnerList)
partner_recyclerView.autoCycleDirection = SliderView.LAYOUT_DIRECTION_LTR
partner_recyclerView.setSliderAdapter(partnerAdapter)
partner_recyclerView.scrollTimeInSec = 3
partner_recyclerView.isAutoCycle = true
partner_recyclerView.startAutoCycle()
Item_layout
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="150dp"
android:layout_height="80dp"
xmlns:app="http://schemas.android.com/apk/res-auto">
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginStart="5dp"
android:layout_marginTop="8dp"
app:cardElevation="8dp"
android:layout_marginEnd="5dp"
android:layout_marginBottom="8dp"
app:cardCornerRadius="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical">
<ImageView
android:id="#+id/myPartnerLogo"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerHorizontal="true"
android:layout_gravity="center"
android:contentDescription="#string/app_name"
android:scaleType="fitXY"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</LinearLayout>
</androidx.cardview.widget.CardView></androidx.constraintlayout.widget.ConstraintLayout>
Main_Layout
<com.smarteist.autoimageslider.SliderView
android:id="#+id/partnership_slider"
android:layout_width="wrap_content"
android:layout_height="100dp"
android:layout_centerInParent="true"
app:sliderAnimationDuration="600"
app:sliderAutoCycleDirection="back_and_forth"
app:sliderScrollTimeInSec="1"
android:padding="10dp"
app:layout_constraintBottom_toTopOf="#+id/constraintLayout3"
app:layout_constraintEnd_toEndOf="#+id/constraintLayout3"
app:layout_constraintStart_toStartOf="#+id/constraintLayout3"
app:layout_constraintTop_toTopOf="#+id/constraintLayout3"/>
Adapter_class
class PartnerAdapter (val mList: ArrayList<PartnershipData>) :
SliderViewAdapter<PartnerAdapter.SliderViewHolder>() {
override fun getCount(): Int {
return mList.size
}
override fun onCreateViewHolder(parent: ViewGroup?): PartnerAdapter.SliderViewHolder {
val inflate: View = LayoutInflater.from(parent!!.context).inflate(R.layout.partner_item, null)
return PartnerAdapter.SliderViewHolder(inflate)
}
override fun onBindViewHolder(viewHolder: PartnerAdapter.SliderViewHolder?, position: Int) {
if (viewHolder != null) {
// if view holder is not null we are simply
// loading the image inside our image view using glide library
Glide.with(viewHolder.itemView).load(mList.get(position).pwa_iconpng).fitCenter()
.into(viewHolder.myPartnerLogo)
}
}
class SliderViewHolder (itemView: View?) : SliderViewAdapter.ViewHolder(itemView) {
val myPartnerLogo: ImageView = itemView!!.findViewById(R.id.myPartnerLogo)
}}
For better understanding
Expected
Problem
This has been asked before but those solutions didn't help me out. I have a RecyclerView that is scrollable and only partially visible on the screen at any one time. It holds a list of RadioButtons and some text related to each item. When you click on a RadioButton, the last button selected prior to this one should turn off. It does not. Each button you click gets selected and the other ones that you selected before stay selected as well.
It's set up like this:
<?xml version="1.0" encoding="utf-8"?>
<androidx.appcompat.widget.LinearLayoutCompat
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="#color/white"
tools:context=".ui.SettingsActivity">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="15dp"
android:layout_marginTop="5dp"
android:layout_marginBottom="5dp"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/video_settings"
android:textColor="#color/black"
android:textStyle="bold"
android:textSize="#dimen/clickableTextSize"
android:layout_alignParentStart="true"
/>
</RelativeLayout>
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="match_parent"
android:layout_height="match_parent"
android:weightSum="100"
android:gravity="bottom"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="15"
android:paddingStart="15dp"
android:paddingEnd="15dp"
android:text="#string/set_resolution_and_fps"
android:textSize="#dimen/history_text_size"
android:gravity="center_vertical"
android:background="#color/grey_200"
android:textColor="#color/black"
/>
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/rv_video_resolution"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="80"
android:layout_marginTop="5dp"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/submitVideoSettings"
android:text="#string/submit"
android:layout_marginBottom="5dp"
android:textColor="#color/black"
android:layout_gravity="center|bottom"
/>
</androidx.appcompat.widget.LinearLayoutCompat>
</androidx.appcompat.widget.LinearLayoutCompat>
The recycler looks like this:
<?xml version="1.0" encoding="utf-8"?>
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="3dp"
android:orientation="horizontal"
xmlns:android="http://schemas.android.com/apk/res/android">
<RadioButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/resolutionRVRadioButton"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/resolutionRVTV"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:text="#string/at_symbol"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/framesPerSecondRVTV"
android:layout_marginStart="5dp"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/fps"
/>
</androidx.appcompat.widget.LinearLayoutCompat>
The code for the adapter looks like this.
class ResolutionRVAdapter(private val sharedNavAndMenuViewModel: SharedNavAndMenuViewModel,
private val lastResolutionSelected: String,
private val capabilityDataList: List<CameraCapabilitySpecificFPS>) : RecyclerView.Adapter<CameraResolutionRVAdapter.ViewHolder>(){
private var lastCheckedPosition = -1
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CameraResolutionRVAdapter.ViewHolder {
val v = LayoutInflater.from(parent.context).inflate(R.layout.camera_resolution_recycler_item, parent, false)
val viewHolder = ViewHolder(v)
Timber.i("Added viewHolder: ${viewHolder.resolutionText.text}")
return ViewHolder(v)
}
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView){
val resolutionRadioButton : RadioButton = itemView.findViewById(R.id.resolutionRVRadioButton)
val resolutionText : TextView = itemView.findViewById(R.id.resolutionRVTV)
val framesPerSecondText : TextView = itemView.findViewById(R.id.framesPerSecondRVTV)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val child = capabilityDataList[position]
Timber.i("onBindViewHolder currently working with resolution ${child.resolution} range of $child.range")
holder.resolutionText.text = child.resolution.toString()
holder.framesPerSecondText.text = child.fps
Timber.d("OnBindViewholder and lastResolutionSelected = $lastResolutionSelected")
if(lastResolutionSelected.isEmpty()) {
if(position == 0) {
holder.resolutionRadioButton.isChecked = true
Timber.i("No last resolution was selected. Setting index 0 to true")
}
} else if(lastResolutionSelected == holder.resolutionText.text.toString()) {
Timber.i("An old resolution of ${holder.resolutionText.text} was selected. Setting index $position to true")
holder.resolutionRadioButton.isChecked = true
}
holder.resolutionRadioButton.setOnClickListener {
Timber.i("Index hit was: $position resolution at that index was: ${holder.resolutionText.text}")
if(holder.resolutionRadioButton.isChecked) {
//Button was already checked...must be the same button hit before...nothing to do
} else {
holder.resolutionRadioButton.isChecked = true
runOnUiThread {
notifyItemChanged(lastCheckedPosition)
notifyItemChanged(position)
}
lastCheckedPosition = position
}
}
}
override fun getItemCount(): Int {
return capabilityDataList.size
}
}
Can anyone tell me what it is I'm doing wrong?
I looked at many solutions posted online but they couldn't solve my problem. Probably the adapter position is returning -1 but why?
java.lang.ArrayIndexOutOfBoundsException: length=10; index=-1
at java.util.ArrayList.get(ArrayList.java:439)
at
com.firebase.ui.common.BaseObservableSnapshotArray.getSnapshot(BaseObservableSnapshotArray.java:70)
at com.example.twitterclone.adapters.MyAdapter$TweetViewHolder.<init>(MyAdapter.kt:36)
at com.example.twitterclone.adapters.MyAdapter.onCreateViewHolder(MyAdapter.kt:50)
at com.example.twitterclone.adapters.MyAdapter.onCreateViewHolder(MyAdapter.kt:21)
at androidx.recyclerview.widget.RecyclerView$Adapter.createViewHolder(RecyclerView.java:7078)
at
RecyclerViewAdapterCode:
class MyAdapter(
options: FirestoreRecyclerOptions<Tweet>,
private val clickInterface: ClickInterface
):FirestoreRecyclerAdapter<Tweet, MyAdapter.TweetViewHolder>(options) {
inner class TweetViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val profile =
view.findViewById<de.hdodenhof.circleimageview.CircleImageView>(R.id.userProfile)
val tweet = view.findViewById<TextView>(R.id.tweet)
val like = view.findViewById<TextView>(R.id.totalLikes)
val thumbsUp = view.findViewById<ImageView>(R.id.thumbsUp)
val name=view.findViewById<TextView>(R.id.tweetUserName)
init {
val tweetId=snapshots.getSnapshot(adapterPosition).get("tweetId")
thumbsUp.setOnClickListener {
clickInterface.clickLike(tweetId.toString())
}
}
}
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): TweetViewHolder {
val viewHolder = TweetViewHolder(
LayoutInflater.from(parent.context).inflate(R.layout.post_tweets_item, parent, false)
)
return viewHolder
}
override fun onBindViewHolder(
holder: TweetViewHolder,
position: Int,
model: Tweet
) {
holder.tweet.text = model.content.toString()
holder.like.text = model.likes.toString()
UserDao().getUser(model.uid!!).get().addOnSuccessListener {
holder.name.text = it.get("name").toString()
Glide.with(holder.profile.context).load(it.get("profileUrl").toString())
.into(holder.profile)
}
}
}
LayoutCode
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:orientation="vertical"
android:layout_margin="10dp"
android:layout_height="wrap_content">
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="2dp"
app:cardCornerRadius="5dp"
app:cardElevation="2dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<de.hdodenhof.circleimageview.CircleImageView
android:id="#+id/userProfile"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginTop="8dp"
android:layout_marginLeft="8dp" />
<TextView
android:id="#+id/tweetUserName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:textColor="#android:color/black"
android:layout_marginTop="8dp" />
</LinearLayout>
<TextView
android:id="#+id/tweet"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="13dp"
android:textColor="#android:color/black" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="80dp"
android:orientation="vertical"
android:padding="8dp">
<ImageView
android:layout_width="30dp"
android:id="#+id/thumbsUp"
android:clickable="true"
android:layout_height="30dp"
android:src="#drawable/ic_baseline_thumb_up_24" />
<TextView
android:id="#+id/totalLikes"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="80dp"
android:orientation="vertical"
android:padding="8dp">
<ImageView
android:clickable="true"
android:layout_width="30dp"
android:layout_height="30dp"
android:id="#+id/comments"
android:src="#drawable/ic_baseline_comment_24" />
<TextView
android:id="#+id/totalComments"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
</androidx.cardview.widget.CardView>
</LinearLayout>
adapterPosition in the ViewHolder's init block is returning -1. So we just need to call adapterPosition in the listener which will solve the issue, since it would retrieve the position when the view holder is created and attached to the recyclerview. Setting any kind of listeners in the init block is a good approach since the onCreateViewHolder called only once to create a view holder but the onBindViewHolder called multiple times for the same view holder, so setting listeners in the onBindViewHolder would be redundant.
init {
thumbsUp.setOnClickListener {
val tweetId=snapshots.getSnapshot(adapterPosition).get("tweetId")
clickInterface.clickLike(tweetId.toString())
}
}
Attache you onClickListener in onBindViewHolder
override fun onBindViewHolder(
holder: TweetViewHolder,
position: Int,
model: Tweet
) {
//your code of attaching data to view
holder.thumbsUp.setOnClickListener{
clickInterface.clickLike(model.tweetId.toString())
}
}
Reconsider your application architecture. As asking DAO for data inside RecyclerView.Adapter is quite strange approach
Yeah, I'd expect adapterPosition in your getSnapshotcall in the ViewHolder's init block is returning -1. As far as I'm aware, all the adapter position getters return the position of the last requested item for display. But when you're first creating ViewHolders, nothing has bound to the adapter yet (it's all still in the setup phase), so its default position is probably -1.
Basically the way RecyclerViews work is this - you define these ViewHolder objects which are basically things that display data for each item. Instead of having one for each item in the list, the adapter creates a handful of them (enough to fill the visible area and a couple either side), and then recycles them by moving them around when they're out of view, and updates their contents to represent a different item.
onCreateViewHolder is what the adapter calls to create those container objects. It's where you inflate the layout and usually do findViewById on the views so you have a reference to them, and you can just set a TextView's text instead of having to fnd the view every time you want to update it.
onBindViewHolder is what gets called when you need to update a ViewHolder with a different item's info. This is where you actually do all the setting of data, changing images and whatever, updating click listeners if necessary.
Basically you shouldn't be using adapterPosition in your ViewHolder's init block, because that gets called once, when the object is constructed. Not only do you not have a meaningful position at that point, but isn't that something you'll want to update often, as the position changes? That's what onBindViewHolder is for! It has the position passed in and everything. That's the method where you set state
I'm currently working on a UI that looks like this
The blue part is a ConstraintLayout while the purple part is a RecyclerView inside it (it's a RecyclerView because it's content are dynamic based on service response).
I'm setting onClick handler on the ConstraintLayout that would take the user to another page. The problem is the RecyclerView is consuming the clicks and not forwarding it to its parent. Thus onClick handler works for the blue area, but not for the purple area.
I tried setting android:clickable="false" and android:focusable="false" in the RecyclerView but it still won't propagate the clicks to its parent.
One solution I came across is to extend from ConstraintLayout and override onInterceptTouchEvent() to return true. However I have a strict requirement in my project to not create custom widgets, so I cannot use this solution.
Is there a way to tell RecyclerView to stop consuming touch events?
Activity layout:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="300dp"
android:layout_margin="16dp"
android:background="#42d7f4"
android:onClick="navigate"
android:padding="16dp">
<TextView
android:id="#+id/headerText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="FRUITS"
android:textSize="36sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/itemsList"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="#9f41f2"
android:clickable="false"
android:focusable="false"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</FrameLayout>
Item layout:
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/itemText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:clickable="false"
android:focusable="false" />
Activity.kt:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val rcView = findViewById<RecyclerView>(R.id.itemsList)
rcView.layoutManager = LinearLayoutManager(this)
val items = listOf("Apple", "Banana", "Oranges", "Avocado")
rcView.adapter = ItemAdapter(items)
}
fun navigate(view: View) {
Toast.makeText(this, "Navigating to details page", Toast.LENGTH_SHORT)
.show()
}
}
class ItemAdapter(private val data: List<String>) : RecyclerView.Adapter<ItemViewHolder>() {
override fun getItemCount(): Int = data.size
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.item, parent, false)
return ItemViewHolder(view)
}
override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
holder.bind(data[position])
}
}
class ItemViewHolder(view: View) : RecyclerView.ViewHolder(view) {
private val itemTv: TextView = view.findViewById(R.id.itemText)
fun bind(item: String) {
itemTv.text = item
}
}
If you freeze the layout after you've set the adapter it no longer swallows clicks:
recyclerView.isLayoutFrozen = true // kotlin
recyclerView.setLayoutFrozen(true); // java
Just keep in mind if you need to change the data in the adapter you have to unfreeze the layout before you call notifyDataSetChanged and then re-freeze the layout. I don't love this solution but it's the only one that worked for me.
One way to approach this is to set the RecyclerView row with a click listener and to disable clicks and long clicks on the children of the RecyclerView row as follows:
ConstraintLayout: `android:onClick="navigate"`
For the item layout: `android:onClick="navigate"`
TextView: android:clickable="false"
android:longClickable="false"
(etc. for all children of the row)
I think what is missing is to make the TextView not long clickable.
You can set your RecyclerView focus to false.
recyclerView.setFocusable(false);
And/Or set its rows view.setFocusable(false);
EDIT
If you want to give the ConstraintLayout the focus
layout.setFocusable(true);
layout.setClickable(true);
//
// add click listener and event ....
//
For more information please refer to the official documentation provided by Google
Hope this helps, cheers
Probably the easiest way to completely block interaction with anything inside a single view is to put a transparent view over it that intercepts all touch events. You can set click on that view manage all the functionality through that view.
For example like this
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="300dp"
android:layout_margin="16dp"
android:background="#42d7f4"
android:padding="16dp">
<TextView
android:id="#+id/headerText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="FRUITS"
android:textSize="36sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/itemsList"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="#9f41f2"
android:clickable="false"
android:focusable="false"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<View
android:id="#+id/clickView"
android:layout_width="0dp"
android:layout_height="0dp"
android:onClick="navigate"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:clickable="true"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</FrameLayout>
Now you can functionality perform through that view instead through ConstraintLayout.
Moreover, you look at this answer
When attempting to use Kotlin Extensions within a RecyclerView.Adapter to refer to Views in an XML layout file, Intellij cannot recognize the Views by their IDs, even with seemingly correct imports. The odd thing about this is that it will work inside of an Activity or Fragment. Is there additional context needed for this to work in a RecyclerView.Adapter?
Intellisense not picking up Views in a RecyclerView.Adapter implementation:
View was found with practically the same imports
And here is my XML, just in case
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp"
>
<LinearLayout
android:layout_weight="1"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:orientation="vertical">
<TextView
android:id="#+id/tv_list_repo_name"
android:text="#string/repo_name"
android:textStyle="bold"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:id="#+id/tv_item_repo_description"
android:text="#string/description"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_gravity="center_vertical">
<TextView
android:id="#+id/tv_item_forks"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:drawableStart="#drawable/ic_fork"
android:text="10"
android:gravity="end"/>
<TextView
android:id="#+id/tv_item_stars"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:drawableStart="#drawable/ic_star"
android:gravity="end"
android:text="14,000"
/>
</LinearLayout>
</LinearLayout>
It seems you're trying to get the view outside the onBindViewHolder.
Please take a look at this sample:
import kotlinx.android.synthetic.main.row_badges_item.view.*
class BadgesAdapter(private val mActivity: Activity) : RecyclerView.Adapter<BadgesAdapter.ViewHolder>() {
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(position)
}
override fun getItemCount(): Int {
ba // Error here
return mData.size
}
inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
fun bind(position: Int) {
with(itemView) {
val badge = mData[position]
badges_title.text = badge.badgeTitle // Fine here
}
}
}
}
Hope that helps. Thanks. :)