Viewpager onpageselected return wrong position - android

I ran into some strange issue, whenever I'm trying to assign position to my textview, it goes very strange with the first and second page, it returns 0 for the first one and 0 for the second one, page three says 1, and whenever I swipe back to page 0 it says 1.
Here is my activity (theabcactivity):
public class theabcactivity extends FragmentActivity {
String[] heabc;
int backColor;
int currentnumber = 0;
private ViewPager mPager;
private PagerAdapter mPagerAdapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.content_theabcactivity);
heabc = getResources().getStringArray(R.array.heabc);
// Instantiate a ViewPager and a PagerAdapter.
mPager = (ViewPager) findViewById(R.id.pager);
mPager.setPageTransformer(true, new ZoomOutPageTransformer());
mPagerAdapter = new
ScreenSlidePagerAdapter(getSupportFragmentManager());
mPager.setAdapter(mPagerAdapter);
mPager.setCurrentItem(0);
mPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
#Override
public void onPageSelected(int CurPage) {
currentnumber = CurPage;
Log.e("CurPage", " " + CurPage);
}
#Override
public void onPageScrollStateChanged(int state) {
}
});
}
private class ScreenSlidePagerAdapter extends FragmentStatePagerAdapter {
public ScreenSlidePagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
Random rnd = new Random();
backColor = Color.argb(40, rnd.nextInt(256), rnd.nextInt(256), rnd.nextInt(256));
return new theabc();
}
#Override
public int getCount() {
return heabc.length;
}
}
}
Here I'm assigning the currentnumber = CurPage;, that I reuse later on at my fragment.
Fragment code (theabc):
public class theabc extends Fragment {
TextView itemscount;
String [] heabc;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
ViewGroup rootView = (ViewGroup) inflater.inflate(
R.layout.theabc, container, false);
LinearLayout contentll = (LinearLayout)rootView.findViewById(R.id.content);
heabc = getResources().getStringArray(R.array.heabc);
itemscount = (TextView)rootView.findViewById(R.id.itemcount);
theabcactivity abcact = (theabcactivity) getActivity();
contentll.setBackgroundColor(abcact.backColor);
itemscount.setText(String.valueOf(abcact.currentnumber));
Log.e("www", "current number value" + abcact.currentnumber);
return rootView;
}
}
One other thing i've noticed that whenever this activity opens up, it loads the fragment two times as I'm receiving the following Log:
E/www: current number value0
E/www: current number value0
It leads me into conclusion that when Viewpager opens up it loads the first two items, the first one and the second one.
How can I solve that it would load the first item as 0, the next one 1 and so on?
Thanks in advance.

This is the normal behaviour of the viewPager widget, it first instantiates the first element (which is the current element) then it loads the next element of the list/array to give the scroll activity a smooth animation. I have faced the same issue and solved it by adding a listener to my view pager
mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
// This is called a lot of times when the user is scrolling
}
#Override
public void onPageSelected(int position) {
// Check position here to see which page was selected
Log.i("current pos_page", "\n" + "\t\t" + "current pos of selected page after scroll = " + position );
display(position);
}
#Override
public void onPageScrollStateChanged(int state) {
// Called when the scroll state changes (scroll started - ended)
if ( state == ViewPager.SCROLL_STATE_IDLE) {
currentPosition = mViewPager.getCurrentItem();
Log.i("current pos_scrolled", "\n" + "\t\t" + "current pos SCROLLED FINISH = " + currentPosition );
}
}
});
Then I have used the overrided methods of the page change listener to set the current position to the one given by the onPageScrollStateChanged once the animation of scroll is finished.
Finally, i just call the function i created to set the views i'd like to update inside of onPageSelected.

Related

ViewPager Loads Blank Screen Until User Clicks Anywhere On Screen

