Coordinator layout scrolling with unwanted parallax - android

So we have a 3 part app structure with 2 toolbars: 1 that is significant and should be always visible and 1 that is insignificant and should only be visible when user has scrolled to the top of page .the third part is a complex structure with nested view group, swipe to refresh, recycler view and some extra views . Note that we are not using the Collapsible toolbar or any other toolbar, but rather custom viewgroups.
I tried to achieve the behavior of toolbars with my current xml structure like this (notice the layout behavior flags) (PS: i cannot share the exact code,but would provide more details if needed)
<Coordinator layout>
<include layout=“xyz”> <— <appbarlayout>
<linearlayout :layout_behavior:scroll|enterAlways …/>
<linearlayout :layout_behavior:noscroll …/>
</appbarlayout>
<swipe refresh layout : layout_behavior="....AppBarLayout$ScrollingViewBehavior”>
<Nested ScrollView : mp/mp , fillviewport :true>
<constraint layout : mp/mp>
<RecyclerView : nested scrollin enabled:false (via java code)>
<View>
</constraint layout>
</nested scrollview>
</swipe refresh layout>,
</Coordinator layout>
<!-- mp = match_parent -->
the result looks something like this( gif / video ) (note the gif/video has some delay, please wait 7-8 seconds to see the action ) .
As you can see, the upper toolbar gets hidden when scrolled very hardly, but when scrolled slowly, the upper bar does not hide.
What can i do to fix this? i have tried changing the behavior flags as well as setting the heights as wrap content. I am guessing it is either due to wrong flags or due to complexity in bottom layout

Related

BottomSheet: issue with nested scrolling (RecyclerView & NestedScrollView)

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!

Collapse Layout inside ContraintLayout based on view focus or scroll

I am looking into CoordinatorLayout and ConstraintLayout and I want to know if it's possible to achieve something as in :
As you can see my layout, has:
the toolbar which is not affected by this. The toolbar is on the main activity and it's not changed.
under the toolbar there is a fragment loaded with its layout. The layout contains a ImageView at the top, some EditTexts and a RecyclerView
Behavior:
When user taps on the red EditText I want the layout to scroll up, so that the focused EditText is at the top of the screen with the RecyclerView under it.
At any time the user can scroll down and the initial layout gets shown.
My question is: what would be the best way to create this animation and behavior?
I managed to obtain the desired behavior by using in the layout:
<CoordinatorLayout>
<AppBarLayout>
<CollapsingToolbarLayout
app:layout_scrollFlags="scroll|exitUntilCollapsed|snap">
<My layout that will get scrolled to the top and be hidden>
</CollapsingToolbarLayout>
<RedEditText which will scroll up until the CollapsingToolbar is collapsed>
</AppBarLayout>
<RecyclerView/>
</CoordinatorLayout>

CardView with RecyclerView/ListView in ScrollView

