Viewpager optimization with custom views - android

Scenario - I am working on an app that shows events respective to particular dates in a viewpager, where each page represents a day(i.e 24 hrs in a vertical manner, similar to google calendar). Each page/fragment contains a vertical scrollview(it has a framelayout inside it) and based on list of events i am dynamically creating custom-views(view position and dimension is based on the corresponding event timing and duration) and adding it to the scrollview. User can drag and drop events between dates.
Issue - I have successfully achieved the view creation part, now the issue is with performance. Sometimes vewpager(using FragmentStatePagerAdapter) lags while swiping through pages.
Someone please suggest me how to reduce the lag or any better ways to achieve this

Yes I had a similar performance with viewpager and FragmentStatePagerAdapter, the problem with viewpager is that pre creates the views either side of the current view to speed up the swipe to next view.
This works well for static views but for views with dynamic data the pre-created view was usually out of date and needed to be regenerated when the user swiped to it.
Thus it was having to call onCreateView on 3 views while the user swiped between views leading to lag sometimes.
I thought of 2 improvements for performance, though only used one.
1) The view holder/model pattern e.g. https://www.androidcode.ninja/android-viewholder-pattern-example/ and https://developer.android.com/topic/libraries/architecture/viewmodel where the inflation and finding items re-used / separated from onCreateView activities.
I did not use this method
2) Create bare bones views for the dynamic ones (The first view was static and then there were 2 dynamic ones at positions 1 and 2 that contained listviews of different aspects of the dynamic data.)
The listviews were inflated and had an adapter set to an empty list in onCreateView of these views thus they were fast to create.
Then I added an on OnPageChangeListener which notified the adapter backing the ViewPager that the pages had changed
mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
#Override
public void onPageSelected(int position) {
if (position == 1 || position == 2)
{
mSectionsPagerAdapter.notifyDataSetChanged();
}
}
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
// Code goes here
}
#Override
public void onPageScrollStateChanged(int state) {
// Code goes here
}
});
The notifyDataSetChanged causes the viewPager to call getItemPosition in the FragmentStatePagerAdapter class to work out if the page position has changed and it if need to re-create it in the a new position.
Then in FragmentStatePagerAdapter extended class I overrode getItemPosition with
#Override
public int getItemPosition(Object object) {
if (object instanceof LogFragment) {
LogFragment f = (LogFragment) object;
if (f != null) {
f.update();
}
} else if (object instanceof SummaryFragment){
SummaryFragment f = (SummaryFragment) object;
if (f != null) {
f.update();
}
} else {
return POSITION_UNCHANGED;
}
return super.getItemPosition(object);
}
This allowed me to get the Fragment object and then call the update method on it but still returning POSITION_UNCHANGED so the viewpager did not try and re-create the Fragments.
Then in the update method of the Fragment I get the listview adapter and update the data.
public void update(){
adapter.clear();
adapter.addAll(datasource.getData());
}
Thus the more costly getting and display the dynamic data is only done when the page is actually display, NOT when it is pre-created by viewPager (because that pre-created view would be old out of date data anyway and would need to be updated)
There is one downside to this approach, the screen shown during the swipe is still the old data (either empty or data from when that page was last updated), this was acceptable to me.

Related

How to update a single page content in a ViewPager without scrolling to the first page?

I want to update a specific page in a ViewPager, and stay on that page and not scroll to the first page
I tried to update the data list and call notifyDataSetChanged() but it scrolls to the first page, ViewPager doesn't have notifyItemChanged() as RecyclerView.
Some code would help understand the problem better, showing what you are trying to update on the page and what you are calling notifyDataSetChanged() on.
Your question and comment are two separate problems.
You are on Tab Y and you want to update Tab Y
You are on Tab X and you want to update Tab Y when you change to it.
Usually for problem 1, this can be handled within the code for that Tab(page)
For Problem 2 in general if you want to do something when you move to a page then you need to add a OnPageChangeListener but this might only be the start of what you need to do to update the Tab(page).
https://developer.android.com/reference/android/support/v4/view/ViewPager.OnPageChangeListener.html#onPageSelected(int)
mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
#Override
public void onPageSelected(int position) {
if (position == 1 || position == 2)
{
// do something here
}
if (position == 0)
{
// do something else
}
}
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
// Code goes here
}
#Override
public void onPageScrollStateChanged(int state) {
// Code goes here
}
});
But also without code I cannot tell if you have implemented the adapter in a way allows it to key the current page active when notifyDataSetChanged() as
https://developer.android.com/reference/androidx/viewpager/widget/PagerAdapter.html#top_of_page
PagerAdapter supports data set changes. Data set changes must occur on the main thread and must end with a call to notifyDataSetChanged() similar to AdapterView adapters derived from BaseAdapter. A data set change may involve pages being added, removed, or changing position. The ViewPager will keep the current page active provided the adapter implements the method getItemPosition(Object).

