I'm trying to implement a similar feature that Google Calendar has.
Basically, I have two views:
View A)
ViewPager with CustomViews of calendars. Let's call this MonthViewPager which extends ViewPager
View B)
-- On the top of the ViewPager -- a Year View which says what month the View A) has to go. Let's call this YearView.
Scope: I want the user to be able to "infinitely" scroll along the months desired, and being able to switch month from the YearView.
The way I initialize my ViewPager is this:
public MonthViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
this.context = context;
this.attrs = attrs;
setAdapter(new EndLessAdapter(Calendar.getInstance()));
setCurrentItem(Integer.MAX_VALUE / 2);
}
which takes this performance:
I've been alternating the number of the pages kept offscreen in an idle state with setOffscreenPageLimit(int limit). I've tried with 1, 3, 12...
My PagerAdapter is this one:
public class EndLessAdapter extends PagerAdapter {
Calendar calendar;
public EndLessAdapter(Calendar calendar) {
this.calendar = calendar;
}
public int getCount() {
return Integer.MAX_VALUE;
}
#Override
public CalendarView instantiateItem(ViewGroup collection, int position) {
calendar.set(Calendar.MONTH, calendar.get(Calendar.MONTH) + position - Integer.MAX_VALUE/2);
final CalendarView calendarView = new CalendarView(context, attrs);
((ViewPager) collection).addView(calendarView, 0, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
return calendarView;
}
#Override
public void destroyItem(ViewGroup arg0, int arg1, Object arg2) {
((ViewPager) arg0).removeView((View) arg2);
}
#Override
public boolean isViewFromObject(View arg0, Object arg1) {
return arg0 == ((View) arg1);
}
#Override
public Parcelable saveState() {
return null;
}
}
but then when I want to set a month from YearView (for example December), if you see my adapter, I set the position 8 + Integer.MAX_VALUE / 2 which is 8 + April
I do it like:
int differenceMonths = Utils.differenceMonthsToNow(date);
monthViewPager.setCurrentItem(differenceMonths + Integer.MAX_VALUE / 2, false);
and this is the performance:
the UI gets blocked for all that time.
I debugged everything with the Android Monitor, and I saw that 99.0% of the time was taking time on:
ViewPager.setCurrentItem(int item, boolean smoothScroll):
and that the method instantiateItem is calling every single view. (I think it shouldn't be like this and maybe it's a bug).
Alternatively I tried to embed everything on a Fragment and change PageAdapter by FragmentStatePagerAdapter but the result was really similar.
What's going on with my code? Isn't it possible to skip, for example, 1000 pages without avoiding to draw each one?
Thanks to have taken the whole reading ;).
setCurrentItem(int) uses a smooth scroll, so that means that it will scroll through ALL the views until it arrives on the one that you asked for, so that means that all the views have to be drawn.
Isn't it possible to skip, for example, 1000 pages without avoiding to draw each one?
well, the solution here would be to make it without the scroll, setCurrentItem(int, false); that way the ViewPager will only draw that item and skip all the items in-between.
Hope it helps.
Related
as in topic i have huge problem with optymalization of my activity.
wireframe
At root level we have three controls:
- Tab Layout, related with ViewPager
- SurfaceView displaying plot for every fragment
- and ViewPager itself
ViewPager displays fragments marked on the picture with red color. Every Fragment contains child fragment(green border) displaying List of elements with some controls responsible for edition. Elements are shown in ListView with custom adapter. I have two problems with this view.
First problem:
I am using FragmenStatePagerAdapter for displaying views. I am storing instances of already created fragments inside adapter Map, to reuse them.
protected Map<Integer, Fragment> fragmentReferenceMap = new HashMap<Integer, Fragment>();
public MyPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
if(!fragmentReferenceMap.containsKey(position)) {
econetFragmentReferenceMap.put(position, MyFragment.newInstance(position));
}
return fragmentReferenceMap.get(position);
}
When I swipe back to once created Fragment adapter returns created instance from map but even with that, onCreate() on my Fragment is called and whole fragment is created once again. I know that ViewPager has option for setting offscreen limit but isn't there any way to reuse this fragment view?
The second problem is that the whole proces of inflating ListView take to long. I tried changing offScreenLimit but when it is set to 7(max number of view pages) app freeze for a few seconds before showing activity, and i see lags in swipe animations. When I set it to other number lik 1 or 0, activity creating is faster but I loss a lot of frames when swiping. I am using all fragments data are stored in single data table. This approach is ease what to store all changes even if one of fragment is destroyed by ViewPager, so I would like to stay with that. I tried mocking data for adapter and I found that it is not making big affect on its creation time. I have also tires using async task to getView method for gathering whole data for single row and in onPostExecute method add it to view but it not giving satisfing result
This is my getView method:
#NonNull
#Override
public View getView(final int position, #Nullable View convertView, #NonNull ViewGroup parent) {
convertView = super.getView(position,convertView,parent);
Spinner spinner = (Spinner) convertView.findViewById(R.id.UserMode);
final TextView startTime = (TextView) convertView.findViewById(R.id.rangeStart);
final TextView stopTime = (TextView) convertView.findViewById(R.id.rangeStop);
if(getConverter() != null){
List<String> values = getConverter().getValuesLabels();
ArrayAdapter spinerAdapter = new ArrayAdapter(context, R.layout.custom_sec_spinner, R.id.tvSpinnerText, values);
spinerAdapter.setDropDownViewResource(R.layout.spinner_dodatkowe_wiersz);
spinner.setAdapter(spinerAdapter);
spinner.setSelection(getConverter().getUserMode(modeIndex, position));
spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
#Override
public void onItemSelected(AdapterView<?> adapterView, View view, int position, long id) {
getConverter().setUserMode(modeIndex, position, position) ;
}
#Override
public void onNothingSelected(AdapterView<?> adapterView) {
}
});
int[] start = getConverter().getRangeStart(modeIndex, position);
int[] end = getConverter().getRangeEnd(modeIndex, position);
startTime.setText(start[0] + ":" + start[1]);
stopTime.setText(end[0] + ":" + end[1]);
startTime.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
TimePickerDialog mTimePicker;
int[] selectedTime = getConverter().getRangeStart(modeIndex, position);
mTimePicker = new TimePickerDialog(getContext(), new TimePickerDialog.OnTimeSetListener() {
#Override
public void onTimeSet(TimePicker timePicker, int selectedHour, int selectedMinute) {
getConverter().setRangeStart(modeIndex, position, new int[]{selectedHour, selectedMinute});
startTime.setText(selectedHour + ":" + selectedMinute);
}
}, selectedTime[0], selectedTime[1], true);
mTimePicker.show();
}
});
stopTime.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
TimePickerDialog mTimePicker;
int[] selectedTime = getConverter().getRangeEnd(modeIndex, position);
mTimePicker = new TimePickerDialog(getContext(), new TimePickerDialog.OnTimeSetListener() {
#Override
public void onTimeSet(TimePicker timePicker, int selectedHour, int selectedMinute) {
getConverter().setRangeEnd(modeIndex, position, new int[]{selectedHour, selectedMinute});
stopTime.setText(selectedHour + ":" + selectedMinute);
}
}, selectedTime[0], selectedTime[1], true);
mTimePicker.show();
}
});
}
else{
spinner.setEnabled(false);
}
return convertView;
}
So, could you please tell me how can I optimaze my adapter and ListView to make it work faster, or give user the experience that nothing is lagging? I would be gratefull for any advice or even links to similar articles because I've been searching for whole day and tried almost everything. Thank you :)
Create list view on a background thread.
Using a background thread ("worker thread") removes strain from the main thread so it can focus on drawing the UI. In many cases, using AsyncTask provides a simple way to perform your work outside the main thread. AsyncTask automatically queues up all the execute() requests and performs them serially. This behavior is global to a particular process and means you don’t need to worry about creating your own thread pool. reference link
Try to Use Holder Class to increase performance
A ViewHolder object stores each of the component views inside the tag field of the Layout, so you can immediately access them without the need to look them up repeatedly. First, you need to create a class to hold your exact set of views.
View row=view;
Holder holder=null;
if(row==null){
LayoutInflater inflater= (LayoutInflater)mContext.getSystemService(mContext.LAYOUT_INFLATER_SERVICE);
row= inflater.inflate(R.layout.half_movie,viewGroup,false);
holder=new Holder(row);
row.setTag(holder);
}else{
holder= (Holder) row.getTag();
}
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 have a challenging problem and wanted to see if the community has some comments about its implementation.
I have a ViewPager and added 20 pages in it, and each page has a WebView. – This provides vertical page flipping between multiple websites
It should work as a ViewPager by default but when I pinch to zoom, each page will shrink and the ViewPager will scroll like a List View.
I didn’t want to use a different view like a List View as I want a smooth transition and the two modes use the same web contents so want to avoid any construction/deconstruction that might affect a smooth transition.
I was able to implement this scrolling ViewPager with VelocityViewPager.
What I want to do is add a scrollbar to navigate down the pages by holding and dragging a scroller, just like a PC’s behaviour – as opposed to a ListView which only shows the scrollbar but allows no interaction. I have made a custom view that looks like a scrollbar and performs a ViewPager’s fake drag as the scrollbar is moved. The problem is I can only scroll pages ViewPager is holding - so when the ViewPager keeps the whole 20 pages, it works well but If I keep 3 pages as default to make use of page recycling, it doesn’t scroll past 3. It seems as though it doesn’t create the next pages that it usually would when scrolling with your finger.
I’m not sure why ViewPager doesn’t create next pages while fake dragging. Is there anything I have to do other than beginFakeDrag(), endFakeDrag() and fakeDragBy()?
And, Is there any way I can implement this behaviour without keeping all 20 pages in ViewPager?
Have a look into this code, this may be useful to you:
public class MainActivity extends Activity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ViewPagerAdapter adapter = new ViewPagerAdapter(this, imageArra);
ViewPager myPager = (ViewPager) findViewById(R.id.myfivepanelpager);
myPager.setAdapter(adapter);
myPager.setCurrentItem(0);
}
private int imageArra[] = { R.drawable.gallery_photo_1, R.drawable.gallery_photo_2,
R.drawable.gallery_photo_3, R.drawable.gallery_photo_5,
R.drawable.gallery_photo_6, R.drawable.gallery_photo_7,
R.drawable.gallery_photo_8, R.drawable.ic_launcher };
}
and ViewPager class is:
public class ViewPagerAdapter extends PagerAdapter {
Activity activity;
int imageArray[];
public ViewPagerAdapter(Activity act, int[] imgArra) {
imageArray = imgArra;
activity = act;
}
public int getCount() {
return imageArray.length;
}
public Object instantiateItem(View collection, int position) {
ImageView view = (ImageView) collection.findViewById(R.id.image);
/*
* view.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT,
* LayoutParams.FILL_PARENT)); view.setScaleType(ScaleType.FIT_XY);
*/
view.setBackgroundResource(imageArray[position]);
((ViewPager) collection).addView(view, 0);
return view;
}
#Override
public void destroyItem(View arg0, int arg1, Object arg2) {
((ViewPager) arg0).removeView((View) arg2);
}
#Override
public boolean isViewFromObject(View arg0, Object arg1) {
return arg0 == ((View) arg1);
}
#Override
public Parcelable saveState() {
return null;
}
}
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);
}
}
I use a ViewPager with many WebViews, it is for showing an ePub. My Problem is that the WebViews are just rendered/loaded (im not quite sure) wenn their corresponding page becomes visible. After this the page doesn't need to be re-rendered until it is destroyed from the ViewPagerAdapter.
The consequence is that there is always a white page for a little while. How can i pre-render the page that it scrolls smooth to next webView (which was not rendered before).
This is my PagerAdapter:
public class MagazineReaderPagerAdapter extends PagerAdapter {
private MagazineReaderActivity activity;
private EpubDocument epub;
public MagazineReaderPagerAdapter(Context ctx, EpubDocument epub)
{
activity = (MagazineReaderActivity) ctx;
this.epub = epub;
}
#Override
public int getCount() {
return epub.getContentDocuments().size();
}
#Override
public Object instantiateItem(ViewGroup container, int position) {
ReaderWebViewMulti view = new ReaderWebViewMulti(activity);
view.loadContentDocument(epub.getContentDocuments().get(position), epub);
((ViewPager) container).addView(view, 0);
return view;
}
#Override
public boolean isViewFromObject(View view, Object object) {
return view.equals(object);
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
((ViewPager) container).removeView((View) object);
}
}
ReaderWebViewMulti extends from WebView ind implements a method loadContentDocument wich loads the content via loadDataWithBaseURL.
edit:
At activity-oncreate the setOffscreenPageLimit is set to 3
viewPager.setOffscreenPageLimit(3);
To point out what the problem is, i made a little video on YouTube
From second 4 you can see that every page is just rendered when it is already visible. When i go back everything is fine.
I had the same problem and fixed it by disabling hardware acceleration for the web view:
webView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
See WebView inside ViewPager or ScrollView - weird rendering bug on Android 3.0+
I put some thoughts into this. Scrolling the screen 1 pixel would render the right page:
class SomeClass implements OnPageChangeListener {
private ViewPager viewPager;
...
viewPager = ...
viewPager.setOnPageChangeListener(this);
...
#Override
public void onPageScrollStateChanged(int state) {
if (state == ViewPager.SCROLL_STATE_IDLE) {
// viewpager finished scrolling to a page
viewPager.scrollBy(1, 0);
}
}
}
You would need to check if you are coming from right or left and according to this scroll by -1 or 1 pixels (the page where you are coming from will stay in memory, so you don't need to reload that one). Only at the the very first scroll (after opening the app) the problem described above will still persist.
Drawback: you can see the page scrolling 1 pixel, if you look real closely.
Someone with more time than me could dive into the android code to see what exactly happends when 1 pixel comes into the screen, and mimic some of this behaviour.
set this property for your application in manifest file
android:hardwareAccelerated="false"