Is NestedScrollView still useful from API 26 and above? - android

I am currently checking my app for any issues with the new Android 12 overscroll animation. And I came across plenty screens which contain a RecyclerView inside a NestedScrollView. Usually like this:
<androidx.core.widget.NestedScrollView 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="true">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="#+id/constraintLayout_root"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView [...] />
<TextView [...] />
<TextView [...] />
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/recyclerView_attachment_classifications"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="?marginM"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#id/textView_categorize_title"
tools:itemCount="4"
tools:listitem="#layout/list_item_adm_attachment_classification" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.core.widget.NestedScrollView>
This causes some problems with the new overscroll animation. Unfortunately, I cannot show you a recording of the animation problem, but let me describe it: When the screen is at the topmost position and the user is scrolling upwards, all views should stretch a little in relation to the drag event. But it does not do that. It only shows this stretch animation for a very short period of time AFTER the user released their thumb from the screen.
What I have found out so far:
Setting the RecyclerViews isScrollContainer to any value has no impact
Setting the RecyclerViews isNestedScrollingEnabled to any value has no impact
Setting the RecyclerViews overScrollMode to any value has no impact
The same goes for the NestedScrollView
Ironically, replacing the NestedScrollView with a standard ScrollView solves my issue.
I was unable to replicate the problem in a sample app, so it is relatively safe to say that this issue is somewhere in my apps config and architecture. But since using a ScrollView solves my issue, I wanted to know if a NestedScrollView still has any usefulness on API 26 and above or if NestedScrollView is just for backwards compatibility for apps which support older Android versions as well?

Turns out the issues were produced by NestedScrollView and fixed by Google in core-ktx:1.7.0. I had it on version 1.6.0.

Related

Scrollable list trailed by fixed views

