I'm trying to create a calendar application. Each month is one Fragment. I'm using ViewPager and FragmentPageAdapter. I want to add one Fragment when swiping to the left and position is 2 and I want to add one Fragment when swiping to the right and position is fragments.size() - 2.
I have no problem to add Fragment to the right but the problem occurs when adding to 0 position because of Can't change tag of fragment exception. I was looking to the solution to the problem with no success. I'm wondering if there is some alternative to ViewPager or another solution to this problem.
Here is my adapter:
public class ViewPagerAdapter extends FragmentPagerAdapter {
private ArrayList<Fragment> fragments = new ArrayList<>();
public ViewPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
return fragments.get(position);
}
#Override
public int getCount() {
return fragments.size();
}
public void addFragment(Fragment fragment) {
fragments.add(fragment);
notifyDataSetChanged();
}
public void addFragment(Fragment fragment, int position) {
fragments.add(position, fragment);
notifyDataSetChanged();
}
public void removeFragment(int position) {
fragments.remove(position);
notifyDataSetChanged();
}
public void removeFragment(Fragment fragment) {
fragments.remove(fragment);
notifyDataSetChanged();
}
}
Related
Need a help with dynamic ViewPager with EditText (straggling to do a multiple tabbed editor).
Here are problems I faced:
e. g. I create 3 pages and put some text, so I have
page0 - "a"
page1 - "b"
page2 - "c"
Adding works normally here, removing page0 leads not to "a" "c" but to "a" "b" left, removing page0 leads to "a" "b" instead of "b" "c" as well, removing page2 is ok. So it seems like pager remembers text of removed page n and moves it to page n+1, text from page n+1 to n+2 and so on
same pages with the same text, but now I remove page1 and page2 (the order doesn't matter) so text "a" left. Adding two new pages will recreate "b" and "c", next pages would be blank as they should be.
if I remove just page1 and add new page, text "c" will appear.
I've been here, here and here, no help.
Tried to follow getItem(int position) calls
Also searched ViewPager and FragmentStatePagerAdapter sources, it looks like it has smth to do with instantiateItem and/or destroyItem but I have no clue what exactly.
Here is my code:
Adapter
public class PagerAdapter extends FragmentStatePagerAdapter {
private ArrayList<Fragment> fragmentsList = new ArrayList<>();
private ArrayList<String> titlesList = new ArrayList<>();
private TabLayout layout;
public PagerAdapter(FragmentManager manager, Context context, ViewPager pager, TabLayout layout) {
super(manager);
this.layout = layout;
}
#Override
public Fragment getItem(int position) {
return fragmentsList.get(position);
}
#Override
public int getItemPosition(#NonNull Object object) {
return POSITION_NONE;
}
#Override
public int getCount() {
return fragmentsList.size();
}
#Override
public CharSequence getPageTitle(int position) {
return titlesList.get(position);
}
public void addFrag(Fragment f, String s) {
fragmentsList.add(f);
titlesList.add(s);
}
public void removeFragment(int position) {
if (layout.getChildCount() > 0)
layout.removeTabAt(position);
Fragment fragment = fragmentsList.get(position);
fragmentsList.remove(fragment);
titlesList.remove(position);
FragmentManager manager = fragment.getFragmentManager();
FragmentTransaction trans = manager.beginTransaction();
trans.remove(fragment);
trans.commit();
notifyDataSetChanged();
}
}
RootFragment
public class RootFragment extends Fragment {
private TabLayout layout;
private ViewPager pager;
private PagerAdapter adapter;
private int currentTab;
#Override
public View onCreateView(#NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.root_fragment, container, false);
pager = view.findViewById(R.id.viewpager);
layout = view.findViewById(R.id.sliding_tabs);
adapter = new PagerAdapter(getFragmentManager(), getActivity(), pager, layout);
pager.setAdapter(adapter);
layout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
#Override
public void onTabSelected(TabLayout.Tab tab) {
currentTab = tab.getPosition();
pager.setCurrentItem(currentTab);
}
#Override
public void onTabUnselected(TabLayout.Tab tab) {
}
#Override
public void onTabReselected(TabLayout.Tab tab) {
}
});
return view;
}
public void addTab(String title) {
Bundle bundle = new Bundle();
bundle.putString("data", title);
LeafFragment leafFragment = new LeafFragment();
leafFragment.setArguments(bundle);
adapter.addFrag(leafFragment, title);
adapter.notifyDataSetChanged();
layout.setupWithViewPager(pager);
currentTab = adapter.getCount() - 1;
pager.setCurrentItem(currentTab);
}
public void deleteTab(int position) {
adapter.removeFragment(position);
}
}
Thanks and sorry for my English.
FragmentStatePagerAdapter already stores a list of Fragments, you don't need (and potentially, you should not) implement your own list while using it. Simply:
public class MyAdapter extends FragmentStatePagerAdapter {
public MyAdapter(FragmentManager fm) {
super(fm);
}
#Override
public int getCount() {
return 3;
}
#Override
public Fragment getItem(int position) {
//Simply create a fragment based on position here
}
}
Second, the unfortunate truth is, removing/reordering fragments in a FragmentStatePagerAdapter appears to have still open bugs as here:
https://issuetracker.google.com/issues/36956111
My suggestion is to simply re-create the adapter if you need to delete fragments from the adapter.
For example, I have
MainActivity (contain a ViewPager)
Fragment0
Fragment1 (contain a ViewPager)
Fragment1a
Fragment1b
Fragment2
I want to refresh Fragment1a every time it visible so I use setUserVisibleHint inside Fragment1a but not working well.
When I switch between fragment Fragment1a and Fragment1b, setUserVisibleHint of Fragment1a called
However when I go from fragment Fragment0 to Fragment1 or Fragment2 to Fragment1 setUserVisibleHint of Fragment1a (and Fragment1b) not called
How can I detect Fragment1a (and Fragment1b) visible? Any help or suggestion would be great appreciated.
setUserVisibleHint also not working me I used another way to update data in fragment when view pager change fragment, Below is The way,
viewpager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
#Override
public void onPageSelected(int position) {
try {
if(viewPagerAdapter.getItem(position) instanceof Fragment1a){
Fragment1a frag1 = (Fragment1a) viewPagerAdapter.getItem(position);
frag1.updateData(data); // here is the public method of fragment which declared for udpate data in fragment
}else if(viewPagerAdapter.getItem(position) instanceof Fragment1b){
Fragment1b frag2 = (Fragment1b) viewPagerAdapter.getItem(position);
frag2.updateData(data); // here is the public method of fragment which declared for udpate data in fragment
}
} catch (Exception e) {
e.printStackTrace();
}
}
#Override
public void onPageScrollStateChanged(int state) {
}
});
viewpager.setCurrentItem(0);
This way you can update data directly Just you need to create one public method in your fragment.
Here is the Adapter
class ViewPagerAdapter extends FragmentPagerAdapter {
private final List<Fragment> mFragmentList = new ArrayList<>();
private final List<TabModel> mFragmentTitleList = new ArrayList<>();
public ViewPagerAdapter(FragmentManager manager) {
super(manager);
}
#Override
public Fragment getItem(int position) {
return mFragmentList.get(position);
}
#Override
public int getCount() {
return mFragmentList.size();
}
public void addFragment(Fragment fragment, TabModel title) {
mFragmentList.add(fragment);
mFragmentTitleList.add(title);
}
#Override
public CharSequence getPageTitle(int position) {
return mFragmentTitleList.get(position).getTabName();
}
}
I've a ViewPager (Parent ViewPager) in my activity with 4 fragments. One of the fragments implements a sliding ViewPager (Child ViewPager) with automatic sliding.
When I swipe the parent ViewPager to, say, tab 3 and then I go back to the first fragment which contains the sliding ViewPager, some of the slide disappears and some of it stays. When I slide the ViewPager (the child ViewPager) back and forth couple of times, it gets restored to its original state.
I assume the fragment state of the sliding view pager is getting lost when the parent ViewPager is being slid.
Here's the parent ViewPager's adapter:
public class ViewPagerAdapter extends FragmentPagerAdapter {
private final List<Fragment> mFragmentList = new ArrayList<>();
private final List<String> mFragmentTitleList = new ArrayList<>();
public ViewPagerAdapter(FragmentManager manager) {
super(manager);
}
#Override
public Fragment getItem(int position) {
return mFragmentList.get(position);
}
#Override
public int getCount() {
return mFragmentList.size();
}
public void addFragment(Fragment fragment, String title) {
mFragmentList.add(fragment);
mFragmentTitleList.add(title);
}
#Override
public CharSequence getPageTitle(int position) {
return mFragmentTitleList.get(position);
}
}
Here's the child ViewPager's adapter (slider):
class sliding_PageAdapters extends FragmentPagerAdapter {
int mNumOfTabs;
public sliding_PageAdapters(FragmentManager fm, int numTabs) {
super(fm);
this.mNumOfTabs = numTabs;
}
#Override
public void destroyItem(ViewGroup viewPager, int position, Object object) {
viewPager.removeView((View) object);
}
#Override
public Fragment getItem(int position) {
Bundle ar = new Bundle();
FragmentHomeImages fragment = new FragmentHomeImages();
switch (position) {
case 0:
ar.putString("number", "0");
fragment.setArguments(ar);
return fragment;
case 1:
ar.putString("number", "1");
fragment.setArguments(ar);
return fragment;
case 2:
ar.putString("number", "2");
fragment.setArguments(ar);
return fragment;
case 3:
ar.putString("number", "3");
fragment.setArguments(ar);
return fragment;
default:
return null;
}
}
#Override
public int getCount() {
return mNumOfTabs;
}
}
The child ViewPager is being called with getChildFragmentManager() here:
sliding_pagerAdapter = new sliding_PageAdapters(getChildFragmentManager(), 4);
And the parent ViewPager is being called with getSupportFragmentManager() here:
adapter = new ViewPagerAdapter(getSupportFragmentManager());
I can think of one problem. On the parent viewpager call setOffscreenPageLimit(limit).
here limit is 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. So set the appropriate limit based on your use-case.
viewPager = (ViewPager) findViewById(R.id.pager);
viewPager.setAdapter(new MyAdapter(fragmentManager));
viewPager.setOffscreenPageLimit(2);
I have a problem while changing fragments in my viewpager. In specific, my fragment is replaced with an empty one.
Here's some code:
public void replaceFragment() {
supportManager.getFragments().set(1,getNewFragment()));
pageAdapter.notifyDataSetChanged();
menuControl.setView(1);//this just changes the fragment to be shown
}//here is where i change the fragment
//and here is the adapter
public class SimplePageAdapter extends FragmentPagerAdapter{
final List<Fragment> fragment;
final FragmentManager fragmentManager;
public SimplePageAdapter(FragmentManager fm, List<Fragment> fragment) {
super(fm);
fragmentManager = fm;
this.fragment=fragment;
}
#Override
public Fragment getItem(int arg0) {
return fragment.get(arg0);
}
#Override
public int getCount() {
return fragment.size();
}
#Override
public int getItemPosition(Object o) {
return POSITION_NONE;//I read that this is for making notifyDataSet work
}
}
I want to prepend a new View as first page of my ViewPager.
My adapter looks like this:
public class PagerAdapter extends FragmentPagerAdapter {
private final List<Fragment> fragments;
public PagerAdapter(FragmentManager fm, List<Fragment> fragments) {
super(fm);
this.fragments = fragments;
}
#Override
public Fragment getItem(int index) {
return fragments.get(index);
}
#Override
public int getCount() {
return fragments.size();
}
public void add(int i, ImageFileObject imageFile) {
ImageViewFragment f = new ImageViewFragment();
f.setImage(imageFile);
fragments.add(0, f);
notifyDataSetChanged();
}
public void add(ImageFileObject imageFile) {
ImageViewFragment f = new ImageViewFragment();
f.setImage(imageFile);
fragments.add(f);
notifyDataSetChanged();
}
}
But when calling add(0, aImageFile) the item is not prepended to the fragment list. (It's not even appended).
Any ideas?
Update: The problem that you described in comment is real, I missed it in first testing. After analyzing the source code of FragmentPagerAdapter I realized that all we need to do is to Override getItemId() in a way that it won't return same id for different fragment items. E.g. current default implementation would just return position as an id for fragment which won't work for this case:
public long getItemId(int position) {
return position;
}
I am updating this answer, please have a look. As before I have tested this code and the problem you described is not happening now.
Only thing you need is not to keep reference of fragments by yourself in List, This is done for you by FragmentPagerAdapter. And as far as I know its not a good practise either. And also even if you return POSITION_NONE from getItemPosition() as suggested by other answer(s), you will end up with Exception
Caused by: java.lang.IllegalStateException: Can't change tag of fragment.
This is because you are trying to reposition alive Fragments in your List by adding a fragment at 0th index (which causes other fragments to reposition) and ViewPager assigns tags based on position.
Keeping all this in mind, here is a tested and working modified adapter:
public class PagerAdapter extends FragmentPagerAdapter {
public static class FragmentInfo {
public String classz;
public ImageFileObject imageFile;
public static long IDS;
public long id;
public FragmentInfo(){
id = IDS++;
}
}
private final List<FragmentInfo> fragments;
private Context context;
public PagerAdapter(FragmentManager fm, List<FragmentInfo> fragments,
Context context) {
super(fm);
this.fragments = fragments;
this.context = context;
}
#Override
public Fragment getItem(int index) {
FragmentInfo info = fragments.get(index);
ImageViewFragment frag = (ImageViewFragment) Fragment.instantiate(
context, info.classz, /* null arguments*/ null);
frag.setImage(info.imageFile);
return frag;
}
#Override
public long getItemId(int position) {
return fragments.get(position).id;
}
#Override
public int getCount() {
return fragments.size();
}
#Override
public int getItemPosition(Object object) {
return POSITION_NONE;
}
public void add() {
FragmentInfo f = new FragmentInfo();
f.classz = ImageViewFragment.class.getName();
fragments.add(0, f);
notifyDataSetChanged();
}
}
Please change your code accordingly.
The problem is actually with notifyDataSetChanged. Just add to your Adapter:
public int getItemPosition(Object object) {
return POSITION_NONE;
}
However, I think you will get other problems when the Adapter actually tries to call getItem again. These problems will be in the lines of "Fragment Already Added".