ListView nested scrolling on API<21 - android

Title is clear. I'm having this layout:
_________________
|_______________| <- Toolbar
|___|___|___|___| <- Tablayout
| |
| |
| ViewPager |
| |
|_______________|
Both toolbar and tablayout are inside an AppBarLayout, so I can use scroll flags to hide the toolbar on scrolling toward the top. The problem is that this only works with nested-scrolling-supported views. Most of the tabs - I mean, most of the pages - are support.v4.NestedScrollViews, so that is OK; others are (and need to be) ListViews.
From Lollipop on, I can simply add android:nestedScrollingEnabled="true" to the list view, and the toolbar hides correctly on scroll.
On API<21, though, there's no such attribute and the toolbar doesn't hide. Even more important, the very last items in the list are hidden, because of some measuring bug in CoordinatorLayout: listview acts as if it had the space currently occupied by the toolbar.
Solutions:
Switch to RecyclerView, which does support nested scrolling: I can't, because I need to use an external-library adapter that works only with adapter views and that I can't replace (namely, ParseQueryAdapter);
Extend ListView and implement nested scrolling: seems way to complicated;
Extend ListView and implement some workaround, like measuring stuff to avoid the last-item issue or (and) a custom behavior to make the toolbar hide: seems complicated too;
Use some layout trick: found none.
Any help?
For example, I (desperately) tried:
<android.support.v4.widget.NestedScrollView
android:nestedScrollingEnabled="true"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ListView
android:id="#+id/list"
android:layout_width="match_parent"
android:layout_height="match_parent">
</ListView>
</android.support.v4.widget.NestedScrollView>
But this way the ListView is not laid out as match_parent. I get a little view with small height, and the rest of the page is empty.

Unfortunately, there is no way to get nested scrolling working on ListView - otherwise it wouldn't require the modifications that were done in API 21.
You'll note that the current Parse SDK has actually removed ParseQueryAdapter entirely. Given that, it may make sense to start building your own RecyclerView based adapter using the Parse query APIs directly.

For those intersted in the specific ParseQueryAdapter issue,
Guys at parse.com are working on a RecyclerView.Adapter version;
There's a beta version of it.

Related

Having RecyclerView inside a NestedScrollView calls onBindView for all the items

I have two RecyclerViews placed vertically in a LinearLayout. I need to make both of them scrollable and that is why I have put the LinearLayout inside NestedScrollView
This is the my layout file.
<android.support.v4.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true"
android:scrollbars="none">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<android.support.v7.widget.RecyclerView
android:id="#+id/featured_list"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<android.support.v7.widget.RecyclerView
android:id="#+id/all_topic_list"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
Also, I am disabling nested scrolling in Java code.
disableNestedScrolling(findViewById(R.id.all_topic_list));
disableNestedScrolling(findViewById(R.id.featured_list));
My RecylerView library version is 26.1.0
This works fine perfectly, but then onBindViewHolder method is getting called for all the items in the list. Ideally it should only be called for the visible items in the list.
I think the issue is happening because I am giving wrap_content to the RecyclerView. A lot of answers on this question suggest that the issue is solved in v23.2.1, but I am already using v26.1.0. How to solve this issue?
I had exactly the same problem. RecyclerViews are not meant to be placed inside scroll containers with the same scroll direction. The view recycling only works when the height is set to MATCH_PARENT.
Depending on the complexity of the content inside of the NestedScrollView and the anticipated amount of RecyclerView items:
Ignore the problem. If there are only a few simple items, you may
not need view recycling at all.
When I hit the problem, I analysed the layouts of other popular apps: For example, WhatsApp only uses RecyclerViews (or ListViews with view recycling) in some parts of their app.
Particularly, this group settings screen with hundreds of possible items is made of multiple ListViews wrapped by a ScrollView, without any view recycling.
Replace the NestedScrollView with a single
ReyclerView with multiple item types and put all of your scrollable content inside of it. This is the way to go if you need view recycling.
Beware that you also have to convert all the other content in the NestedScrollView (headers and footers, spacing) to RecyclerView items with their own ViewHolders.
If the setup is rather simple, I would recommend you to implement it without additional libraries, following the link above.
There are a few different libraries available to solve your problem (all of them follow the second approach with a single RecyclerView), but most come with a lot of extra features which you may not need:
RendererRecyclerViewAdapter
It comes with a ViewRenderer/ViewModel interface, which works like a
"partial" RecyclerView for a single item type. You would create one
for every item type and then register them in a single adapter.
Epoxy
A library/framework create by airbnb and used heavily in their app.
They have a lot of scrollable content (similar to a web page) with a
lot of different item types. Epoxy also helps with the composition of
the different items on a page, and it handles animations when the
content or its order changes. Too much if you only need it for a single screen.
Litho
A complete UI framework created by Facebook which comes with it's own rendering engine, a replacement for xml layouts and much more. As far as I understand, it allows you to do to handle large amounts of items (like the Facebook timeline) and the view recycling is handled automatically. Like Epoxy, you would only use this if your app includes things like endless scrolling with a lot of different item types and you really need the performance.
I tried Epoxy and RendererRecyclerViewAdapter, but after all I created my own multiple item type adapter. It can be created in less than 100 lines of code.
Starting from RecyclerView:1.2.0-alpha04 we can use ConcatAdapter to solve this problem
https://developer.android.com/reference/androidx/recyclerview/widget/ConcatAdapter
I tried your problem by adding 20 items in each recyclerview, with NestedScrollView application called onBindViewHolder method 40 times. As you disabling nested scrolling in Java code i suggest to use Scrollview. By using ScrollView application called onBindViewHolder 33 times.
If you fix your recyclerView's height to specific size instead of "match-parent" it will reduce call to onBindViewHolder greatly.
<ScrollView 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:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="false">
<android.support.v7.widget.LinearLayoutCompat
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.example.vishal.my2.MainActivity">
<android.support.v7.widget.RecyclerView
android:id="#+id/featured_list"
android:layout_width="match_parent"
android:layout_height="300dp" />
<android.support.v7.widget.RecyclerView
android:id="#+id/all_topic_list"
android:layout_width="match_parent"
android:layout_height="300dp" />
</android.support.v7.widget.LinearLayoutCompat>
</ScrollView>
If Specifying hardcoded value to recyclerView's height does not meet your application requirement then you can try using ListView instead of recyclerView. pardon me if i am wrong, This was my first time answering any question.
Add this to nested scroll view android:fillViewport="false"

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

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.

