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).
Related
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;
}
}
});
Im using my Custom FragmentAdapter to hold the fragment into a List
The Process i want:
1) User click in a list item and it create a new Activity within arguments and than create the fragment using the TagsPageAdapter.addPage(fragment);
2) This created fragment has a "id" int inside his arguments
3) When i hit the addPage, it verify all the fragments inside the List to check if any fragments has the id, if yes, restore it, if not, than create new and add to the list
this 3 steps are working perfeclty, the problem is when i try to remove one (error after the adapter code below)
adapter:
private class TabsPagerAdapter extends FragmentPagerAdapter {
private Bundle bundle;
private String titulo;
private TabsPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public LeiAberta getItem(int position) {
current = fragments.get(position);
return current;
}
private void addPage(LeiAberta page) {
boolean found = false;
for (int i = 0; i < fragments.size(); i++) {
leiAberta = fragments.get(i);
if (leiAberta != null) {
if (leiAberta.getArguments() != null) {
if (leiAberta.getArguments().getInt("id") == page.getArguments().getInt("id")) {
viewPager.setCurrentItem(i);
found = true;
}
}
}
}
if (!found) {
fragments.add(page);
notifyDataSetChanged();
viewPager.setCurrentItem(fragments.size() - 1);
}
}
#Override
public int getCount() {
return fragments.size();
}
#Override
public CharSequence getPageTitle(int position) {
this.bundle = fragments.get(position).getArguments();
if (this.bundle != null) {
this.titulo = bundle.getString("titulo");
}
return titulo;
}
}
what im using to remove the fragment from the list:
int current_position = viewPager.getCurrentItem();
if(current_position == 0) {
viewPager.setCurrentItem(current_position +1);
} else {
viewPager.setCurrentItem(current_position -1);
}
fragments.remove(current_position);
adapter.notifyDataSetChanged();
if i remove the current tab/fragment, and then open another item in the list that will add another tab, no matter what item, it just restores the old fragment in the same position, for some reason the position of the removed fragment in the List keep filled in the shadows, and the strange thing is: the list.size(); decrease when i remove, so WHY? From where is this old fragment restored? how can i really destroy it, so the addPage doesnt find it in the "shadows"
i had tried:
Update ViewPager dynamically?
Removing fragments from FragmentStatePagerAdapter
Remove Fragment Page from ViewPager in Android
How to remove pages in Android ViewPager?
and many others
apparently the only solution was this framework:
https://github.com/inloop/UpdatableFragmentStatePagerAdapter?utm_source=android-arsenal.com&utm_medium=referral&utm_campaign=4851
using this override
#Override
public int getItemPosition(Object object) {
if (fragments.contains(object)) return fragments.indexOf(object);
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;
}
I was just wondering if it is the normal behaviour of viewpager and its adapter to always call the getItem() method for index 0 and 1, even if I immediately set a current position.
Here is my code:
mNewsPagerAdapter = new NewsDetailPagerAdapter(getChildFragmentManager());
mNewsPagerAdapter.updateNewsList(news);
mViewPager = (ViewPager) mView.findViewById(R.id.horizontal_view_pager);
mViewPager.setPageMargin(2);
mViewPager.setPageMarginDrawable(R.color.black);
mViewPager.setAdapter(mNewsPagerAdapter);
mViewPager.setCurrentItem(mCurrentPositionPager, false);
If I switch from my overview activity to my detail activity with this viewpager, the adapter always calls the getItem() method for position 0 and 1 and after that the getItem() method for the position of mOriginalPosition and its neighbors. I was wondering if this is the correct behaviour or if I missed something to implement it in a right way. Thanks for your help :)
Edit: Added my adapter code
public class NewsDetailPagerAdapter extends FragmentStatePagerAdapter {
private SparseArray<Fragment> mPageReferenceMap = new SparseArray<Fragment>();
private ArrayList<News> mNewsList;
public NewsDetailPagerAdapter(FragmentManager fm) {
super(fm);
}
/**
* Setzt die neuen News.
**/
public void updateNewsList(ArrayList<News> list) {
mNewsList = list;
}
#Override
public Fragment getItem(int position) {
Log.d("debug", "getItem position:" + position);
News newsItem = mNewsList.get(position);
NavigationFragment fragment = new NavigationFragment();
mPageReferenceMap.put(position, fragment);
return fragment;
}
#Override
public int getCount() {
return mNewsList.size();
}
#Override
public int getItemPosition(Object object) {
return POSITION_NONE;
}
public Fragment getFragment(int position) {
return mPageReferenceMap.get(position);
}
}
It is normal (and intelligent in my opinion).
ViewPager class has one property named mOffscreenPageLimit with default value of 1. This number determines how many pages on the left and on the right of the current page that the Viewpager will preload. For instance, you have 10 pages, current position is 5 and mOffcreenPageLimit is 1, the page at position 4 and 6 will be loaded.
You could change this property by calling this method
viewpager. setOffscreenPageLimit(int)
If you pass in an integer that is smaller than 1, it has no effect.
Yes, this is the normal behaviour of the ViewPager, because it will always try to stay ahead of the user by rendering tabs that limit with the drawing area. I personally don't recommend creating a custom ViewPager as you are almost sure to break functionality unless you really know what you are doing. Your adapter class should look something like this:
public class YourCustomPagerAdapter extends FragmentStatePagerAdapter {
private List<Fragment> fragmentList = new ArrayList<>();
private List<String> titleList = new ArrayList<>();
public WizardPagerAdapter(FragmentManager fm) {
super(fm);
}
public void addFragment(Fragment fragment, String title) {
fragmentList.add(fragment);
titleList.add(title);
}
#Override
public Fragment getItem(int position) {
return fragmentList.get(position);
}
#Override
public CharSequence getPageTitle(int position) {
super.getPageTitle(position);
return titleList.get(position);
}
#Override
public int getCount() {
return fragmentList.size();
}
}
and you should add your fragments as such:
#Override
public void onCreate(Bundle savedInstanceState) {
...
YourCustomPagerAdapter adapter = new YourCustomPagerAdapter (getSupportFragmentManager());
adapter.addFragment(FragmentOne.newInstance(), "Frag 1");
adapter.addFragment(FragmentTwo.newInstance(), "Frag 2");
viewPager.setAdapter(adapter);
...
}
Actually this is the normale behavior. In fact, as soos as you associate the ViewPager with the adapter, the adapter creates the first visibile layout (index 0) end the next one (index 1). This is done by default in the "setAdapter". Then, when you set a different position, the adapter will instantiate the fragment at the selected index, the previous one and the next one.
This is the usual ViewPager setAdapter code:
public void setAdapter(PagerAdapter adapter) {
if (mAdapter != null) {
mAdapter.setViewPagerObserver(null);
mAdapter.startUpdate(this);
for (int i = 0; i < mItems.size(); i++) {
final ItemInfo ii = mItems.get(i);
mAdapter.destroyItem(this, ii.position, ii.object);
}
mAdapter.finishUpdate(this);
mItems.clear();
removeNonDecorViews();
mCurItem = 0;
scrollTo(0, 0);
}
final PagerAdapter oldAdapter = mAdapter;
mAdapter = adapter;
mExpectedAdapterCount = 0;
if (mAdapter != null) {
if (mObserver == null) {
mObserver = new PagerObserver();
}
mAdapter.setViewPagerObserver(mObserver);
mPopulatePending = false;
final boolean wasFirstLayout = mFirstLayout;
mFirstLayout = true;
mExpectedAdapterCount = mAdapter.getCount();
if (mRestoredCurItem >= 0) {
mAdapter.restoreState(mRestoredAdapterState, mRestoredClassLoader);
setCurrentItemInternal(mRestoredCurItem, false, true);
mRestoredCurItem = -1;
mRestoredAdapterState = null;
mRestoredClassLoader = null;
} else if (!wasFirstLayout) {
populate();
} else {
requestLayout();
}
}
if (mAdapterChangeListener != null && oldAdapter != adapter) {
mAdapterChangeListener.onAdapterChanged(oldAdapter, adapter);
}
}
In order to change the ViewPager behavior, you could extend the classic ViewPager overriding the setAdapter method and set the mCurrItem to the desired position.
I hope it helped
Edit:
After different tests, we found a solution.
If the ViewPager adapter is set after ViewPager layout become visible, items 0 and 1 are load.
If you want to avoid this behavior but you can't set the adapter before the layout become visible (because you are waiting for data), than you can use this workaround:
1) Set the ViewPager visibility initially to GONE
2) After you receive all the data, you update the adapter and you set the current item value
3) Finally you set the ViewPager visibility to VISIBLE
Here you can find an example:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.detail_overview_fragment, container, false);
final int position = getArguments().getInt("position");
final ViewPager viewPager = (ViewPager) v.findViewById(R.id.viewpager);
viewPager.setVisibility(View.GONE);
Handler handler = new Handler();
handler.postDelayed(new Runnable() {
#Override
public void run() {
viewPager.setAdapter(new PagerAdapter(getChildFragmentManager()));
viewPager.setCurrentItem(position);
viewPager.setVisibility(View.VISIBLE);
}
},5000);
return v;
}
i think the error is the adapter:
/**
* Setzt die neuen News.
**/
public void updateNewsList(ArrayList<News> list) {
//mNewsList = list;
mNewsList.clear();
mNewsList.addAll(list);
/**
* Notifies the attached observers that the underlying data has been changed
* and any View reflecting the data set should refresh itself.
*/
this.notifyDataSetChanged();
}
error reason :this list is diffent entity for that adapter.
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'