How to disable fragment scroll in horizontal recyclerview - android

I have a horizontal recyclerView that I populate with imageViews, so that when the user swipes from right to left, the rest of the images appear one by one. Everything is behaving correctly, images are loaded and horizontal scroll works as expected, even the clickListeners for individual items work fine...
BUT... if the touch gesture to scroll to the right is not a pure horizontal swipe (or left if the user reaches the end of the recyclerView and wants to go back), the fragment where the recyclerView is being loaded scrolls down, too. I want to avoid this behavior, so that when the user intends to reach the images swipping from right to left, the recyclerView only moves in the horizontal direction, ignoring any vertical movement that is performed over the recyclerView. If the user taps or swipes outside of the recyclerView screen area, I want usual behavior to occur (scroll down as expected). I have tried with other alternatives seen in this site, like using the setOnTouchListener option to detect a MotionEvent and act accordingly, as you can check below (this is actual code from my fragment):
advertPicturesRecyclerView.setOnTouchListener { recyclerView, motionEvent ->
when(motionEvent.action) {
MotionEvent.ACTION_UP -> {
Snackbar.make(recyclerView, "Touch down!", Snackbar.LENGTH_LONG).show()
recyclerView.parent.requestDisallowInterceptTouchEvent(true)
true
}
else -> {
recyclerView.parent.requestDisallowInterceptTouchEvent(false)
true
}
}
}
In this context, ACTION_UP means in the direction of the recyclerView, so this just detects movement inside the recyclerView and sort of "passes" vertical movement to the parent (fragment), which scrolls down.
How can I "block" the recyclerView to ignore any vertical swipe, and only move horizontally?

val viewManager = LinearLayoutManager(requireContext(),LinearLayoutManager.HORIZONTAL, false)
myRecylcerView?.layoutManager = viewManager
The above snippet will allow only a horizontal scroll based on your question but I guess there is something else that is causing the problem like you might be using a NestedScrollView/ScrollView, so please provide the layout files and a sample video to have a clear perspective of the problem.
Update - 25th September 2020
You can try the below to your NestedScrollView and recylcerview
<androidx.core.widget.NestedScrollView
android:id="#+id/nsvMain"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:overScrollMode="never">
// .....
// .....
</androidx.core.widget.NestedScrollView
myRecylcerView?.isNestedScrollingEnabled = false
It would be much better if you attach your layout file.

Related

How to pass touch events from part of ViewPager2 to the view underneath it?