RecyclerView onBindViewHolder called only once inside Tab layout

I've four tabs and four fragments (each one for each tab).
Each fragment has a vertical recycler view. Since all fragments view look similar I'm re-using the same layout file, same recycler view items and same adapter.
The issue is that only one item is loaded under the first tab and third tab and fourth tab, While the second tab successfully loads the entire data.
I hope image added below gives better understanding regarding the issue.
Here is my adapter code
public class OthersAdapter extends RecyclerView.Adapter<OthersAdapter.OthersViewHolder> {
private final Context context;
private final ArrayList<LocalDealsDataFields> othersDataArray;
private LayoutInflater layoutInflater;
public OthersAdapter(Context context, ArrayList<LocalDealsDataFields> othersDataArray) {
this.context = context;
this.othersDataArray = othersDataArray;
if (this.context != null) {
layoutInflater = LayoutInflater.from(this.context);
}
}
class OthersViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
TextView othersSmallTitleTextView;
ImageView othersImageView;
OthersViewHolder(View itemView) {
super(itemView);
othersSmallTitleTextView = (TextView) itemView.findViewById(R.id.others_small_title);
othersImageView = (ImageView) itemView.findViewById(R.id.others_image);
itemView.setOnClickListener(this);
}
#Override
public void onClick(View view) {
Intent couponDetailsItem = new Intent(context, LocalDealsActivity.class);
Bundle extras = new Bundle();
extras.putString(Constants.SECTION_NAME, context.getString(R.string.local_deals_section_title));
// Add the offer id to the extras. This will be used to retrieve the coupon details
// in the next activity
extras.putInt(Constants.COUPONS_OFFER_ID, othersDataArray.get(
getAdapterPosition()).getLocalDealId());
couponDetailsItem.putExtras(extras);
context.startActivity(couponDetailsItem);
}
}
#Override
public OthersViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = layoutInflater.inflate(R.layout.others_items, parent, false);
return new OthersViewHolder(view);
}
#Override
public void onBindViewHolder(OthersViewHolder holder, int position) {
String lfImage = othersDataArray.get(position).getLocalDealImage();
String lfCategoryName = othersDataArray.get(position).getLocalDealSecondTitle();
if (lfCategoryName != null) {
// Set the second title
holder.othersSmallTitleTextView.setText(lfCategoryName);
}
if (lfImage != null) {
if (!lfImage.isEmpty()) {
// Get the Uri
Uri lfUriImage = Uri.parse(lfImage);
// Load the Image
Picasso.with(context).load(lfUriImage).into(holder.othersImageView);
}
}
}
#Override
public int getItemCount() {
return othersDataArray.size();
}
}
I like to point out couple of things -
I've checked other answers on Stack Overflow. They talk about setting the recycler view layout_height to wrap_content. This isn't the issue as the layout_height is already wrap_content and also the second tab loads all the data as expected.
And some others answers mentioned to used same versions for all support libraries and I'm already using 25.1.0 version for all the support libraries.
Size of the data array is 20 and returning 20 from the adapter's getItemCount() method.
The data array has the expected number of items in it and they are not null or empty.
Clean build, invalidate/caches doesn't work either.
Finally, I'm using FragmentStatePagerAdapter to load the fragments when the tabs are in focus.
EDIT:
This is how I'm parsing the JSON data received
private void parseLocalDeals(String stringResponse) throws JSONException {
JSONArray localJSONArray = new JSONArray(stringResponse);
// If the array length is less than 10 then display to the end of the JSON data or else
// display 10 items.
int localArrayLength = localJSONArray.length() <= 20 ? localJSONArray.length() : 20;
for (int i = 0; i < localArrayLength; i++) {
// Initialize Temporary variables
int localProductId = 0;
String localSecondTitle = null;
String localImageUrlString = null;
JSONObject localJSONObject = localJSONArray.getJSONObject(i);
if (localJSONObject.has(JSONKeys.KEY_LOCAL_DEAL_ID)) {
localProductId = localJSONObject.getInt(JSONKeys.KEY_LOCAL_DEAL_ID);
}
if (localJSONObject.has(JSONKeys.KEY_LOCAL_DEAL_CATEGORY)) {
localSecondTitle = localJSONObject.getString(JSONKeys.KEY_LOCAL_DEAL_CATEGORY);
}
if (localJSONObject.has(JSONKeys.KEY_LOCAL_DEAL_IMAGE)) {
localImageUrlString = localJSONObject.getString(JSONKeys.KEY_LOCAL_DEAL_IMAGE);
}
if (localImageUrlString != null) {
if (!localImageUrlString.isEmpty()) {
// Remove the dots at the start of the Product Image String
while (localImageUrlString.charAt(0) == '.') {
localImageUrlString = localImageUrlString.replaceFirst(".", "");
}
// Replace the spaces in the url with %20 (useful if there is any)
localImageUrlString = localImageUrlString.replaceAll(" ", "%20");
}
}
LocalDealsDataFields localDealsData = new LocalDealsDataFields();
localDealsData.setLocalDealId(localProductId);
localDealsData.setLocalDealSecondTitle(localSecondTitle);
localDealsData.setLocalDealImage(localImageUrlString);
localDealsDataArray.add(localDealsData);
}
// Initialize the Local Deals List only once and notify the adapter that data set has changed
// from second time. If you initializeRV the localDealsRVAdapter at an early instance and only
// use the notifyDataSetChanged method here then the adapter doesn't update the data. This is
// because the adapter won't update items if the number of previously populated items is zero.
if (localDealsCount == 0) {
if (localArrayLength != 0) {
// Populate the Local Deals list
// Specify an adapter
localDealsRVAdapter = new OthersAdapter(context, localDealsDataArray);
localDealsRecyclerView.setAdapter(localDealsRVAdapter);
} else {
// localArrayLength is 0; which means there are no rv elements to show.
// So, remove the layout
contentMain.setVisibility(View.GONE);
// Show no results layout
showNoResultsIfNoData(localArrayLength);
}
} else {
// Notify the adapter that data set has changed
localDealsRVAdapter.notifyDataSetChanged();
}
// Increase the count since parsing the first set of results are returned
localDealsCount = localDealsCount + 20;
// Remove the progress bar and show the content
prcVisibility.success();
}
parseLocalDeals method is inside a helper class and it is called by using initializeHotels.initializeRV();
initializeRV() initializes the Recycler view, makes a network call to the server and the received data is passed to the parseLocalDeals method. initializeHotels being an instance variable of the Helper class.
EDIT 2:
For those who wants to explore the code in detail, I've moved the part of the code to another project and shared it on Github. Here is the link https://github.com/gSrikar/TabLayout and to understand the hierarchy check out the README file.
Can anyone tell me what I'm missing?
Not much of an answer but too long for a comment.
I have duplicated (almost) your adapter code and it fully works for me. I believe I have done the same as you. I'm using the same layout file, the same item & same adapter for all tabs. I think there are no problems with your adapter code.
I say 'almost' because I had to change a couple of things since I don't have access to your data. I changed your LocalDealsDataField model to include a BitmapDrawable & I changed onBindViewHolder() to handle it.
BitmapDrawable lfImage = othersDataArray.get(position).getLocalDealImage();
holder.othersImageView.setBackground(lfImage);
Since there seems to be no problem with your adapter, I would focus on getting the data or setting up the adapter as your problem. Sorry I can't be of help beyond that.
FYI, here's how I setup the adapter in onCreateView()
rootView = inflater.inflate(R.layout.recycler_view, container, false);
mRecyclerView = (RecyclerView) rootView.findViewById(R.id.recyclerview);
mRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
mAdapter = new OthersAdapter(this.getContext(), list);
mRecyclerView.setAdapter(mAdapter);
Summary
Solved the layout issue at point 1 replacing a LinearLayout by a RelativeLayout, inverting visibility logic to avoid ghost effect and catching exceptions and preventing them when the related view is not found.
Added point 2 to demonstrate that the visual defect is only present on Marshmallow and Nougat devices.
Finally FragmentStatePagerAdapter loads pages before getting focus so a fix is proposed at point 3 (load all pages and update them when are selected).
Further information in the comments below and #d4h answer.
The fourth page is not using the same layout, only the same RecyclerView and id, perhaps a work in progress. The layout issue can be solved using the same layout that previous pages but I consider this change out of scope.
1. Partially fixed for Marshmallow and Nougat devices. Work in progress.
Update2 Changing LinearLayout by RelativeLayout and inverting visibility logic solves layout issue:
Update: Commenting initializeTrending in all the fragment initializations also works onApi23+
I'll check it later, seems as deals are correctly loaded but then trending is loaded and deals are lost. WIP here.
If trending array empty and trending view gone, deals are not shown, but using invisible are shown
2. You are loading a wrong page on Marshmallow and Nougat devices
FragmentStatePagerAdapter first call to getItem() wrong on Nougat devices
This ended up having nothing to do with the FragmentStatePagerAdapter
code. Rather, in my fragment, I grabbed a stored object from an array
using the string ("id") that I passed to the fragment in init. If I
grabbed that stored object by passing in the position of the object in
the array, there was no problem. Only occurs in devices with Android 7.
FragmentStatePagerAdapter - getItem
A FragmentStatePager adapter will load the current page, and one page
either side. That is why it logs 0 and 1 at the same time. When you
switch to page 2, it will load page 3 and keep page 1 in memory. Then
when you get to page 4 it will not load anything, as 4 was loaded when
you scrolled to 3 and there is nothing beyond that. So the int that
you're being given in getItem() is NOT the page that is currently
being viewed, is the one being loaded into memory. Hope that clears
things up for you
These comments are confirmed in this branch and commit
All pages load correctly on Lollipop emulator, the last page has an extra issue, see OthersFragment:
3. Initialize all pages at creation and update them on selection.
Increase OffScreenPageLimit so all pages are initialised
Add on page selected/unselected/reselected listener
These changes solve the issue commented below:
/**
* Implement the tab layout and view pager
*/
private void useSlidingTabViewPager() {
// Create the adapter that will return a fragment for each of the three
// primary sections of the activity.
BottomSectionsPagerAdapter mBottomSectionsPagerAdapter = new BottomSectionsPagerAdapter(getChildFragmentManager());
// Set up the ViewPager with the sections adapter.
ViewPager mBottomViewPager = (ViewPager) rootView.findViewById(R.id.local_bottom_pager);
mBottomViewPager.setOffscreenPageLimit(mBottomSectionsPagerAdapter.getCount());
mBottomViewPager.setAdapter(mBottomSectionsPagerAdapter);
TabLayout tabLayout = (TabLayout) rootView.findViewById(R.id.tab_layout);
tabLayout.setupWithViewPager(mBottomViewPager);
tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
/**
* Called when a tab enters the selected state.
*
* #param tab The tab that was selected
*/
#Override
public void onTabSelected(TabLayout.Tab tab) {
// TODO: update the selected page here
Log.i(LOG_TAG, "page " + tab.getPosition() + " selected.");
}
/**
* Called when a tab exits the selected state.
*
* #param tab The tab that was unselected
*/
#Override
public void onTabUnselected(TabLayout.Tab tab) {
// Do nothing
Log.i(LOG_TAG, "Page " + tab.getPosition() + " unselected and ");
}
/**
* Called when a tab that is already selected is chosen again by the user. Some applications
* may use this action to return to the top level of a category.
*
* #param tab The tab that was reselected.
*/
#Override
public void onTabReselected(TabLayout.Tab tab) {
// Do nothing
Log.i(LOG_TAG, "Page " + tab.getPosition() + " reselected.");
}
});
}
Previous Comments:
Check your LocalFragment getItem() method using breakpoints.
If you select one page, next page is also initialized, and you are sharing the recyclerView, etc.
I would move the initialization outside of getItem() as suggested here:
ViewPager is default to load the next page(Fragment) which you can't
change by setOffscreenPageLimit(0). But you can do something to hack.
You can implement onPageSelected function in Activity containing the
ViewPager. In the next Fragment(which you don't want to load), you
write a function let's say showViewContent() where you put in all
resource consuming init code and do nothing before onResume() method.
Then call showViewContent() function inside onPageSelected. Hope this
will help
Read these related questions (the first has possible workarounds to hack the limit to zero):
ViewPager.setOffscreenPageLimit(0) doesn't work as expected
Does ViewPager require a minimum of 1 offscreen pages?
Yes. If I am
reading the source code correctly, you should be getting a warning
about this in LogCat, something like:
Requested offscreen page limit 0 too small; defaulting to 1
viewPager.setOffscreenPageLimit(couponsPagerAdapter.getCount());
public void setOffscreenPageLimit(int limit) {
if (limit < DEFAULT_OFFSCREEN_PAGES) {
Log.w(TAG, "Requested offscreen page limit " + limit + " too small; defaulting to "
+ DEFAULT_OFFSCREEN_PAGES);
limit = DEFAULT_OFFSCREEN_PAGES;
}
if (limit != mOffscreenPageLimit) {
mOffscreenPageLimit = limit;
populate();
}
}
I have looked at your code, problem is same as explained by #ardock
Solution i would like to propose,
You have to change your code at 3 place ::
Inside all Fragment You are using in ViewPager Don't call initializeRESPECTIVEView() from onCreateView method.
Inside LocalFragment make a list of Fragments you are going to use with ViewPager and pass it to BottomSectionsPagerAdapter. and return Fragment from that list from getItem(int position) of BottomSectionsPagerAdapter.
Add Following code to LocalFragment inside useSlidingTabViewPager().
tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
#Override
public void onTabSelected(TabLayout.Tab tab) {
}
#Override
public void onTabUnselected(TabLayout.Tab tab) {
}
#Override
public void onTabReselected(TabLayout.Tab tab) {
}
});
//Call Respective fragment initializeRESPECTIVEView() method from onTabSelected , you can get fragment instance from list you passed to BottomSectionsPagerAdapter

