Viewpager2 scroll stops working when scrolling back - android

I am facing a weird issue, I have a Viewpager2, the fragment page contains a Scrollview, HorizontalScrollView, and a RecyclerView.
When I launch the fragment that hosts the ViewPager the UI works, it also works if I swipe right, but as soon as I swipe left to the previous page the touch stops working, I lose the ability to swipe up and down on the page.
I feel lost here, it worked perfectly fine with the old ViewPager
ViewPager fragment page
<ScrollView
android:isScrollContainer="true"
android:measureAllChildren="true"
android:background="?backgroundColor"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
...
</androidx.constraintlayout.widget.ConstraintLayout>
<ScrollView/>
ViewPager
<androidx.viewpager2.widget.ViewPager2
android:orientation="horizontal"
android:id="#+id/pager"
android:layout_width="0dp"
android:layout_height="0dp"
...
/>

I fixed the issue by updating ViewPager's PageTransformer.
I extended ViewPager2.PageTransformer on my PageTransformer thinking the old transformer would work just fine.
Looks like this is not the case, the old version was missing the translationZ looks like not setting the translationZ causes this behavior.

Related

ViewPager within a CoordinatorLayout (for sticky header purposes) leads to broken scrolling

Test app demonstrating the problem here: https://github.com/jstubing/ViewPagerTest
I'm using a CoordinatorLayout/AppBarLayout combo to achieve a sticky header within a scrollable page. At the very bottom of the page is a ViewPager2. When I swipe to a new ViewPager page, content loads asynchronously and populates a RecyclerView within the ViewPager. This all works well.
The problem occurs when I swipe to a new ViewPager page. At this point, the content loads and renders fine but I am no longer able to initiate a scroll outside of the ViewPager. In other words, swiping up and down in the "UPPER PAGE CONTENT" section (see XML) does nothing.
However, scrolling up and down does work within the ViewPager. If I scroll up or down in the ViewPager, or even just tap the ViewPager once, it fixes everything and I am again able to initiate a scroll from outside of the ViewPager.
I tried switching to the original ViewPager instead of ViewPager2 and it has definitely been more reliable, but the problem still happens every so often.
Any ideas how I can fix this? Ultimately I just want to be able to swipe between ViewPager pages and have the whole entire activity remain scrollable. I'm so confused as to why I lose scrolling ability in the first place, and why tapping on the ViewPager fixes it.
<androidx.constraintlayout.widget.ConstraintLayout 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="match_parent">
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="0dp"
android:layout_height="0dp"
android:elevation="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#color/background"
app:elevation="0dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="32dp"
app:layout_scrollFlags="scroll">
<!-- UPPER PAGE CONTENT -->
</androidx.constraintlayout.widget.ConstraintLayout>
<!-- Sticky header -->
<include
android:id="#+id/sticky_header"
layout="#layout/sticky_header"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</com.google.android.material.appbar.AppBarLayout>
<androidx.viewpager2.widget.ViewPager2
android:id="#+id/events_pager"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="#string/appbar_scrolling_view_behavior" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
The problem occurs when I swipe to a new ViewPager page. At this
point, the content loads and renders fine but I am no longer able to
initiate a scroll outside of the ViewPager.
I am again able to initiate a scroll from outside of the ViewPager.
So, the problem is now limited to the ViewPager. Most probably because the ViewPager2 supports nested scrolling through its inner RecyclerView (I don't mean the RecyclerView of the page, but the one that makes the ViewPager functions internally.
So, first thing we need to disable this nested-scrolling of the ViewPager2:
Kotlin:
viewPager.children.find { it is RecyclerView }?.let {
(it as RecyclerView).isNestedScrollingEnabled = false
}
Java:
for (int i = 0; i < viewPager.getChildCount(); i++) {
View child = viewPager.getChildAt(i);
if (child instanceof RecyclerView)
child.setNestedScrollingEnabled(false);
}
We can't completely disable the nested scrolling because this will badly affect the scrolling outside, so, we can manipulate this by a NestedScrollView that wraps the ViewPager:
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true"
app:layout_behavior="#string/appbar_scrolling_view_behavior">
<androidx.viewpager2.widget.ViewPager2
android:id="#+id/events_pager"
android:layout_width="match_parent"
android:layout_height="match_parent />
</androidx.core.widget.NestedScrollView>

Best way to implement a nested viewpager

I have a viewpager that inflates a layout that (sometimes) has another viewpager to view photos
<androidx.viewpager.widget.ViewPager
android:id="#+id/gallery"
android:clickable="true"
android:focusableInTouchMode="true"
android:focusable="true"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
They both use swipes. I know there is a similar question to this, but seeing as all the answers are old and didn't work for me, does anyone know any good methods of letting users swipe the nested viewpager without forcing a swipe on the parent viewpager? I know it can be done since apps like Diaro do it, but I'm not sure how to go about it myself

NestedScrollView not saving fragment scroll stated when navigated back

My App uses a single activity architecture which has a NestedScrollView in the activity layout
<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:id="#+id/root_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.example.android.navigationadvancedsample.MainActivity">
<androidx.core.widget.NestedScrollView
android:id="#+id/app_scroll_view"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:fillViewport="true">
<FrameLayout
android:id="#+id/nav_host_container"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
</androidx.core.widget.NestedScrollView>
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="#+id/bottom_nav"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:menu="#menu/bottom_nav"/>
</LinearLayout>
My issue is that that when I scroll a fragment, then navigate to another, then go back, the scroll position is reset. I've seen another answer which stated that adding IDs to the layouts should fix the issue, but it hasn't for me. Also, interestingly, the scroll position saves fine on configuration change.
I'm using NavigationComponents, could this be related? Here's a sample project that reproduces the issue (based on Google's NavigationAdvancedSample)
NestedScrollView has wrapped the Navigation Container in the activity_main layout. So it is the activity that saves the scroll state. There are 3 fragments. Home fragment has fixed height, Leaderboard and Register fragments are scrolling. When you scroll in the Leaderboard or Register and switch to the other one the scroll state does not change(since both can scroll to roughly the same height) but if you switch to home fragment scroll state resets because it has fixed height(size of the screen). Replacing NestedScrollView with the ScrollView didn't change anything as I examined it.
I think the right design is to wrap each fragment with NestedScrollView and set and get scroll state for each fragment.
Navigation component does not add the fragment to the activity state but replace it. So fragments get recreated after by switching between them. So you see scroll state is being reset. You can check it yourself by putting some log in the onCreateView of first fragment and see the log appears twice.

ViewPager in CoordinatorLayout shrinks unexpectedly

In my Android app running on Android 5.1.1 I have a layout using a Toolbar with a TabLayout, and underneath is a ViewPager. All of these are put together in a CoordinatorLayout.
In the first page of the ViewPager is a RecyclerView serving CardView items.
My problem is that my ViewPager keeps getting resized in a way so that my CardView list items are cropped.
My main layout looks basically like this:
<android.support.design.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent" >
<android.support.design.widget.AppBarLayout
android:layout_height="wrap_content"
android:layout_width="match_parent" >
<android.support.v7.widget.Toolbar
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<android.support.design.widget.TabLayout
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</android.support.design.widget.AppBarLayout>
<android.support.v4.view.ViewPager
app:layout_behavior="#string/appbar_scrolling_view_behavior"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</android.support.design.widget.CoordinatorLayout>
And the first fragment served by my ViewPager looks like:
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent" >
<android.support.v7.widget.RecyclerView
android:scrollbars="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="8dp"/>
</FrameLayout>
This renders something that looks like this:
When clicking a button in my layout, I use startActivityForResult to invoke another activity, and when returning to my activity sometimes suddenly my list is cropped so that only half of the first item is visible:
After swiping horizontally to another pager in the ViewPager and then back, the problem disappears, so it does seem a re-layout has not been properly triggered. Pressing HOME and then resuming my activity does NOT resolve the problem though. Note that this happens even if I am not modifying my layout in any way, I am simply returning from a startActivityForResult call. And yes, it only happens sometimes... And I have no background threads running that could explain the apparent random behavior.
At first I thought it was the RecyclerView that had shrunk, but using HierarchyViewer I was able to find that it was actually the entire ViewPager that had shrunk to about half its original height.
I tried various hacks to get around this, including calling invalidate() and requestLayout() on my entire view hiearchy, but nothing seemed to help (although swiping to another page and back again fixes it). Also, those are not the kind of solutions I want to resort to... Then I tried changing my ViewPager height to wrap_content, which did in fact solve this particular problem; after returning to my activity the first item in my RecyclerView is never cropped, and I can scroll down to the other items. However, now instead the very last item of my list is always cropped, as can be seen in this screenshot where the list is scrolled all the way to the bottom:
Since I am now at a point where I don't really understand what's going on, I need some help. What should I really use as the layout_height for my ViewPager, and - above all - why? To me, match_parent makes sense, but how should I be thinking here? Is there a rational reason my views got cropped when using match_parent, or did I in fact encounter a bug in ViewPager, RecyclerView and/or CoordinatorLayout? How do I make sure that my ViewPager consistently fills the entire screen area below the AppBar, and that my RecyclerView can be scrolled vertically to properly render all CardView list items?
It turns out this is almost certainly a bug in CoordinatorLayout or even more likely in AppBarLayout$ScrollingViewBehavior. In an effort to create a MCVE I realized it was the fact that my sub-activity had an IME on screen that caused the shrinking of the ViewPager - when my activity is resumed after onActivityResult, the ViewPager is shrunk as a result of reduced screen real-estate from the IME, but is never expanded again despite the fact that the IME is no longer being shown and the fact that the CoordinatorLayout is indeed expanded.
After debugging and stepping through onLayout and onMeasure of CoordinatorLayout and ViewPager I am now fairly sure that the CoordinatorLayout does not properly propagate the change in size to its children.
I found that I can "fix" the problem by calling requestLayout on my ViewPager, but only if the call is sufficiently delayed (10 ms never works, 100 ms works most of the time):
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
mHandler.postDelayed(new Runnable() {
#Override
public void run() {
mViewPager.requestLayout();
}
}, 100);
}
This obviously isn't a robust solution, but after investigating some more it turns out I probably don't even need CoordinatorLayout since I don't really have any advanced scrolling behavior. So my solution will be to simply go with a LinearLayout or RelativeLayout as my root view group instead. Nice and simple, no need to complicate things.
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<android.support.design.widget.AppBarLayout
android:layout_height="wrap_content"
android:layout_width="match_parent" >
<android.support.v7.widget.Toolbar
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<android.support.design.widget.TabLayout
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</android.support.design.widget.AppBarLayout>
<android.support.v4.view.ViewPager
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
I will however try to condense this into a simple example and file a bug with Google.
As to what I should use as the height for my ViewPager, my original use of match_parent still seems reasonable. The fact that wrap_content solved the problem (but caused other problems) is probably just due to inconsistencies caused by the bug.
I've experienced a similar issue when using an older version of the support library.
See these related issues:
https://code.google.com/p/android/issues/detail?id=176406
https://code.google.com/p/android/issues/detail?id=176187
Make sure you're using the latest Support library, version 23.1 as of this writing.
In your fragment just remove the frameLayout and make recyclerView parent...I hope it will work:
<android.support.v7.widget.RecyclerView
android:scrollbars="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="8dp">
<android.support.v7.widget.RecyclerView/>

SwipeRefreshLayout with Tabs and fragments per page has premature pull to refresh functionality

I have setup an app with slidingTabs nested in a swipeRefreshLayout where I have fragments for the pages which have recyclerView lists in them. It works fine but when I scroll down the list and go to scroll back to the top it pulls down the refresh icon before reaching the top again, Im lost as to why...
<com.example.android.ui.widgets.MultiSwipeRefreshLayout
android:id="#+id/swipe_refresh_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:foreground="#drawable/header_shadow">
<!-- Two fragments for the application -->
<android.support.v4.view.ViewPager
android:id="#+id/view_pager"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:background="#color/my_activity_background">
</android.support.v4.view.ViewPager>
</com.example.android.ui.widgets.MultiSwipeRefreshLayout>
I have followed a lot of the google io app but I can't seem to figure it out, thanks for any help
Turns out I had to override canChildScrollUp() of the SwipeRefreshLayout
I got that from here!

Categories

Resources