Structure:
CoordinatorLayout -> AppBarLayout + ViewPager
Scroll RecyclerView or other touch gestures throw setPrimaryItem in ViewPager. Called from ViewPager.onMeasure. Its normal behavior?
(support libs 25.1.0)
Trace:
ViewPager calls setPrimaryItem() in it's populate method.
ViewPager.populate()
The populate method in turn is called in a couple of cases, but the really important one (at least for figuring out these multiple calls) is the onMeasure() invocation.
ViewPager.onMeasure()
Look at like 851. Anything that causes the view hierarchy to remeasure is going to lead to your Adapter getting it's primary item calls set again.
Related
I have a view that contains a ViewPager, whose visibility is set to GONE on startup. By introducing a print on the pager instantiateItem method I noticed that this method is only called when the pager is set to VISIBLE.
Since the instantiation seems to be a bit heavy, the user is able to notice a slight "bump" when the pager must be shown for the first time (which is when the instantiation method is called).
However, at app startup, there is loading screen (establishing connections, etc), and I would like to be able to make the pager call instantiateItem while that loading screen is running (but without showing the view to the user).
How can I achieve this?
Thanks!
That is in place as an optimization because there would be no reason to instantiate items if the user may not ever see them.
If instantiateItem is such a long process, then you can call this on your adapter yourself and cache the instantiated items. This is ok if you don't have that many items.
Otherwise, you have to make ViewPager think it's visible. Rather than setting it's visibility, you can just hide it behind a View then make the View go away when everything is ready. Just make sure you capture touch events on the View so they don't get passed down to the ViewPager.
Is it possible to remove a single Fragment at a position from a ViewPager without causing the whole ViewPager to redraw, i.e., destroy only 1 fragment without destroying the others in the ViewPager?
Using the example code on this page: FragmentStatePagerAdapter, calling notifyDataSetChanged() after decrementing NUM_ITEMS, causes all Fragments to get destroyed and drawn again except the last due to overriding getItemPosition() to return POSITION_NONE. How can this be avoided?
Use activityfragment from support jar and using getFragmentManager API, manually add remove fragments at the runtime.
Only issue here is you will have to keep track of each of the fragment references.
Having searched regarding this issue beforehand, I can find many discussions regarding dynamically adding and removing selected Fragments from a ViewPager. What I'm actually concerned about here however is how I can programmatically remove an entire ViewPager 'cleanly' from its containing ViewGroup, when that ViewPager has been used to display Fragments via a FragmentPagerAdapter, and ensure that the contained Fragments are destroyed properly.
To expand on the question a bit more, I have a landscape two-pane layout where a selection is made from a list within a Fragment on the left-hand-side, and chosen content is then placed on the right within a FrameLayout. The key thing is that the content may or may not be paginated. Therefore, the content must either be displayed in a ViewPager format, or if it is not paginated then it shall be represented by a single Fragment directly.
To show a single Fragment, I simply perform a FragmentTransaction as you normally would in order to place the Fragment into the FrameLayout container. If on the other hand it's paginated content to be shown, then instead I create a ViewPager and add it as a child of the FrameLayout.
When I need to change the content, then if the previous content was a stand-alone Fragment then I can simply remove it via FragmentTransaction .remove(). When I do this, the Fragment goes through the onPause() ... onDestroy() cycle as expected. If the previous content was a ViewPager then I remove it from the FrameLayout using .removeAllViews(). Here I come to the problem: I don't see any of the onPause() ... onDestroy() methods being called in any of the Fragments that were held within that ViewPager via the FragmentPagerAdapter.
From a user point of view, the application works fine. After several rounds of ViewPager being removed, I can see the GC reclaiming memory. However, I don't like the fact that those Fragments' end of life methods aren't called as I can't do any cleanup within them, and it just doesn't seem 'right'.
Is there a method I can hook into in order to remove the ViewPager's Fragments when the ViewPager is detached from its parent, perhaps? In other words, when I know that the ViewGroup is no longer in used, I would perform FragmentTransactions somewhere (perhaps in the FragmentPagerAdapter) to remove those Fragments.
Alternatively, I realise that I could just keep the ViewPager on the right permanently, and dynamically swap the Fragments within it. Of course it simply would not matter that at certain times it would only hold one page. If this would be a better way to go then I shall refactor my code to do this, but I would appreciate opinions.
However, I don't like the fact that those Fragments' end of life methods aren't called as I can't do any cleanup within them, and it just doesn't seem 'right'.
They should get cleaned up when the activity is destroyed, if that is not too late for you (e.g., heap issues).
In other words, when I know that the ViewGroup is no longer in used, I would perform FragmentTransactions somewhere (perhaps in the FragmentPagerAdapter) to remove those Fragments.
You did not execute the transactions to put the fragments there. Hence, you cannot readily execute the transactions to remove the fragments. If you switch to FragmentStatePagerAdapter, and call setAdapter(null), it should cause all existing fragments in the pager to be destroyed, by my reading of the source code. FragmentPagerAdapter never uses remove(), but FragmentStatePagerAdapter does, from its destroyItem() method, and all extant fragments are destroyed via destroyItem() when a new adapter (or null) is supplied to setAdapter().
I'd like to show and hide ViewPager on particular occassions, however this ViewPager element holds quite a bit of Bitmaps that should be recycled. When I do setVisibility(GONE) on VierPager it doesn't trigger any of the callbacks related to its' pages.
Is there any system way to tell ViewPager to destroy view of all the pages it hosts ? I think I could wright somewhat like:
foreach(page from viewPagerPages) {
page.onPause();
page.onStop();
page.onDestroyView();
}
but I'm not sure about it.
Thanks.
To destroy Views use ViewPager's method removeAllViews (). It is inherited from ViewGroup.
removeAllViews () - Call this method to remove all child views from the ViewGroup.
Here a lot of different methods to manipulate ViewPager.
I use ActionBarSherlock compatibility library and experience a strange behavior when paging between tabs of Action Bar. Each tab contains a simple Fragment, nothing special. I observed that fragment's onCreateView method is called too often even though there is no screen orientation change. It looks like some kind of pre-caching. I have three tabs there, when the activity is created, the onCreateView is called only for the first two fragments. The last fragment doesn't create view until I page one step forward. The same behavior occurs when paging from the last tab to the first.
Has anybody any idea why this occurs? I would assume creating all views at once, when the parent activity finishes its creating. I don't want to create views again and again, there are no changes in the fragments, they are static. It has no sense and causes paging to be sluggish a bit...
After a few hours I found what's happening there. ViewPager has a default setting DEFAULT_OFFSCREEN_PAGES which sets the maximum number of views (fragments in my case) to be stored in the view container of ViewPager. It is obviously some kind of resource optimization; invisible views can be thrown away and restored when needed.
There is nothing easier then change this value by setOffscreenPageLimit(int limit) setter which I overlooked.
I think it was done consciously to increase user experience.
The same way ViewPager from compatibility lib is implemented.
Anyway, sources are available.