Android ViewPager preloading too much fragments - android

A ViewPager preloads fragments.
But with a pageWidth=0.5 (2 fragments per screen), I think it loads too much views before the current position...
I have pageLimit=1 and pageWidth=0.5.
It should be ok to preload LEFT1 and LEFT2, but why LEFT3 ?
LEFT3 is not on the next left screen...
I tried to understand ViewPager.java from support v4:
https://android.googlesource.com/platform/frameworks/support/+/support-library-27.1.1/core-ui/src/main/java/android/support/v4/view/ViewPager.java#1155
line 1155, it says
// Fill 3x the available width or up to the number of offscreen
// pages requested to either side, whichever is larger.
But for multiple fragments per screen, it loads more before than current and after the current position.
With the Google sample project https://github.com/googlesamples/android-SlidingTabsBasic/
adding
protected void onCreate(Bundle savedInstanceState) {
[...]
mViewPager.setCurrentItem(25);
#Override
public float getPageWidth(int position) {
// 2 fragments per screen
return .5f;
}
#Override
public int getCount() {
// 50 fragments
return 50;
}
public Fragment getItem(int position) {
[...]
Log.d("debugpageviewer", "position="+position);
}
And it loads 7 fragments (3 before current position). The smaller the pageWidth, the more fragments are loaded before.
So, is this a bug or did I miss something ?

Try calling setOffscreenPageLimit() on your ViewPager in your onCreate method.
For example mViewPager.setOffscreenPageLimit(2) will make sure that no more than 3 fragments are always kept around: the current one and the two adjacent ones.

Related

How to implement a nested master detail flow on Android?

I have a list, within a list, within a list, and so on. There's about 5 tiers.
It's easy enough to create 5 activities for each list on phones, but what if I want to support tablets as well? So I'd need to work with master detail flow.
However, I can't seem to find any tutorials or information in relations to a nested master detail flow.
Anyway, here is an illustration of what I'm describing:
In the tablet layout, I want the screen to shift 2 tiers at a time. User can advanced to the next tier by selecting a list item from the right tier. To go back to the previous tier, user can tap the back button.
Any idea how I can achieve this?
After a full day scouring the internet, I finally found a solution. To get a "Nested Master Details Flow" effect simply use a ViewPager with FragmentPageAdapter. The Master Detail Flow will look like this:
To change to a two panel mode when the user switches to landscape, in your extended FragmentPagerAdapter class, override the following method:
#Override
public float getPageWidth(int position) {
DisplayMetrics metrics = getResources().getDisplayMetrics();
// if the width is greater than 900dp halve the width of the page
if ((metrics.widthPixels / metrics.density) > 900) {
return (0.5f);
}
return super.getPageWidth(position);
}
To provide an "up button" for the view pager:
viewpager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
// This method will be invoked when a new page becomes selected.
#Override
public void onPageSelected(int position) {
if (position == 0) {
getSupportActionBar().setDisplayHomeAsUpEnabled(false);
} else {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
}
}
});
You can tell the "up button" to go back a page like this (where viewpager is a member variable of your activity, holding the a reference to your ViewPager):
#Override
public boolean onOptionsItemSelected(MenuItem item) {
int position = viewpager.getCurrentItem();
if (position > 0) viewpager.setCurrentItem(position-1);
return true;
}
REFERENCES:
ViewPager with FragmentPagerAdapter
Display back button on action bar
Multiple-View ViewPager Options
How to implement a ViewPager with different Fragments / Layouts and example github project

Android: Hiding/Unhiding Tab from FragmentPagerAdapter?

Okay, so I have a FragmentPagerAdapter with 3 pages on it...
Is there a way I can HIDE a page from the SlidingTabLayout? I don't want to destroy the page, as I want to be able to unhide it later. Is there a way to do this? Or do I have to destroy the page, and add it back in later?
Hold all your pages in an Array (or a List).
boolean isHide;
public int getCount (){
if(isHide){
return container.size() - 1;
}
return container.size();
}
public Fragment getItem(int position) {
if(isHide && position == positionToHide){
return container.get(position + 1);
}
return container.get(position);
}
One solution would be to prevent transition to that fragment by hiding the tab button and/or disabling swipe.
fragment will still be loaded but you want be able to move to it.

ViewPager behavior, it does not destroy old fragment

