I have created a simple app with 3 tab TAB1, TAB2, TAB3
When i select the TAB1 adjacent TAB2 get loaded into memory after that when i press on the TAB2 or TAB3 my content on the TAB2 will not get refresh.
If i click tab in the sequence TAB1->TAB2->TAB1->TAB2 my fragment will not refresh
I want load the selected tab each time when i select the tab.
I am using the following code to load the fragment on tab selected
public class SampleFragmentPageAdapter extends FragmentStatePagerAdapter {
final int PAGE_COUNT = 3;
private String tabTitles[] = new String[]{"HOME", "BEACON", "NEARBY"};
private int[] imageResId = {
R.drawable.home,
R.drawable.beacon,
R.drawable.nearby
};
private Context context;
public SampleFragmentPageAdapter(FragmentManager fm, Context context) {
super(fm);
this.context = context;
}
#Override
public Fragment getItem(int position) {
Fragment fragment = null;
if (position == 0)
fragment = new FragmentTab1();
if (position == 1)
fragment = new FragmentTab2();
if (position == 2)
fragment = new FragmentTab3();
return fragment;
}
#Override
public int getCount() {
return PAGE_COUNT;
}
#Override
public CharSequence getPageTitle(int position) {
// Generate title based on item position
Drawable image = context.getResources().getDrawable(imageResId[position]);
image.setBounds(0, 0, 32, 32);
// Replace blank spaces with image icon
SpannableString sb = new SpannableString(" " + tabTitles[position]);
ImageSpan imageSpan = new ImageSpan(image, ImageSpan.ALIGN_BOTTOM);
sb.setSpan(imageSpan, 0, 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
return sb;
}
#Override
public Object instantiateItem(ViewGroup container, int position) {
return super.instantiateItem(container, position);
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
super.destroyItem(container, position, object);
}
}
Your_Pager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
#Override
public void onPageSelected(int position) {
if (position == WHATEVER) {
//do your Staff
}
}
#Override
public void onPageScrollStateChanged(int state) {
}
});
You can visit here for more information
http://developer.android.com/reference/android/support/v4/view/ViewPager.OnPageChangeListener.html
FragmentPagerAdapter:
FragmentPagerAdapter stores the whole fragment in memory, and could increase a memory overhead if a large amount of fragments are used in ViewPager.
FragmentStatePagerAdapter:
FragmentStatePagerAdapter only stores the savedInstanceState of fragments, and destroys all the fragments when they lose focus. Therefore FragmentStatePagerAdapter should be used when we have to use dynamic fragments, like fragments with widgets, as their data could be stored in the savedInstanceState
USE CASES:
FragmentPagerAdapter should be used when we need to store the whole fragment in memory. When I say the whole fragment is kept in memory it means, its instances wont be destroyed and would create a memory overhead. Therefore it is advised to use FragmentPagerAdapter only when there are low number of fragments for ViewPager. It would be even better if the fragments are static, since they would not be having large amount of objects whose instances would be stored.
In your case if your using Listview, recyclerview or any heavy work you should use 'FragmentStatePagerAdapter' other wise 'FragmentPagerAdapter'
Related
actually i have 4 fragments in my app and i switch the fragments by swiping left or right. I used view pager for swiping the fragments are swiping perfectly but there is a problem if i swipe and fragment B shows but the backend functionality of fragment C runs. If i go to C then backend functionality of D runs. At fragment A first backend functionality of fragment A runs then it automatically shifted to fragment B but front end view is of fragment A
This is adapter
public class TabsPagerAdapter extends FragmentPagerAdapter {
private int mNumTabs;
public TabsPagerAdapter(FragmentManager fm, int numTabs) {
super(fm);
this.mNumTabs= numTabs;
}
#Override
public Fragment getItem(int position) {
switch (position) {
case 0:
PersonalSettings tab0 = new PersonalSettings();
return tab0;
case 1:
Health tab1 = new Health();
return tab1;
case 2:
Statistics tab2 = new Statistics();
return tab2;
case 3:
Motivation tab3 = new Motivation();
return tab3;
default:
return null;
}
}
#Override
public int getCount() {
return mNumTabs;
}
}
This is main activity
viewPager = (ViewPager) findViewById(R.id.pager);
mAdapter = new TabsPagerAdapter(getSupportFragmentManager(),4);
viewPager.setAdapter(mAdapter);
viewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
#Override
public void onPageSelected(int position) {
}
#Override
public void onPageScrolled(int arg0, float arg1, int arg2) {
}
#Override
public void onPageScrollStateChanged(int arg0) {
}
});
}
If u want further code i will give u
It because the viewpager will preload the item next to the current item to improve UX (in your case, the item is the fragment)
You can set offscreenPageLimit to ViewPager to limit it:
viewPager.setOffscreenPageLimit(1);
But if you do this, you will found the fragment will reload everytime you enter, IMO this is not a good practice.
Override destroyItem method in FragmentpagerAdapter
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
// remove your fragment obj from here
}
I have a HomeActivity where I have used ViewPager. There are 2 fragments home1F and home2F that come when swipe right. The HomeActivity has a top bar (used by Linerlayout) where there are 2 imagebuttons. When home1F is in the screen one button is shown(other is invisible) and when home2F is in the screen the other button is shown while the first one becomes invisible.
But when I run the app I find at first the second button is visible instead of the first one (when there is home1F). When i swipe to home2F nothing happens. When I swipe left to come back to home1F still nothing happens.
Here is the code:
private class MyPagerAdapter extends FragmentPagerAdapter {
public MyPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int pos) {
switch(pos) {
case 0:
//imagebutton-1 visible
//imagebutton-2 invisible
return Home1F.newInstance("FirstFragment, Instance 1");
case 1:
//imagebutton-1 invisible
//imagebutton-2 visible
return Home2F.newInstance("SecondFragment, Instance 1");
default:
//imagebutton-1 visible
//imagebutton-2 invisible
return Home1F.newInstance("FirstFragment, Instance 1");
}}
#Override
public int getCount() {
return 2;
}
}
Try this
Show the imageButton1 and hide the imageButton2 in onCreate of your Activity
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_scrolling);
imag1.setVisibility(View.VISIBLE);
imag2.setVisibility(View.GONE);
So when your app launch it will show the first imageButton and hide the second imageButton
Now when you switch between tabs
mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
#Override
public void onPageSelected(int position) {
if(position==0){
imag1.setVisibility(View.VISIBLE);
imag2.setVisibility(View.GONE);
}
else if(position==1){
imag1.setVisibility(View.GONE);
imag2.setVisibility(View.VISIBLE);
}
}
#Override
public void onPageScrollStateChanged(int state) {
}
});
if you want to switch out the actual fragments that are being displayed, you need to avoid FragmentPagerAdapter and use FragmentStatePagerAdapter.
using getItemPosition() we can update the fragments. In below example have updated fragment titles :
ViewPager pager = /* get my ViewPager */;
// assume this actually has stuff in it
final ArrayList<String> titles = new ArrayList<String>();
FragmentManager fm = getSupportFragmentManager();
pager.setAdapter(new FragmentStatePagerAdapter(fm) {
public int getCount() {
return titles.size();
}
public Fragment getItem(int position) {
MyFragment fragment = new MyFragment();
fragment.setTitle(titles.get(position));
return fragment;
}
public int getItemPosition(Object item) {
MyFragment fragment = (MyFragment)item;
String title = fragment.getTitle();
int position = titles.indexOf(title);
if (position >= 0) {
return position;
} else {
return POSITION_NONE;
}
}
});
I am currently using Material Design in an Android app that I am making. In this app, I am using the Material Design tab layout to display some information that I am receiving. However when I tap the tabs, the animation is not smooth, and it is very abrupt. Sliding to go to the other tab, however is very smooth.
mTabLayout = (TabLayout) findViewById(R.id.chem_tab_layout);
mGenericAdapter = new GenericPagerAdapter(getSupportFragmentManager());
mPager = (ViewPager) findViewById(R.id.view_pager);
mPager.setAdapter(mGenericAdapter);
//Notice how the Tab Layout links with the Pager Adapter
mTabLayout.setTabsFromPagerAdapter(mGenericAdapter);
//Notice how The Tab Layout and View Pager object are linked
mTabLayout.setupWithViewPager(mPager);
mPager.addOnPageChangeListener(new TabLayout.TabLayoutOnPageChangeListener(mTabLayout){
#Override
public void onPageSelected(int position) {
mGenericAdapter.notifyDataSetChanged();
}
});
That is my code for setting the adapter, etc.
This is my custom adapter code for the tabs:
class GenericPagerAdapter extends FragmentStatePagerAdapter {
public GenericPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
ChemGridActivity.MyFragment myFragment = new ChemGridActivity.MyFragment();
return myFragment;
}
#Override
public int getCount() {
return 3; //returns number of tabs that need to be created
}
#Override
public CharSequence getPageTitle(int position) {
if (position == 0) return "Chemistry";
if (position == 1) return "Mathematics";
if (position == 2) return "Physics";
else return null;
}
#Override
public int getItemPosition(Object object) {
return POSITION_NONE;
}
I feel that the choppy transition between tabs is caused by the overriden method onPageSelected method when I add onPageChangeListener. What do I add to this method to make tapping on tabs a smoother animation?
Without knowing much about the internals of your classes, I imagine the problem is not that you have a listener, but what you are doing inside that listener.
In the case of most adapters notifyDataSetChanged() will cause it to re-render the entire view again (including all pages).
Seeing as you haven't specified what the intent here with the notification is, it's hard to tell you how you can do this in an alternative way, but you do need to do something less intensive if you want the animation to remain smooth.
I suspect you just want to change which fragment is shown, in which case just use the FragmentManager where necessary, remembering to reuse fragments which have already been seen once.
EDIT Based on additional info in comments
#Override
public Fragment getItem(int position) {
//POSITION_SOMETHINHG would be one of a set of constants to indicate hwa to display
return ChemGridActivity.MyFragment.newInstance(ChemGridActivity.MyFragment.POSITION_SOMETHINHG);
}
public class ChemGridActivity.MyFragment ... {
private static final String KEY_DISPLAY_TYPE = "KEY_DISPLAY_TYPE";
public static final int POSITION_SOMETHINHG = 11111;
public static MyFragment newInstance(int display) {
MyFragment f = new MyFragment();
Bundle bund = new Bundle();
bund.putInt(KEY_DISPLAY_TYPE, display);
f.setArguments(bund);
return f;
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Bundle args = getArguments();
if (args != null) {
mDisplay = args.getInt(KEY_DISPLAY_TYPE, 0);
}
}
#Override
public View onCreateView(final LayoutInflater inflater, final ViewGroup container, final Bundle savedInstanceState) {
final View view = inflater.inflate(R.layout.my_layout, container, false);
//TODO: change something based on mDisplay
return view;
}
There are quite a few discussions around this topic
ViewPager PagerAdapter not updating the View
Update ViewPager dynamically?
Removing fragments from FragmentStatePagerAdapter
I have tried various solutions (including the invalidation with POSITION_NONE)
. But I still donT know how to remove an item properly.
What happens is
either I get a blank page (meaning the fragment is destroyed, but the
instantiateItem was not called for a replacement)
or the whole thing crashes probably because the way the Android manages the
fragment instances do not match how I keep them in my
arraylist/sparsearray
Here s my adapter
private class DatePickerPagerAdapter extends FragmentStatePagerAdapter {
ArrayList<Fragment> registeredFragments = new ArrayList<Fragment>();
public DatePickerPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
return CreateWishFormDatePaginationFragment.newInstance(position);
}
#Override
public int getItemPosition(Object object){ //doesnt change much..
return PagerAdapter.POSITION_NONE;
}
#Override
public int getCount() {
return iPageCount;
}
#Override
public Object instantiateItem(ViewGroup container, int position) {
Fragment fragment = (Fragment) super.instantiateItem(container, position);
registeredFragments.add(position, fragment);
return fragment;
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
super.destroyItem(container, position, registeredFragments.get(position));
}
public void removePage(ViewGroup pager, int position) {
destroyItem(pager, position, null);
registeredFragments.remove(position);
iPageCount--;
pagerIndicator.notifyDataSetChanged();
pagerAdapter.notifyDataSetChanged();
}
public void addPage() {
iPageCount++;
pagerIndicator.notifyDataSetChanged();
pagerAdapter.notifyDataSetChanged();
}
}
I am using a view pager with ViewPagerIndicator and I want to be able to remove a page in between, for example.
Hence remains the question, what is the proper way handling addition and removal of fragments in a ViewPager?
Thanks!
If you want to remove items from a ViewPager, this following code does not make sense:
#Override
public Fragment getItem(int position) {
return CreateWishFormDatePaginationFragment.newInstance(position);
}
Essentially, you create a Fragment based on the position. No matter which page you remove, the range of the position will change from [0, iPageCount) to [0, iPageCount-1), which means that it will always get rid of the last Fragment.
What you need is more or less the following:
public class DatePickerPagerAdapter extends FragmentStatePagerAdapter
{
private ArrayList<Integer> pageIndexes;
public DatePickerPagerAdapter(FragmentManager fm) {
super(fm);
pageIndexes = new ArrayList<>();
for (int i = 0; i < 10; i++) {
pageIndexes.add(new Integer(i));
}
}
#Override
public int getCount() {
return pageIndexes.size();
}
#Override
public Fragment getItem(int position) {
Integer index = pageIndexes.get(position);
return CreateWishFormDatePaginationFragment.newInstance(index.intValue());
}
// This is called when notifyDataSetChanged() is called
#Override
public int getItemPosition(Object object) {
// refresh all fragments when data set changed
return PagerAdapter.POSITION_NONE;
}
// Delete a page at a `position`
public void deletePage(int position)
{
// Remove the corresponding item in the data set
pageIndexes.remove(position);
// Notify the adapter that the data set is changed
notifyDataSetChanged();
}
}
Please refer to this complete example for more details about removing item from FragmentStatePagerAdapter.
The ViewPager doesn't remove your fragments with the code above because it loads several views (or fragments in your case) into memory. In addition to the visible view, it also loads the view to either side of the visible one. This provides the smooth scrolling from view to view that makes the ViewPager so cool.
To achieve the effect you want, you need to do a couple of things.
Change the FragmentPagerAdapter to a FragmentStatePagerAdapter. The reason for this is that the FragmentPagerAdapter will keep all the views that it loads into memory forever. Where the FragmentStatePagerAdapter disposes of views that fall outside the current and traversable views.
Override the adapter method getItemPosition (shown below). When we call mAdapter.notifyDataSetChanged(); the ViewPager interrogates the adapter to determine what has changed in terms of positioning. We use this method to say that everything has changed so reprocess all your view positioning.
And here's the code...
private class MyPagerAdapter extends FragmentStatePagerAdapter {
//... your existing code
#Override
public int getItemPosition(Object object){
return PagerAdapter.POSITION_NONE;
}
}
I have a viewpagerindicator by Jake Wharton and in that viewpager i have 6 pages.All of the pages contains listviews and i'm loading them with an AsyncTask.Sometimes viewpager shows wrong pages at wrong indexes.Here is my viewpager adapter:
public class TestFragmentAdapter extends FragmentPagerAdapter{
protected static final String[] CONTENT = new String[] { "a","b","c","d","e","f" };
private int mCount = CONTENT.length;
Fragment fragment1,fragment2,fragment3,fragment4,fragment5,fragment6;
public TestFragmentAdapter(FragmentManager fm) {
super(fm);
fragment1 = new CategoryListView();
fragment2 = CustomizedListviewRecent.newInstance(41);
fragment3 = CustomizedListviewMostRecents.newInstance(0);
fragment4 = CustomizedListviewPopulars.newInstance();
fragment5 = CustomizedListviewRecent.newInstance(42);
fragment6 = CustomizedListviewRecent.newInstance(43);
}
#Override
public Fragment getItem(int position) {
if(position == 0)
return fragment1;
else if(position == 1)
return fragment2;
else if(position == 2)
return fragment3;
else if(position == 3)
return fragment4;
else if(position == 4)
return fragment5;
else
return fragment6;
}
#Override
public int getCount() {
return mCount;
}
#Override
public CharSequence getPageTitle(int position) {
return TestFragmentAdapter.CONTENT[position % CONTENT.length];
}
public void setCount(int count) {
if (count > 0 && count <= 10) {
mCount = count;
notifyDataSetChanged();
}
}
}
Here is how i create my viewpager:
public class SampleTitlesDefault extends BaseSampleActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mAdapter = new TestFragmentAdapter(getSupportFragmentManager());
mPager = (ViewPager)findViewById(R.id.pager);
mPager.setAdapter(mAdapter);
mIndicator = (TitlePageIndicator)findViewById(R.id.indicator);
mIndicator.setViewPager(mPager);
mIndicator.setCurrentItem(1);
}
}
What can be problem here?How can i solve this?Thanks for your help
I know its too late but may be some one need it.
I have also faced the similar problem regarding wrong position and some time it shows same results on two fragments due to wrong positon.I solved my problem by replacing the FragmentPagerAdapter with FragmentStatePagerAdapter
And I also passed index postion from onPageScrolled method using OnPageChangeListener inteface
viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
#Override
public void onPageSelected(int position) {
bundle = new Bundle();
bundle.putInt("position", position);
primaryFragment.setCurrentPostionAndData(position);
}
#Override
public void onPageScrollStateChanged(int state) {
}
});
Like the docs say, think about it this way. If you were to do an application like a book reader, you will not want to load all the fragments into memory at once. You would like to load and destroy Fragments as the user reads. In this case you will use FragmentStatePagerAdapter. If you are just displaying 3 "tabs" that do not contain a lot of heavy data (like Bitmaps), then FragmentPagerAdapter might suit you well. Also, keep in mind that ViewPager by default will load 3 fragments into memory. The first Adapter you mention might destroy View hierarchy and re load it when needed, the second Adapter only saves the state of the Fragment and completely destroys it, if the user then comes back to that page, the state is retrieved.
Difference between FragmentPagerAdapter and FragmentStatePagerAdapter
Please don't "cache" the Fragments yourself because it already does that for you.
That said, you should newInstance them inside the getItem method, and not when creating the adapter. Example:
public TestFragmentAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
if(position == 0)
return new CategoryListView();
else if(position == 1)
return CustomizedListviewRecent.newInstance(41);
else if(position == 2)
return CustomizedListviewMostRecents.newInstance(0);
else if(position == 3)
return CustomizedListviewPopulars.newInstance();
else if(position == 4)
return CustomizedListviewRecent.newInstance(42);
else
return CustomizedListviewRecent.newInstance(43);
}
Common reasons to do that are:
People think it's more efficient to do it that way; or
A reference to the Fragment may be needed later on.
For (1), as I said, you don't need to worry about it (unless you want to learn it and tweak later, but for now let it be). And if you need (2), then you can do this (originally seen here).