Current State:
I have a Viewpager2 which contains a bottom sheet (using BottomSheetBehaviour). When the bottom sheet collapses, the Viewpager2 doesn't resize to fit the now shorter content. I also have a map (using MapBox's MapView) that shows underneath the ViewPager2.
Since this description is a little ambiguous, I have also created a minimal reproducible example (hosted on GitHub) for clarification. In order to get MapBox to show a map, you will have to enter your secret/public tokens in gradle.properties and strings.xml files respectively. Even if you don't, you can probably get what I'm saying by looking at the scale bar at the top left corner of the MapView.
What I want to happen:
When the bottom sheet collapses, the section of the view that the bottom sheet had previously occupied that is now transparent does not handle user interaction but instead passes the events on to the FragmentContainerView that is shown beneath it.
Problem:
When the bottom sheet collapses, the height of the ViewPager2 stays the same, so touch events within the aforementioned area are handled as per normal by the ViewPager2, when they should intuitively be handled by the MapView below, since it is now visible in that area. You can just drag your finger or pinch the area to get what I mean.
What I've tried so far:
Making ViewPager2 dynamically 'wrap content'
It seems to me like ViewPager2 will not handle this out of the box from this Issue.
It's probably possible to get the expanded and collapsed height of each fragment in the ViewPager2 and then reset ViewPager2's height each time the fragment/page changes and each time the bottom sheet goes to the expanded/collapsed state but that that requires a lot of calls to requestLayout() which makes the UI flicker and disrupts animations.
Handling touch events
I tried the following, but it seems that perhaps ViewPager2 requires the touch events to be registered, because trying to swipe to the next page crashed the app. However, I'm not very familiar with handling touch events or ViewPager2's implementation and I didn't go too deep into exploring this, so input on the feasibility of this/whether it even makes sense will be appreciated.
viewPager2[position].setOnTouchListener { v, event ->
// If background is collapsed, allow touch events to pass through transparent space in ViewPager2
if (event.y < bottomSheetBehavior.peekHeight && isBottomSheetCollapsed) {
false
// Else, handle them as per normal
} else {
when (event.action) {
ACTION_DOWN -> v.performClick()
}
true
}
}
Note:
I also want the bottom sheet to be able to swipe to the next page like a viewpager, which is why I put the bottom sheet inside the viewpager. Something like how Citymapper does it.
I ended up doing the following:
Collapse bottomSheet and change the height of viewPager2 to match the collapsed height by default
Set an onTouchListener() on bottomSheet to detect upward drag events, which when detected would increase the height of viewPager2 and expand bottomSheet
Setting the onTouchListener():
binding.bottomSheet.setOnTouchListener { _, event ->
when (event.action) {
ACTION_DOWN -> {
touchStartY = event.rawY
true
}
ACTION_UP -> {
false
}
ACTION_MOVE -> {
touchCurrentY = event.rawY
if (touchStartY - touchCurrentY > TOUCH_THRESHOLD && isBottomSheetCollapsed) {
// Update flag
isBottomSheetCollapsed = false
// Change viewPager2 height
viewPager2.updateLayoutParams {
height = bottomSheetExpandedHeight
}
// I don't think that updateLayoutParams calls requestLayout(), but you
// would want to expand the bottomSheet only after the viewPager2 height has updated
viewPager2.doOnLayout {
bottomSheetBehavior.state = STATE_EXPANDED
}
false
} else true
}
else -> true
}
}
Note: Might need to play around with doOnLayout() or doOnNextLayout() to get the expanded height of the bottom sheet if it is set to WRAP_PARENT in xml.

How to disable ViewPager scrolling on diagonal swipes in Android?

I have an Activity with a ViewPager, which hosts several pages, each containing a RecyclerView. Horizontal and vertical swipes are working as expected: Horizontal changes the page, vertical scrolls the RecyclerView.
When the user makes a diagonal swipe, the page changes.
How can I prevent this? Diagonal swipes should only scroll the RecyclerView.
<?xml version="1.0" encoding="utf-8"?>
<androidx.viewpager2.widget.ViewPager2
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/page"
android:layout_width="match_parent"
android:layout_height="match_parent" />
I already tried this:
binding.page.setOnTouchListener(object : View.OnTouchListener {
override fun onTouch(v: View?, event: MotionEvent?): Boolean {
when (event?.action) {
MotionEvent.ACTION_DOWN -> binding.page.isUserInputEnabled = false
}
return v?.onTouchEvent(event) ?: true
}
})
I just tested out the behavior you described. In my opinion ViewPager2 and RecyclerView work together flawlessly:
On a diagonal swipe with less than 45°, the ViewPager2 gets scrolled.
On diagonal swipes with more than 45°, the RecyclerView gets scrolled.
However, this seems only true when the Activity is newly created. It also seems to depend a little bit on the prior scroll gesture.
When you first make a vertical swipe (RecyclerView gets scrolled), after that, diagonal scrolls again will scroll the RecyclerView.
When you just made a horizontal swipe (ViewPager2 gets scrolled), then diagonal scrolls tend to scroll the ViewPager2.
Maybe this is the intended behavior. To provide a more intuitive user experience,
I'd probably keep it as is, as I had to test quite some time to reproduce your described behavior.
Actually, all the scrolling felt very intuitive the way it is.
And keep in mind: If you really only want to allow exactly 0° horizontal swipes to scroll the ViewPager2, you're giving your users a very hard time.
It would be practically impossible to scroll the ViewPager2.

