There is 3 TabLayout with ViewPager in my application.
If I swipe for change Tabs then it behaves strangely.
Below is the scenario.
Let's say 3 Tabs A, B, C.
Default Tab A will open when I start App.
Go to B From A - Works OK.
Go to A From B - Works Ok.
Go To B From A - Works Ok.
Go To C From B - Works Ok.
Now Problem Starts here.
Go To B From C - It will Load A and B Both but display Only B.
Why A and B loads if I come from C ??
Below is my code.
private Toolbar toolbar;
private TabLayout tabLayout;
private ViewPager viewPager;
ViewPagerAdapter adapter;
toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(false);
viewPager = (ViewPager) findViewById(R.id.viewpager);
setupViewPager(viewPager);
tabLayout = (TabLayout) findViewById(R.id.tabs);
tabLayout.setupWithViewPager(viewPager);
private void setupViewPager(ViewPager viewPager) {
adapter = new ViewPagerAdapter(getSupportFragmentManager());
adapter.addFragment(new FragmentOpen(), "OPEN");
adapter.addFragment(new FragmentClose(), "CLOSE");
adapter.addFragment(new FragmentTest(), "TEST");
viewPager.setAdapter(adapter);
}
class ViewPagerAdapter extends FragmentPagerAdapter {
private final List<Fragment> mFragmentList = new ArrayList<>();
private final List<String> mFragmentTitleList = new ArrayList<>();
public ViewPagerAdapter(FragmentManager manager) {
super(manager);
}
#Override
public Fragment getItem(int position) {
return mFragmentList.get(position);
}
#Override
public int getCount() {
return mFragmentList.size();
}
public void addFragment(Fragment fragment, String title) {
mFragmentList.add(fragment);
mFragmentTitleList.add(title);
}
#Override
public CharSequence getPageTitle(int position) {
return mFragmentTitleList.get(position);
}
}
I want to load Tabs every time.
For that, I use below code.
viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
#Override
public void onPageSelected(int position) {
if(adapter != null){
adapter.getItem(position).onResume();
}
Toast.makeText(getApplicationContext(), "Page Selected", Toast.LENGTH_LONG).show();
}
#Override
public void onPageScrollStateChanged(int state) {
}
});
But it will always load both tabs when I came from C.
What I want is: only Tab B must load when came from C, not Both.
Its not possible when using ViewPager. ViewPager loads at least one right/left adjacent fragment of current fragment as per your swipe direction.
If you read the ViewPager documentation, there is a method ViewPager.setOffscreenPageLimit(LIMIT) which supports off screen page limit and its default value is 1.
SetOffscreenPageLimit:
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.
This is offered as an optimization. If you know in advance the number
of pages you will need to support or have lazy-loading mechanisms in
place on your pages, tweaking this setting can have benefits in
perceived smoothness of paging animations and interaction. If you have
a small number of pages (3-4) that you can keep active all at once,
less time will be spent in layout for newly created view subtrees as
the user pages back and forth.
You should keep this limit low, especially if your pages have complex
layouts. This setting defaults to 1.
See documentation.
Here are some related answers:
1. ViewPager.setOffscreenPageLimit(0) doesn't work as expected
2. How can make my ViewPager load only one page at a time ie setOffscreenPageLimit(0);
3. How does setOffscreenPageLimit() improve ViewPager performance by retaining more offscreen Fragments?
its Default behavior of ViewPager if you are in A then it loads B
also and when your reach B it will Load C and A also is Loaded now
when to reach at C its destroy the View of A and now when you come
again to B it will Load A again, in a simple way ViewPager Maintain
the One Next and One previous state of View by Default.
to Overcome this you should use ViewPager.setOffscreenPageLimit(number) here number is the count of page to load Simulationally for Next and Previous View.
I had only 3 tabs and settingViewPager.setOffscreenPageLimit(2) worked for me.
Try ViewPager.setOffscreenPageLimit(totTabs - 1)
Related
I'm not quite sure if there's a "best way" to tackle the following design issue.
I have a Tablayout with 2 Tabs in my MainActivity. Each Tab is a different Fragment. I go to Tab1 and see Fragment1. I need to launch a new Fragment (1A) from Fragment 1 and am not sure the best way to do it? I was thinking about one of these.
A) Take the Tabs out of my MainActivity and place them in a separate MainFragment, which gets launched with the app. That way when the user launches Fragment 1A, it replaces just the 1 MainFragment with the Tabs.
or
B) Keep the Tabs in the MainActivity and find a way to replace Fragment 1 with Fragment 1A when under Tab1.
Any suggestions would be appreciated. Thank you.
I think you shouldn't do both of points... Frag1 visible under Tab1 should contain all the layout (including initialy hidden) and logic for this view. If you need to show smth new it may be smaller (then popup, dialog etc.) or expand some layout, maybe with some animation (you may still use ViewPager inside Fragment inside ViewPager, disable touch events and manipulate scrolling programmatically...).
When Action picked by user is intended to show smth so-much-improtant that previous screen is not needed at all then you should probably open new Activity
PS. If you insist to replace current "screen" (in fact Activitys content) note that title of Tab1 may not representing what contains Frag1A. It very depends what kind of content you have there. You may consider move TabLayout/RecyclerView to e.g. FrameLayout container and add to it you Frag1A covering whole previous view including Tabs. In current design guidelines you can even find suggested solution for way of showing new fragment - with circular reveal animation
I do not understand very well what you want, but possibly this.
Use FragmentPagerAdapter..
public class TabsPagerAdapter extends FragmentPagerAdapter {
private static final int NUM_TABS = 3;
public TabsPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
switch(position){
case 0:
return Tab1Fragment.newInstance();
case 1:
return Tab2Fragment.newInstance();
default:
return Tab3Fragment.newInstance();
}
}
#Override
public int getCount() {
return NUM_TABS;
}
#Override
public CharSequence getPageTitle(int position) {
if (position == 0){
return "Tab 1";
}
if (position == 1){
return "Tab 2";
}
return "Tab 3";
}
In your activity...
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Tabs
TabLayout tabs = (TabLayout) findViewById(R.id.tabs);
ViewPager pager = (ViewPager) findViewById(R.id.pager);
TabsPagerAdapter adapter = new TabsPagerAdapter(getSupportFragmentManager());
pager.setAdapter(adapter);
tabs.setupWithViewPager(pager);
}
Since in Android L the the Action bar navigation modes are deprecated I'm searching an other way to have the tab and I found that is possible to use the PagerTabStrip (android.support.v4.view.PagerTabStrip), so I created a FragmentPageAdapter in this way:
public class TitleAdapter extends FragmentPagerAdapter {
private final String titles[] = new String[] { "Home", "Events", "People", "Books"};
private final Fragment frags[] = new Fragment[titles.length];
Context context;
public TitleAdapter(FragmentManager fm) {
super(fm);
}
#Override
public CharSequence getPageTitle(int position) {
Log.v("TitleAdapter - getPageTitle=", titles[position]);
return titles[position];
}
#Override
public Fragment getItem(int position) {
Log.v("TitleAdapter - getItem=", String.valueOf(position));
//return frags[position];
switch (position) {
case 0:
return Home.newInstance(0, "Home");
case 1:
return Events.newInstance(1, "Events");
case 2:
return People.newInstance(2, "People");
case 4:
return Books.newInstance(3, "Books");
}
return null;
}
#Override
public int getCount() {
return frags.length;
}
}
The strange way that i see in LogCat is that the method getItem() is called 4 times when the mainActivity starts so I've to wait a lots because in each Tab there is a quite long list and this list is populated via HTTP request calling a web service.
I wish load only a fragment each times and not all. When i used actionbar.Tablistener it was possible but now the method is deprecated so is there a way to do that?
I set the adapter and the viewPager in the onCreate method in this way:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.prova_page_tab_stripes);
mViewPager = (ViewPager) findViewById(R.id.viewpager);
titleAdapter = new TitleAdapter(getSupportFragmentManager());
mViewPager.setAdapter(titleAdapter);
mViewPager.setOffscreenPageLimit(1);
}
The number of pages initialized depends on setOffscreenPageLimit function of ViewPager. As per android doc in http://developer.android.com/reference/android/support/v4/view/ViewPager.html#setOffscreenPageLimit(int),
public void setOffscreenPageLimit (int limit)
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.
This is offered as an optimization. If you know in advance the number of pages you will need to support or have lazy-loading mechanisms in place on your pages, tweaking this setting can have benefits in perceived smoothness of paging animations and interaction. If you have a small number of pages (3-4) that you can keep active all at once, less time will be spent in layout for newly created view subtrees as the user pages back and forth.
You should keep this limit low, especially if your pages have complex layouts. This setting defaults to 1.
Parameters
limit How many pages will be kept offscreen in an idle state.
Set it to 1 or keep it to default if you wan to limit pages to be retained.
But if you want to load the data only in one page at a time, you can determine when fragment becomes visible and then load the data (insted of loading in onCreateView.)
Refer this question: How to determine when Fragment becomes visible in ViewPager
I have ActionBar Tabs setup. It consists of 4 tabs. Everything is fine until I navigate away from TabbedFragment and returning back.
I create tabs like this:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final ActionBar actionBar = getActionBar();
tabs = Lists.newArrayList();
tabs.add(new TabDefinition<>("Tab 1"));
tabs.add(new TabDefinition<>("Tab 2"));
tabs.add(new TabDefinition<>("Tab 3"));
tabs.add(new TabDefinition<>("Tab 4"));
for (TabDefinition tab : tabs) {
actionBar.addTab(actionBar.newTab()
.setText(tab.text)
.setTag(tab.tag)
.setTabListener(this));
}
}
And initialize adapter like this:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.paging_tab_container, container, false);
}
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
viewPager = (ViewPager) view.findViewById(R.id.pager);
viewPager.setAdapter(new FragmentStatePagerAdapter(getFragmentManager()) {
#Override
public Fragment getItem(int position) {
return tabs.get(position).fragment;
}
#Override
public int getCount() {
return tabs.size();
}
});
viewPager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
#Override
public void onPageSelected(int position) {
getActionBar().setSelectedNavigationItem(position);
}
});
viewPager.setCurrentItem(getActionBar().getSelectedNavigationIndex(), true);
}
When returning back to TabbedFragment selected tab and 1 next to it would not have any content. Just empty view. But if I select current + 2 fragment content is loaded. And then returning to that first fragment content is reloaded.
For example I have A, B, C, D tabs. Before leaving TabbedFragment I had selected tab A.
When returning to TabbedFragment I still am at tab A, but it's empty. So is tab B.
But when selecting tab C it is created and loaded. Returning to tab A it is recreated.
What could be the problem here?
After a while ran into the same problem again, so updating this question.
If you're using FragmentStatePagerAdapter you should provide FragmentManager via getChildFragmentManager() instead of getFragmentManager(). See Issue 55068: ViewPager doesn't refresh child fragments when back navigation via backstack
Okay so When using a FragmentStatePagerAdapter your fragments will be destroyed when you navigate anymore than one fragment Away since by default offScreenPageLimit is set to 1 by default just as mentioned above.
Typically this Class is used for an activity that has a very large set of Fragments, i.e have to scroll through a large amount of views. If your application does not need more than say 3-4 tabs I would suggest using FragmentPagerAdapter instead, and then specifying your offScreenPageLimit to something like 3, so if you get to the 4th Tab, all 3 tabs before will still be in memory.
Here is some Sample Code for a project on github that i created illustrating how to dynamically load the fragments if you don't want to add this offScreenPageLimit.
https://github.com/lt-tibs1984/InterfaceDemo/blob/master/src/com/divshark/interfacedemo/InterfaceDemoMain.java
Walk through all this code in this Class, and you will see how I'm dynamically loading the fragments, each time my ViewPager is slid over. Most notably at the bottom.
You can download this code, and use it as a test base for what you want to do.
Try adding the setOffScreenPageLimit(2) in the onCreate() method for the viewPager and notice the different behavior. To check the behavior, edit the text in fragment 1. Navigate Away and navigate back, with this set or not. You will see when it is set, the fragment's text remains what you change it to, since the fragment is never recreated.
Please provide additional questions if you have them.
GoodLuck
UPDATE
private static final String [] fragmentClasses = {"com.example.project.YourFragment1","com.example.project.YourFragment2","com.example.project.YourFragment3"};
viewPager.setAdapter(new FragmentStatePagerAdapter(getFragmentManager()) {
#Override
public Fragment getItem(int position) {
Fragment fragmentAtPosition = null;
// $$$$ This is the Important Part $$$$$
// Check to make sure that your array is not null, size is greater than 0 , current position is greater than equal to 0, and position is less than length
if((fragmentClasses != null) && (fragmentClasses.length > 0)&&(position >= 0)&& (position < fragmentClasses.length))
{
// Instantiate the Fragment at the current position of the Adapter
fragmentAtPosition = Fragment.instantiate(getBaseContext(), fragmentClasses[position]);
fragmentAtPosition.setRetainInstance(true);
}
return fragmentAtPosition;
}
#Override
public int getCount() {
return fragmentClasses.length;
}
});
The problem exists in the Fragments you use as tabs, I think. They seem to not show anything when they are resumed (see Fragment lifecycle). The "weird" issue that only the currently selected +/-1 tab is empty, is because the offScreenPageLimit of your ViewPager is 1 by default. All tabs above this threshold are re-created.
Therefore, increasing the value will -- in your case -- cause all your tabs to appear empty after resuming. Check in your Fragment code which lifecycle methods you use to inflate your layout, set adapters and so forth, because that's what's causing your trouble.
I guess this happens because while loading fragment android loads current and current+1, if you debug you would not see onPause getting called for the immediate next fragment.
You can reload content programmatically in onTabChanged() method of TabHost.OnTabChangeListener.
After doing much research, this worked for me.
I have a complex layout with 3 tabs in a fragment, that gets switched out for other fragments. I realized that the ViewpagerAdapter will retain state, even if you press the home button. My problem was switching back and forth would null out the child fragment UI view elements and crash. The key is to not new out your ViewPagerAdapter. Adding the null check for the Adapter worked for me. Also, be sure to allocate setOffscreenPageLimit() for your needs. Also, from what I understand setRetainInstance(true); should not be used for fragments that have UI, it is designed for headless fragments.
In the fragment that holds your Tabs:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.fragment_tab, container, false);
tabLayout = (TabLayout) view.findViewById(R.id.tablayout);
viewPager = (ViewPager) view.findViewById(R.id.viewPager);
//Important!!! Do not fire the existing adapter!!
if (viewPagerAdapter == null) {
viewPagerAdapter = new ViewPagerAdapter(getChildFragmentManager());
viewPagerAdapter.addFragments(new AFragment(), "A");
viewPagerAdapter.addFragments(new BFragment(), "B");
viewPagerAdapter.addFragments(new CFragment(), "C");
}
//Allocate retention buffers for three tabs, mandatory
viewPager.setOffscreenPageLimit(3);
tabLayout.setupWithViewPager(viewPager);
viewPager.setAdapter(viewPagerAdapter);
return view;
}
Or more simply when navigating back to tabbedfragment (assuming you use an intent and the fragment is within an activity) use:
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
This keeps the original activity and moves it to the top of the stack rather than recreating it, thus you never need to recreate the viewPager.
I am using viewpager for displaying 7 pages . I am also using viewpagerindicator lib to get as no of circles as the pages are in viewpager.
When i scroll pages in viewpager I want to display user that you are in page 1 , pager 2 ... . respectively. when he scroll the pages in viewpager.
i am using getcurrentitem() of pagedapter to get in which page i am .. But it go not give me correct result.
Is i am doing some thing wrong?
How to count exact page number in viewpager ?
I tried two ways.
First
scScreens = new Fragment[mSessionManager.getPosScreens()];
#Override
public Fragment getItem(int position) {
return scScreens[position];
}
In each Fragment i Wrote the page Number. So that if the respective fragment show change the page Number. (Very bad Way) I have to create 7 Classes (Maximum pages) .
Second
pagestextview.setText(getCurrentItem()+1);
It work while scrolling first , but gives unexpected result when scrolling back to first
i can recommend the FragmentStateAdapter, as it handles the creating/destroying of the fragments pretty well.
mViewPager = new ViewPager(this);
mViewPager.setId(R.id.viewPager);
setContentView(mViewPager);
final FragmentManager fm = getSupportFragmentManager();
mViewPager.setAdapter(new FragmentStatePagerAdapter(fm) {
#Override
public int getCount() {
return mContactList.size();
}
#Override
public Fragment getItem(int pos) {
Contact mContact = mContactList.get(pos);
int mCurrentPosition = mContact.getPosition();
return ContactDetailsFragment.newInstance(mCurrentPosition);
}
});
//loads the proper fragment on its meant-to-be position into the ViewPager
mViewPager.setCurrentItem(position);
I'm working on a specialized eBook application, where one of the requirements is that it should only display one page of text at a time, with formatting and what not. This proved more challenging than I thought it would, but I've finally managed to do it successfully.
However, now I've run into another problem, namely that the client wants to be able to flip back and forth between pages, ViewPager-style, where the page follows the finger. Now, the number of pages in these books and scriptures aren't predetermined, as I calculate them on-the-fly, each time a chapter is loaded, so that the view automatically handles changes in text size, etc.
I've been looking into various forms of "unlimited ViewPager-flipping", but thus far I haven't been able to find one that seems to work for my purpose.
What I've been considering, and that I hope someone may be able to help me with, is this:
Three pages, sort of. The one being viewed currently, and one on each side for the flipping.
When the user flips to a new page, the views will switch places behind the scenes, so that the new page becomes the one in the middle, and loads the next page, while moving the previously viewed page to the opposite end.
It doesn't technically have to be a ViewPager, if the animation, including the gesture-following, can be achieved in some other way.
I would offer up extra reputation points for this, as I'd really love to solve this quickly, but unfortunately I haven't managed to get a lot of those yet.
When you want to solve your problem with a ViewPager, you need this in your onCreate method:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mPager = (ViewPager) findViewById(R.id.pager);
android.support.v4.app.FragmentManager fm = getSupportFragmentManager();
// Capture ViewPager page swipes
ViewPager.SimpleOnPageChangeListener ViewPagerListener = new ViewPager.SimpleOnPageChangeListener() {
#Override
public void onPageSelected(int position) {
super.onPageSelected(position);
if (fistswitch) {
mPager.setCurrentItem(1);
fistswitch = false;
} else {
// here you can use the replace method, to switch the
// fragment behind the scenes
ArrayList<Fragment> fragmentList = new ArrayList<Fragment>();
Collections.addAll(fragmentList, new Fr2(), new Fr3(), new Fr4());
mPagerAdapter.addFragments(fragmentList);
// after the first switch you can use the the second page
// continuously as middle
mPager.setCurrentItem(1);
}
}
};
mPager.setOnPageChangeListener(ViewPagerListener);
mPagerAdapter = new ViewPagerAdapter(fm);
mPager.setAdapter(mPagerAdapter);
ArrayList<Fragment> fragmentList = new ArrayList<Fragment>();
Collections.addAll(fragmentList, new Fr1(), new Fr2(), new Fr3());
mPagerAdapter.addFragments(fragmentList);
}
The pager adapter class can be a private class in your activity like this:
private class ViewPagerAdapter extends FragmentStatePagerAdapter {
private List<Fragment> fragmentList = new ArrayList<Fragment>();
public ViewPagerAdapter(FragmentManager fm) {
super(fm);
}
public void addFragments(List<Fragment> fragments) {
fragmentList.clear();
fragmentList.addAll(fragments);
notifyDataSetChanged();
}
public void replaceFragment(int position, Fragment fr) {
fragmentList.remove(position);
fragmentList.add(position, fr);
notifyDataSetChanged();
}
#Override
public int getItemPosition(Object object) {
if (fragmentList.contains(object)) {
return POSITION_UNCHANGED;
}
return POSITION_NONE;
}
#Override
public Fragment getItem(int item) {
if (item >= fragmentList.size()) {
return null;
}
return fragmentList.get(item);
}
#Override
public int getCount() {
return fragmentList.size();
}
}
I have not tested the code, but it works for me in a similar case.
There are some good ways to add animation to a ViewPager and alternatively use your own custom views. The first is an open source project called JazzyViewPager by jfeinstein10
This library is really nice and have some animations built in. I've looked at the source in the past and it should be simple to create your own animations if necessary.
The second option is using what's used in JazzyViewPager, namely ViewHelper, which is a class is in the NineOldAndroids animation library by Jake Wharton.