Android: how can I create a ListView header outside of a list?

This is my situation:
I would like the list to treat the "header" and tabs sections as a list header so that the "header" and tabs do not stay fixed on the screen and they all scroll together with the list.
I can't simply add the header and tabs as a headerView for the list via mListView.addHeaderView() because the tabs will swap out the "List" content area when pressed. The content area for the other tabs will contain other lists, and the header and tabs should scroll with the new list as well.
I'd appreciate any help.
You can override the getViewType method in order to precise how many kind of rows you listview has.
Have a look here.
Think of the entire thing as the ListView. You can call addHeaderView() as many times as you want so you can have two header views ("Header", and "Tab | Tab | Tab").
Re:
I can't simply add the header and tabs as a headerView for the list via mListView.addHeaderView() because the tabs will swap out the "List" content area when pressed.
Yes you can.
Update the reference to your list data and call mAdapter.notifyDataSetChanged().
I had the same need as you, I found this project that implements exactly what you need, plus some more eye-candy tricks.
https://github.com/kmshack/Android-ParallaxHeaderViewPager
The magic is binding the scrolling that happens in the fragment list to the header defined in the activity. There is also a fundamental binding between the selection of a tab and the scroll position in the list, to adjust lists when swiping.
This code is based upon that github repo but is simpler (therefore less robust), read it just to understand what happens, then read the source in the repo.
public class MyFragment extends Fragment implements OnScrollListener {
#Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
((MyParentActivity) getActivity()).onScrollFragment(view, mTabPosition);
}
public void adjustScroll(int scrollHeight) {
if (scrollHeight == 0 && fragmentListView.getFirstVisiblePosition() >= 1) {
return;
}
officesListView.setSelectionFromTop(1, scrollHeight);
}
}
then in the activity you just need these two specific methods
public class MyParentActivity implements ViewPager.OnPageChangeListener{
#Override
public void onPageSelected(int position)
myFragmentPagerAdapter.getFragmentAt(position)
.adjustScroll((int) (mHeader.getHeight() + ViewHelper.getTranslationY(mHeader)));
}
public void onScrollFragment(AbsListView view, int tabPosition) {
if (mViewPager.getCurrentItem() == tabPosition) {
int scrollY = getScrollY(view);
ViewHelper.setTranslationY(mHeader, Math.max(-scrollY, mMinHeaderTranslation));
}
}
}
this code is good up to API 8 thanks to NineOldAndroids' ViewHelper

