If we have 4 pages in a View pager and swipe from positions 0 do 3
Item at position 0 is destroyed when we get to position 2, item at position 1 is destroyed when we get to position 4.
I need them to be destroyed right away, because I want the view to be recreated if I go back to it, which doesnt happen at the moment.
public class ViewPagerTutorialAdapter extends FragmentStatePagerAdapter {
public ViewPagerTutorialAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
Bundle args = new Bundle();
args.putInt("page", position);
TutorialPageFragment fragment = new TutorialPageFragment();
fragment.setArguments(args);
return fragment;
}
#Override
public int getCount() {
return 4;
}
}
public class TutorialPageFragment extends android.support.v4.app.Fragment {
private ImageView ivTutorialPage;
private TransitionDrawable myTransitionDrawable;
private View rootView;
public TutorialPageFragment() {
}
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
rootView = inflater.inflate(R.layout.fragment_tutorial_page, container, false);
return rootView;
}
#Override
public void onResume() {
super.onResume();
ivTutorialPage = (ImageView) rootView.findViewById(R.id.ivTutorialPage);
ivTutorialPage.setImageResource(R.drawable.fadein);
myTransitionDrawable = (TransitionDrawable) ivTutorialPage.getDrawable();
new Timer().schedule(new TimerTask() {
#Override
public void run() {
getActivity().runOnUiThread(new Runnable() {
#Override
public void run() {
myTransitionDrawable.startTransition(1000);
}
});
}
}, 1000);
}
}
While this is not exactly answer to asked question, a better practice is not recreate Views if not necessary because it can be expensive to do on more complicated layouts + cause stuttering / extra lag which makes user experience worse.
In this case (running animations when fragment becomes visible) it's better to override setUserVisibleHint in fragment with code to run animation which will trigger when user visits the screen. The main advantage is of course no need to recreate Views and allows Android to perform it's internal optimization naturally.
Try viewPager.setOffscreenPageLimit(1);. This should save only one page near selected one. There is not way to only load selected item at a time according to this.
Context:
I'm making a music player and I'm currently working on the miniplayer. The miniplayer lives in a ViewPager along with a floating action button (FAB) for shuffle. Initially, only the FAB is seen on screen. When you click on the FAB, music playback starts, the Fab-Miniplayer ViewPager's page count goes from 1 to 2, and its page is set to the second page, where the miniplayer is. The miniplayer itself in the second page is a vertical viewpager, so one can swipe through to different songs. The miniplayer/vertical viewpager is backed by a PagerAdapter that gets songs from a SongQueue obejct.
The Problem:
If I click on the FAB once, the miniplayer opens and everything works. Then I dismiss the miniplayer to stop playback by swiping back to the FAB page in the ViewPager. After clicking on the FAB to start playback and open the miniplayer for the second time, the first two fragments are never created. The miniplayer vertical ViewPager's PagerAdapter's getItem() methods are never called for item 0 and item 1. On a higher level, the first two songs in the miniplayer aren't showing up. Upon further investigation, the onCreate() methods are being called for the first two songs from the last time the miniplayer was opened, so I'm guessing the ViewPager is implementing some kind of caching or a reference is being held onto for too long because the miniplayer vertical viewpager thinks that it already has the first two songs the second time it is instantiated. Hopefully this problem description makes sense. If there are any questions, I'd be happy to try and go into more detail.
The Code
SongsFragment
/**
* Fragment used to display all of the songs on the device.
*/
public class SongsFragment extends Fragment implements LoaderManager.LoaderCallbacks<Cursor> {
private static final String TAG = "SongsFragment";
private SwipeListView mSwipeListView;
private ViewPager fabMiniPlayerViewPager;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
setHasOptionsMenu(true);
// Initialize the loader to load the list of songs
getLoaderManager().initLoader(SongCursorLoader.ALL_SONGS_ID, null, this);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.list_view_songs, parent, false);
if (view != null) {
mSwipeListView = (SwipeListView) view.findViewById(R.id.list_view_songs);
}
LinearLayout emptyView = (LinearLayout) inflater.inflate(R.layout.list_view_songs_empty, parent, false);
mSwipeListView.setEmptyView(emptyView);
fabMiniPlayerViewPager = (ViewPager) view.findViewById(R.id.fab_miniplayer_ViewPager);
Integer viewPagerPageCount = new Integer(1);
fabMiniPlayerViewPager.setTag(viewPagerPageCount); // The fabMiniPlayerViewPager gets it's page count from the tag so it can be dynamically modified
fabMiniPlayerViewPager.setAdapter(new FragmentStatePagerAdapter(getChildFragmentManager()) {
#Override
public Fragment getItem(int i) {
if (i == 0) {
// ShuffleFabFragment just houses a single button, the code is below
ShuffleFabFragment fragment = new ShuffleFabFragment();
fragment.setViewPager(fabMiniPlayerViewPager);
return fragment;
}
MiniPlayerFragment fragment = new MiniPlayerFragment();
return fragment;
}
#Override
public int getCount() {
Integer count = (Integer) fabMiniPlayerViewPager.getTag();
return count.intValue();
}
});
fabMiniPlayerViewPager.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View view, MotionEvent motionEvent) {
// Pass touch events to the list view behind it
mSwipeListView.onTouchEvent(motionEvent);
return false;
}
});
return view;
}
#Override
public void onResume() {
super.onResume();
if (!MusicPlayerService.isStopped()) {
Integer viewPagerPageCount = 2;
fabMiniPlayerViewPager.setTag(viewPagerPageCount);
PagerAdapter adapter = fabMiniPlayerViewPager.getAdapter();
fabMiniPlayerViewPager.setAdapter(adapter);
fabMiniPlayerViewPager.setCurrentItem(1, false);
} else {
Integer viewPagerPageCount = 1;
fabMiniPlayerViewPager.setTag(viewPagerPageCount);
fabMiniPlayerViewPager.getAdapter().notifyDataSetChanged();
}
}
}
ShuffleFabFragment
/**
* Fragment representing the Shuffle Floating Action Button. On FAB click, it turns on shuffle and
* plays a random song. Depending on the user preference, the mini-player will appear or the
* Now Playing view will appear. The ShuffleFabFragment holds a reference to its containing
* ViewPager so it can do the following:
* <p/>
* 1. Add the MiniPlayer fragment to the ViewPager to enable swiping & animations
* 2. Remove the MiniPlayer fragment when it is swiped away
* <p/>
* Once the MiniPlayer is swiped away (to the right), it stops music playback.
*/
public class ShuffleFabFragment extends Fragment {
private static final String TAG = "ShuffleFabFragment";
private ViewPager fabMiniPlayerViewPager;
public void setViewPager(ViewPager viewPager) {
fabMiniPlayerViewPager = viewPager;
fabMiniPlayerViewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
#Override
public void onPageScrolled(int i, float v, int i2) {
}
#Override
public void onPageSelected(int i) {
if (i == 0) { // MiniPlayer swiped away
Integer viewPagerPageCount = new Integer(1);
fabMiniPlayerViewPager.setTag(viewPagerPageCount);
PagerAdapter adapter = fabMiniPlayerViewPager.getAdapter();
Intent stopMusicIntent = new Intent(getActivity(), MusicPlayerService.class);
stopMusicIntent.setAction(MusicPlayerService.ACTION_STOP);
getActivity().startService(stopMusicIntent);
fabMiniPlayerViewPager.setAdapter(adapter);
}
}
#Override
public void onPageScrollStateChanged(int i) {
}
});
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup viewGroup, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.shuffle_fab, viewGroup, false);
ImageView shuffleFabImageView = (ImageView) view.findViewById(R.id.shuffle_fab_ImageView);
shuffleFabImageView.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View view, MotionEvent motionEvent) {
switch (motionEvent.getAction()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
// Scale up on touch to make the button appear to come closer
view.setScaleX(8f / 7f);
view.setScaleY(8f / 7f);
break;
case MotionEvent.ACTION_UP:
view.setScaleX(1f);
view.setScaleY(1f);
break;
default:
view.setScaleX(1f);
view.setScaleY(1f);
}
return false;
}
});
shuffleFabImageView.setClickable(true);
shuffleFabImageView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
String shuffleMode = PreferencesHelper.getShuffleMode(getActivity());
if (shuffleMode.equals(PreferencesHelper.SHUFFLE_MODE_OFF)) {
PreferencesHelper.setShuffleMode(getActivity(), PreferencesHelper.SHUFFLE_MODE_SMART);
}
SongQueue.initializeQueue(null, true, Song.COLLECTION_TYPE_ALL_SONGS, getActivity().getApplicationContext(), null);
Song firstSong = SongQueue.getSong(0);
firstSong.play(getActivity(), false);
// On Click, update the page count, then set page to miniplayer
Integer viewPagerPageCount = new Integer(2);
fabMiniPlayerViewPager.setTag(viewPagerPageCount);
fabMiniPlayerViewPager.getAdapter().notifyDataSetChanged();
fabMiniPlayerViewPager.setCurrentItem(1, true);
}
});
return view;
}
}
MiniPlayerFragment
/**
* Hosts MiniplayerCardFragments and allows for song skipping
*/
public class MiniPlayerFragment extends Fragment implements ViewPager.OnPageChangeListener {
VerticalViewPager miniplayerCardViewPager;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup viewGroup, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.view_pager_miniplayer, viewGroup, false);
miniplayerCardViewPager = (VerticalViewPager) view.findViewById(R.id.miniplayer_cards_ViewPager);
miniplayerCardViewPager.setAdapter(new FragmentStatePagerAdapter(getChildFragmentManager()) {
#Override
public Fragment getItem(int position) {
Song song = SongQueue.getSong(position);
return MiniPlayerCardFragment.newInstance(song);
}
#Override
public int getCount() {
return SongQueue.getTotalSize();
}
});
miniplayerCardViewPager.setOnPageChangeListener(this);
SongQueue.addOnQueueChangeListener(new SongQueue.OnQueueChangeListener() {
#Override
public void onNextSongChanged() {
onQueueChanged();
}
#Override
public void onQueueChanged() {
// Force re-layout to update fragments
int queuePosition = SongQueue.getQueuePositionCurrentSong();
PagerAdapter adapter = miniplayerCardViewPager.getAdapter();
miniplayerCardViewPager.setAdapter(adapter);
miniplayerCardViewPager.setCurrentItem(queuePosition, true);
}
#Override
public void onCurrentSongChanged() {
onQueueChanged();
}
});
return view;
}
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
#Override
public void onPageSelected(int position) {
int queuePosition = SongQueue.getQueuePositionCurrentSong();
if (queuePosition < position) {
Intent nextIntent = new Intent(getActivity(), MusicPlayerService.class);
nextIntent.setAction(MusicPlayerService.ACTION_NEXT);
getActivity().startService(nextIntent);
} else if (queuePosition > position) {
Intent prevIntent = new Intent(getActivity(), MusicPlayerService.class);
prevIntent.setAction(MusicPlayerService.ACTION_PREV);
getActivity().startService(prevIntent);
}
}
#Override
public void onPageScrollStateChanged(int state) {
}
#Override
public void onResume() {
super.onResume();
// Force re-layout to update fragments
int queuePosition = SongQueue.getQueuePositionCurrentSong();
PagerAdapter adapter = miniplayerCardViewPager.getAdapter();
miniplayerCardViewPager.setAdapter(adapter);
miniplayerCardViewPager.setCurrentItem(queuePosition, true);
SongQueue.setPagerAdapter(adapter);
}
}
Screenshots to help visualization
After clicking on the FAB to start playback and open the miniplayer for the second time, the first two fragments are never created. The miniplayer vertical ViewPager's PagerAdapter's getItem() methods are never called for item 0 and item 1.
That is because the standard ViewPager already has pages 0 and 1. To clarify Sanket's answer, setOffscreenPageLimit() indicates how many pages to either side of the current page to create (via getItem()) and cache, and the minimum value is 1. Calls to setOffscreenPageLimit(0) will be ignored.
getItem() is not a means of finding out when the user switches pages. A PageChangeListener is your means of finding out when the user switches pages.
the miniplayer vertical viewpager thinks that it already has the first two songs the second time it is instantiated
It's not being instantiated a second time AFAICT.
ViewPager has bydefault offScreenPagerLimit value 3.. so it may be not loading your first 2 item second time..
try with this
mViewPager.setOffscreenPageLimit(0);
more info
I'm trying to build an app that shows a book in portrait and landscape mode. So obviously there's 1 page showed in portrait and 2 in landscape. Each of the mode works pretty good but when i change the orientation from portrait to landscape the viewPager try to get 2 pages from the portrait mode instead of trying to get 2 double pages from the landscape mode ...
In fact FragmentStatePagerAdapter keeps The 2 fragments created in portrait and uses them before creating 2 double page for the landscape mode. If i switch again in portrait mode, FragmentStatePagerAdapter uses the 2 fragments previously created in landscape mode so i see again 1 double page instead of 1 single page etc.. if i continue switching orientation, i get an OutOfMemoryError due to the fact that FragmentStatePagerAdapter never flush it's fragments on orientation change.
Here's 2 use cases for easier understanding :
i launch viewPager in portrait mode
i see page 1 ok
i swipe to right and i see page 2 ok
i swipe to right and i see page 3 ok
i rotate the screen to landscape mode
i see page 3 wrong
i swipe to right and i see page 4 wrong
i swipe to right and i see page 5 and 6 ok
i launch viewPager in portrait mode
i see page 1 ok
i swipe to right and i see page 2 ok
i swipe to right and i see page 3 ok
i swipe to right and i see page 4 ok
i swipe to right and i see page 5 ok
i rotate the screen to landscape mode
i see page 5 wrong
i swipe to left and i see page 4 wrong
i swipe to left and i see page 2 and 3 ok
public class PlayerFragmentActivity extends FragmentActivity {
private Intent mIntent;
private ViewPager mPortraitPager;
private ViewPager mLandscapePager;
private Boolean mIsLandscape;
private String mKey;
private int mNbPages;
private int mNbDoublePages;
private PageFactory mPageFactory;
private DoublePageFactory mDoublePageFactory;
private PagerAdapter mPagerAdapter;
#Override
protected void onStop(){
super.onStop();
mPagerAdapter = null;
mDoublePageFactory = null;
mPageFactory = null;
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.fragment_activity_player);
mIntent = getIntent();
mKey = mIntent.getStringExtra("key");
mNbPages = mIntent.getIntExtra("nbPages", 0);
mNbDoublePages = DoublePageFactory.getDoublePageNumFromPageNum(mNbPages);
Resources res = getResources();
mIsLandscape = (res.getConfiguration().orientation == 1) ? false : true;
mPortraitPager = (ViewPager) findViewById(R.id.portraitPager);
mLandscapePager = (ViewPager) findViewById(R.id.landscapePager);
mPagerAdapter = new MyPagerAdapter(getSupportFragmentManager());
if (mIsLandscape) {
mDoublePageFactory = new DoublePageFactory(this, mKey, mNbPages, res.getInteger(R.integer.nb_page_columns), res.getInteger(R.integer.nb_page_columns));
mPortraitPager.setVisibility(View.GONE);
mLandscapePager.setAdapter(mPagerAdapter);
mPortraitPager.setAdapter(null);
} else {
mPageFactory = new PageFactory(this, mKey, this.mNbPages, res.getInteger(R.integer.nb_page_columns), res.getInteger(R.integer.nb_page_columns));
mLandscapePager.setVisibility(View.GONE);
mPortraitPager.setAdapter(mPagerAdapter);
mLandscapePager.setAdapter(null);
}
}
public class MyPagerAdapter extends FragmentStatePagerAdapter {
public MyPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
Bundle args = new Bundle();
if(mIsLandscape){
Fragment doublePageFragment = new DoublePageFragment();
args.putInt(DoublePageFragment.ARG_DOUBLEPAGE_NUM, position + 1);
doublePageFragment.setArguments(args);
return doublePageFragment;
}else{
Fragment pageFragment = new PageFragment();
args.putInt(PageFragment.ARG_PAGE_NUM, position + 1);
pageFragment.setArguments(args);
return pageFragment;
}
}
#Override
public int getCount() {
return (mIsLandscape) ? mNbDoublePages:mNbPages;
}
/* j'ai essayé cette méthode mais ça ne fonctionne pas :( */
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
FragmentManager manager = ((Fragment) object).getFragmentManager();
FragmentTransaction trans = manager.beginTransaction();
trans.remove((Fragment) object);
trans.commit();
super.destroyItem(container, position, object);
}
#Override
public int getItemPosition(Object object){
return PagerAdapter.POSITION_NONE;
}
#Override
public CharSequence getPageTitle(int position) {
return "p." + position + 1;
}
}
public boolean isLandscape() {
return mIsLandscape;
}
public ImageView getSinglePage(int position) {
return mPageFactory.getPage(position);
}
public LinearLayout getDoublePage(int position) {
return mDoublePageFactory.getDoublePage(position);
}
}
public class PageFragment extends Fragment {
private PlayerFragmentActivity mPlayerFragmentActivity;
public static final String ARG_PAGE_NUM = "page_number";
public static final String ARG_WOBOOK_DIRECTORY = "book_directory";
public static final String ARG_NB_PAGE_COLUMNS = "nb_page_columns";
public static final String ARG_NB_PAGE_ROWS = "nb_page_rows";
#Override
public ImageView onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
mPlayerFragmentActivity = ((PlayerFragmentActivity) getActivity());
return mPlayerFragmentActivity.getSinglePage(getArguments().getInt(ARG_PAGE_NUM));
}
#Override
public void onStop(){
super.onStop();
mPlayerFragmentActivity = null;
}
}
public class DoublePageFragment extends Fragment {
private PlayerFragmentActivity mPlayerFragmentActivity;
public static final String ARG_DOUBLEPAGE_NUM = "double_page_number";
public static final String ARG_WOBOOK_DIRECTORY = "book_directory";
public static final String ARG_NB_PAGE_COLUMNS = "nb_page_columns";
public static final String ARG_NB_PAGE_ROWS = "nb_page_rows";
#Override
public LinearLayout onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
mPlayerFragmentActivity = ((PlayerFragmentActivity) getActivity());
return mPlayerFragmentActivity.getDoublePage(getArguments().getInt(ARG_DOUBLEPAGE_NUM));
}
#Override
public void onStop(){
super.onStop();
mPlayerFragmentActivity = null;
}
}
i've read that the FragmentManager in the framework takes care of saving the state and restore any active fragments that the pager has made. So i guess that when the orientation changes, the FragmentManager says
Hey ! I already have 2 items created so i don't need to create news,
lets get them
But in fact it gets the 2 items (page/double page) from the old orientation mode. So i need to fnd a way to delete the fragments saved by the FragmentManager when the orientation change occurs.
I've also tested this method. The problem seems to be related to this one
Best regards
Try to limit orientation to either portrait or Landscape mode.Because we can use view pager only in exact mode.
I have narrowed my problem down to being a problem with the FragmentManager retaining instances of old fragments and my viewpager being out of sync with my FragmentManager. See this issue: http://code.google.com/p/android/issues/detail?id=19211#makechanges. I still have no clue how to solve this. Any suggestions?
I have tried to debug this for a long time and any help would be greatly appreciated. I am using a FragmentPagerAdapter which accepts a list of fragments like so:
List<Fragment> fragments = new Vector<Fragment>();
fragments.add(Fragment.instantiate(this, Fragment1.class.getName()));
...
new PagerAdapter(getSupportFragmentManager(), fragments);
The implementation is standard. I am using ActionBarSherlock and v4 computability library for Fragments.
My problem is that after leaving the app and opening several other applications and coming back, the fragments lose their reference back to the FragmentActivity (ie. getActivity() == null). I can not figure out why this is happening. I tried to manually set setRetainInstance(true); but this does not help. I figured that this happens when my FragmentActivity gets destroyed, however this still happens if I open the app before I get the log message. Are there any ideas?
#Override
protected void onDestroy(){
Log.w(TAG, "DESTROYDESTROYDESTROYDESTROYDESTROYDESTROYDESTROY");
super.onDestroy();
}
The adapter:
public class PagerAdapter extends FragmentPagerAdapter {
private List<Fragment> fragments;
public PagerAdapter(FragmentManager fm, List<Fragment> fragments) {
super(fm);
this.fragments = fragments;
}
#Override
public Fragment getItem(int position) {
return this.fragments.get(position);
}
#Override
public int getCount() {
return this.fragments.size();
}
}
One of my Fragments stripped but I commented everything out that's stripped and it still doesn't work.
public class MyFragment extends Fragment implements MyFragmentInterface, OnScrollListener {
...
#Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
handler = new Handler();
setHasOptionsMenu(true);
}
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
Log.w(TAG,"ATTACHATTACHATTACHATTACHATTACH");
context = activity;
if(context== null){
Log.e("IS NULL", "NULLNULLNULLNULLNULLNULLNULLNULLNULLNULLNULL");
}else{
Log.d("IS NOT NULL", "NOTNOTNOTNOTNOTNOTNOTNOT");
}
}
#Override
public void onActivityCreated(Bundle savedState) {
super.onActivityCreated(savedState);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.my_fragment,container, false);
return v;
}
#Override
public void onResume(){
super.onResume();
}
private void callService(){
// do not call another service is already running
if(startLoad || !canSet) return;
// set flag
startLoad = true;
canSet = false;
// show the bottom spinner
addFooter();
Intent intent = new Intent(context, MyService.class);
intent.putExtra(MyService.STATUS_RECEIVER, resultReceiver);
context.startService(intent);
}
private ResultReceiver resultReceiver = new ResultReceiver(null) {
#Override
protected void onReceiveResult(int resultCode, final Bundle resultData) {
boolean isSet = false;
if(resultData!=null)
if(resultData.containsKey(MyService.STATUS_FINISHED_GET)){
if(resultData.getBoolean(MyService.STATUS_FINISHED_GET)){
removeFooter();
startLoad = false;
isSet = true;
}
}
switch(resultCode){
case MyService.STATUS_FINISHED:
stopSpinning();
break;
case SyncService.STATUS_RUNNING:
break;
case SyncService.STATUS_ERROR:
break;
}
}
};
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
menu.clear();
inflater.inflate(R.menu.activity, menu);
}
#Override
public void onPause(){
super.onPause();
}
public void onScroll(AbsListView arg0, int firstVisible, int visibleCount, int totalCount) {
boolean loadMore = /* maybe add a padding */
firstVisible + visibleCount >= totalCount;
boolean away = firstVisible+ visibleCount <= totalCount - visibleCount;
if(away){
// startLoad can now be set again
canSet = true;
}
if(loadMore)
}
public void onScrollStateChanged(AbsListView arg0, int state) {
switch(state){
case OnScrollListener.SCROLL_STATE_FLING:
adapter.setLoad(false);
lastState = OnScrollListener.SCROLL_STATE_FLING;
break;
case OnScrollListener.SCROLL_STATE_IDLE:
adapter.setLoad(true);
if(lastState == SCROLL_STATE_FLING){
// load the images on screen
}
lastState = OnScrollListener.SCROLL_STATE_IDLE;
break;
case OnScrollListener.SCROLL_STATE_TOUCH_SCROLL:
adapter.setLoad(true);
if(lastState == SCROLL_STATE_FLING){
// load the images on screen
}
lastState = OnScrollListener.SCROLL_STATE_TOUCH_SCROLL;
break;
}
}
#Override
public void onDetach(){
super.onDetach();
if(this.adapter!=null)
this.adapter.clearContext();
Log.w(TAG, "DETACHEDDETACHEDDETACHEDDETACHEDDETACHEDDETACHED");
}
public void update(final int id, String name) {
if(name!=null){
getActivity().getSupportActionBar().setTitle(name);
}
}
}
The update method is called when a user interacts with a different fragment and the getActivity is returning null. Here is the method the other fragment is calling:
((MyFragment) pagerAdapter.getItem(1)).update(id, name);
I believe that when the app is destroyed then created again instead of just starting the app up to the default fragment the app starts up and then viewpager navigates to the last known page. This seems strange, shouldn't the app just load to the default fragment?
You are running into a problem because you are instantiating and keeping references to your fragments outside of PagerAdapter.getItem, and are trying to use those references independently of the ViewPager. As Seraph says, you do have guarantees that a fragment has been instantiated/added in a ViewPager at a particular time - this should be considered an implementation detail. A ViewPager does lazy loading of its pages; by default it only loads the current page, and the one to the left and right.
If you put your app into the background, the fragments that have been added to the fragment manager are saved automatically. Even if your app is killed, this information is restored when you relaunch your app.
Now consider that you have viewed a few pages, Fragments A, B and C. You know that these have been added to the fragment manager. Because you are using FragmentPagerAdapter and not FragmentStatePagerAdapter, these fragments will still be added (but potentially detached) when you scroll to other pages.
Consider that you then background your application, and then it gets killed. When you come back, Android will remember that you used to have Fragments A, B and C in the fragment manager and so it recreates them for you and then adds them. However, the ones that are added to the fragment manager now are NOT the ones you have in your fragments list in your Activity.
The FragmentPagerAdapter will not try to call getPosition if there is already a fragment added for that particular page position. In fact, since the fragment recreated by Android will never be removed, you have no hope of replacing it with a call to getPosition. Getting a handle on it is also pretty difficult to obtain a reference to it because it was added with a tag that is unknown to you. This is by design; you are discouraged from messing with the fragments that the view pager is managing. You should be performing all your actions within a fragment, communicating with the activity, and requesting to switch to a particular page, if necessary.
Now, back to your problem with the missing activity. Calling pagerAdapter.getItem(1)).update(id, name) after all of this has happened returns you the fragment in your list, which has yet to be added to the fragment manager, and so it will not have an Activity reference. I would that suggest your update method should modify some shared data structure (possibly managed by the activity), and then when you move to a particular page it can draw itself based on this updated data.
I found simple solution which worked for me.
Make your fragment adapter extends FragmentStatePagerAdapter instead of FragmentPagerAdapter and override method onSave to return null
#Override
public Parcelable saveState()
{
return null;
}
This prevent android from recreating fragment
One day later I found another and better solution.
Call setRetainInstance(true) for all your fragments and save references to them somewhere. I did that in static variable in my activity, because it's declared as singleTask and fragments can stay the same all the time.
This way android not recreate fragments but use same instances.
I solved this issue by accessing my fragments directly through the FragmentManager instead of via the FragmentPagerAdapter like so. First I need to figure out the tag of the fragment auto generated by the FragmentPagerAdapter...
private String getFragmentTag(int pos){
return "android:switcher:"+R.id.viewpager+":"+pos;
}
Then I simply get a reference to that fragment and do what I need like so...
Fragment f = this.getSupportFragmentManager().findFragmentByTag(getFragmentTag(1));
((MyFragmentInterface) f).update(id, name);
viewPager.setCurrentItem(1, true);
Inside my fragments I set the setRetainInstance(false); so that I can manually add values to the savedInstanceState bundle.
#Override
public void onSaveInstanceState(Bundle outState) {
if(this.my !=null)
outState.putInt("myId", this.my.getId());
super.onSaveInstanceState(outState);
}
and then in the OnCreate i grab that key and restore the state of the fragment as necessary. An easy solution which was hard (for me at least) to figure out.
Global working tested solution.
getSupportFragmentManager() keeps the null reference some times and View pager does not create new since it find reference to same fragment. So to over come this use getChildFragmentManager() solves problem in simple way.
Don't do this:
new PagerAdapter(getSupportFragmentManager(), fragments);
Do this:
new PagerAdapter(getChildFragmentManager() , fragments);
Do not try to interact between fragments in ViewPager. You cannot guarantee that other fragment attached or even exists. Istead of changing actionbar title from fragment, you can do it from your activity. Use standart interface pattern for this:
public interface UpdateCallback
{
void update(String name);
}
public class MyActivity extends FragmentActivity implements UpdateCallback
{
#Override
public void update(String name)
{
getSupportActionBar().setTitle(name);
}
}
public class MyFragment extends Fragment
{
private UpdateCallback callback;
#Override
public void onAttach(SupportActivity activity)
{
super.onAttach(activity);
callback = (UpdateCallback) activity;
}
#Override
public void onDetach()
{
super.onDetach();
callback = null;
}
public void updateActionbar(String name)
{
if(callback != null)
callback.update(name);
}
}
You can remove the fragments when destroy the viewpager, in my case, I removed them on onDestroyView() of my fragment:
#Override
public void onDestroyView() {
if (getChildFragmentManager().getFragments() != null) {
for (Fragment fragment : getChildFragmentManager().getFragments()) {
getChildFragmentManager().beginTransaction().remove(fragment).commitAllowingStateLoss();
}
}
super.onDestroyView();
}
After a few hours of looking for a similar issue I think a have another solution. This one at least it worked for me and I only have to changed a couple of lines.
This is the problem I had, I have an activity with a view pager that uses a FragmentStatePagerAdapter with two Fragments. Everything works fine until I force the activity to get destroyed (developer options) or I rotate the screen. I do keep a reference to the two fragments after they get created inside the method getItem.
At that point the activity will be created again and everything works fine at this point but I have lost the reference to my fragmetns as getItem doesn't' get called again.
This is how I fixed that problem, inside the FragmentStatePagerAdapter:
#Override
public Object instantiateItem(ViewGroup container, int position) {
Object aux = super.instantiateItem(container, position);
//Update the references to the Fragments we have on the view pager
if(position==0){
fragTabOne = (FragOffersList)aux;
}
else{
fragTabTwo = (FragOffersList) aux;
}
return aux;
}
You won't get a call on getItem again if the adapter already has a reference to it internally, and you shouldn't change that. Instead you can get the fragment it's being used by looking at this other method instantiateItem() which will be called for each of your fragments.
Hope that helps anyone.
Since people don't tend to read comments, here is an answer that mostly duplicates what I wrote here:
the root cause of the issue is the fact that android system does not call getItem to obtain fragments that are actually displayed, but instantiateItem. This method first tries to lookup and reuse a fragment instance for a given tab in FragmentManager. Only if this lookup fails (which happens only the first time when FragmentManager is newly created) then getItem is called. It is for obvious reasons not to recreate fragments (that may be heavy) for example each time a user rotates his device.
To solve this, instead of creating fragments with Fragment.instantiate in your activity, you should do it with pagerAdapter.instantiateItem and all these calls should be surrounded by startUpdate/finishUpdate method calls that start/commit fragment transaction respectively. getItem should be the place where fragments are really created using their respective constructors.
List<Fragment> fragments = new Vector<Fragment>();
#Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.myLayout);
ViewPager viewPager = (ViewPager) findViewById(R.id.myViewPager);
MyPagerAdapter adapter = new MyPagerAdapter(getSupportFragmentManager());
viewPager.setAdapter(adapter);
((TabLayout) findViewById(R.id.tabs)).setupWithViewPager(viewPager);
adapter.startUpdate(viewPager);
fragments.add(adapter.instantiateItem(viewPager, 0));
fragments.add(adapter.instantiateItem(viewPager, 1));
// and so on if you have more tabs...
adapter.finishUpdate(viewPager);
}
class MyPagerAdapter extends FragmentPagerAdapter {
public MyPagerAdapter(FragmentManager manager) {super(manager);}
#Override public int getCount() {return 2;}
#Override public Fragment getItem(int position) {
if (position == 0) return new Fragment0();
if (position == 1) return new Fragment1();
return null; // or throw some exception
}
#Override public CharSequence getPageTitle(int position) {
if (position == 0) return getString(R.string.tab0);
if (position == 1) return getString(R.string.tab1);
return null; // or throw some exception
}
}
Since the FragmentManager will take care of restoring your Fragments for you as soon as the onResume() method is called I have the fragment call out to the activity and add itself to a list. In my instance I am storing all of this in my PagerAdapter implementation. Each fragment knows it's position because it is added to the fragment arguments on creation. Now whenever I need to manipulate a fragment at a specific index all I have to do is use the list from my adapter.
The following is an example of an Adapter for a custom ViewPager that will grow the fragment as it moves into focus, and scale it down as it moves out of focus. Besides the Adapter and Fragment classes I have here all you need is for the parent activity to be able to reference the adapter variable and you are set.
Adapter
public class GrowPagerAdapter extends FragmentPagerAdapter implements OnPageChangeListener, OnScrollChangedListener {
public final String TAG = this.getClass().getSimpleName();
private final int COUNT = 4;
public static final float BASE_SIZE = 0.8f;
public static final float BASE_ALPHA = 0.8f;
private int mCurrentPage = 0;
private boolean mScrollingLeft;
private List<SummaryTabletFragment> mFragments;
public int getCurrentPage() {
return mCurrentPage;
}
public void addFragment(SummaryTabletFragment fragment) {
mFragments.add(fragment.getPosition(), fragment);
}
public GrowPagerAdapter(FragmentManager fm) {
super(fm);
mFragments = new ArrayList<SummaryTabletFragment>();
}
#Override
public int getCount() {
return COUNT;
}
#Override
public Fragment getItem(int position) {
return SummaryTabletFragment.newInstance(position);
}
#Override
public void onPageScrollStateChanged(int state) {}
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
adjustSize(position, positionOffset);
}
#Override
public void onPageSelected(int position) {
mCurrentPage = position;
}
/**
* Used to adjust the size of each view in the viewpager as the user
* scrolls. This provides the effect of children scaling down as they
* are moved out and back to full size as they come into focus.
*
* #param position
* #param percent
*/
private void adjustSize(int position, float percent) {
position += (mScrollingLeft ? 1 : 0);
int secondary = position + (mScrollingLeft ? -1 : 1);
int tertiary = position + (mScrollingLeft ? 1 : -1);
float scaleUp = mScrollingLeft ? percent : 1.0f - percent;
float scaleDown = mScrollingLeft ? 1.0f - percent : percent;
float percentOut = scaleUp > BASE_ALPHA ? BASE_ALPHA : scaleUp;
float percentIn = scaleDown > BASE_ALPHA ? BASE_ALPHA : scaleDown;
if (scaleUp < BASE_SIZE)
scaleUp = BASE_SIZE;
if (scaleDown < BASE_SIZE)
scaleDown = BASE_SIZE;
// Adjust the fragments that are, or will be, on screen
SummaryTabletFragment current = (position < mFragments.size()) ? mFragments.get(position) : null;
SummaryTabletFragment next = (secondary < mFragments.size() && secondary > -1) ? mFragments.get(secondary) : null;
SummaryTabletFragment afterNext = (tertiary < mFragments.size() && tertiary > -1) ? mFragments.get(tertiary) : null;
if (current != null && next != null) {
// Apply the adjustments to each fragment
current.transitionFragment(percentIn, scaleUp);
next.transitionFragment(percentOut, scaleDown);
if (afterNext != null) {
afterNext.transitionFragment(BASE_ALPHA, BASE_SIZE);
}
}
}
#Override
public void onScrollChanged(int l, int t, int oldl, int oldt) {
// Keep track of which direction we are scrolling
mScrollingLeft = (oldl - l) < 0;
}
}
Fragment
public class SummaryTabletFragment extends BaseTabletFragment {
public final String TAG = this.getClass().getSimpleName();
private final float SCALE_SIZE = 0.8f;
private RelativeLayout mBackground, mCover;
private TextView mTitle;
private VerticalTextView mLeft, mRight;
private String mTitleText;
private Integer mColor;
private boolean mInit = false;
private Float mScale, mPercent;
private GrowPagerAdapter mAdapter;
private int mCurrentPosition = 0;
public String getTitleText() {
return mTitleText;
}
public void setTitleText(String titleText) {
this.mTitleText = titleText;
}
public static SummaryTabletFragment newInstance(int position) {
SummaryTabletFragment fragment = new SummaryTabletFragment();
fragment.setRetainInstance(true);
Bundle args = new Bundle();
args.putInt("position", position);
fragment.setArguments(args);
return fragment;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
mRoot = inflater.inflate(R.layout.tablet_dummy_view, null);
setupViews();
configureView();
return mRoot;
}
#Override
public void onViewStateRestored(Bundle savedInstanceState) {
super.onViewStateRestored(savedInstanceState);
if (savedInstanceState != null) {
mColor = savedInstanceState.getInt("color", Color.BLACK);
}
configureView();
}
#Override
public void onSaveInstanceState(Bundle outState) {
outState.putInt("color", mColor);
super.onSaveInstanceState(outState);
}
#Override
public int getPosition() {
return getArguments().getInt("position", -1);
}
#Override
public void setPosition(int position) {
getArguments().putInt("position", position);
}
public void onResume() {
super.onResume();
mAdapter = mActivity.getPagerAdapter();
mAdapter.addFragment(this);
mCurrentPosition = mAdapter.getCurrentPage();
if ((getPosition() == (mCurrentPosition + 1) || getPosition() == (mCurrentPosition - 1)) && !mInit) {
mInit = true;
transitionFragment(GrowPagerAdapter.BASE_ALPHA, GrowPagerAdapter.BASE_SIZE);
return;
}
if (getPosition() == mCurrentPosition && !mInit) {
mInit = true;
transitionFragment(0.00f, 1.0f);
}
}
private void setupViews() {
mCover = (RelativeLayout) mRoot.findViewById(R.id.cover);
mLeft = (VerticalTextView) mRoot.findViewById(R.id.title_left);
mRight = (VerticalTextView) mRoot.findViewById(R.id.title_right);
mBackground = (RelativeLayout) mRoot.findViewById(R.id.root);
mTitle = (TextView) mRoot.findViewById(R.id.title);
}
private void configureView() {
Fonts.applyPrimaryBoldFont(mLeft, 15);
Fonts.applyPrimaryBoldFont(mRight, 15);
float[] size = UiUtils.getScreenMeasurements(mActivity);
int width = (int) (size[0] * SCALE_SIZE);
int height = (int) (size[1] * SCALE_SIZE);
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(width, height);
mBackground.setLayoutParams(params);
if (mScale != null)
transitionFragment(mPercent, mScale);
setRandomBackground();
setTitleText("Fragment " + getPosition());
mTitle.setText(getTitleText().toUpperCase());
mLeft.setText(getTitleText().toUpperCase());
mRight.setText(getTitleText().toUpperCase());
mLeft.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
mActivity.showNextPage();
}
});
mRight.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
mActivity.showPrevPage();
}
});
}
private void setRandomBackground() {
if (mColor == null) {
Random r = new Random();
mColor = Color.rgb(r.nextInt(255), r.nextInt(255), r.nextInt(255));
}
mBackground.setBackgroundColor(mColor);
}
public void transitionFragment(float percent, float scale) {
this.mScale = scale;
this.mPercent = percent;
if (getView() != null && mCover != null) {
getView().setScaleX(scale);
getView().setScaleY(scale);
mCover.setAlpha(percent);
mCover.setVisibility((percent <= 0.05f) ? View.GONE : View.VISIBLE);
}
}
#Override
public String getFragmentTitle() {
return null;
}
}
My solution: I set almost every View as static. Now my app interacts perfect. Being able to call the static methods from everywhere is maybe not a good style, but why to play around with code that doesn't work? I read a lot of questions and their answers here on SO and no solution brought success (for me).
I know it can leak the memory, and waste heap, and my code will not be fit on other projects, but I don't feel scared about this - I tested the app on different devices and conditions, no problems at all, the Android Platform seems to be able handle this. The UI gets refreshed every second and even on a S2 ICS (4.0.3) device the app is able to handle thousands of geo-markers.
I faced the same issue but my ViewPager was inside a TopFragment which created and set an adapter using setAdapter(new FragmentPagerAdapter(getChildFragmentManager())).
I fixed this issue by overriding onAttachFragment(Fragment childFragment) in the TopFragment like this:
#Override
public void onAttachFragment(Fragment childFragment) {
if (childFragment instanceof OnboardingDiamondsFragment) {
mChildFragment = (ChildFragment) childFragment;
}
super.onAttachFragment(childFragment);
}
As known already (see answers above), when the childFragmentManager recreate itself, it also create the fragments which were inside the viewPager.
The important part is that after that, he calls onAttachFragment and now we have a reference to the new recreated fragment!
Hope this will help anyone getting this old Q like me :)
I solved the problem by saving the fragments in SparceArray:
public abstract class SaveFragmentsPagerAdapter extends FragmentPagerAdapter {
SparseArray<Fragment> fragments = new SparseArray<>();
public SaveFragmentsPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Object instantiateItem(ViewGroup container, int position) {
Fragment fragment = (Fragment) super.instantiateItem(container, position);
fragments.append(position, fragment);
return fragment;
}
#Nullable
public Fragment getFragmentByPosition(int position){
return fragments.get(position);
}
}
Just so you know...
Adding to the litany of woes with these classes, there is a rather interesting bug that's worth sharing.
I'm using a ViewPager to navigate a tree of items (select an item and the view pager animates scrolling to the right, and the next branch appears, navigate back, and the ViewPager scrolls in the opposite direction to return to the previous node).
The problem arises when I push and pop fragments off the end of the FragmentStatePagerAdapter. It's smart enough to notice that the items change, and smart enough to create and replace a fragment when the item has changed. But not smart enough to discard the fragment state, or smart enough to trim the internally saved fragment states when the adapter size changes. So when you pop an item, and push a new one onto the end, the fragment for the new item gets the saved state of the fragment for the old item, which caused absolute havoc in my code. My fragments carry data that may require a lot of work to refetch from the internet, so not saving state really wasn't an option.
I don't have a clean workaround. I used something like this:
public void onSaveInstanceState(Bundle outState) {
IFragmentListener listener = (IFragmentListener)getActivity();
if (listener!= null)
{
if (!listener.isStillInTheAdapter(this.getAdapterItem()))
{
return; // return empty state.
}
}
super.onSaveInstanceState(outState);
// normal saving of state for flips and
// paging out of the activity follows
....
}
An imperfect solution because the new fragment instance still gets a savedState Bundle, but at least it doesn't carry stale data.
i am using ActionBarSherlock (which is basically an extension of the Android Support Package).
What i'm trying to do is the following:
I have a FragmentActivity which hosts just a single ViewPager. This ViewPager has a FragmentStatePagerAdapter (because there will be many items in the future). But for now it is just loaded with 2 items for testing.
Everything is working just fine while i am in portrait orientation. But when i change so landscape orientation it switches back to the first item in the adapter (which is fine since everything is reloaded etc), but i am unable to swype to the next item. There is just nothing happening.
From debugging i can see that the Loader return the two items just fine. getItem(...) is also called with position 0 and 1. So basicall everything looks fine, except it isn't ;)
Btw: the same thing is happening vice versa when i start in landscape orienation and switch to portrait orientation.
Any ideas what might be wrong here?
Here is some of my code:
public class QuotesStatePagerAdapter extends FragmentStatePagerAdapter {
private List<Quote> mQuotes;
public QuotesStatePagerAdapter(FragmentManager fm, List<Quote> quotes) {
super(fm);
mQuotes = quotes;
}
#Override
public Fragment getItem(int position) {
Bundle arguments = new Bundle();
arguments.putSerializable("quote", mQuotes.get(position));
QuoteFragment fragment = new QuoteFragment();
fragment.setArguments(arguments);
return fragment;
}
#Override
public int getCount() {
return mQuotes.size();
}
}
public QuotesFragment() {
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
public void updateOrdering(ORDERING newOrdering) {
mOrdering = newOrdering;
getLoaderManager().getLoader(0).startLoading();
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.quotes, container, false);
}
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mViewPager = (ViewPager) view.findViewById(R.id.viewpager);
mViewPager.setOnPageChangeListener(this);
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
getLoaderManager().initLoader(0, null, this);
}
#Override
public Loader<List<Quote>> onCreateLoader(int id, Bundle args) {
return new QuotesLoader(getActivity(), mCategoryId);
}
#Override
public void onLoadFinished(Loader<List<Quote>> loader, List<Quote> data) {
mQuotes = data;
mViewPager.setAdapter(new QuotesStatePagerAdapter(
getSupportFragmentManager(), mQuotes));
}
android:configChanges="orientation" worked like a charm, but i saw that it's not recommended by android team (only last case resource)... See here http://developer.android.com/guide/topics/manifest/activity-element.html#config
I found out that putting:
setRetainInstance(true); on the onCreateView of each fragment retained the instance do the trick. (please not that the onActivityCreated will be called again)
Try adding android:configChanges="orientation"to manifest inside Activity tag