I have implemented a viewpager adapter, the scrolling and contents returned works fine but the issue is, when I start my app, the screen is blank and only when I click anywhere on the blank screen does the viewpager load the first page (item 0) and everything else works fine after that. How can I get my app to load viewpager items without having to click first:
MainActivity:
viewPager = (ViewPager) findViewById(R.id.pager);
viewPager.setAdapter(new ViewPagerAdapter(getApplicationContext(), userid, names, status, phonenumbers, rates, urls, dobyear, dobmonth, dobday, starrate));
viewPager.setOnPageChangeListener(new OnPageChangeListener() {
public void onPageScrollStateChanged(int state) {
}
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
public void onPageSelected(int position) {
}
});
}
ViewPagerAdapter:
#Override
public int getCount() {
return this._userid.size();
}
#Override
public int getCount() {
return this._userid.size();
}
#Override
public boolean isViewFromObject(View view, Object object) {
return view == (object);
}
#Override
public Object instantiateItem(ViewGroup container, final int position) {
ImageView imgDisplay, ivRateBar;
TextView tvName, tvPhoneNumber, tvRate, tvStatus, tvYear, tvDistance;
inflater = (LayoutInflater) c.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View viewLayout = inflater.inflate(R.layout.viewpager_item, container,false);
....
//set my textviews and imges
I tried:
private static int currentPage = 0;
public void onPageSelected(int position) {
currentPage = position;
}
OK, it works when I use a handler.postDelayedof half a second before setting my viewPager adapter, the logical reason behind this may be that the viewpager loads before all my data can be retrieved from the database.
After you get the data you are looking for try calling
myAdapter.notifyDataSetChanged();
You just have to save the Adapter instance like so:
// Change this
viewPager.setAdapter(new ViewPagerAdapter(getApplicationContext(), userid, names, status, phonenumbers, rates, urls, dobyear, dobmonth, dobday, starrate));
// To this
ViewPagerAdapter myAdapter = new ViewPagerAdapter(getApplicationContext(), userid, names, status, phonenumbers, rates, urls, dobyear, dobmonth, dobday, starrate);
viewPager.setAdapter(myAdapter);
...
// When the data is finished loading (wherever you load the data)
if (myAdapter != null) {
myAdapter.notifyDataSetChanged();
}
And the adapter should refresh itself with the correct data.
The only way worked for me:
if(viewPager.getAdapter()!=null){
viewPager.getAdapter().notifyDataSetChanged();
container.removeView(viewPager);
container.addView(viewPager);
}

FragmentPagerAdapter in ViewPager

I want to show 3 different views in ViewPager and I want to navigate between them with bottom navigation bar. But I have a serious performance problem. When I tried to switch the view, it switches with laggy swipe and I think it's because every fragment is re-created everytime I switch or it's because I didn't figure onPageSelected method out. I couldn't fix it.
Here is my codes.
MainActivity.java
public class MainActivity extends FragmentActivity {
private BottomBar mBottomBar;
private User currentUser;
private NonSwipeableViewPager viewPager;
private Context context;
private FragmentAdapter fragAdapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
context = this;
// To retrieve object in second Activity
currentUser = (User) getIntent().getSerializableExtra("currentUser");
//initialize views
initView();
//bottom bar
mBottomBar = BottomBar.attach(this, savedInstanceState);
mBottomBar.noTabletGoodness();
//mBottomBar.setActiveTabColor(R.color.app_design_color);
mBottomBar.setItems(R.menu.bottombar_menu);
mBottomBar.setActiveTabColor("#6c4853");
//mBottomBar.setBottom(1);
mBottomBar.setOnMenuTabClickListener(new OnMenuTabClickListener() {
#Override
public void onMenuTabSelected(#IdRes int menuItemId) {
if (menuItemId == R.id.bottomContacts) {
// The user selected item number one.
setPage(Constants.CONTACTS_NUM);
}else if (menuItemId == R.id.bottomQR) {
// The user selected item number two.
//setAdapterClick(Constants.ADD_NUM);
}else if (menuItemId == R.id.bottomProfile) {
// The user selected item number three.
setPage(Constants.PROFILE_NUM);
}
}
#Override
public void onMenuTabReSelected(#IdRes int menuItemId) {
if (menuItemId == R.id.bottomContacts) {
// The user selected item number one.
}else if (menuItemId == R.id.bottomQR) {
// The user selected item number one.
}else if (menuItemId == R.id.bottomProfile) {
// The user selected item number one.
}
}
});
}
private void initView(){
viewPager = (NonSwipeableViewPager) findViewById(R.id.viewpager);
fragAdapter = new FragmentAdapter(getSupportFragmentManager(), currentUser, context);
viewPager.setAdapter(fragAdapter);
viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
#Override
public void onPageSelected(int position) {
}
#Override
public void onPageScrollStateChanged(int state) {
}
});
}
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
// Necessary to restore the BottomBar's state, otherwise we would
// lose the current tab on orientation change.
mBottomBar.onSaveInstanceState(outState);
}
private void setPage(final int pageNum){
viewPager.setCurrentItem(pageNum, true);
Global.setCurrentPageNum(pageNum);
}
private void setAdapterClick(int no){
switch (no){
case Constants.CONTACTS_NUM :
setPage(Constants.CONTACTS_NUM);
break;
case Constants.ADD_NUM :
setPage(Constants.ADD_NUM);
break;
case Constants.PROFILE_NUM :
setPage(Constants.PROFILE_NUM);
break;
}
}
}
FragmentAdapter.java
public class FragmentAdapter extends FragmentPagerAdapter{
private Context context;
private User currentUser;
private FragmentManager fm;
public FragmentAdapter(FragmentManager fm, User currentUser, Context context) {
super(fm);
this.fm = fm;
this.context = context;
this.currentUser = currentUser;
}
#Override
public Fragment getItem(int position) {
switch (position){
case 0 :
return new ContactsFragment().newInstance(context, currentUser);
case 1 :
return new ContactsFragment().newInstance(context, currentUser);
case 2 :
return new ProfileFragment().newInstance(context, currentUser);
default :
return new ContactsFragment().newInstance(context, currentUser);
}
}
#Override
public int getCount() {
return 3;
}
}
One of my Fragments:
public class ContactsFragment extends Fragment{
// Store instance variables
private Context context;
private User currentUser;
private View actionView;
private View contactsView;
// newInstance constructor for creating fragment with arguments
public static ContactsFragment newInstance(Context context, User currentUser) {
ContactsFragment fragmentFirst = new ContactsFragment();
Bundle args = new Bundle();
args.putSerializable("currentUser", currentUser);
fragmentFirst.setArguments(args);
return fragmentFirst;
}
// Store instance variables based on arguments passed
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.context = getActivity();
this.currentUser = (User) getArguments().getSerializable("currentUser");
}
// Inflate the view for the fragment based on layout XML
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
if(contactsView == null){
contactsView = inflater.inflate(R.layout.contacts_layout, container, false);
RecyclerView recyclerView = (RecyclerView) contactsView.findViewById(R.id.recycler_view);
LinearLayoutManager lLayout = new GridLayoutManager(context, 1);
recyclerView.addItemDecoration(new DividerItemDecoration(context, R.drawable.divider));
recyclerView.setHasFixedSize(true);
recyclerView.setLayoutManager(lLayout);
recyclerView.setAdapter(new CategoryAdapter(context, currentUser.categories));
}
return contactsView;
}
}
Edit:
If I disable animation while setting current item, there is no problem. But I want to use animation.
Try adding this line after you setting adapter for your ViewPager in initView()
viewPager.setOffScreenPageLimit(3);
The following method is alreday provided by ViewPager class in Android
/**
* Set the number of pages that should be retained to either side of the
* current page in the view hierarchy in an idle state. Pages beyond this
* limit will be recreated from the adapter when needed.
*
* <p>This is offered as an optimization. If you know in advance the number
* of pages you will need to support or have lazy-loading mechanisms in place
* on your pages, tweaking this setting can have benefits in perceived smoothness
* of paging animations and interaction. If you have a small number of pages (3-4)
* that you can keep active all at once, less time will be spent in layout for
* newly created view subtrees as the user pages back and forth.</p>
*
* <p>You should keep this limit low, especially if your pages have complex layouts.
* This setting defaults to 1.</p>
*
* #param limit How many pages will be kept offscreen in an idle state.
*/
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();
}
}
One More thing I have noticed inside your code is that you are using getIntentSearializableExtra() cuz you are using Serializable to transport data inside your activity and also in Fragment.
Please Don't Do this try to use Serializable Use Parcelable and getParcelableExtra()
Please consider this link http://www.3pillarglobal.com/insights/parcelable-vs-java-serialization-in-android-app-development
this is why i am suggesting you to use Parcelable.