ViewPager - update view after adapter got empty

I'm using a FragmentStatePagerAdapter with a ViewPager.
Everything is working fine. If I open my activity with an empty Adapter, the ViewPageris empty, if I add items, the ViewPager updates correctly.
BUT, if I open my activity and delete the last item of my ViewPager, the ViewPagerdoes not invalidate correctly and keeps the last Fragment visible.
How can I avoid this?
I'm using my library, it's a wrapper class for ViewPager + ViewPagerIndicator + FragmentPager(State)Adapter:
The class itself is placed here: https://github.com/MichaelFlisar/PagerManager/blob/master/src/com/michaelflisar/pagermanager/MFragmentPagerStateAdapter.java
The implementation is placed here: https://github.com/MichaelFlisar/PagerManager/blob/master/src/com/michaelflisar/pagermanager/MPagerAdapterHelper.java
It implements a simple FragmentStatePagerAdapter with weak references to it's fragments...
My code looks like following:
mPagerManager = new MPagerManager<ExerciseViewFragment, MFragmentPagerStateAdapter<ExerciseViewFragment>>(pager, tpi,
new MFragmentPagerStateAdapter<ExerciseViewFragment>(fragmentManager)
{
#Override
public CharSequence getPageTitle(int pos)
{
return mData.workout.getWExercise().get(pos).getExercise().getName();
}
#Override
public int getCount()
{
return mData.workout.getWExercise().size();
}
#Override
public ExerciseViewFragment createFragment(int pos)
{
return ExerciseViewFragment.newInstance(pos, mData.workout.getWExercise());
}
});
I'm calling mPagerManager.notifyDataSetChanged(); which forwards the call to the FragmentPagerStateAdapter directly...
PS: I know, I can make it invisible, if item count is 0... But I'm wondering if there's a better solution
This is an old question but I thought you might still need to know what to do. It's very common issue. ViewPager does not invalid views which are already created(including these which are ready on the left and right side of your current view).
Solving this is very easy. Just implement the following method in your adapter like this:
#Override
public int getItemPosition(Object object) {
return POSITION_NONE;
}
By default this method returns PagerAdapter.POSITION_UNCHANGED.
For most developers this method usage is misleading, I had this problem myself till I realised that this method is used by ViewPager.dataSetChanged() to establish which items should be recreated. With above code you tell ViewPager to recreate all items whenever data set change.