horizontal list inside vertical list not smoothly on jetpack compose

I'm trying to implement a screen that use a vertical scroll to show all the contents and some sections of this screen has a horizontal scroll with cards like this:
The screen composable:
LazyColumn{
item { header }
item { texts }
item {
LazyRow{
items(firstCardList){}
}
LazyRow{
items(secondCardList){}
}
}
}
The scrolls are working fine but not smoothly, I don't know how to explain but when I move the list with my finger, the scroll stop when I remove my finger of the screen, it's not smoothly and continuous the scroll like the vertical, looks like the scroll animation is stuck.
I know that in xml I can use nestedScrollingEnabled = false and this problem is solved, but I don't know how to do this in compose.

PagedListAdapter: Weird run time behaviours

I am using PageListAdapter in a fragment dedicated for chatting(just like in any modern messaging application). I initially used RecyclerView only, but it's very jittery. After that I searched further and came across PagedListAdapter, but, It's here with its unique limitations and bugs. it's more jittery and weird scrolling behaviour. Whenever the data is changed in DB which is loaded using RxJava Flowable, most of the views are recreated as all the messages/views in RecyclerView starts flickering, the scroll position automatically changes and scrolls to somewhere in middle.
So, basically I have these questions:
How to start the view with last page of PagedListAdapter?
How to prevent PagedListAdapter from auto-scrolling?
I had a similar issue when using RecyclerView inside the MotionLayout (from beta3/beta4).
I was using MotionLayout to achieve a swipeable overlay (from bottom in portrait and from left in horizontal mode).
In horizontal mode, updating the RecyclerView worked OK because in horizontal mode, the overlay was taking up the entire screen, and so did the RecyclerView.
But in portrait mode, I needed to have the overlay swiped only up to a half of screen, hence, the RecyclerView was not full screen.
In this case, the entire view was changing its size when the RecyclerView was updated, creating a jumpy list (when new chat message inserted, the list would jump to another position).
Still haven't figured it out why exactly, and I can't confirm it's because of the MotionLayout, but its the main suspect.
My solution to it was to fixate the height so the list wouldn't change size on update. I've overriden the getHeight function of the LayoutManager and set a fixed size that I got in onViewCreated() of the fragment.
Also, you need to keep this layout listener to have the size changed if the keyboard appears.
requireView().viewTreeObserver.addOnGlobalLayoutListener {
if (initialHeight != view.height) {
initialHeight = view.height
chatLayoutManager.requestLayout()
}
}
chatLayoutManager = object : LinearLayoutManager(requireContext(), VERTICAL, true) {
override fun getHeight() = initialHeight
}

swiping items on listview, with small vertical margin

So I have a ListView on which I attach :
mList.setOnTouchListener(gestureListener);
What this gesture listener does is basically measure deltaX (horizontal swipe) and detects if it is higher than some value, then it knows that swipe has occured.
Then I have :
mList.setOnItemClickListener(new AdapterView.OnItemClickListener() { ... }
Which checks with gesture object if swipe has happened, if yes its a swipe, otherwise its a click.
All this works fine.
My issue is :
When I swipe elements with my thumb, sometimes I make a list scroll just a bit vertically.
The horizontal swipe is detected, but the problem is that OnItemClickListener doesnt fire, which is where I would launch some actions on the item depending if it is a swipe or not.
So the problem is that the vertical scroll mechanism of listview, makes something that makes onItemClicked event not to fire.
the list on the left works fine, no vertical scrolling appears.
the list on the right, I swipe the item but slightly to the bottom as well (still within the bounds of this item) so that the list moves just a bit. And the OnItemClicked does not fire.
How can I add a small margin for vertical scroll, so that the scroll appears but the onItemClicked is still fired?
Thanks
I was looking for implementation of swiping a listview,
now when in December edition of Gmail there is a swiping effect on each item, its implementation answers my question
It can be found here :
https://gist.github.com/2980593

Categories

Resources