This might be a very beginner question, but I'm yet unable to find myself around the android jungle.
I've already got a RecyclerView working to show a list of items (with data binding and Room database and DiffUtil.ItemCallback and all).
I'd like to put 2 links after the list: "missing something?" and "add new entry" that will lead to other fragments.
What I have:
When I put 2 buttons (I don't know yet how to put links, but this is not the point of this question) after the RecyclerView, all in a LinearLayout, they stay fixed near the screen bottom. I mean, the RecyclerView is scrollable by itself, scrolling "beneath" the two buttons, the entire LinearLayout expanding to fill the screen (match_parent).
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.recyclerview.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:gravity="top"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="Missing something?"
android:onClick="#{...}" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="Add new item"
android:onClick="#{...}" />
</LinearLayout>
What I want
I'd like the 2 buttons to scroll along with the list, so that they are always positioned after the last item (think as if they were items themselves, albeit an heterogeneous list with different types/RecyclerView.ViewHolder).
For a big enough list the buttons will be initially off screen; to be scrolled in if the user happen to scroll to the bottom of the list.
What I tried
I tried with ScrollView around the LinearLayout, and it works, but everywhere everybody say that one should never put a RecyclerView inside a ScrollView (maybe because it is scrollable itself).
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent" >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/routines_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="top"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
<!-- buttons -->
</LinearLayout>
</ScrollView>
Being really a beginner in android programming, I'd like to know how usually this kind of layout should be done. Only main directions will be enough for me.
NB. I don't know if I really need a RecyclerView because I don't expect this list to be lengthy. Maybe usually something around 4 to 8 items, possibly 10. But I really don't expect it to be much bigger than that. For many users the two links will even be visible all the time (i.e. no scroll at all).
RecyclerView is always the most efficient to show a list especially if you are getting the data from a database or an API. Don't put your recyclerview in a scrollview. You can add two items to the bottom of the list as your links and program your recyclerview to exhibit different properties for last two items. That is the best way I can think of. Good Luck!
Also, Recyclerview is very difficult to work with when you are working with complex data. With small lists such as in your case, it can seem inconvenient to create a whole adapter class and do everything you are supposed to do. When you have grasped the concepts on xml android and have plenty experience with that. You can move to jetpack compose and lazy column will make your life easy.

Lazy-load views in RecyclerView within NestedScrollView

Targeting androidx 1.0.0 to allow for pre-Lollipop devices (let's say minSdkVersion 17 or so).
I have tried many combinations of views, settings, scrolling modes and layout managers. I have read everything - e.g. this and this and this - regarding this problem. Either I get bad layout/rendering performance or incorrect/buggy scrolling.
Requirements:
A bottomsheet is draggable from the bottom. A common UI pattern in modern apps.
10-20 heavy equally sized child views. These must not be inflated/drawn when invisible.
Using native Android/Google views is preferable.
How can I achieve this?
Here's some pseudo code, showing what I'm trying to accomplish; a RecyclerView (or equivalent) inside a NestedScrollView with a BottomSheetBehavior:
<ScrollView>
<!-- Main content -->
</ScrollView>
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fillViewport="true"
android:fitsSystemWindows="true"
app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior"
app:behavior_hideable="false"
app:behavior_peekHeight="#dimen/bottom_sheet_peek_height">
<!-- RecyclerView? -->
<TextView android:layout_width="match_parent"
android:layout_height="#dimen/bottom_sheet_peek_height"
android:text="Bottom sheet header" />
<-- N heavy equally sized child views here -->
</androidx.core.widget.NestedScrollView>
I have read that RecyclerView outperforms ListView. Still, it seems to never recycle its views given the above configuration.

Recyclerview onBindViewHolder called for all items when inside LinearLayout with weights

I recently updated from support library recyclerview-v7:23.1.1 to recyclerview-v7:25.1.0.
My Layout contains 2 recylerviews splitted 50% on the screen. The xml code is as follows:
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<android.support.v7.widget.RecyclerView
android:id="#+id/recyclerView1"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:scrollbars="none"/>
<android.support.v4.widget.Space
android:layout_width="#dimen/two_dp"
android:layout_height="match_parent"
android:background="#color/dark_gray"/>
<android.support.v7.widget.RecyclerView
android:id="#+id/recyclerView2"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:scrollbars="none"/>
</LinearLayout>
Now onBindViewHolder is called for all list items instead of only the visible items.
This started happening after update to support library 25.1.0.
It is working fine if weights are removed, but having 2 recylcerviews side by side is required.
How do I tell recyclerview to recycle views instead of loading all?
UPDATE: It works fine on Marshmallow and above devices. The issue is present in Lollipop or below.
You can find a demo project here: https://bitbucket.org/aniketit/recyclerviewtest
I ran into the same issue which persisted after removing weighted and 0dp elements. In my case, the issue was rather trivial - I had inadvertently put my RecyclerView inside of a NestedScrollView with fillViewPort=true. This causes the Adapter will build all the elements for the view and you will notice a significant delay in responsiveness.
Turns out the issue will occur if you just have the RecyclerView in a plain old ScrollView too.
I had the same issue.
RecyclerView was recycling well in Marshmallow but not before.
My mistake was to put my RecyclerView into a ScrollView.
You should check if you have a scrollview, if yes, remove it and your issue will be solved for pre marshmallow devices.
With libraries > than 23.1.1 you can remove
android:layout_width="0dp"
remove it and all works perfectly.

Create a RecyclerView with both horizontal and vertical scrolling

Over the past few weeks I've been learning to use the RecyclerView. I need to implement a horizontal list, ie, that by turning the device in landscape mode like so:
I found the best solution for this (how to create the horizontal displacement of RecyclerView, here), but encountered another problem. The item RecyclerView was larger than the height of the device (in landscape, horizontal), so I need to create a vertical and horizontal displacement, simultaneously.
I looked at the Android Developer methods for the LayoutManager class, but my skills are not high enough to understand most of the methods. I also tried putting a RecyclerView vertically inside another RecyclerView horizontally with all the content, but I get error:
IllegalStateException: RecyclerView has no LayoutManager
To rememedy this I removed all <View ... /> elements from the XML file, but this does not give any results.
To clarify what I am asking: is it possible to have my layout scroll both horizontally and vertically, and if you could explain how I would appreciate it.
I was so angry about all the problems that had tended with the application that had not thought about the easiest solution.
In a RecyclerView consists of two XML files, the main one where the RecyclerView is declared and another with content.
The simplest solution was to introduce the RecyclerView within a ScrollView. So I can move all items at a time thanks to ScrollView vertically and horizontally I can move the items thanks to RecyclerView in landscape mode.
activity_main.xml
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="#dimen/cardIn_margin_ext">
<android.support.v7.widget.RecyclerView
android:id="#+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scrollbarStyle="outsideInset"
android:scrollbars="horizontal" />
</ScrollView>
The accepted answer did'nt work for me. I had to use the HorizontalScrollView instead of simple ScrollView.
<HorizontalScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="#dimen/cardIn_margin_ext">
<android.support.v7.widget.RecyclerView
android:id="#+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scrollbarStyle="outsideInset"
android:scrollbars="horizontal" />
</HorizontalScrollView >

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/>

Categories

Resources