I want to know more about ViewPager behavior. I have a FragmenPagerAdapter :
public class DatePagerAdapter extends FragmentPagerAdapter {
public DatePagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
Calendar calendar = Calendar.getInstance();
calendar.set(Calendar.DAY_OF_MONTH, 1);
int offset = position - 100;
calendar.add(Calendar.MONTH, offset);
// Toast.makeText(TestActivity.this,(calendar.get(Calendar.MONTH)+1)
Log.v("CALENDAR", "" + position);
TestFragmentDate date = TestFragmentDate.newInstance(TestActivity.this, calendar);
return date;
}
#Override
public int getCount() {
return 200;
}
#Override
public Object instantiateItem(ViewGroup container, int position) {
return super.instantiateItem(container, position);
}
}
And the code in my Activity :
adapter = new DatePagerAdapter(getSupportFragmentManager());
MinFragmentPagerAdapter wrapperMin = new MinFragmentPagerAdapter(getSupportFragmentManager());
wrapperMin.setAdapter(adapter);
PagerAdapter wrapper = new InfinitePagerAdapter(wrapperMin);
viewPager = (ViewPager) this.findViewById(R.id.pager);
viewPager.setAdapter(adapter);
viewPager.setCurrentItem(100);
viewPager.addOnPageChangeListener(this);
According to my senior, ViewPager always draw 3 fragments, and keep reuse them. For example, at first I have view at position :
99, 100, 101
If I roll right, it will destroy 99 and create 102, and so on.
But, when I debug at the function getItem, at first it did run into this function, but when I roll right for 5 or 6 page, and then roll back, it didn't run into getItem, at the position which is supposed to be destroyed.
So would anyone please explain for me about ViewPager's behavior ? Thank you.
Google's guide says:
FragmentPagerAdapter
This version of the pager is best for use when there are a handful of
typically more static fragments to be paged through, such as a set of
tabs. The fragment of each page the user visits will be kept in
memory, though its view hierarchy may be destroyed when not visible.
This can result in using a significant amount of memory since fragment
instances can hold on to an arbitrary amount of state. For larger sets
of pages, consider FragmentStatePagerAdapter.
And about FragmentStatePagerAdapter:
This version of the pager is more useful when there are a large number
of pages, working more like a list view. When pages are not visible to
the user, their entire fragment may be destroyed, only keeping the
saved state of that fragment. This allows the pager to hold on to much
less memory associated with each visited page as compared to
FragmentPagerAdapter at the cost of potentially more overhead when
switching between pages.
Conclusion : Use FragmentStatePageAdapter in your case, that is you don't want Fragment attached to your ViewPager to be destroyed.
You can also use viewpager.setOffscreenPageLimit(<no of fragments>); to limit How many pages will be kept offscreen in an idle state.
As said before:
You can also use viewpager.setOffscreenPageLimit(<no of fragments>);
to limit How many pages will be kept offscreen in an idle state.
From the official documentation:
setOffsetPageLimit(): Set the number of pages that should be retained to either side of the current page in the view hierarchy in an idle state. Pages beyond this limit will be recreated from the adapter when needed.
It means setOffScreenPageLimit(1) will keep 1 page to the left and/or 1 page to the right depending on what position of the viewPager you are.

Restarting ViewPager + FragmentStatePagerAdapter to item 0

