I am using a RecyclerView with ListAdapter and DiffCall to implement a chat view. As more data gets added in the middle for threads or replies, or at the end for new messages the RecyclerView scrolls up and down. When a new message gets add I scroll to the bottom and that is working. But if someone replies to message from middle or up, the RecyclerView scrolls down.
I tried all app:StackFromEnd, app:reverseLayout and different LinearLayoutManagers I am unable to stop the RecyclerViews increase in height and push down the last message.
override fun onResume() {
messageViewModel.preprocessMessageList().observe(viewLifecycleOwner, Observer {
handleUpdateAndScroll(it)
})
}
private fun handleUpdateAndScroll(messageListRows: List<MessagesListRow>) {
messageAdapter.submitList(messageListRows)
val messageRowListSize = messageListRows.size
getMessageListLayoutManager().scrollToPositionWithOffset(messageRowListSize, 0)
}
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/messageList"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginStart="12dp"
android:layout_marginEnd="12dp"
android:layout_marginBottom="8dp"
android:scrollbars="vertical"
app:layout_constraintTop_toBottomOf="#id/oneView"
app:layout_constraintBottom_toTopOf="#id/twoView"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
We have a recyclerView inside this recyclerView to render list of replies, that is the only thing I can think of that is different. And we have ConstraintLayout items for this RecyclerView and some of the nested RecyclerView. Plus the messageList RecyclerView itself is in a ConstraintLayout
Anyone any clue?
Related
I have a SwipeRefreshLayout embedding a RecyclerView
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="#+id/swipeRefreshLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/catalog_recycler_view"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
I'd like it to trigger the refresh only when I start pulling down from the top of the scrollable area.
If I'm scrolling though the top of the content, when I reach the top still pulling down, it's not supposed to chain with the refresh, that's another intention.
How can I prevent the refresh to trigger when I didn't start the pull down from the top of the content ? Is there an option for that or do I need to cancel it from the refresh listener in some way ?
You can add a listener to the RecyclerView to know when the moving state is DRAGGING or IDLE, and use this to allow the swipeRefresh to happen only if the RV is not DRAGGING by saving the condition in a Boolean (i.e. canRefresh), so you know the list is not scrolling, hence, you can refresh.
You can disable the SwipeRefreshLayout with isEnabled = false, this won't stop the list or its items functionality, just the SwipeRefreshLayout
When scrolling from left half screen of the mobile scrolling is happening but when i scroll from right half screen scroling is not happening.
This is my xml clode.
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/postRecyclerView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_margin="2dp"
android:nestedScrollingEnabled="false">
</androidx.recyclerview.widget.RecyclerView>
My Java code is
val layoutManager = LinearLayoutManager(this)
layoutManager.orientation = LinearLayoutManager.VERTICAL
binding.postRecyclerView.layoutManager = layoutManager
binding.postRecyclerView.setHasFixedSize(false)
enter code here
Finally after so much search i found the solution. Actually i have used Floating button in that page due to that hieght from right side scrolling was not happend in Recyclerview.
I have a Recyclerview that contains CardViews. The CardViews contain a ViewPager2 and a few other views (text, etc). I want to capture when a user clicks anywhere on the CardView (the parent) and run some logic. The problem: even though the ViewPager is a child of the CardView, it still interferes with the CardView's onClickListener and if the user clicks on the portion of the CardView where the ViewPager is drawn the CardView's listener is never triggered.
This is interesting fundamentally because I would expect the parent view to supersede any click listeners in the children, but the ViewPager seems to be a unique layout in this regard.
I've looked into solutions here but i'm not sure if any of them are the best practice since this onClick logic resides in a RecyclerView in my case. Since i'm instantiating the listener in my ViewHolder's init method it should be avoiding unnecessary object instantiation but i'm cautious not to make an inefficient solution.
The requirements for my solution are:
If a user clicks anywhere on my CardView, run my onClickListener (which'll launch a fragment)
If a user swipes the ViewPager within my CardView, let the ViewPager handle the event as usual and don't run the CardView's onClick logic
What's the proper way to do this? With my code below, clicks outside of the ViewPager are working but clicks on top of the ViewPager don't trigger my fragment-launching logic.
ViewHolder
class RaffleViewHolder(cardView: CardView) : RecyclerView.ViewHolder(cardView), View.OnClickListener {
val itemName = cardView.itemName
val viewPager = cardView.viewPager
val featuredWedge = cardView.quarter_circle
val imageViewPagerAdapter = RaffleItemViewPagerAdapter()
init {
viewPager.adapter = imageViewPagerAdapter
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
viewPager.setPageTransformer(ViewPagerDepthPageTransformer())
}
cardView.setOnClickListener(this)
}
override fun onClick(view: View?) {
Timber.d("onclick fired in recyclerviewadapter")
//do stuff. This is only reached for clicks outside of the ViewPager
}
}
XML for CardView
<androidx.cardview.widget.CardView
android:id="#+id/itemCard"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="2dp"
android:elevation="2dp"
app:cardBackgroundColor="#color/white">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.viewpager2.widget.ViewPager2
android:id="#+id/viewPager"
android:layout_width="300dp"
android:layout_height="300dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="#+id/itemName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#id/viewPager" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>
I have a chat app and i'm trying to ensure that when the user opens the screen the items display from the bottom with the most recent right above the input area. I've used a custom view for the input area and both the recycler and the custom view are within a ConstraintLayout.
My problem is that when I load items into the list, if the number of items is greater than the size of the recycler, it will not fully show the last item. When I make the input area visibility = Gone then the items display properly at the bottom. It's almost like the recyclers LinearLayoutManager thinks that the height of the Recycler is of the screen without the input field. I've manually printed out the size of the views and used layout inspector to ensure that the recycler is indeed drawn in the correct location (above the input and below the navigation bar).
What could be causing such an issue? I should note that whenever you click on a Linkified text in a chat bubble that the list scrolls a small amount equal to the offset that's incorrect when you open the screen. Clearly something is not measuring right here and not sure where to begin.
I should also note that if I try to add a post with smoothScroll it will go to the end of the list but then whenever a new item appears in the list from sending a message the items above the most recently added one seem to jump up a little with an unnecessary animation. It's like the last item in the list is in some special state?
if you're curious this is my scrolling function:
private fun scrollToFirstItem(dueToKeyboard: Boolean = false) {
val stackingFromEnd = (recyclerView.layoutManager as LinearLayoutManager).stackFromEnd
if (stackingFromEnd) {
val firstPosition = recyclerView.adapter?.itemCount?: 0 - 1
if (dueToKeyboard ) {
recyclerView.scrollBy(0, Integer.MAX_VALUE)
} else {
recyclerView.scrollToPosition(firstPosition)
}
recyclerView.post { recyclerView.smoothScrollToPosition(firstPosition) }
} else {
recyclerView.scrollToPosition(0)
}
}
And my xml for my fragment:
<androidx.constraintlayout.widget.ConstraintLayout
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">
<include
android:id="#+id/searchView"
layout="#layout/compose_new_message_include"/>
<com.airbnb.epoxy.EpoxyRecyclerView
android:id="#+id/conversationEpoxyRV"
android:layout_width="match_parent"
android:layout_height="0dp"
android:scrollbars="vertical"
app:layout_constraintTop_toBottomOf="#id/searchView"
app:layout_constraintBottom_toTopOf="#id/composeView"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
tools:listitem="#layout/conversation_item_inbound"/>
<include layout="#layout/conversation_pip_view"
android:id="#+id/selectedMediaContainer"/>
<****.ComposeView
android:id="#+id/composeView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
Any help is appreciated, I'm so lost..
To me this feels like more of an issue in the layout rather than in the scrolling function. I hope that this can be resolved easily if you use a Relative Layout instead of a Linear Layout. So in case if it may be helpful, i'll add my solution below using a Relative Layout.
<RelativeLayout
android:id="#+id/layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/recycle_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_above="#id/input_field">
</androidx.recyclerview.widget.RecyclerView>
<EditText
android:id="#+id/input_field"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_alignParentBottom="true"/>
</RelativeLayout>
So in case if it is not clear what i have done, an EditText element is set to align to the bottom of parent which is a Relative Layout. Then a RecyclerView is added so that RecylerView will be always constraint above the EditText but not overlapped.
Everything looks fine, except the restrictions you added to recycleview design, I see that you are setting the recycleview to top of searchView app:layout_constraintTop_toBottomOf="#id/searchView" while the searchView view is not restricted as should it be.
It's better when using ConstraintLayout to restrict every view inorder avoid unexpected behaviors, because every view has a relation with other view will be effected with other view (x, y), therefore your searchView should be look like:
<include
android:id="#+id/searchView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
layout="#layout/compose_new_message_include"/>
Give the size of arraylist of your data to smoothScrollToPosition
var linearLayoutManager = LinearLayoutManager(context)
recyclerView.layoutManager=linearLayoutManager
recyclerView.adapter=adapter
linearLayoutManager.setStackFromEnd(true)
adapter.notifyDataSetChanged()
recyclerView.smoothScrollToPosition(dataMsgs.size())
I had a similar problem some years back and I solved it with scrollToPositionWithOffset (support library). Of course this was before constraint was used....
I kept my newer items at the bottom, too. The code I used for scrolling after inserting an item was:
((LinearLayoutManager) layoutManager).scrollToPositionWithOffset(getItemCount() - 1, 0);
To scroll and adjust the awkward positioning after removing an item, I used:
View vTopCard = layoutManager.getChildAt(0);
// Get its adapter position
final int topCardPos = layoutManager.getPosition(vTopCard);
// Get top of card position
int topOffset = layoutManager.getDecoratedTop(vTopCard);
int cardMargin = (int) getResources().getDimension(R.dimen.card_vertical_margin);
((LinearLayoutManager) layoutManager).scrollToPositionWithOffset(topCardPos, topOffset - cardMargin);
The getDecoratedTop helps with the "bounce" and final positioning, as does factoring the vertical margin.
I hope this helps (at least part of your issue)! Like I said, this is old code (when I was learning to program Android and Java at the same time), so if I left something out, let me know and I'll reexamine the app's code in more detail (though, I'll have to find the time).
Good luck!
I have a RecyclerView inside a NestedScrollView:
<android.support.v4.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="#dimen/_100sdp">
<android.support.v7.widget.RecyclerView
android:id="#+id/recycler_view_chat"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="#dimen/_100sdp"
android:layout_marginLeft="#dimen/_30sdp"
android:layout_marginRight="#dimen/_30sdp"
android:background="#color/bright_grey"
></android.support.v7.widget.RecyclerView>
</android.support.v4.widget.NestedScrollView>
My RecyclerView is being filled with items in onCreate()
On a device you would see the first item of the RecyclerView on the very top und would have to scroll down the NestedScrollView in order to see the last item.
Since my items are chat message sorted by the time sent I need the NestedScrollView to be scrolled all the way down so users would see the latest chat message first without having to scroll in the first place.
Any ideas on this?
Given that your RecyclerView is the only child of your NestedScrollView, you would be better off removing the NestedScrollView altogether, and instead applying the fixed height to the RecyclerView. Something like this:
<android.support.v7.widget.RecyclerView
android:id="#+id/recycler_view_chat"
android:layout_width="match_parent"
android:layout_height="#dimen/_100sdp"
android:layout_marginLeft="#dimen/_30sdp"
android:layout_marginRight="#dimen/_30sdp"
android:background="#color/bright_grey" />
Doing this allows you to have the RecyclerView itself manage scrolling, rather than the parent scroll view. And that allows you to leverage a property of LinearLayoutManager to achieve what you want.
Reverse layout -- setting this will "invert" your list; the first item in your adapter will appear at the bottom of the list, and the default scroll position of the RecyclerView will be to scroll all the way to the bottom.
https://developer.android.com/reference/android/support/v7/widget/LinearLayoutManager.html#setReverseLayout(boolean)
LinearLayoutManager lm = new LinearLayoutManager(this);
lm.setReverseLayout(true);
If you have the same issue and want to keep the NestedScrollView.
It will work like this.
Handler(Looper.getMainLooper()).postDelayed({
binding.nestedScrollView.smoothScrollTo(
0,
binding.recyclerview.measuredHeight,
500
)
// binding.nestedScrollView.scrollTo(0, binding.recyclerview.measuredHeight)
// binding.nestedScrollView.smoothScrollTo(0, binding.recyclerview.measuredHeight)
}, 50L)
For me, it didn't work without delay.