SwipeRefreshLayout - Pull From Bottom

Is there a way to use SwipeRefreshLayout to refresh ListView when it's pulled from bottom?
I created pull from top, but I would also need pull from bottom. There are many tutorials on how to create pull from top, but I couldn't find any tutorials for pull from bottom?
SwipeRefreshLayout from Android Support Library version 21 does not support pull from bottom.
I have made modification SwipeRefreshLayoutBottom with is based on original SwipeRefreshLayout code.
It is fully based on original Google code with just inversion of coordinates and overridden implementation of canChildScrollDown method. All modification are marked as TODO.
Bitbucker repository
Use this great library:
OrangeGangsters SwipyRefreshLayout
So you can swipe both from top and bottom and supports API 9+.
Library omadahealth/SwipyRefreshLayout must be the answer for your case.
Below Codes make your Recycler view pull up from bottom to refresh :
<com.omadahealth.github.swipyrefreshlayout.library.SwipyRefreshLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/refresh_layout"
style="#style/View_MatchParent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
app:srl_direction="bottom"
>
<android.support.v7.widget.RecyclerView
android:id="#+id/recyclerview"
style="#style/View_MatchParent"
android:layout_height="wrap_content"
android:clipToPadding="true"
app:layout_behavior="#string/appbar_scrolling_view_behavior"/>
</com.omadahealth.github.swipyrefreshlayout.library.SwipyRefreshLayout>
I've encountered the same problem and I have solved it with a combination of swipeRefreshLayout and a touch event in my list. Here is the link:
https://stackoverflow.com/a/41701320/6144027
No, you can't do that with SwipeRefreshLayout. You need to implement your own layout, which is not that hard.
Check this
http://erikw.eu/open-source-android-pull-to-refresh-library/
and this
http://www.oodlestechnologies.com/blogs/Implementing-Pull-to-refresh-(like-in-Facebook-mobile-app)-for-ANDROID-using-Titanium

Sizing a ListView when it's a nested Fragment

I want a ListView to fill the space available to it while still leaving room for a small footer view at the bottom of the screen. I'm trying to use a RelativeLayout to accomplish this and attempted to use the solution discussed at Limit number of rows of listview . The problem I'm running into is I'm using nested Fragments, so my ListView is actually a FrameLayout in my xml then I load a ListFragment into that frame dynamically. Given the nested fragment stipulation, how can I get my FrameLayout to "stackFromBottom" as I would with a ListView? I just need to stop the list from pushing the other View off the bottom of the screen. Thanks for your time all.
Here is the solution I came up with:
<TextView
android:id="#+id/advertisement"
android:layout_width="match_parent"
android:layout_height="30dp"
android:text="Ads will appear here"
android:gravity="center"
android:layout_alignParentBottom="true"/>
<FrameLayout
android:id="#+id/news_frag"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="#id/carousel_menu"
android:layout_above="#id/advertisement"/>
The trick was to set both layout_above AND layout_below for the FrameLayout, I had only been setting one and that was apparently allowing the layout to push it off of the screen. Also worth noting is they had to be declared in reverse order of how they actually appear on the page, so that the FrameLayout could properly reference the other View.

Categories

Resources