Using SDK 19, min 13, and support.v4.app Fragments.
I have searched SO and found similiar threads which should help, but everything I have tried has not addressed my issue yet. Furthermore, many people seem to have the issue of the ViewPager restarting when they don't want it to, whereas my problem seems to be the opposite.
These posts do not seem to have helped me yet:
PagerAdapter start position
ViewPager PagerAdapter not updating the View
How to force ViewPager to re-instantiate its items
Here is my setup:
A (support.v4) Fragment in memory, which contains a ViewPager, inflated from a layout file, thus not added programmatically. The Fragment itself is not recreated, but in memory for the life of the app; it is being attached/detached to the root FragmentActivity's FragmentManager.
When the user visits this Fragment sometime during the app's lifetime, it is attached and the onCreateView is called, where I findViewById the ViewPager and assign a new FragmentStatePagerAdapter to it.
Here is my problem:
The first time the user visits this pager, everything works fine. The next time they visit it, I expect it to "start over" from page 0, meaning that I expect to not be able to scroll left immediately, but must start scrolling right. I also expect the getItem() of the FragmentStatePagerAdapter to start back at position 0. I basically want it to function the same as the first time the user visited it. However, this is not the case.
I have tried several things, but it always seems to start at the previous index of page I left it at. So if the first time I scrolled 5 pages over, and then left the Fragment and returned later, the ViewPager starts at that same page index, meaning I can scroll 5 pages left. I don't want this.
It may have something to do with the ViewPager or FragmentStatePagerAdapter storing pages internally, which are not being released. But I thought the commented out code I tried would have done that. There might be another way that I have not tried. Perhaps it is related to not recreating my Fragment, but doing so at this point in my architecture will not be great. I was hoping I could restart the ViewPager without doing this.
Here is my code:
My Fragment's onCreateView code. Everything commented out I have tried without success.
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
View view = inflater.inflate(R.layout.fragment_playscreen, container, false);
//while (getChildFragmentManager().popBackStackImmediate()) {}
mAdapter = new AdapterPlayPages(this, getChildFragmentManager());
mPager = (ViewPlayPager) view.findViewById(R.id.pagerPlayScreen);
//mPager.setAdapter(null);
mPager.storeAdapter(mAdapter);
//mPager.setCurrentItem(0);
//mPager.removeAllViews();
return view;
}
My ViewPager, which has been overridden like so to get around a completely unrelated bug, as suggested here: https://stackoverflow.com/a/19900206/1002098. This does not effect the problem; I removed these changes so that I could call setAdapter(null) as suggested, but it did not address my question.
public class ViewPlayPager extends ViewPager
{
PagerAdapter mPagerAdapter;
public ViewPlayPager(Context context)
{
super(context);
}
public ViewPlayPager(Context context, AttributeSet attrs)
{
super(context, attrs);
}
#Override
protected void onAttachedToWindow()
{
super.onAttachedToWindow();
if (mPagerAdapter != null)
{
//super.setAdapter(null); // did not help
super.setAdapter(mPagerAdapter);
}
}
#Override
public void setAdapter(PagerAdapter adapter)
{
// do nothing
}
public void storeAdapter(PagerAdapter adapter)
{
mPagerAdapter = adapter;
}
}
My FragmentStatePagerAdapter. I've removed some unrelated logic and members.
public class AdapterPlayPages extends FragmentStatePagerAdapter
{
private FragmentPlayScreen mParent;
public AdapterPlayPages(FragmentPlayScreen parent, FragmentManager fragmentManager)
{
super(fragmentManager);
mParent = parent;
}
#Override
public int getCount()
{
// some logic from parent to determine true count
// the count shouldn't effect the position to start at
return Integer.MAX_VALUE;
}
#Override
public Fragment getItem(int position)
{
// logic to determine type of page to display,
// which is not changing based on position at the moment
return new FragmentPlayPage();
}
// this did not seem to help me
//#Override
//public int getItemPosition(Object object)
//{
// //return super.getItemPosition(object);
// return POSITION_NONE;
//}
}
I had to change my architecture to recreate the Fragments - add/remove them from FragmentManager, as opposed to attach/detach them while kept in memory.
It is not clear to me why the Fragment would have to be recreated for the ViewPager to reset. Android FragmentManager and FragmentTransaction supports keeping Fragments in memory and simply attaching/detaching them, or showing/hiding them, so it should be possible to simply reset the ViewPager from the in-memory Fragment's onCreateView event, but again, none of the commented out calls above seemed to work.
If anyone else has a solution, I'd still consider it. It will help me understand this issue.

Wrong page in ViewPager after screen change

I have a problem with restoring proper ViewPager page after orientation change.
CONCEPT:
I've got ViewPager which is set for 8 pages initially.
It has OnPageCHangeListener and when user reaches 5th page, I fetch data from server and add another 8 pages to ViewPager.
So every 7 pages I add another 8 pages, it makes it endless. This is listener code:
ViewPager mPager = (ViewPager) findViewById(R.id.frame);
mPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
#Override
public void onPageSelected(int arg0) {
if(arg0 % 7) {
addPagesToViewPagerAdapter(isMainPage);
}
}
This is my ViewPager adapter code:
public class FullImageAdapter extends FragmentStatePagerAdapter {
private static ArrayList<Main> response;
public FullImageAdapter(android.support.v4.app.FragmentManager fm,
ArrayList<Main> resp) {
super(fm);
response = resp;
}
#Override
public int getCount() {
return response.size();
}
#Override
public int getItemPosition(Object object) {
return POSITION_NONE;
}
#Override
public Object instantiateItem(ViewGroup container, int position) {
return super.instantiateItem(container, position);
}
#Override
public Fragment getItem(int position) {
return FullImageFragment.newInstance(response.get(position));
}
public void addPages(ArrayList<Main> resp) {
response.addAll(resp);
}
}
I also has onConfigurationChaged in Activity which holds ViewPager fragment.
public void onConfigurationChanged(android.content.res.Configuration newConfig) {
super.onConfigurationChanged(newConfig);
}
PROBLEM:
Problem is with showing proper ViewAdapter page when orientation change.
For example:
At the beggining there are 8 pages in the ViewPager.
User scrolles to the 3rd page, then change orientation and 3rd page is shown. Everything works fine.
Problem is when uses scrolls after 8 pages f.ex to the 9th (at that time there are 16 pages, because at page 7th we added 8 more) when he change orientation at 9th, the 8th page is shown, which is bad.
To sum up:
Changing orientation:
At position 3 - shown is 3 - good
At position 7 - shown is 7 - good
At position 8 - shown is 8 - good
At position 9 - shown is 8 -BAD
At position 10 - shown is 8 - BAD
etc.
It seems like whatever is handling orientation changes doesn't know that I added more pages and it's showing the last page that was in the adapter at the beggining.
This only affects changing orientation, when user scrolls "normally" everything is fine.
I also use diffrent layouts for land and horizontal orientation.
How can I fix this?

Categories

Resources