Viewpager Adapter getItem always called for index 0 and 1

I was just wondering if it is the normal behaviour of viewpager and its adapter to always call the getItem() method for index 0 and 1, even if I immediately set a current position.
Here is my code:
mNewsPagerAdapter = new NewsDetailPagerAdapter(getChildFragmentManager());
mNewsPagerAdapter.updateNewsList(news);
mViewPager = (ViewPager) mView.findViewById(R.id.horizontal_view_pager);
mViewPager.setPageMargin(2);
mViewPager.setPageMarginDrawable(R.color.black);
mViewPager.setAdapter(mNewsPagerAdapter);
mViewPager.setCurrentItem(mCurrentPositionPager, false);
If I switch from my overview activity to my detail activity with this viewpager, the adapter always calls the getItem() method for position 0 and 1 and after that the getItem() method for the position of mOriginalPosition and its neighbors. I was wondering if this is the correct behaviour or if I missed something to implement it in a right way. Thanks for your help :)
Edit: Added my adapter code
public class NewsDetailPagerAdapter extends FragmentStatePagerAdapter {
private SparseArray<Fragment> mPageReferenceMap = new SparseArray<Fragment>();
private ArrayList<News> mNewsList;
public NewsDetailPagerAdapter(FragmentManager fm) {
super(fm);
}
/**
* Setzt die neuen News.
**/
public void updateNewsList(ArrayList<News> list) {
mNewsList = list;
}
#Override
public Fragment getItem(int position) {
Log.d("debug", "getItem position:" + position);
News newsItem = mNewsList.get(position);
NavigationFragment fragment = new NavigationFragment();
mPageReferenceMap.put(position, fragment);
return fragment;
}
#Override
public int getCount() {
return mNewsList.size();
}
#Override
public int getItemPosition(Object object) {
return POSITION_NONE;
}
public Fragment getFragment(int position) {
return mPageReferenceMap.get(position);
}
}
It is normal (and intelligent in my opinion).
ViewPager class has one property named mOffscreenPageLimit with default value of 1. This number determines how many pages on the left and on the right of the current page that the Viewpager will preload. For instance, you have 10 pages, current position is 5 and mOffcreenPageLimit is 1, the page at position 4 and 6 will be loaded.
You could change this property by calling this method
viewpager. setOffscreenPageLimit(int)
If you pass in an integer that is smaller than 1, it has no effect.
Yes, this is the normal behaviour of the ViewPager, because it will always try to stay ahead of the user by rendering tabs that limit with the drawing area. I personally don't recommend creating a custom ViewPager as you are almost sure to break functionality unless you really know what you are doing. Your adapter class should look something like this:
public class YourCustomPagerAdapter extends FragmentStatePagerAdapter {
private List<Fragment> fragmentList = new ArrayList<>();
private List<String> titleList = new ArrayList<>();
public WizardPagerAdapter(FragmentManager fm) {
super(fm);
}
public void addFragment(Fragment fragment, String title) {
fragmentList.add(fragment);
titleList.add(title);
}
#Override
public Fragment getItem(int position) {
return fragmentList.get(position);
}
#Override
public CharSequence getPageTitle(int position) {
super.getPageTitle(position);
return titleList.get(position);
}
#Override
public int getCount() {
return fragmentList.size();
}
}
and you should add your fragments as such:
#Override
public void onCreate(Bundle savedInstanceState) {
...
YourCustomPagerAdapter adapter = new YourCustomPagerAdapter (getSupportFragmentManager());
adapter.addFragment(FragmentOne.newInstance(), "Frag 1");
adapter.addFragment(FragmentTwo.newInstance(), "Frag 2");
viewPager.setAdapter(adapter);
...
}
Actually this is the normale behavior. In fact, as soos as you associate the ViewPager with the adapter, the adapter creates the first visibile layout (index 0) end the next one (index 1). This is done by default in the "setAdapter". Then, when you set a different position, the adapter will instantiate the fragment at the selected index, the previous one and the next one.
This is the usual ViewPager setAdapter code:
public void setAdapter(PagerAdapter adapter) {
if (mAdapter != null) {
mAdapter.setViewPagerObserver(null);
mAdapter.startUpdate(this);
for (int i = 0; i < mItems.size(); i++) {
final ItemInfo ii = mItems.get(i);
mAdapter.destroyItem(this, ii.position, ii.object);
}
mAdapter.finishUpdate(this);
mItems.clear();
removeNonDecorViews();
mCurItem = 0;
scrollTo(0, 0);
}
final PagerAdapter oldAdapter = mAdapter;
mAdapter = adapter;
mExpectedAdapterCount = 0;
if (mAdapter != null) {
if (mObserver == null) {
mObserver = new PagerObserver();
}
mAdapter.setViewPagerObserver(mObserver);
mPopulatePending = false;
final boolean wasFirstLayout = mFirstLayout;
mFirstLayout = true;
mExpectedAdapterCount = mAdapter.getCount();
if (mRestoredCurItem >= 0) {
mAdapter.restoreState(mRestoredAdapterState, mRestoredClassLoader);
setCurrentItemInternal(mRestoredCurItem, false, true);
mRestoredCurItem = -1;
mRestoredAdapterState = null;
mRestoredClassLoader = null;
} else if (!wasFirstLayout) {
populate();
} else {
requestLayout();
}
}
if (mAdapterChangeListener != null && oldAdapter != adapter) {
mAdapterChangeListener.onAdapterChanged(oldAdapter, adapter);
}
}
In order to change the ViewPager behavior, you could extend the classic ViewPager overriding the setAdapter method and set the mCurrItem to the desired position.
I hope it helped
Edit:
After different tests, we found a solution.
If the ViewPager adapter is set after ViewPager layout become visible, items 0 and 1 are load.
If you want to avoid this behavior but you can't set the adapter before the layout become visible (because you are waiting for data), than you can use this workaround:
1) Set the ViewPager visibility initially to GONE
2) After you receive all the data, you update the adapter and you set the current item value
3) Finally you set the ViewPager visibility to VISIBLE
Here you can find an example:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.detail_overview_fragment, container, false);
final int position = getArguments().getInt("position");
final ViewPager viewPager = (ViewPager) v.findViewById(R.id.viewpager);
viewPager.setVisibility(View.GONE);
Handler handler = new Handler();
handler.postDelayed(new Runnable() {
#Override
public void run() {
viewPager.setAdapter(new PagerAdapter(getChildFragmentManager()));
viewPager.setCurrentItem(position);
viewPager.setVisibility(View.VISIBLE);
}
},5000);
return v;
}
i think the error is the adapter:
/**
* Setzt die neuen News.
**/
public void updateNewsList(ArrayList<News> list) {
//mNewsList = list;
mNewsList.clear();
mNewsList.addAll(list);
/**
* Notifies the attached observers that the underlying data has been changed
* and any View reflecting the data set should refresh itself.
*/
this.notifyDataSetChanged();
}
error reason :this list is diffent entity for that adapter.

