Background
I have created a Composable that contains a TopAppBar and a WebView which recreates the enterAlways collapsing-toolbar behavior that Xml has.
The way it works is by applying Modifier.offset {..} to the composables. The offset is calculated whenever the WebView is scrolled.
For example, if I'm at the top of the page, and I scroll towards the bottom of the page (swipe from bottom to top), the Y offset for both the WebView & TopAppBar decreases at the same time.
Problem
The problem is that it is very jittery whenever the TopAppBar is expanding or collapsing. I think this is because the offset for the WebView is changing while I'm still scrolling. In the previous example, the WebView moves up to take the place of the collapsing-toolbar, but my finger is still pressed on the screen while that happens, which may cause the device to interpret that as an upward scroll (swiping from top to bottom).
Basic Outline of Code:
Column {
TopAppBar(
Modifier.offset { IntOffset(0, state.toolbarOffsetHeightPx) },
...
)
WebView(
Modifier.offset { IntOffset(0, state.toolbarOffsetHeightPx) },
onWebPageScrolled = /** Update state.toolbarOffsetHeightPx */
...
)
}
What I tried
One potential solution I thought of involves reading the scrolls (like I currently am) and also consuming them.
When the toolbar is collapsing or expanding, I realized that I should probably only adjust the offset during that time, and I should ensure that the WebView does not scroll its content.
Since the WebView's height is the screen height, when the TopAppBar is collapsing, the part of the WebView which was hidden at the bottom of the screen now comes into view, and to the user, the offset of the WebView changing would make it look like they are scrolling. Therefore, I should force the WebView to not scroll during this time.
--
The problem with this approach is that I'm getting the data about WebView scrolls from the onScrollChangedListener, so if I disable WebView scrolling while the TopAppBar is expanding or collapsing, then I also lose the ability to keep expanding/collapsing the TopAppBar...
Some solutions I came up for this are:
Have a wrapper Composable around the WebView which can receive the scrolls.
I could not figure out how to implement this. It looks like there is a nestedScrolling Modifier which allows capturing scrolls, so I wrapped my WebView in a Box which had that Modifier, but even though I was consuming the scrolls in the NestedScrollConnection the WebView was still receiving scrolls normally..
Use onTouchListener to receive scroll events rather than onScrollListener.
This gets a little complicated because onTouchListener is not only called for scrolls. I tried only running my TopAppBar/WebView offset logic if the onTouchListener was called for MotionEvent.ACTION_DOWN, but the results were very strange.
I did manage to replicate the original expanding/collapsing w/ the TopAppBar & WebView, but rather than expanding/collapsing while I'm scrolling, the entire expanding/collapsing would complete before I even started dragging my finger - just pressing my finger and twisting it up or down completely expanded/collapsed the TopAppBar.
The desired behavior is that the expanding/collapsing should happen linearly with the webpage scrolling, so this did not work.
I really appreciate any help. I've created a simple app on a GitHub repo if you would like to test the behavior.
Related
I need to emulate the behavior of Instagram's Reels which scrolls to the next or previous page when the current page is scrolled more than 50% upwards or downwards respectively and the finger goes up. ViewPager2's default behavior isn't satisfactory for this and I don't think I can change it without changing the PagerSnapHelper inside ViewPager2.
Is there any other way I can implement a custom Snap behavior for ViewPager2 then?
It's 2022, and I'm having the same issue with ViewPager2 that folks had with ViewPager (see NestedScrolling inside a Viewpager inside a BottomSheetDialog ) - 5 years ago.
Although I'm not using a BottomSheetDialogFragment, just a regular old bottom sheet (with a FragmentContainer).
ViewPager2 is a bit different in that it itself uses a horizontal RecyclerView. BottomSheetView.findScollingChild() sees this as the scrolling child!
So, the approach I took to solving this is:
added a page change listener to my ViewPager2 that gets the hosting
CoordinatorLayout, and calls requestLayout() on it after the page change.
copied the BottomSheetBehavior class that matches my current material components version (1.5) into my project, renamed it, and made findScrollingChild public.
subclassed that copy, and set the sub-class as the behavior on my sheet.
Why sub-class and not just change the findScrollingChild method directly? Well, this way it's relatively easy to update the copy of BottomSheetBehavior when we update our material components.
My implementation of findScrollingChild() in the sub-class checks specifically for a ViewPager2.
If it is, it gets child 0 of the ViewPager2 (the horizontal RecyclerView), and then uses recycler?.layoutManager?.findViewByPosition(pager.currentItem) as the view to then search for the scrolling child.
If the view is not a ViewPager2, it uses the same algorithm from the original findScrollingChild()
This basically works. I have a pager with 2 tabs, one containing a ScollView that has nested scrolling enabled, and one containing a RecyclerView. The bottom sheet expands as its scrolled, and then the contents of the nested child scroll down properly once the sheet is open.
The problem is if after the sheet has expanded if the finger gets lifted, then any attempt to scroll up causes the bottom sheet to close rather then scrolling up -- no matter how far it's been scrolled down. At least this is the case for the RecyclerView in the second tab, I don't have enough content in the first tab at the moment that it actually needs to scroll.
The sheet gets closed with the list still scrolled down several pages (or wherever you stopped scrolling). If however you scroll down -- even just a little bit -- scrolling up works again! And you can swipe a couple times and it will work - until it doesn't, and the sheet moves to the half expanded state.
I'm not sure where this behavior is coming from or how to resolve it. It doesn't happen when the bottom sheet has a direct RecyclerView (no ViewPager2 I'm the way).
I tried disabling swiping in the ViewPager2 thinking it might be interfering with touch events, but to no avail.
My pager has one webview inside each fragment. The webviews themselves don't scroll, i.e. their content fit entirely in them.
The problem is the scrolling of the pager doesn't behave as expected. The webview content only follows the scrolling when going through the right edge of the screen (i.e. moving out/in to/from the right). When a webview is going through the left edge though it's content stays still (so that it looks like the next page is sliding over it, like cards).
I tried having other components (TextView, ImageView) along with the WebView on each page. They all slide normally while the WebView content keeps getting stuck on the left.
Is this a known problem? Is there a workaround?
Turns out the issue was CSS related.
I had "position:fixed" on all web content. After changing it to "absolute" the problem was solved.
In my point of view it's still an unexpected behavior, because "fixed" is supposed to position things relative to the browser window. What is expected is that, as the webview is scrolled left, the "browser window" moves past the device screen.
I'm trying to achieve the effect that the Google+ Android app has where there is a View that sits at the bottom of the screen, and when the user scrolls the ListView that sits behind it UP the View animates down, off screen. When the user scrolls the ListView down, even slightly, the View animates back up, on screen.
I've set up a GestureDetector, that is giving me callbacks for the scroll event on my ListView, and the callbacks are constant as I scroll so I know that part is working.
In my callback I'm trying to use the ViewPropertyAnimator to animate my y value as such:
headerView.animate().yBy(distanceY).start();
Nothing happens until I stop scrolling. Is there any way to throw this animation in with the ListView scroll on the UI thread? I get the feeling it's waiting.
I've been fighting this one also. The trick I ended up using was to replace
headerView.animate().yBy(distanceY).start();
with
headerView.setTranslationY(floatValue);
Hope this helps!
I have a ScrollView which has two hidden images, one at the top and one at the bottom. In between there is a bunch of visible content.
What I need to do is make these images hidden by default but when you scroll all the way up or all the way down you could see them as you're scrolling. But then as soon as you stop scrolling it should bounce back to the visible area so that the hidden images aren't showing.
Basically I'm trying to imitate the bounce scrolling feature of the iphone UIScrollView.
I have my ScrollView all setup and I do a scroll at the beginning so as to hide the top hidden image. Now all I need to do is detect when a scrolling has ended, figure out the Y position, and check whether a hidden image is shown. If it is, I would just programmatically scroll the view back so that the hidden image is hidden.
I hope all that made sense.
So anyways, I know how to programmatically scroll a ScrollView. Now what I need is some sort of callback to tell me when a ScrollView ended scrolling and also a way to get the ScrollView's current 'Y' position. Are there any such methods I could use?
I looked through the ScrollView docs but nothing jumped out at me. I'm still not very familiar with the Android naming schemes so maybe I missed something obvious somewhere.
Anyways, any help would be appreciated here. Cheers.
You can use an OnTouchListener to detect when the user presses/releases the list.
You can also use the onScrollStateChanged method of the OnScrollListener class (most likely in conjunction with a touch listener) to detect changes in the SCROLL_STATE - when the list has stopped scrolling the state will change from a state that is not SCROLL_STATE_IDLE to SCROLL_STATE_IDLE.
Alternatively if you are using 2.3 or above you can use an OverScroller to get the desired effect (see Modifying Android OverScroll for how to change the over scroll effect to an iPhone like one).