I have a very simple PagerAdapter that for some reason, removes the views at position 0 and
1 when it is scrolled to position 2.
Specifically, when I first run the app, there are 3 views. I scroll to position 2 and position 1's view will disappear. View 0 is still there. If I scroll to view 0 and back to view 2 and again back to view 0, View 0 suddenly disappears. I do the same again, and I can actually see view 0 being instantiated and immediately destroyed.
what is going on here?
Main Activity
public class MainActivity extends Activity {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final MyPagerAdapter adapter = new MyPagerAdapter(this);
final ViewPager myPager = (ViewPager) findViewById(R.id.mypanelpager);
myPager.setAdapter(adapter);
myPager.setCurrentItem(1);
}
public class MyPagerAdapter extends PagerAdapter {
private Context ctx;
Calendar c = Calendar.getInstance();
int month = c.get(Calendar.MONTH);
int year = c.get(Calendar.YEAR);
ViewGroup collection;
public MyPagerAdapter (Context ctx){
this.ctx = ctx ;
}
#Override
public int getCount() {
return 3;
}
public Object instantiateItem(ViewGroup container, int position ){
this.collection = (ViewPager)container;
NewMonth monthObject = new NewMonth(ctx, month+position-1,2012);
View monthLayout = monthObject.newParentLayout;
collection.addView(monthLayout);
return monthLayout;
return addViewAt(position);
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
collection.removeViewAt(position);
}
#Override
public Parcelable saveState() {
return null;
}
#Override
public boolean isViewFromObject(View view, Object arg1) {
return view==arg1;
}
}
You have to maintain all tabs in memory specifying the OffscreenPageLimit to N-1, in your case put this inside the onCreate method:
myPager.setOffscreenPageLimit(2);
Checkout populate() function from ViewPager source - it has clear checks then to remove currentIndex+1 and currentIndex-1 items. Removing is done based on view sizes and it seems to be entirely internal ViewPager logic. ViewPager source is located
<android sdk folder>\extras\android\support\v4\src\java\android\support\v4\view\ViewPager.java
.
You might debug ViewPager to know that is happening exactly, but I wouldn't suggest so until You faced some serious issue with ViewPager. What's why: if you dig into ViewPager code it might lead You to write some hackish code (even not intentionally) on it based on its internal structure and not on its public interface and documentation. So, the main idea of data encapsulation would be ruined which is definitely not good (sadly, sometimes we need to do so in Android due to lack of documentation / unclear naming / android internal issues etc., check Code Complete by Steve McConnell for more details on good encapsulation etc.).
Here the key is that position is different then index. If they give you a position there is no guarantee that your collections haven't removed other positions.
Example:
Let's say your at position 2. destroyItem might have been called for position 0 which means in your collections now position 2 is actually at index 1. So your indexes will quickly become out of sync from your positions. The same thing happens with listview children. I would recommend using a sparseArray instead.
private final SparseArray<View> views = new SparseArray<>();
public View instantiateItem(ViewGroup container, final int position) {
...
views.put(position, view);
...
}
public void destroyItem(ViewGroup container, int position, Object object) {
View view = views.get(position);
if (view != null) {
container.removeView(view);
views.remove(position);
}
}
Related
I'm new to Android programming and trying to figure out how to optimize my ListView adapter. I wrote a custom adapter to add CardViews to my ListView:
public class CardAdapter extends BaseAdapter {
private final ArrayList<CardView> cards = new ArrayList<CardView>();
private Context context;
public CardAdapter( Context context ) {
this.context = context;
}
public void add( CardView view ) {
cards.add( view );
}
#Override
public int getCount() {
return cards.size();
}
#Override
public int getViewTypeCount() {
return getCount();
}
#Override
public int getItemViewType(int position) {
return position;
}
#Override
public Object getItem(int i) {
if( i >= cards.size() || i < 0 )
return null;
return cards.get( i );
}
#Override
public long getItemId(int i) {
return cards.get(i).getId();
}
#Override
public View getView(int position, View view, ViewGroup viewGroup) {
if( view == null ) {
view = (CardView) getItem( position );
}
return view;
}
}
The MainActivity is something like:
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ListView list = findViewById(R.id.notesList);
CardAdapter adapter = new CardAdapter(getBaseContext());
adapter.add( makeCard() );
// repeat making cards however many times...
list.setAdapter(adapter);
}
private CardView makeCard() {
CardView card = new CardView(this);
// Do some things to customize the CardView
return card;
}
}
Now it seems like Android is trying to recycle the views, but is doing a lousy job of it. Initially the page looks fine, but as soon as I begin scrolling, the cards start to move all over the screen, the spacing changes, the cards begin to overlap, etc. .
Note that I only scrolled for a couple seconds, this was not a gradual change, the cards were kind of 'jumping' around the screen. The only solution I have come up with so far is changing my getView to be:
#Override
public View getView(int position, View view, ViewGroup viewGroup) {
return (CardView) getItem(position);
}
And this works, but even loading ~10 items is very slow, and the scrolling lags.
Kindly use RecyclerView it uses view holder pattern that resolve all these lagging issues
Change your implementation from ListView to RecyclerView.
RecyclerView is a ViewGroup added to the android studio as a successor
of the GridView and ListView. It is an improvement on both of them and
can be found in the latest v-7 support packages. It has been created
to make possible construction of any lists with XML layouts as an item
which can be customized vastly while improving on the efficiency of
ListViews and GridViews. This improvement is achieved by recycling the
views which are out of the visibility of the user. For example, if a
user scrolled down to a position where the items 4 and 5 are visible;
items 1, 2 and 3 would be cleared from the memory to reduce memory
consumption.
From GeeksforGeeks.
Ps: the link has a lot of explanation, tutorial and codes...
The RecyclerView was created specifically to solve the kind of problem you're having. It has a lot of improvements over ListView, like less memory consumption, less lagging, better scrooling...
ok,its too simple
first remember u must pass what u want use in adapter,but now u just pass a (this) to this adapter.
use code below for your Constructor .
have good codding.
private final ArrayList<CardView> cards = new ArrayList<CardView>();
private Context context;
public CardAdapter( Context context , ArrayList<CardView> cards) {
this.context = context;
this.cards =cards
}
this question is related with this one that I asked before.
I create a viewpager in MainActivity.java like this:
final ViewPager viewPager = (ViewPager) findViewById(R.id.vp_horizontal_ntb);
viewPager.setAdapter(new PagerAdapter() {
#Override
public int getCount() {
return 5;
}
#Override
public boolean isViewFromObject(final View view, final Object object) {
return view.equals(object);
}
#Override
public void destroyItem(final ViewGroup container, final int position, final Object object) {
((ViewPager) container).removeView((View) object);
}
#Override
public Object instantiateItem(final ViewGroup container, final int position) {
if(position==0) {
// here is important!
} else if(position == 1) {
}
...
}
});
Now I want fill each page with some json RecyclerView data list.(get json from network).
each page has independent data list.
For first time, I create a fragment for each page like this:
if (position == 0) {
final View view = LayoutInflater.from(getActivity().getBaseContext()).inflate(R.layout.fragment_tab0, null, false);
tabFragment.MyAdapter adapter = new tabFragment.MyAdapter(getActivity().getSupportFragmentManager());
adapter.addFragment(new tab0Fragment(), getResources().getString(R.string.tab0));
container.addView(view);
return view;
}
(so for 5 page, I have 5 fragment.
DEMO
But My application run slow (laggy) when I swipe pages.(with tap buttom is normal)
So I tried write an Adapter class directly for each page like this:
if (position == 0) {
final View view = LayoutInflater.from(getBaseContext()).inflate(R.layout.item_vp_list, null, false));
final RecyclerView recyclerView = (RecyclerView) view.findViewById(R.id.rv);
recyclerView.setHasFixedSize(true);
recyclerView.setLayoutManager(new LinearLayoutManager(getBaseContext(), LinearLayoutManager.VERTICAL, false));
recyclerView.setAdapter(new Tab0RecycleAdapter());
container.addView(view);
return view;
}
with top code,my application run fast again with swap pages!
Is it important to create fragment per each page?
why I must use fragment?(because some programmer recommended it in viewpager)
my method (second method without fragment) is true or false for a real application?
(I am noob and this is my first app)
Now I want fill each page with some json RecyclerView data list.(get json from network).
If you perform this network task on the UI thread, it will block and could cause laggy performance. This could be the reason your pages load slowly. You should perform network tasks on a separate thread.
So I tried write an Adapter class directly for each page like this
You only need one adapter per recycler view. If you want to support multiple views within the same adapter, override getItemViewType(). Example here: https://stackoverflow.com/a/26245463/7395923
Is it important to create fragment per each page?
Why I must use fragment? (because some programmer recommended it in view pager)
It is possible to use a view pager without fragments. Base on your previous question (linked at the top), it does seem overkill to load an entire fragment just to inflate a view. Here is a link to an example of a view pager without fragments: https://stackoverflow.com/a/18710626/7395923
I hope this helps.
In my app, there is currently a section that holds a fragment that queries the database for a list of objects and then displays them (using a ListView).
When a user clicks one of the objects in the list, the section grows bigger in height to incorporate a different fragment that .replace()s the ListView fragment and moves up.
This new fragment displays information related to the object the user clicked.
One of the views in this fragment is a ViewPager that contains only 2 fragments (at most):
A timer fragment.
A calendar fragment.
The timer fragment is a fragment that simply uses a CountDownTimer and changes a TextView's text onTick(). This fragment may or may not be shown, depending on the object.
The calendar fragment however, contains an heavier view - a custom calendar view.
The calendar is supposed to show the days a year before and after the current date (now).
The calendar fragment (or view, actually, since there is no logic at all in the fragment) causes the animation that fires when a user clicks on an object to stutter and it looks bad. This only happens in the first time an object is clicked. In the second time it's smoother (I guess it's thanks to the ViewPager saving the fragment's state, but I'm not sure...).
I was wondering whether there is any specific bottleneck/problem in the code that would cause it to behave like that. My best guess is that there are just too many views being loaded together.
The first solution I thought of, is to basically move the animation inside the fragment and fire it only after the fragment is fully loaded and ready to be displayed. That, however, would mean that I will be controlling the parent (the container of the fragment) from within the fragment itself... not sure whether this is a good design or not. I could create a listener for that, and place its call in the end of onCreateView(), though.
Another possible solution, and this is just a theory of mine, but creating all of the views on a separate thread, and only then adding them to the UI in the main thread could maybe slightly speed up the process. Although, I'm not really sure how better that would be (in terms of performance), if at all, and how much of a good practice this is.
How can I optimize my CalendarView (or maybe my whole "Object View Fragment") in order to allow the animation to work properly?
CalendarView consists of:
A vertical LinearLayout that contains 2 sub-views:
A topbar which is a TableLayout with a single with the names of the 7 days.
A GridView which is filled with TextViews of day numbers.
Some code:
MainActivity - replaces the current fragment
#Override
public void OnGoalSelected(Parcelable goal) {
Log.i(TAG, "GoalSelected");
isMinimized = false;
HabitGoalViewFragment newFragment = new HabitGoalViewFragment();
Bundle args = new Bundle();
args.putParcelable(GOAL_POSITION_KEY, goal);
newFragment.setArguments(args);
getSupportFragmentManager().addOnBackStackChangedListener(this);
getSupportFragmentManager().beginTransaction()
.setCustomAnimations(R.anim.fadein, R.anim.fadeout, R.anim.fadein, R.anim.fadeout)
.replace(R.id.cardFragmentContainer, newFragment)
.addToBackStack(null)
.commit();
mActionBar.setCustomView(R.layout.ab_goal_view);
mLL.getLayoutParams().height += screenHeight;
ObjectAnimator objAnim = ObjectAnimator.ofFloat(mLL, "translationY", AmountToMove).setDuration(500);
objAnim.setInterpolator(new DecelerateInterpolator());
objAnim.start();
}
HabitGoalView
public static Class<?>[] mFragmentArray = {
HabitCalendarFragment.class,
HabitDurationTimerFragment.class
};
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_goal_view, container, false);
mActionBar = getActivity().getActionBar();
mActionBar.getCustomView().findViewById(R.id.goal_view_back_button_parent).setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
getActivity().getSupportFragmentManager().popBackStackImmediate();
}
});
mHabitGoal = getArguments().getParcelable(MainActivity.GOAL_POSITION_KEY);
............
mViewPager = (ViewPager) view.findViewById(R.id.pager);
mViewPager.setAdapter(new ViewPagerAdapter(getActivity().getSupportFragmentManager(), getActivity()));
return view;
}
private class ViewPagerAdapter extends FragmentStatePagerAdapter {
private Context mContext;
public ViewPagerAdapter(FragmentManager fm, Context context) {
super(fm);
mContext = context;
}
#Override
public Fragment getItem(int position) {
return Fragment.instantiate(mContext, mFragmentArray[position].getName());
}
#Override
public int getCount() {
return mFragmentArray.length;
}
}
CalendarView (This is the View itself, not the fragment!)
public class CalendarView extends LinearLayout {
private final String TAG = this.getClass().getName();
private Context mContext;
private TableLayout mTopBarTableLayout;
private TableRow mTableRow;
public CalendarView(Context context) {
super(context);
mContext = context;
this.setOrientation(VERTICAL);
mTopBarTableLayout = new TableLayout(mContext);
mTopBarLinearLayout.setStretchAllColumns(true);
mTableRow = new TableRow(mContext);
int[] mDaysList = {R.string.days_sunday, R.string.days_monday, R.string.days_tuesday,
R.string.days_wednesday, R.string.days_thursday, R.string.days_friday, R.string.days_saturday}; //
AutoScrollingTextView mDayTextView;
int padding;
for (int i = 0; i < 7; i++) {
mDayTextView= new AutoScrollingTextView(mContext);
padding = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 8, getResources().getDisplayMetrics());
mDayTextView.setPadding(padding, padding, padding, padding);
mDayTextView.setTextSize(16);
mDayTextView.setGravity(Gravity.CENTER);
mDayTextView.setText(getResources().getString(mDaysList[j]).substring(0, 3).toUpperCase(Locale.US));
mDayTextView.setWidth(0);
mDayTextView.setSingleLine(true);
mDayTextView.setHorizontallyScrolling(true);
mDayTextView.setEllipsize(TextUtils.TruncateAt.MARQUEE);
mTableRow.addView(mDayTextView, i);
}
mTopBarLinearLayout.addView(mTableRow, 0);
this.addView(mTopBarLinearLayout, 0);
this.addView(new CalendarGridView(mContext), 1);
}
private class CalendarGridView extends GridView {
Context mContext;
DateTime CurrentMonthDateTime, NextYearDT, LastYearDT;
int offset;
public CalendarGridView(Context context) {
super(context);
mContext = context;
init();
}
public CalendarGridView(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
init();
}
public CalendarGridView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mContext = context;
init();
}
public void init() {
this.setNumColumns(7); // 7 columns - 1 for each day
this.setStretchMode(STRETCH_COLUMN_WIDTH);
this.setAdapter(new CalendarAdapter());
CurrentMonthDateTime = DateTime.now();
LastYearDT = DateTime.now().minusYears(1).withDayOfMonth(1);
offset = LastYearDT.getDayOfWeek();
if (offset == 7)
offset = 0;
int n = Days.daysBetween(LastYearDT.withTimeAtStartOfDay(), CurrentMonthDateTime.withTimeAtStartOfDay()).getDays() + offset;
Log.i(TAG, "Days Offset = " + n);
this.setSelection(n);
}
private class CalendarAdapter extends BaseAdapter {
private int offset, n;
private DateTime mDateTime, mDateToPrint;
public CalendarAdapter() {
mDateTime = DateTime.now().minusYears(1).withDayOfMonth(1);
NextYearDT = DateTime.now().plusYears(1).withDayOfMonth(1);
n = Days.daysBetween(mDateTime.withTimeAtStartOfDay(),
NextYearDT.withTimeAtStartOfDay()).getDays();
// round up to the nearest number divisible by 7
n += (7 - n%7);
offset = mDateTime.getDayOfWeek(); // 1 - mon, 2 - tue ... 7 - sun
// set first day to Sunday
if (offset == 7)
offset = 0;
mDateTime = mDateTime.minusDays(offset);
}
#Override
public int getCount() {
return n;
}
#Override
public Object getItem(int position) {
return null;
}
#Override
public long getItemId(int position) {
return 0;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
SquareTextView view = (SquareTextView) convertView;
if (convertView == null) {
view = new SquareTextView(mContext);
view.setTextSize(18);
view.setGravity(Gravity.CENTER);
view.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
// TODO Auto-generated method stub
Log.i(TAG, ((SquareTextView) v).getText().toString());
}
});
} else {
view.setBackgroundResource(0); // TODO set as drawable and then remove it
view.setTag(null);
}
mDateToPrint = mDateTime.plusDays(position);
if (mDateToPrint.getDayOfMonth() == 1)
view.setTag(mDateToPrint.monthOfYear().getAsShortText(Locale.ENGLISH));
view.setText(mDateToPrint.getDayOfMonth() + "");
if (mDateToPrint.withTimeAtStartOfDay().isEqual(CurrentMonthDateTime.withTimeAtStartOfDay())) {
//view.setBackgroundResource(R.color.background_gray);
view.setBackgroundColor(getResources().getColor(R.color.green));
view.setTag("today");
}
return view;
}
}
}
}
So I finally found a solution! At the end the source to my problem was elsewhere, but just in case someone would have the same kind of problem/stuttering, I will share the best way to solve it based on my knowledge - using listeners. It was also recommended in the Android development tutorials.
Using a custom listener interface you can let the containing activity know that the fragment has finished all of it's loading. That way, you can make the animation run only after the fragment has been loaded, which would result in a smooth animation.
Example interface:
public interface OnFragmentLoadedListener {
public abstract void OnFragmentLoaded(Object A);
}
You then implement the interface in the activity that contains the fragment:
public class MainActivity extends Activity implements MyFragment.OnFragmentLoadedListener {
................
public void OnFragmentLoaded(Object A) {
//Do something...
}
Then you need to set the listener inside the fragment. You can either create a method like setOnFragmentListener(OnFragmentListener mListener) or, as I prefer, get the reference to the listener in the onAttach() method of the Fragment class:
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
this.mListener = (OnFragmentLoadedListener) activity;
} catch (final ClassCastException e) {
throw new ClassCastException(activity.toString() + " must implement OnFragmentLoadedListener");
}
}
Finally, you need to let the listener know when the fragment is ready to be shown.
In my case, that would be after ANOTHER fragment would load, so I put my listener call in the interface implementation of the containing (and contained) fragment.
if(mListener != null)
mListener.OnFragmentLoaded(A);
My source of my problem was really... JODA TIME
The source to my problem was actually my usage of the joda-time library.
I used the original joda-time for java, which was a mistake.
Everything worked fine and all but after trying hard to solve my problem I wondered whether it was possible that joda-time was actually the problem since the rest of my calendar code was very much like the calendar widget provided with android.
Surprisingly, I came across this question/answer, which proved my theory right.
At first, I was actually quite disappointed, because I thought I'd have to go back to using androids' default Calendar class, and then I remembered there was a version of joda for android, and thought there was nothing to lose by trying it out.
Anyways, turns out this android version really did wonders. It worked like magic and sped up my whole app! You can find this great library here: joda-time-android
I don't really know why, which is why I invite anyone who knows to explain it in a new answer, which I'll gladly mark as accepted.
I hope I can show 3 items in a page of viewpager, but now I only could set viewpager's padding value and margin value, so it only show one item in a page of viewpager. How can I set the item width? I think if I can set more little width of item, the viewpager will show more items in a page.
Quick Answer
What you want is overriding getPageWidth in the PagerAdapter implementation you made for that ViewPager.
For example setting it to 0.8f will make a single page cover only 80% of the viewpagers width so that part of the next and/or previous pages are visible.
#Override
public float getPageWidth(final int position) {
return 0.8f;
}
More information at https://developer.android.com/reference/android/support/v4/view/PagerAdapter.html#getPageWidth(int)
Other remarks
Also note that the negative margins will only cause the separate pages to overlap each other. The margin is used to define the space between each page.
If no alternative pageWidth is configured (there is no setter, only the override) it will cover 100% by default making no part of the other pages visible unless dragged.
Code example
A very nice overview of what is possible can be found here https://commonsware.com/blog/2012/08/20/multiple-view-viewpager-options.html
The view pager in your layout
<android.support.v4.view.ViewPager
android:id="#+id/viewpager"
android:layout_width="match_parent"
android:layout_height="match_parent">
</android.support.v4.view.ViewPager>
Example of an inline configured Adapter implementation, this would go in the onCreate or onViewCreated methods:
// get a reference to the viewpager in your layout
ViewPager mViewPager = (ViewPager) findViewById(R.id.viewpager);
// this margin defines the space between the pages in the viewpager mViewPager.setPageMargin(context.getResources().getDimensionPixelSize(R.dimen.margin_normal));
// setting the adapter on the viewpager, this could/should be a proper Adapter implementation in it's own class file instead
mViewPager.setAdapter(new PagerAdapter() {
// just example data to show, 3 pages
String[] titles = {"Eins", "Zwei", "Drei"};
int[] layouts = {R.layout.layout1, R.layout.layout2, R.layout.layout3};
#Override
public Object instantiateItem(ViewGroup container, int position) {
LayoutInflater inflater = LayoutInflater.from(MainActivity.this);
// here you can inflate your own view and dress up
ViewGroup layout = (ViewGroup) inflater.inflate(layouts[position], container, false);
container.addView(layout);
return layout;
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
container.removeView((View)object);
}
#Override
public float getPageWidth(final int position) {
return 0.8f;
}
#Override
public CharSequence getPageTitle(int position) {
return titles[position];
}
#Override
public int getCount() {
return layouts.length;
}
#Override
public boolean isViewFromObject(View view, Object object) {
return view == object;
}
});
Example code based on https://newfivefour.com/android-viewpager-simple-views.html
Have you tried setting the page margins to a negative value, see setPageMargin(int)? If I remember correctly, I read someone realizing something similar to what you're describing that way.
Alternatively, you could have a look at using a Gallery in stead, although I have to admit I'm not a big fan of them as they seem to be less flexible and more buggy.
I'm designing an app that allows users to flip between multiple pages in a ViewPager. I've been struggling trying to figure out how it is possible to remove a Fragment instance from a page when it is no longer visible on screen, cache it (to, say, a HashMap), and then restore it so that when the user flips back to that page, the views and everything else in it will be in the same state it was before removal. For example, my first page is a login screen that makes certain layout elements on that particular page visible/invisible on a successful login. When I flip forward enough pages then flip back to the first page, the layout is reset. This becomes more of a problem for another one of my pages which contains a huge, horizontal/vertical scrolling grid of data that I use a thread in the background to draw when it initializes. I use a progress dialog to notify the user of loading progress and that becomes really annoying everytime I have to load it.
So I did some research...
I browsed through the source code for FragmentStatePageAdapter and in the destroyItem() callback, the state of the Fragment instance being removed is saved to an ArrayList. When a new instance of the Fragment is being created in the instantiateItem() callback, if an instance of an item doesn't already exist (they keep track of this by using an ArrayList), a new Fragment instance is created and its saved state is initialized with the corresponding Fragment.SavedState data. Unfortunately, this data does not include the state that the Views were in although I noticed that for pages with a GridView/ListView, the state of the Views were somehow restored (if I scrolled to some random position, flipped a few pages and came back, it would not be reset).
According to the API:
The saved state can not contain dependencies on other fragments --
that is it can't use putFragment(Bundle, String, Fragment) to store a
fragment reference because that reference may not be valid when this
saved state is later used. Likewise the Fragment's target and result
code are not included in this state.
Being a noob to Android, I'm not quite sure I understand the last statement.
That being said, is there any way to cache View state? If not, I think I'll just go ahead and go with leaving all the fragment pages in memory.
I had the same problem problem and solved it by implementing these two functions
public void onSaveInstanceState (Bundle outState)
public void onActivityCreated (Bundle savedInstanceState)
on the fragments that I wanted to save. On the first function, you should save in the Bundle the date that you need to restore the views ( in my case I had a bunch of spinner so I used
an int array to save their positions). The second function, which is called when restoring your fragment, is where you implement the restoring process.
I hope this helps. I also made my adapter to inherit from FragmentStatePageAdapter but I am not sure that this is mandatory.
Listing of main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="fill_parent"
android:layout_height="fill_parent">
<TextView android:text="Page 1" android:id="#+id/textViewHeader"
android:layout_width="fill_parent" android:layout_height="wrap_content"
android:gravity="center" android:padding="10dip" android:textStyle="bold"></TextView>
<android.support.v4.view.ViewPager
android:layout_width="fill_parent" android:layout_height="fill_parent"
android:id="#+id/viewPager" />
</LinearLayout>
Setting up the ViewPager
ViewPager viewPager = (ViewPager) findViewById(R.id.viewPager);
MyPagerAdapter adapter = new MyPagerAdapter(this);
viewPager.setAdapter(adapter);
The PagerAdapter
#Override
public void destroyItem(View view, int arg1, Object object) {
((ViewPager) view).removeView((View)object);
}
#Override
public int getCount() {
return views.size();
}
#Override
public Object instantiateItem(View view, int position) {
View view = views.get(position);
((ViewPager) view).addView(view);
return view;
}
#Override
public boolean isViewFromObject(View view, Object object) {
return view == object;
}
look here for more details view pager example
Looking at the various documentation pieces, my best guess is that the views you are creating do not have an ID attached to them. Assuming that the fragment's saved state is created from Fragment.onSaveInstanceState, then the fragment will automatically save any view's state that has an id. You probably have a default id associated with your ListView/GridView if you created them from a layout file. You can also associate an id with the views by calling setId.
Also, for your custom filled fragment, you may also have to do something custom in onSaveInstanceState.
Here's an example of how I implemented caching in PagerAdapter. After filling the cache all future view requests are served from cache, only data is replaced.
public class TestPageAdapter extends PagerAdapter{
private int MAX_SIZE = 3;
private ArrayList<SoftReference<View>> pageCache = new ArrayList<SoftReference<View>>(3);
public TestPageAdapter(Context context){
// do some initialization
}
#Override
public int getCount() {
// number of pages
}
private void addToCache(View view){
if (pageCache.size() < MAX_SIZE){
pageCache.add(new SoftReference<View>(view));
} else {
for(int n = (pageCache.size()-1); n >= 0; n--) {
SoftReference<View> cachedView = pageCache.get(n);
if (cachedView.get() == null){
pageCache.set(n, new SoftReference<View>(view));
return;
}
}
}
}
private View fetchFromCache(){
for(int n = (pageCache.size()-1); n>= 0; n--) {
SoftReference<View> reference = pageCache.remove(n);
View view = reference.get();
if (view != null) {
return view;
}
}
return null;
}
#Override
public Object instantiateItem(View collection, int position) {
View view = fetchFromCache();
if (view == null) {
// not in cache, inflate manually
LayoutInflater inflater = (LayoutInflater) collection.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
view = inflater.inflate(R.layout.page, null);
}
setData(view, position);
((ViewPager) collection).addView(view, 0);
return view;
}
private void setData(View view, int position){
// set page data (images, text ....)
}
public void setPrimaryItem(ViewGroup container, int position, Object object) {
currentItem = (View)object;
}
public View getCurrentItem() {
return currentItem;
}
#Override
public boolean isViewFromObject(View view, Object object) {
return view == ((View) object);
}
#Override
public void destroyItem(View collection, int arg1, Object view) {
((ViewPager) collection).removeView((View) view);
addToCache((View) view);
}
}
I also ran into this problem when I was using PagerSlidingTabStrip and using and instance of FragmentPagerAdapter, switching to FragmentStatePagerAdapter definitely worked.
Then I use onSaveInstanceState() to save sate