Endless adapter for ViewPager

I've been using CWAC's EndlessAdapter to achieve infinite scrolling on ListViews.
I'd like to accomplish the equivalent for a ViewPager. Unfortunately, PageAdapter and ListAdapter do not share the same common base class.
What's the best way to go about this? Does a library exist that already handles this?
What's the best way to go about this?
Add "endless" logic to your own implementation of PagerAdapter. Or, if you wish, try creating a decorating PagerAdapter, the way that EndlessAdapter decorates a regular Adapter.
The latter is likely to be tricky, given that PagerAdapter is designed for pages to be views or fragments, and the fragment handling inside of classes like FragmentPagerAdapter is a bit scary.
Does a library exist that already handles this?
None that I am aware of.
Mainly, that is because the use case doesn't seem as compelling. With a ListView, the user can fling the list, scrolling through dozens or hundreds of rows very quickly. Hence, using "we got to the end" as the trigger to load more data seems reasonable. With a ViewPager, though, it typically takes a lot longer to get to the end, particularly if you are not using PagerTabStrip or the equivalent. Hence, waiting until the user gets all the way to the end to begin loading additional data seems like it would be annoying to the user -- you had all this time to go retrieve more data, but didn't use it.
An alternative, therefore, is for you to register a ViewPager.OnPageChangeListener with your ViewPager. When onPageSelected(), and you consider yourself to be close to the end, kick off an AsyncTask (or whatever) to go gather more data. The catch then is that you will need to update the data used by the PagerAdapter and call notifyDataSetChanged() on that adapter once the data has been updated.
#Override
public int getCount() {
return (Integer.MAX_VALUE);
//artificially large value for infinite scrolling
}
public int getRealCount(){
//Do something to return the actual number of objects.
}
#Override
public Object instantiateItem(ViewGroup container, int position) {
int virtualPosition = position % getRealCount();
return instantiateVirtualItem(container, virtualPosition);
}
public Object instantiateVirtualItem(ViewGroup container, final int position) {
//Do the required part here
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
int virtualPosition = position % getRealCount();
destroyVirtualItem(container, virtualPosition, object);
}
public void destroyVirtualItem(ViewGroup container, int position, Object object){
container.removeView((View) object);
}
Now, the most important part
pager.setOffscreenPageLimit(10); //your choice
pager.setCurrentItem(Integer.MAX_VALUE/2,false);
//pager is the ViewPager object
PS: I have successfully implemented this. Ask if you still have doubt.
Maybe you can 'fake it out' as follows:
You are likely to show a huuuuge number of pages. Use FragmentStatePagerAdapter class:
https://developer.android.com/reference/android/support/v13/app/FragmentStatePagerAdapter.html
Implement the getCount method by returning Integer.MAX_VALUE.
Implement the getItemPosition method by always returning POSITION_NONE.
Implement the getItem method as you wish, returning the appropriate Fragment.
Then, when the Activity that hosts the ViewPager starts, set the initial position of the ViewPager to a very large number, e.g. viewPager.setCurrentItem(Integer.MAX_VALUE / 2);.
I haven't tried this myself..., YMMV! :)

Categories

Resources