tl;dr: How to achieve the layout shown in the screenshot below? Placing ListView to a ScrollView is apparently not recommended, but is there actually any other way to achieve it?
The whole question: I want to have multiple CardViews in my app, and one (or more) of them will have either RecyclerView or ListView in it (it doesn't really matter to me which one of those). The whole view is supposed to be scrollable - not only the ListViews in their parent CardViews. I basically need to achieve similar layout as the Play Store app has.
The first option I tried was this (the code is obviously simplified):
<LinearLayout android:orientation="vertical">
<CardView>
<!-- Some content of the first card. -->
</CardView>
<CardView>
<ListView/>
</CardView>
</LinearLayout>
The result was not what I wanted, the ListView was only scrollable in its parent CardView but the whole view wasn't scrollable like it is in the Play Store app. So now I wrapped it all in a ScrollView:
<ScrollView
android:fillViewport="true"
android:isScrollContainer="true">
<LinearLayout orientation="vertical">
<CardView>
<!-- Some content of the first card. -->
</CardView>
<CardView>
<ListView/>
</CardView>
</LinearLayout>
</ScrollView>
And I programmatically set the height of the bottom card to fit the ListView's height (number of elements in the ListView * height of one list item element). Now the whole view is scrollable, and the bottom card's height is the same as the height of the ListView, so the ListView isn't scrollable inside the CardView which is exactly what I wanted.
Now the actual problem: I got it working as described above, but I know this particular issue (ListView in a ScrollView) has been asked about many times before and the answer has always been the same - don't put neither RecyclerView nor ListView in a ScrollView because it causes performance problems. Well, so what's the correct approach then? How did Google do it in the Play Store app? I tried decompiling the Play Store app with APKTool but there weren't any layout files (maybe I did something wrong). Is my approach correct? My ListView will only display a few items (I guess it will be at most 20 items) - will it cause some performance issues in this case?
I wouldn't ask about this if all the answers wouldn't always mention that we shouldn't put ListView in a ScrollView. Is there any other way how to achieve the layout described by the screenshot above?
The first thing to address is why you're "not supposed to" use wrap_content on a ListView or a RecyclerView and put it in a scrollable container: it defeats the entire view-recycling purpose of these components.
What makes a ListView or RecyclerView better than a LinearLayout inside a ScrollView is that the system only needs to create enough views to display everying that fits inside the visible area. When you "scroll" the visible area, the views that disappear off one end can be re-used for the views that scroll into view from the other end. When you make your list/recycler wrap_content, this recycling is impossible, so you might as well just manually add your views to a LinearLayout instead.
That being said, RecyclerView does support using wrap_content... it just means you won't get view recycling. If this performance hit doesn't cause you problems, there's no objectively evil code here.
The only way to know for sure if the performance penalty is problematic or not is to just try it, test it, measure it, and decide for yourself. With 20 items, I suspect you have nothing to worry about.
The next thing to think about is the fact that Google has tons of resources and manpower and can afford to be extremely clever. Perhaps the Play Store app is as you say, with some sort of scrollable parent container that holds cards, each of which have some sort of adapter view within. But it's equally possible that they're doing something completely different, like using a single RecyclerView and "faking" the appearance of cards by using an ItemDecoration. Or perhaps they are using some sort of custom view subclass that the public doesn't have access to.
As for how you could recreate something similar, I suspect a hierarchy like this will work just fine:
<NestedScrollView>
<LinearLayout>
<CardView>
<RecyclerView/>
</CardView>
<CardView>
<RecyclerView/>
</CardView>
<CardView>
<RecyclerView/>
</CardView>
</LinearLayout>
</NestedScrollView>
I would recommend you to use Sectioned RecyclerView for this purpose. Every single item layout would have a cardView in it instead of creating a cardView as a parent.
Refer to this library: https://github.com/luizgrp/SectionedRecyclerViewAdapter

Android Layout :- NestedScrollView + RecyclerView ----- Scrollable

In my application I have a three textview which I want to be scroll-able and then below that I want to place a recyclerView. Then after I want that I can scroll these three textview separately and when scrolling the recyclerView the whole layout scrolls so that the textview are scrolled up and recyclerView items gets displayed. I have tried a variour options but failed.
One way through which I am able to achieve this is:-
<ScrollView
<RelativeLayout
NestedScrollView
TextView1
TextView2
TextView3
</NestedScrollView>
<RecyclerView
</RecyclerView>
</RelativeLayout>
</ScrollView>
But while using this layout if recyclerView has less items (say 50-60 ) then it works well otherwise the application hangs and have to force close application.
After so many different tries I think when I place recyclerView inside ScrollView (or NestedScrollView) then such behavior occurs.
Please help me with layout setup

Where should 'app:layout_behavior' be set?

Should it be set at the AppBarLayout sibling's parent or at the first Scrollable View inside its sibling?
With Material Design for Android, there are Views that let us work with the behavior of the layout depending on its surroundings, one of them is the CoordinatorLayout, as this CodePath guide mentions:
CoordinatorLayout extends the ability to accomplish many of the
Google's Material Design scrolling effects. Currently, there are
several ways provided in this framework that allow it to work without
needing to write your own custom animation code.
The one I'm interested in now is:
Expanding or contracting the Toolbar or header space to make room for the main content.
So, we would use the AppBarLayout with a Toolbar with app:layout_scrollFlags set and another ViewGroup sibling to the AppBarLayout with app:layout_behavior.
My question is: in what exact ViewGroup (or maybe View) should we put that
app:layout_behavior?
So far, I've tried with (And they have all worked, and they are all siblings to the AppBarLayout):
Scrolling View
First ViewGroup inside a Scrollable View
ScrollView inside a ViewGroup
And this one didn't work:
ViewGroup with no Scrollable View children.
There are multiple examples online, but none of them really state where should you put it, like:
http://www.ingloriousmind.com/blog/quick-look-on-the-coordinatorlayout/
https://guides.codepath.com/android/Handling-Scrolls-with-CoordinatorLayout
https://developer.android.com/training/basics/firstapp/building-ui.html
https://www.bignerdranch.com/blog/becoming-material-with-android-design-support-library/
Check this link: https://developer.android.com/reference/android/support/design/widget/AppBarLayout.html
AppBarLayout also requires a separate scrolling sibling in order to
know when to scroll. The binding is done through the
AppBarLayout.ScrollingViewBehavior class, meaning that you
should set your scrolling view's behavior to be an instance of AppBarLayout.ScrollingViewBehavior. A string resource containing the
full class name is available.
They mentioned about that, it should be the View which will be shown under the AppBarLayout like this:
<android.support.design.widget.CoordinatorLayout
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">
<android.support.v4.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="#string/appbar_scrolling_view_behavior">
<!-- Your scrolling content -->
</android.support.v4.widget.NestedScrollView>
<android.support.design.widget.AppBarLayout
android:layout_height="wrap_content"
android:layout_width="match_parent">
<android.support.v7.widget.Toolbar
...
app:layout_scrollFlags="scroll|enterAlways"/>
<android.support.design.widget.TabLayout
...
app:layout_scrollFlags="scroll|enterAlways"/>
</android.support.design.widget.AppBarLayout>
</android.support.design.widget.CoordinatorLayout>
My question is: in what exact ViewGroup (or maybe View) should we put
that app:layout_behavior?
And in this link: http://guides.codepath.com/android/Handling-Scrolls-with-CoordinatorLayout
Next, we need to define an association between the AppBarLayout and
the View that will be scrolled. Add an app:layout_behavior to a
RecyclerView or any other View capable of nested scrolling such as
NestedScrollView. The support library contains a special string
resource #string/appbar_scrolling_view_behavior that maps to
AppBarLayout.ScrollingViewBehavior, which is used to notify the
AppBarLayout when scroll events occur on this particular view. The
behavior must be established on the view that triggers the event.
Make sure you added the appbar_scrolling_view_behavior field in your String.xml
<!-- The class name to the ScrollingChildBehavior required for AppBarLayout -->
<string name="appbar_scrolling_view_behavior" translatable="false">android.support.design.widget.AppBarLayout$ScrollingViewBehavior</string>
And as everyone knows we just can use this like below
<android.support.v7.widget.RecyclerView
android:id="#+id/rvSomeList"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="#string/appbar_scrolling_view_behavior" />
Its just for info not OP answer.
app:layout_behavior should be set to those views which are direct child of Coordinator layout
AppBarLayout also requires a separate scrolling sibling in order to
know when to scroll.
This description from Android is woefully incomplete and caused me hours of wasted time.
Scrolling sibling is a misnomer and need not be a scrolling view of any type.
For example, below my AppBarLayout, I'm using a ViewPager2 that will render a Fragment that will render a Scrollview, so I needed to set app:layout_behavior="#string/appbar_scrolling_view_behavior" directly on the ViewPager2 in the main layout, NOT the deeply nested Scrollview in the fragment layout.
I also have no use for scrolling the AppBarLayout or any of its children on or off the screen, so I falsely assumed I could get away with not setting the app:layout_behavior anywhere.
Wrong.
This reveals a more insidious issue: AppBarLayout requires the scrolling sibling, yes. But not just to "know when to scroll", but to actually adjust the size of the sibling to fit properly on screen alongside it! Otherwise, the sibling maintains its configured size and will be nudged downward offscreen by the height of the AppBarLayout! You can even see this in Android Studio's layout editor.
Long story short: If you're going to use an AppBarLayout, you need to mark one of your views with app:layout_behavior="#string/appbar_scrolling_view_behavior", whether it's a scroll view or not.
I had to add the following to the gradle file otherwise it gave me a compile error.
implementation 'com.google.android.material:material:1.0.0'
Hope this would help some others too!
For someone who uses CoordinatorLayout with FragmentContainer and AppBarLayout:
It is really good to set the app:layout_behavior also on the container (not just on NestedScrollView or RecyclerView). It deletes unnecessary bottom margin of the FragmentContainer and guarantees that the appbar hides when the keyboard is shown.

Categories

Resources