Status Update
Comments
il...@google.com <il...@google.com> #2
ne...@gmail.com <ne...@gmail.com> #3
What is 'partially' exactly?
How do I see it?
Thanks!
ma...@gmail.com <ma...@gmail.com> #4
- Nesting scroll views with a scroll direction perpendicular to the ViewPager2's orientation inside ViewPager2 works
- Nesting scroll views with a scroll direction parallel to the ViewPager2's orientation inside ViewPager2 does not work
il...@google.com <il...@google.com>
mi...@gmail.com <mi...@gmail.com> #5
Set a setNestedScrollingEnabled to the RecyclerView into the ViewPager2 (across reflection) resolves the problem
jo...@gmail.com <jo...@gmail.com> #6
il...@google.com <il...@google.com> #7
Verified that it works correctly on a:
- Nexus 4 emulator with API 17
- Nexus 5X emulator with API 28
- Pixel 2 device with API 29
To reproduce:
- Check out the Android Jetpack source (at commit 256899f482ff85cddfb050f37550be7b5ec927ef) (see steps in [1])
- Apply the patch (`git apply 0001-Add-sample-where-a-horizontal-ViewPager2-is-nested-i.patch`)
- Build and install viewpager2's integration-tests app: `./gradlew viewpager2:integration-tests:testapp:installDebug`
- Run it
Closing the issue for now, but please reopen if you have a minimal reproduction app
[1]
da...@gmail.com <da...@gmail.com> #8
il...@google.com <il...@google.com> #9
I searched a lot for workarounds on Google and SO, but it seems no one really found a suitable solution yet.
The thing is that all other VerticalViewPagers out there have other problems that are similarly annoying. Most of them revolve around touch issues like "What's a click and what's a swipe" or "what's the fling threshold to snap to the next page".
So if someone knows of a working sample of VerticalViewPagers, I'd be happy to see a link :)
da...@gmail.com <da...@gmail.com> #10
One possible option for VerticalViewPager with nested scrolling in the same scroll direction is using a RecyclerView implementing NestedScrollingParent + PagerSnapHelper to get snapping.
You'd then have to implement the NestedScrollingParent/Child contract between parent/child RecyclerView instances.
il...@google.com <il...@google.com> #11
I will try implementing what you suggested and in case it is readable code, I will post it here as sample project or somthing similar.
da...@gmail.com <da...@gmail.com> #12
ar...@gmail.com <ar...@gmail.com> #13
But I figured out another way to achieve the same goal. I attached a demo project with the following setup:
1. A parent RecyclerView with PagerSnapHelper:
This vertical RecyclerView has two items/pages.
The upper page is just a simple TextView.
The lower page contains the nested RecyclerView.
Both pages are Fragments if that is of any concern.
2. A PagingLinearLayoutManager:
This is a LinearLayoutManager with the capability to enable/disable scrolling.
It is used for the parent RecyclerView.
3. A OnItemTouchListener:
This is also used for the parent RecyclerView.
It saved the initial y coordinate on ACTION_DOWN
During ACTION_MOVE event, it decides whether the PagingLinearLayoutManager is allowed to scroll (i.e. to switch between pages)
The important parts of the code are commented. Hope this helps someone else!
ko...@gmail.com <ko...@gmail.com> #14
re...@infinum.com <re...@infinum.com> #16
We're working on a solution that'll work reliably with ViewPager2 1.0. See
Not finalized at the time of writing, but might help some people already. Notice NestedScrollableHostLayout wrapping [1] a scrollable child.
[1]
ko...@gmail.com <ko...@gmail.com> #17
Branch: androidx-master-dev
commit 1316417bc87741ea9f739a453eb9e792512c9651
Author: Jelle Fresen <jellefresen@google.com>
Date: Tue Oct 01 16:58:57 2019
Add sample of nested scrolling in ViewPager2
This shows how nested scrolling can be achieved in ViewPager2. Every
child on a page that scrolls in the same direction as ViewPager2 must be
wrapped by a NestedScrollableHostLayout, and one must make sure that the
scrollable child implements canScrollHorizontally/Vertically in order
for this to work.
Bug: 123006042
Test: Manual, run ViewPager2 demo app
Change-Id: I79e24b7668cf7ff5c05c350ea3ab9134dd74b3a4
M viewpager2/integration-tests/testapp/src/main/AndroidManifest.xml
M viewpager2/integration-tests/testapp/src/main/java/androidx/viewpager2/integration/testapp/BrowseActivity.kt
A viewpager2/integration-tests/testapp/src/main/java/androidx/viewpager2/integration/testapp/NestedScrollableHost.kt
A viewpager2/integration-tests/testapp/src/main/java/androidx/viewpager2/integration/testapp/ParallelNestedScrollingActivity.kt
A viewpager2/integration-tests/testapp/src/main/res/layout/item_nested_recyclerviews.xml
M viewpager2/integration-tests/testapp/src/main/res/values/colors.xml
M viewpager2/integration-tests/testapp/src/main/res/values/strings.xml
am...@gmail.com <am...@gmail.com> #18
Notice how the scrollable children of ViewPager2 are wrapped in NestedScrollableHost -- optional for orthogonal elements, required for same-orientation elements.
[Deleted User] <[Deleted User]> #19
ar...@gmail.com <ar...@gmail.com> #20
We're keeping this bug open to track effort in the area.
si...@gmail.com <si...@gmail.com> #21
It worked just as I would expect it to work. A fling inside of the host flinged the recyclerview to the end. A second fling in the same direction changed the page in the viewpager. I had to lift my finger during a scroll all the way to the end of the recyclerview to get the viewpager to page. Great results.
il...@google.com <il...@google.com>
ke...@map72.com <ke...@map72.com> #22
ca...@gmail.com <ca...@gmail.com> #23
To clarify, does the scrolling functionality work correctly with NestedScrollableHost (both: with 'pre-tap' and directly)?
ke...@gmail.com <ke...@gmail.com> #24
[Deleted User] <[Deleted User]> #25
To elaborate a little more, I initially added NestedScrollableHost after #21 reported that it solved the same problem we have. However, through elimination I discovered that the inclusion of NestedScrollableHost didn't seem to make a difference but the 'pre-tap' before attempting to scroll did.
ng...@gmail.com <ng...@gmail.com> #26
ViewPager2 (horizontal) > NestedScrollView (vertical) > ConstraintLayout > RecyclerView (horizontal)
ViewPager2 1.0.0
Core (NestedScrollView) 1.1.0
RecyclerView 1.1.0
ta...@gmail.com <ta...@gmail.com> #27
In the meantime, we still recommend NestedScrollableHost as per
mo...@gmail.com <mo...@gmail.com> #28
ca...@gmail.com <ca...@gmail.com> #29
Possibly you could override SeekBar's onInterceptTouchEvent similar to
ke...@map72.com <ke...@map72.com> #30
The good thing is it also allows my nested RV to recycle properly, compared to wrapping it in a NestedScrollView. Saved my day, thanks.
il...@google.com <il...@google.com> #31
is...@gmail.com <is...@gmail.com> #32
am...@gmail.com <am...@gmail.com> #33
Only the vertical scrolling of the map works, the horizontal scroll doesn't.
Any lead would be appreciated. Thanks.
li...@booking.com <li...@booking.com> #34
Re NestedScrollableHost
directly.
If that doesn't work, check out what it does and follow a similar pattern, the key is wrapping an element in a ViewGroup (e.g. FrameLayout
) and calling requestDisallowInterceptTouchEvent
when appropriate. In case it requires a custom solution like this, share it here so others can benefit.
vi...@gmail.com <vi...@gmail.com> #35
b9...@gmail.com <b9...@gmail.com> #36
Before recommending migration from ViewPager documentation must declare features removed - like nested scroll, scroll interpolators, ability to use Adapter not bound to RecyclerView. It's total pita to restore current functionality provided by ViewPager by default :(
[Deleted User] <[Deleted User]> #37
I tried the NestedScrollableHost solution but the problem is, if I use a wrapper on this ConstraintLayout, the code will break since the View that is defined as a BottomSheetBehaviour has to be a direct child of CoordinatorLayout (Which is the root view for the ViewPager).
ra...@gmail.com <ra...@gmail.com> #38
I was unable to get horizontal scrolls with NestedScrollableHost
to work when I had ViewPager2 (H) > ViewPager2 (V) > ViewPager2 (H)
because it would only check to see if the immediate ancestor ViewPager2
would scroll in the same direction. I made a modification to solve this issue, which checks all ancestor ViewPager2
s.
package live.spin.ui.main
/*
* Copyright 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import android.content.Context
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.View
import android.view.ViewConfiguration
import android.widget.FrameLayout
import androidx.viewpager2.widget.ViewPager2
import androidx.viewpager2.widget.ViewPager2.ORIENTATION_HORIZONTAL
import kotlin.math.absoluteValue
import kotlin.math.sign
/**
* Based on https://github.com/android/views-widgets-samples/blob/master/ViewPager2/app/src/main/java/androidx/viewpager2/integration/testapp/NestedScrollableHost.kt
* but modified by Seth Westphal (westy92@gmail.com) to support nested ViewPager2.
*/
/**
* Layout to wrap a scrollable component inside a ViewPager2. Provided as a solution to the problem
* where pages of ViewPager2 have nested scrollable elements that scroll in the same direction as
* ViewPager2. The scrollable element needs to be the immediate and only child of this host layout.
*
* This solution has limitations when using multiple levels of nested scrollable elements
* (e.g. a horizontal RecyclerView in a vertical RecyclerView in a horizontal ViewPager2).
*/
class NestedScrollableHost : FrameLayout {
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
private var touchSlop = 0
private var initialX = 0f
private var initialY = 0f
private val ancestorViewPagers: List<ViewPager2>
get() {
val ancestors = mutableListOf<ViewPager2>()
var v: View? = parent as? View
while (v != null) {
if (v is ViewPager2) ancestors.add(v)
v = v.parent as? View
}
return ancestors
}
private val child: View? get() = if (childCount > 0) getChildAt(0) else null
init {
touchSlop = ViewConfiguration.get(context).scaledTouchSlop
}
private fun canChildScroll(orientation: Int, delta: Float): Boolean {
val direction = -delta.sign.toInt()
return when (orientation) {
0 -> child?.canScrollHorizontally(direction) ?: false
1 -> child?.canScrollVertically(direction) ?: false
else -> throw IllegalArgumentException()
}
}
override fun onInterceptTouchEvent(e: MotionEvent): Boolean {
handleInterceptTouchEvent(e)
return super.onInterceptTouchEvent(e)
}
private fun handleInterceptTouchEvent(e: MotionEvent) {
for (ancestor in ancestorViewPagers) {
val orientation = ancestor.orientation
// Early return if child can't scroll in same direction as parent
if (!canChildScroll(orientation, -1f) && !canChildScroll(orientation, 1f)) {
continue
}
if (e.action == MotionEvent.ACTION_DOWN) {
initialX = e.x
initialY = e.y
parent.requestDisallowInterceptTouchEvent(true)
} else if (e.action == MotionEvent.ACTION_MOVE) {
val dx = e.x - initialX
val dy = e.y - initialY
val isVpHorizontal = orientation == ORIENTATION_HORIZONTAL
// assuming ViewPager2 touch-slop is 2x touch-slop of child
val scaledDx = dx.absoluteValue * if (isVpHorizontal) .5f else 1f
val scaledDy = dy.absoluteValue * if (isVpHorizontal) 1f else .5f
if (scaledDx > touchSlop || scaledDy > touchSlop) {
if (isVpHorizontal == (scaledDy > scaledDx)) {
// Gesture is perpendicular, allow all parents to intercept
parent.requestDisallowInterceptTouchEvent(false)
} else {
// Gesture is parallel, query child if movement in that direction is possible
if (canChildScroll(orientation, if (isVpHorizontal) dx else dy)) {
// Child can scroll, disallow all parents to intercept
parent.requestDisallowInterceptTouchEvent(true)
} else {
// Child cannot scroll, allow all parents to intercept
parent.requestDisallowInterceptTouchEvent(false)
}
}
}
}
}
}
}
mo...@tikalk.com <mo...@tikalk.com> #39
ka...@gmail.com <ka...@gmail.com> #40
Yes, I'd be happy to! Feel free to email me information on where to start that process.
al...@gmail.com <al...@gmail.com> #41
te...@gmail.com <te...@gmail.com> #42
mo...@gmail.com <mo...@gmail.com> #43
Unfortunately, the workaround #18 dose not suitable for the case that a ScrollerView or NestedScrollView that has the same orientation in a ViewPager2.
Any other solution will be appreciated.
he...@gmail.com <he...@gmail.com> #44
Re #43. Finally I found a workaround after some modifications.
It also suitable for the following case: ViewPager2(V) -> NestedScrollView(V) -> RecyclerView(H) (See
Just simply intercept events if child view do not need to scroll.
Also, don't change the touch slop since it seems that the ScrollView will consume the event which result in we can't receive enough move distance.
as...@gmail.com <as...@gmail.com> #45
ca...@gmail.com <ca...@gmail.com> #46
ka...@gmail.com <ka...@gmail.com> #47
ca...@gmail.com <ca...@gmail.com> #48
When sliding quickly, NestedScrollableHost.onInterceptTouchEvent will not be called, nested scrollable view cannot scroll to the edge.
If ViewPager2 is nested with an imageview that supports zooming/panning, and then quickly swipe after zooming in, the displayed image is only the width of the screen.
ca...@gmail.com <ca...@gmail.com> #49
Hi
vi...@gmail.com <vi...@gmail.com> #50
ri...@gmail.com <ri...@gmail.com> #51
ap...@google.com <ap...@google.com> #52
ap...@google.com <ap...@google.com> #53
ap...@google.com <ap...@google.com> #54
ia...@gmail.com <ia...@gmail.com> #56
I have observed sometime the page is changed randomly also, while we are in scrolling recyclerview and suddenly the page is changed.
Any update on this issue? It's leading to some major app functioning issues
[Deleted User] <[Deleted User]> #57
The attached example works like a charm - and will have to do until Google solves this at a lower level...
mo...@gmail.com <mo...@gmail.com> #58
any update on this?
il...@google.com <il...@google.com> #59
As many have written here, NestedScrollableHost workaround doesn't work for fast scrolling (even on the newest version 1.1.0). This would annoy users, so I went back to ViewPager.
Why fix obvious flaws when you can work on AI, AI, AI...
[Deleted User] <[Deleted User]> #60
il...@google.com <il...@google.com> #61
re...@lunabee.com <re...@lunabee.com> #62
Re #57
You could theoretically instead "previousBackStackEntry" use "getBackStackEntry(int destinationId)" and get its' savedStateHandle.. But it would be a very bad practice to hardcode a fragment to return it's result 2 steps back..
For your use case it's much more appropriate to use navGraphScoped ViewModel which would be common and shared among all A, B, C:https://developer.android.com/reference/kotlin/androidx/navigation/package-summary#navgraphviewmodels .
Or just pass it back through B...
What if I want to reuse fragments B&C in another nested graph so I don't want to hardcode the nav graph id in navGraphScoped? The only solution I found is to pass the graph id trough fragment args.
il...@google.com <il...@google.com> #63
Re #62 - that's certainly a valid option if you want to deliver results to a specific destination on your back stack. previousBackStackEntry
is just a helper for the most common case, nothing more.
[Deleted User] <[Deleted User]> #64
il...@google.com <il...@google.com> #66
Re #65 - yes, you can use the new Fragment Result APIs between Fragments when using the Navigation Component. Note that much of the
ja...@gmail.com <ja...@gmail.com> #67
hi. i found some issues with this
1) in code example..you have 'findNavController()' inside fragment 'onCreate'. When you rotate device app crash due to 'findNavController()' call. I found that when i rotate device then fragment (onCreate) is 'called' first and after that is called 'activity' (onCreate). I think that this is reason why navController is not available and throws this exception: 'Caused by: java.lang.IllegalStateException: NavController is not available before onCreate()'.
2) when i put code inside 'onViewCreated' then 'result' from dialog fragment is correctly propagated into 'previous fragment' but when i rotate device when dialog is shown and then i set 'result' and dialog is closed, no 'result' is received on 'previous fragment'. I guess its because
navBackStackEntry.lifecycle.addObserver(LifecycleEventObserver { _, event ->
if (event == Lifecycle.Event.ON_RESUME) // This event is never 'ON_RESUME' when fragment was recreated in time when dialog was shown
I also tried 'findNavController().currentBackStackEntry?.savedStateHandle?.getLiveData<String>("key")?.observe(viewLifecycleOwner) { //action }' but when device is rotated when 'dialog fragment' is shown, then this live data is not called (otherwise its called fine)
Please can you check this and show us some example how to handle 'dialog fragment' results in previous fragment even when you rotate device when dialog is shown? You can reproduce this issues very easily.
Just small suggested improvement 'navController.currentBackStackEntry!!' -> navController.requireCurrentBackStackEntry()
il...@google.com <il...@google.com> #68
Re #67 - please see
b9...@gmail.com <b9...@gmail.com> #69
#68 Is that link require googler account?
il...@google.com <il...@google.com> #70
il...@google.com <il...@google.com> #71
Well, apparently I can't paste links correctly today. It's
br...@gmail.com <br...@gmail.com> #72
lb...@gmail.com <lb...@gmail.com> #73
We have registerForActivityResult for Activity which is much nicer.
Why can't we have something like that?
Even the startActivityForResult is easier and shorter to remember than this...
And it's not even clear on
Description
screen that then returns the result to its previous screen).
Is that something planned? How is it expected to create flows like this with current version?