How to display different text on each page in ViewPager, instantiateItem() confusion

In the CustomPagerAdapter of the ViewPager, in instantiateItem() method I'm trying to create an TextView and then for each page set a different text depending on certain condition. Text is read from a pages Cursor. Here is a code:
#Override
public Object instantiateItem(ViewGroup collection, int position) {
sc = new ScrollView(context);
sc.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
sc.setFillViewport(true);
tv = new TextView(context);
if(position < count) {
tv.setText(pages.getString(1));
pages.moveToPosition(position);
}else {
tv.setText("LOCKED");
}
tv.setTag(TAG_PAGE + position);
tv.setGravity(Gravity.CENTER);
tv.setTextColor(Color.BLACK);
tv.setTextSize(30);
sc.addView(tv);
((ViewPager) collection).addView(sc);
return sc;
}
However ViewPager behaves not as expected. The first and the second page have the same text, rest of the pages has a sign "LOCKED" as expected. When I swipe into the 4th page and come back to the first page then the first page consists of the text that suppose to be in the second page. I also tried to use myViewPager.setOffscreenPageLimit(numberOfPages) however it doesn't help.
I found this answer:
"Inside of instantiateItem, the position parameter is the position that is in need of rendering. It is NOT the position of the currently focused item that the user would see. The pages to the left and right of the currently displayed view need to be pre rendered in memory so that the animations to those screens will be smooth. "
It make sense to me but how then can I correctly display the pages content and then update it if desired? Please advise if there is different way to do it with skipping instantiateItem() method that introduce the mess and confusion into the problem. Thank you.
I have solved this problem by using a different implementation:
// Adapter class
private static class MyFragmentPagerAdapter extends FragmentPagerAdapter {
public MyFragmentPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int index) {
return PageFragment.newInstance(pages[index]); // Pages is an array of Strings
}
#Override
public int getCount() {
return numberOfPages;
}
}
// PageFragment class
public class PageFragment extends Fragment {
TextView tv;
public static PageFragment newInstance(String page) {
PageFragment pageFragment = new PageFragment();
Bundle bundle = new Bundle();
bundle.putString("pageContent", page);
pageFragment.setArguments(bundle);
return pageFragment;
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment, container, false);
tv = (TextView) view.findViewById(R.id.text_view);
tv.setText(getArguments().getString("pageContent"));
return view;
}
}
You can Create ViewPager Object and then set Listener onthis object.
ViewPager myPager = (ViewPager) findViewById(R.id.yourPagerid);
myPager.setAdapter(adapter);
myPager.setCurrentItem(0);
myPager.setOnPageChangeListener(new OnPageChangeListener() {
#Override
public void onPageSelected(int position) {
//You can change textview word according to current page
switch (position) {
case 0:
break;
case 1:
break;
case 2:
break;
}
}
#Override
public void onPageScrolled(int arg0, float arg1, int arg2) {
// Log.d("check","onPageScrolled");
}
#Override
public void onPageScrollStateChanged(int arg0) {
// Log.d("check","onPageScrollStateChanged");
}
});

