How can I prevent diagonal swipe on Viewpager2, only allow to swipe if gesture is strictly horizontally.
I dot not want this
I want this
This how make it work
source from Medium
fun ViewPager2.reduceDragSensitivity() {
val recyclerViewField = ViewPager2::class.java.getDeclaredField("mRecyclerView")
recyclerViewField.isAccessible = true
val recyclerView = recyclerViewField.get(this) as RecyclerView
val touchSlopField = RecyclerView::class.java.getDeclaredField("mTouchSlop")
touchSlopField.isAccessible = true
val touchSlop = touchSlopField.get(recyclerView) as Int
touchSlopField.set(recyclerView, touchSlop*3)
}
Also you must have Recyclerview like this
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layerType="hardware"
android:orientation="vertical"
android:overScrollMode="never"
android:scrollbars="vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/recyclerArtist"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center_horizontal"
android:clipToPadding="false"
android:layerType="hardware"
android:orientation="vertical"
android:overScrollMode="never" />
</androidx.core.widget.NestedScrollView>
also
ViewCompat.setNestedScrollingEnabled(recyclerView,false)
I found this awesome article that addresses this issue in depth. The problem is actually with the recyclerview which is the backbone of the new viewpager2.
Many times, even though an horizontal gesture is performed on a child RecyclerView, the parent RecyclerView kicks in and intercepts the touch event instead of the child RecyclerView, resulting in a small vertical scroll instead of an horizontal scroll. This actually happens when the scroll gesture is not perfectly horizontal. It’s actually diagonal, even if you can see that the vertical distance of the gesture is small compared to its horizontal distance and its intent was clearly horizontal
Further, the author of the article explains the cause of this behavior by digging into the recyclerview code
In summary, the problem is that when the RecyclerView is configured to scroll in a single direction as it’s usually the case, it doesn’t test if the global shape of the gesture is more horizontal (abs(dx) > abs(dy)) or vertical ( abs(dy) > abs(dx)) before deciding to intercept it, potentially conflicting with a child view scrolling in the opposite direction.
Here's the article link. Scroll to the bottom, you'll find the solution. I think this solution is better than modifying the touch slop value to reduce the drag sensitivity.
Related
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 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 rather complex BottomSheetLayout which layout is as follow
The root view of my bottom sheet is a custom FrameLayout that allows to round it's corner (both background and children). Nothing else (nothing touch-related)
Then, I use the usual ConstraintLayout in order to layout my Bottom sheet.
This ConstraintLayout contains, amongst other views, a vertical RecyclerView:
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="10dp">
<!-- other views -->
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/events"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginTop="25dp"
android:layout_marginBottom="74dp"
app:layout_constraintTop_toBottomOf="#+id/days"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:background="#{viewModel.colors.defaultBackgroundColor}"
tools:background="#ECF0F3"
android:orientation="vertical"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:listitem="#layout/event_item"
tools:itemCount="10" />
</androidx.constraintlayout.widget.ConstraintLayout>
I have no particular issue while dragging my bottom sheet, however, when fully expanded I was expecting the be able to scroll the content of my RecyclerView. But I cannot.
After a lot of researches, I managed to make it scroll by enabling scrolling when my Fragment's view is inflated :
ViewCompat.setNestedScrollingEnabled(this.binding.bottomSheetEvents.getRoot(), true);
However, doing so has a weird consequence. When my bottom sheet's state is EXPANDED, I can finally scroll my RecyclerView, but then there is absolutely no way to drag my Bottom sheet any more : it remains fully expanded.
I have tried a few other ways.
I have tried wrapping my NestedScrollView. In past experience I was able to have the full content of my bottom sheet scrollable thanks to NestedScrollView, but in this case, I only want to scroll my RecyclerView. What ever is above it must remain idle.
I have tired this.binding.bottomSheetEvents.events.setNestedScrollingEnabled(false); but there is no difference.
My belief is that when the bottom sheet is fully expanded, it dispatches scroll events to inner children that can supports its. And, backwards, it knows, at some point, when uses wishes to collapse said bottom sheet. So I guess, something wrong must be happening there.
Further informations:
this bottomsheet is included in my fragment which roots view is a CoordinatorLayout obviously.
the fragment is also hosted in CoordinatorLayout with an AppBar
the include layout uses the app:layout_behavior="#string/bottom_sheet_behavior"
and the include layout also uses behavior_fitToContents set to false so that I can use method setExpandedOffset to prevent the bottom sheet to reach the top.
Version used : 1.1.0-alpha07
Thanks for the help!
When I'm scrolling down, the items above the RecyclerView does not scroll unless I start touching from the layout above, and it only scrolls down when I have reached the end of the RecyclerView.
<NestedScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout>
<Some other items.../>
</LinearLayout>
<RecyclerView
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</NestedScrollView>
Note:
I actually use a fixed size for the RecyclerView, setting it via the code below:
float height_recyclerview = (ScreenUtil.getHeight(context) - (height_banner + height_bottom_navigation + height_create_post));
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, (int) height_recyclerview);
rv.setLayoutParams(layoutParams);
Why do I use fixed size if it works smoothly with wrap_content?
I will be displaying possibly thousands of items that may have
images, which will hurt performance if it does not actually do
recycling because of the issue that the RecyclerView is inside the
NestedScrollView
I have implemented an EndlessRecyclerViewScrollListener which has an
issue that it keeps loading more data from server continuously if
implemented with a RecyclerView that is within whatever scrollable
view, or if it is in a scrollable view, but does not have a fixed
height, even if you are not scrolling down.
I have tried the following:
set nested scrolling to false on the recycler view
try using scroll view instead of nested scroll view
a bunch of other code related to layouts and scrolling behaviors that others suggested which didn't work for me because I'm implementing it in a much more complicated layout and the fact that I use EndlessRecyclerViewScrollListener
What I want to fix?
I want to make the page scroll like a single page, not as a separate scrollable view.
Note that my recycler view has a fixed height that takes the entire screen's space meaning that its height is actually fit assuming that the linear layout above is not visible anymore if the user has scrolled down.
The ideal scenario is to make the scrollview scroll down first, to make the recycler view take the entire screen, so that the recyclerview will scroll however the user wants to.
Then the linearlayout above which should not be visible anymore if the recycler view has taken up all the space of the screen, should only show up if the recycler view has reached the top/first item, if the user keeps scrolling back up.
Read this.
Add app:layout_behavior="#string/appbar_scrolling_view_behavior" to your recycler xml.
<android.support.v7.widget.RecyclerView
android:id="#+id/conversation"
app:layout_behavior="#string/appbar_scrolling_view_behavior"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
NestedScrollView Smooth Scrolling
recyclerView.isNestedScrollingEnabled = true
Do this programmatically
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="0dp"
android:fillViewport="true"
...
I have a recyclerview under a nestedsrcollview. I want to implement the scrolling to a specific position for the recyclerview but I am having difficulty with it. The xml code is:
<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.NestedScrollView 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"
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:context=".HomeFragment"
android:background="#ffffff"
android:fillViewport="true"
android:id="#+id/nestedscrollview"
>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="1dp"
android:orientation="vertical"
>
<<some other layouts>>
<android.support.v7.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/home_blog_list"
android:layout_marginBottom="52dp"
/>
</LinearLayout>
</android.support.v4.widget.NestedScrollView>
I want to implement the scrolltoposition for the home_blog_list recyclerview to a position (say 26). How to do it? P.S.- I have set nestedscrollingenabled to false for the home_blog_list.Please note that I want to scroll the nestedscrollview to a specific row of the recyclerview. I dont want the case where the recyclerview is scrolled only. Thanks in advance!
I stumbled into the same issue and I found an easy solution that does not require to refactor by using the library suggested by asif-ali.
On my current project, I have a NestedScrollView that holds a ConstraintLayout.
This ConstraintLayout contains a complex header made of multiple view, and then my RecyclerView.
Much like you, I needed the whole thing to be scrollable.
That said, when the user wishes to see an item from the particular RecyclerView, you would normally call:
RecyclerView#smoothScrollToPosition(int position)
But since the RecyclerView's height is set to wrap_content the full list is displayed, with as many ViewHolders as there are items in its adapter.
Granted, we do not benefit from recycling, but then why would we need a ScrollView ? Using #asif-ali solution might surely bring recycling optimizations but that's not the point here.
So, we have a fully laid-out RecyclerView. In order to scroll to a particular item (ViewHolder#itemView) position, you can do as followed:
final void smoothScrollToPosition(final int position) {
final ViewHolder itemViewHolder = this.recyclerView.findViewHolderForAdapterPosition(position);
// at this point, the ViewHolder should NOT be null ! Or else, position is incorrect !
final int scrollYTo = (int) itemViewHolder.itemView.getY();
// FYI: in case of a horizontal scrollview, you may use getX();
this.nestedScrollView.smoothScrollTo(
0, // x - for horizontal
scrollYTo
);
}
That's it !
It might be possible child is not fully visible after doing so (in my test case) so I'd suggest to add half the height of the itemView to the scrollYTo variable to make sure the nestedScrollView will scroll enough. If you do so, you might also want to check out in which direction the nestedScrollView must scroll to (either up, then remove half height, or down, then add half height.
[EDIT 1]
After further testing and research, based on this answer: https://stackoverflow.com/a/6831790/3535408 it is actually better and simpler to target the itemView.getBottom. On my app, it works flawlessly.
So the updated code looks as followed:
final void smoothScrollToPosition(final int position) {
final ViewHolder itemViewHolder = this.recyclerView.findViewHolderForAdapterPosition(position);
// at this point, the ViewHolder should NOT be null ! Or else, position is incorrect !
// FYI: in case of a horizontal scrollview, you may use getX();
this.nestedScrollView.smoothScrollTo(
0, // x - for horizontal
itemViewHolder.itemView.getBottom()
);
}
I think this is what you want, have a look: link