I am attempting to create a MultiDirectional Paging application that is based off an Activity that contains a Support ViewPager and within that are Fragments that contain VerticalViewPagers. Both ViewPagers are using a FragmentStatePagerAdapter and the data is coming from a ContentProvider that is loaded via CursorLoaders. The vertical fragments contain just a simple TextView that has the name of the horizontal fragment and the vertical fragment position.
The issue that I'm coming across is that when you page horizontally to the end and back to the beginning multiple times, the UI begins to lag. The more that you do this, the more lag occurs and eventually, you are able to page from the beginning to the end without the UI ever updating as it's taking so long. The more you page horizontally, the worse the UI lag becomes.
I've enabled strict mode to see if there's too much work happening on the UI thread, but it isn't. I have also tried to check if LeakCanary can find any memory leaks, but it has yet to find any.
I've just started doing some Tracing via Traceview but as I have just started, I've yet to pinpoint the problem (I'm also not too familiar with Traceview yet, but am working on that).
Here is what the Android Monitor looks like when paging horizontally for a bit and then at the end, paging vertically. You'll notice that paging vertically works fine.
The spikes seen here are color coded as VSync Delay. I have also noticed that if you run the app and let it sit there, the memory will slowly climb until the Garbage Collector is run, which clears some memory and the slow climb will continue again.
My initial thought is that either I'm doing the Loaders wrong or there is an issue with Loaders and nested ViewPagers.
Is there something fundamentally wrong with what I'm attempting here or something wrong with the Loaders, etc.? I am going to continue digging into this, but in the meantime, I was hoping to get some thoughts from others as to what my problem may be or some suggestions into how to pinpoint the issue.
I have created a very basic sample app that can be found at https://github.com/hooked82/MultiDirectionalPaging
If you pull down the project and run it, on first app launch you will need to click the "+" button in the toolbar to populate the database.
It is using the VerticalViewPager from https://github.com/castorflex/VerticalViewPager
UPDATE 8/1/2016
Per Joe's answer, the cause is the amount of inflations and destruction of views when swapping out the adapter of the VerticalViewPager. I was unable to implement swapCursor() on my custom FragmentStatePagerAdapter due to the bug that causes the adapter to not update with new data. If I were to implement swapCursor(), I would get good horizontal paging, but my adapter's UI would not update when the Cursor was notified of dataset changed.
I've swapped out the VerticalViewPager for a custom CursorRecyclerView to get the same functionality, but still need to setup proper flinging of the RecyclerView to mimic that of the VerticalViewPager.
Thanks for the help, Joe!
The issue is the number of fragment transactions that occur on a horizontal scroll.
1) In any view pager, there are at most 3 fragments displayed. For the vertical at position v1 there are 3 fragments v0, v1 and v2. If you scroll from v1 to v2, the v0 fragment is destroyed and v3 is created in anticipation of the next scroll.
So for each vertical scroll a max of two fragment operations may occur.
2) Now the same thing exists horizontally. If at position h1, you have fragments h0, h1, h2 created. Now because the vertical frags also are created you have up to 3 vertical fragments for each horizontal one. Now things get interesting. Assuming you are a v1 in each horizontal fragment the following happens (worse case) scrolling from h1 to h2. First h0 is destroyed, but also the v0, v1, v2 frag owned by h0. Then h3 is created along with at min v0/v1. However if you previously scrolled to v1 it remembers that position and also creates v2.
Therefore for each horizontal scroll a minimum of 6 fragment operations and a max of 8 occur per scroll (ie up to 4x more expensive). When you get into view inflation/cleanup this can multiply really fast causing your performance problem.
You would be better off using a recycler view for at a minimum to handle the vertical scrolling. You likely would need to intercept the scrolling events to "simulate" the snap to page the view pager performs on partial scrolls.
Related
Summary:
When using talkback with nested recyclerview using the swiping the scrolling seems to hop as in skip complete rows and sometimes not scroll all the way to the right. Keep in mind this only happens when both the width of the nested recyclerview is larger than the screen. I included a video of me just swiping to the right and you can see the issues. Weirdness like this is happening on my work project as well.
Video
https://drive.google.com/file/d/1fUT4F0CaD34lLVdELBoBK3EPZ4PeNmMy/view?usp=sharing
Keep in mind there is 7 in each row and sometimes it skips the 7th. Also sometimes it just skips to a completely new roll or scroll weird. On my work project I have seen it skip rows as well.
Details:
I recreated it on a simple example online of nested recyclerview. I simplified it as much as I could here
https://github.com/DavidCorrado/NestedRecyclerview
I tried what other articles suggested which it seems to me everyone just eventually gave up
nestedScrollingEnabled
descendantFocusability="blocksDescendants"
focusableInTouchMode="true"
descendantFocusability="beforeDescendants"
Question:
Is this just broken or is there any work arounds for nested recyclerview?
I have successfully followed the tutorial on RecyclerView here.
but I was wondering if I could build more complex behaviour using Fragments inside the RecyclerView instead of plain old Views.
using fragments can help make a reusable item with complex behaviour that can be placed in other places and can be much more flexible.
Is it possible and if so , how ?
RecyclerView is for a view that repeats a lot with different data, it's designed for use in long scrolly lists because inflating (constructing) the View Object takes some effort, so recycling takes the View you just scrolled off the top of the screen, quickly fills it with new data and puts it at the bottom.
I wouldn't have thought using a Fragment like that would work well, whenever you scroll the list it'll need to be re-created and run its various setup methods which will be quite expensive. And if you make it so it doesn't have to do that (setRetainInstance()) then you're not getting anything from it that you wouldn't get from a view.
If that's what you're getting at then yes you could create a Fragment, set it up, mark it as retained and keep a reference to it so it hangs around in memory for you to re-attach to some other parent at some other time. But if you have to change its contents and re-build it then you're not gaining much.
Currently, I am having a ListView with different list item view for each row (Most cases Different).
I am having 6 different item layout, I will add more in future, and I will have only like 5-15 list items, some time may be less and may be many in other cases.
Some item views contains:
ViewPager
ListViews
ImageViews
Texviews
Gridviews
CAROUSEL
Webviews
As this is dynamically generated depends on data, I am facing following issues :
Scrolling Slowly (Some Times)
List Item height
Performance
Is RecyclerView the best solution in this case?
Without seeing your code to identify specific concerns, it's hard to address specific reasons why you are seeing such performance problems. Such as, are you properly using the ViewHolder paradigm? Or are you inappropriately loading stuff on the UI thread when it should be loaded on a background thread? Android has a small section talking about scrolling smoothly with a ListView you should check out. That aside,based on what you have mentioned so far...I think you major problem is design.
Problems
If your ViewPager is using a FragmentPagerAdapter...then that will definitely be causing a lot of overhead and performance drag.
ListView: You should never ever place a ListView within another ListView. This will cause all sorts of problems. Android does not like embedding two scrollable widgets that scroll the same direction. Even if this worked, it'll cause some major performance problems.
GridView: Same goes with the GridView. You should never ever place a GridView within another ListView. Even if this worked, it'll cause some major performance problems.
If you're ImageView is loading some large graphics, it should be on a background thread and not the UI thread. Else you'll get some slow performance
Carousel - I have no idea what API this is but if it scrolls vertically, then it's a no go.
WebViews are a very heavy weight object. I can definitely see this guy slowing things down, especially if it's loading a lot of content.
To build off what #Joffrey has said. There are some major concerns in your choice of UI. Based on what you are placing in this ListView tells me that you need to seriously rethink how to display your content to the user. Eg, try using a TableLayout or GridLayout instead of a GridView.
I find listView recycles its views too fast.
When my listView scrolls, views falls off the screen gets removed right away.
Each cell(row) has image loaded using universal-image-loader.
Views which fell off the screen has to reload the image when they comes back into visible area. (it shows the stub image for short time period and loads the correct image).
I definately need to keep the view recycling behavior, but can I modify the list view's behavior so that user won't notice constant reloading of images?(maybe I keep 2-3 times of # of views in a cache than a regular list view would)
Unfortunately the code for ListView and friends is horribly complicated by the fact that it's designed to scroll unevenly-sized items without knowing the height ahead of time. That makes it brutally difficult to run with anything but the default behavior. In addition, most of the methods you'd need access to, to easily customize the behavior are hidden or private. It would be a massive job to try and roll your own (across multiple platforms, subtleties of scrolling, flinging, dragging, scrolling, keyboard focus &c).
The best solution is probably to maintain an image cache that fills in lazily around the view positions that are active. Not neccesarily easy. But way easier that trying to mess with ListView.
A very useful API for this is ListView.setRecyclerListener(AbsListView.RecyclerListener listener), which gives you a hook to track which images are actively displayed.
I can suggest you to use a ScrollView instead of the list view. The ListView is designed to display a lot of data efficiently, that's why your off screen items are destroyed. In which concerns the scroll view, once loaded, you will be able to scroll up and down without recreating the off screen objects (because they ill not be destroyed).
See: http://developer.android.com/reference/android/widget/ScrollView.html
EDIT:
If it is mandatory for you to use the listView, you cluld take a look here: How do i prevent recycled ListView items from showing old content?
There is a posibility to override the listView getView method and keep more items not to be destroyed that the number the listView is keeping.
I have implemented a ViewPager in my app, and aside from the swipe paging, I have buttons for "Next" and "Back", with a simple onClick method with the only line being a setCurrentItem for the ViewPager.
While the paging animation is completely smooth during swiping, it is more or less instantaneous (just flips instead of scrolling) when I click Next or Back. There is no visible "scrolling" motion, or sometimes barely visible, however not even close to smooth. It does not hurt the usability of the app in any way, since the transition still happens immediately, but visually it looks a lot less appealing, and the user experience suffers.
Now, I suspect it has something to do with the app drawing the next View and therefore "skipping" the animation. My instantiateItem uses LayoutInflater and a simple switch statement to inflate each view (4 pages at most), and I am loading custom ListViews inside each case in the switch statement. The custom ListViews are at most 3 items long, with a TextView and an ImageView in each row (ImageView resource is 5kb in size). To me it seems this amount of objects should not be reason enough to slow down the paging animation.
Aside from that, I also have a custom background assigned to my app in styles.xml, and this is the background used through every activity (160kb in size).
Those are the only things that I think could be slowing down the app, since the onClick method contains only one line (setCurrentItem), so nothing aside from going to the next view is happening in the app, and the instantiateItem is the simplest implementation of the method possible that I know of.
Things I've tried:
Setting the ViewPager's setOffScreenPageLimit to 4, which I thought would pre-load the views and eliminate any loading in between paging, but in the end setting it to 0 seemed to make the thing slightly more likely to show at least a frame of animation
Optimizing the custom ListViews by using convertView to reuse old views, while it did result in more of the animation being shown, it still wasn't completely smooth, varying from "almost perfect animation" to "instant flip"
Removing the custom background, which together with the above two fixes lead to an almost good enough solution, but still resulted in flips every few steps.
The tinkering described above leads me to believe it has something to do with the views being loaded, but I've not found a good solution for pre-loading these to the desirable effect.
Knowing my newbie programming skills, I know there is a sure fire way to solve this without resorting to gimping the design of the app itself, but reading through the documentation, Stack Overflow questions, Googling for advice, I've just ran out of places to look to.
To sum up this probably too verbally descriptive post my main questions are:
Is there a way to ensure the animation of the ViewPager is executed smoothly? A separate thread perhaps? As I understand though, UI stuff should only be on the main thread, where it already is, and nothing else aside from the view loading is executed anywhere in the code when paging.
Should I be using a different way to load the pages? Would Fragments help?
Thank you for any advice.
ps. The lack of code is due to the methods in question being the most basic implementations possible, but I will provide the code if necessary.