Circular ViewPager which uses FragmentPagerAdapter

I would like to implement a ViewPager which uses Fragments and can be swiped in a curcular motion e.g. Page (A<-->B<-->C<-->A).
I have read a couple of posts on how this is done, e.g. returning a fake count of how many elements there are and setting the position at the start in the middle.
how to create circular viewpager?
These all seem to be based of a PagerAdapter. When I try to do a similar thing while extending FragmentPagerAdapter, as soon as I return a fakeCount of pages I get an exception when I Swipe through my Fragments, I only have 2 Fragments.
Exception: java.lang.IllegalStateException: Can't change tag of fragment.
I think this is caused as the FragmentManager thinks I am in position 2 but position 2 points to the fragment at position 0. Does anyone know how I can avoid this? I am thinking I should experiment with extending Fragmentmanager. Any examples or help with this would be greatly appreciated.
I know it is a bit late but this is how it worked for me:
I needed a circular swipe between 3 fragments, so I made those 3 and two more virtual to help me implement the page looping:
public static class FirstViewFragment extends Fragment {
// Empty Constructor
public FirstViewFragment() {
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_landing_1, container, false);
}
}
public static class SecondViewFragment extends Fragment {
// Empty Constructor
public SecondViewFragment() {
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_landing_2, container, false);
}
}
public static class ThirdViewFragment extends Fragment {
// Empty Constructor
public ThirdViewFragment() {
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_landing_3, container, false);
}
}
And two more virtual fragments that enabled me to swipe left from the first and right from the last. The first virtual inflates the same layout as the last actual and the last virtual the same layout as the first actual:
public static class StartVirtualFragment extends Fragment {
public StartVirtualFragment() {}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_landing_3, container, false);
}
}
public static class EndVirtualFragment extends Fragment {
public EndVirtualFragment() {}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_landing_1, container, false);
}
}
My Adapter:
private class ViewPagerAdapter extends FragmentPagerAdapter {
public ViewPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int i) {
switch (i) {
case 0:
return new StartVirtualFragment();
case 1:
if (firstViewFragment == null) {
firstViewFragment = new FirstViewFragment();
}
return firstViewFragment;
case 2:
if (secondViewFragment == null) {
secondViewFragment = new SecondViewFragment();
}
return secondViewFragment;
case 3:
if (thirdViewFragment == null) {
thirdViewFragment = new ThirdViewFragment();
}
return thirdViewFragment;
case 4:
return new EndVirtualFragment();
}
return null;
}
#Override
public int getCount() {
return 5;
}
}
And my page listener I used the onPageScrollStateChanged to set the correct page and implement the loop:
viewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
#Override
public void onPageSelected(int position) {
}
#Override
public void onPageScrollStateChanged(int state) {
if (state == ViewPager.SCROLL_STATE_DRAGGING) {
int pageCount = viewPager.getChildCount();
int currentItem = viewPager.getCurrentItem();
if (currentItem == 0) {
viewPager.setCurrentItem(pageCount - 2, false);
} else if (currentItem == pageCount - 1) {
viewPager.setCurrentItem(1, false);
}
}
}
});
And in the end:
viewPager.setCurrentItem(1);
Hope I helped
I have a project in the GitHub with some widgets I've created. Here it its:
https://github.com/CyberEagle/AndroidWidgets
In the following package, there are the adapters to be used with the CircularViewPager:
https://github.com/CyberEagle/AndroidWidgets/tree/master/src/main/java/br/com/cybereagle/androidwidgets/adapter
First, you will use CircularViewPager instead of ViewPager in your layout. The CircularViewPager is here: https://github.com/CyberEagle/AndroidWidgets/blob/master/src/main/java/br/com/cybereagle/androidwidgets/view/CircularViewPager.java
This ViewPager expects a WrapperCircularPagerAdapter, instead of a PagerAdapter. This wrapper is used to trick the ViewPager, making it to think there are a lot of items in the ViewPager, but it actually repeat your items to make the circular effect. So, instead of implementing either PagerAdapter, FragmentPagerAdapter or FragmentStatePagerAdapter, you will implement either CircularFragmentPagerAdapter, CircularFragmentStatePagerAdapter or CircularPagerAdapter. Then, you will wrap your adapter with the WrapperCircularPagerAdapter and set the wrapper in the CircularViewPager, instead of your adapter. Also, when it's time to notify dataset changed, you will call the notifyDatasetChanged() in the wrapper.
When implementing one of the circular adapter, you will notice that instead of implementing instantiateItem, you will have to implement instantiateVirtualItem. For the fragment's pager adapter, you will implement getVirtualItem instead of getItem. That is because I've created the concept of virtual items.
To make it clear, imagine a view pager with 4 items, giving that each item represents a music. When you go all the way to left, you will see the 4th item in the left of the first. Actually, it's a whole new item, but it's linked to the virtual item that represents the 4th music.
Another example: imagine there's only one music now. You will see the same music on the left and on the right. There're 3 items at a time, but only one virtual item.
So, as explained, the Wrapper is tricking the ViewPager, making it think that there are a lot of items. To make it more difficult for the user to reach one of the ends of the ViewPager (it'd take a long time anyway), everytime a change happens to the dataset, the ViewPager goes to the same virtual item, but to one of the real items near the middle.
One more important thing is that the CircularViewPager has the method setCurrentVirtualItem. This method calculates which real item is the nearest desired virtual item and then it uses the setCurrentItem to set it. You have also the option to use the getCurrentVirtualItem, that will return the index of the current virtual item. Notice that if you use getCurrentItem, you'll get a large index.
Well, this is it. I'm sorry for the lack of documentation of the project. I'm planning document it soon. I'm also planning to remove the need for the wrapper. Feel free to copy the code (respecting the Apache 2.0 license), to fork or even contribute to it.
**If you want to make 3 views visible at same time and make it circular**
public abstract class CircularPagerAdapter extends PagerAdapter{
private int count;
int[] pagePositionArray;
public static final int EXTRA_ITEM_EACH_SIDE = 2;
private ViewPager.OnPageChangeListener pageChangeListener;
private ViewPager viewPager;
public CircularPagerAdapter(final ViewPager pager, int originalCount ) {
super();
this.viewPager = pager;
count = originalCount + 2*EXTRA_ITEM_EACH_SIDE;
pager.setOffscreenPageLimit(count-2);
pagePositionArray = new int[count];
for (int i = 0; i < originalCount; i++) {
pagePositionArray[i + EXTRA_ITEM_EACH_SIDE] = i;
}
pagePositionArray[0] = originalCount - 2;
pagePositionArray[1] = originalCount -1;
pagePositionArray[count - 2] = 0;
pagePositionArray[count - 1] = 1;
pager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
public void onPageSelected(final int position) {
if(pageChangeListener != null)
{
pageChangeListener.onPageSelected(pagePositionArray[position]);
}
pager.post(new Runnable() {
#Override
public void run() {
if (position == 1){
pager.setCurrentItem(count-3,false);
} else if (position == count-2){
pager.setCurrentItem(2,false);
}
}
});
}
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
if(pageChangeListener != null)
{
pageChangeListener.onPageScrolled(pagePositionArray[position],positionOffset,positionOffsetPixels);
}
}
public void onPageScrollStateChanged(int state) {
if(pageChangeListener != null)
{
pageChangeListener.onPageScrollStateChanged(state);
}
}
});
}
#Override
public int getCount() {
return count;
}
#Override
public boolean isViewFromObject(View view, Object object) {
return false;
}
public abstract Object customInstantiateItem(ViewGroup container, int position);
public void setPageChangeListener(ViewPager.OnPageChangeListener pageChangeListener)
{
this.pageChangeListener = pageChangeListener;
}
#Override
public Object instantiateItem(ViewGroup container, int position) {
int pageId = pagePositionArray[position];
return customInstantiateItem(container,pageId);
}
#Override
public void destroyItem(View container, int position, Object object) {
((ViewPager) container).removeView((View) object);
}
public void setFirstItem()
{
viewPager.setCurrentItem(EXTRA_ITEM_EACH_SIDE - 1);
}
}

Categories

Resources