I have a Fragment A holds a view pager that display serval nested fragments. B and C are the fragments directly hold by the view pager. And each B and C holds a listView. When click listView item fragment D will show up. The problem is that, I could only hide the Fragment C and B from the viewPager, which means Fragment D will come over Fragment A. The question is how could I hide Fragment A from B and C not just hide the Fragment B and C from the viewPager.
here is the code of Fragment A
public class FragmentA extends Fragment {
private View view;
private PagerSlidingTabStrip tabs;
private DisplayMetrics dm;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
view = inflater.inflate(R.layout.fragment_a, null);
dm = getResources().getDisplayMetrics();
ViewPager pager = (ViewPager) view.findViewById(R.id.pager);
tabs = (PagerSlidingTabStrip) view.findViewById(R.id.tabs);
pager.setAdapter(new MyPagerAdapter(getChildFragmentManager()));
tabs.setViewPager(pager);
setTabsValue();
initView();
return view;
}
private void initView() {
TextView titleView = (TextView) view.findViewById(R.id.title_content_text);
titleView.setVisibility(View.VISIBLE);
titleView.setText(getString(R.string.label_alert));
}
public class MyPagerAdapter extends FragmentPagerAdapter {
public MyPagerAdapter(FragmentManager fm) {
super(fm);
}
private final String[] titles = { "B", "C" };
#Override
public CharSequence getPageTitle(int position) {
return titles[position];
}
#Override
public int getCount() {
return titles.length;
}
#Override
public Fragment getItem(int position) {
switch (position) {
case 0:
if (BFrag == null) {
BFrag = new BFrag();
}
return BFrag;
case 1:
if (CFrag == null) {
CFrag = new CFrag();
}
return CFrag;
default:
return null;
}
}
}
}
here is the code in Fragment B that trying to show fragment D:
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
public void onItemClick(AdapterView<?> list, View v, int pos, long id) {
if (pos < Item.size()) {
FragmentTransaction transaction = getActivity().getSupportFragmentManager().beginTransaction();
transaction.hide(BFrag.this);
DFrag dFrag = new DFrag();
activity.dFeedFrag = dFrag;
transaction.add(R.id.home_frame_content, dFeedFrag, "dFrag");
transaction.addToBackStack(null);
transaction.commit();
}
}
}
});
The layout file of Fragment A:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<include layout="#layout/inculde_title_bar"/>
<com.example.blah.PagerSlidingTabStrip
android:id="#+id/tabs"
android:layout_width="match_parent"
android:layout_height="40dp" />
<android.support.v4.view.ViewPager
android:id="#+id/pager"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="#+id/tabs" />
Anyone could help me, Thanks! =)
Because B, C is hosted in the ViewPager, so they are in the fragment A, so you just need to find fragment A and hide it.
The real question is, where is the id home_frame_content.
If this id is in Fragment A, D will be hidden once A is hidden. I think this is what you want.
So the id home_frame_content must be somewhere not in A. Then you can find A, hide it, you will get what you want.
update 1
And there is a demo: https://github.com/liaohuqiu/ABCDFragment
Hope it would be helpful.
Related
Although I can scroll in the ViewPager, the physical fragments themselves are not visible for some reason.
FYI - I have an Activity with tabs using a ViewPager. Inside the first tab/fragment, I have the code below. So essentially, I have an App with a ViewPager controlling the tabs and there's another ViewPager inside one of the Tabs which is to control a bunch of images. I have tested the Fragment in question by putting it inside the top level view pager and it works perfectly fine! It's only when I put it inside the view pager in question does it not render anything...
So, this is the hierarchy for a better understanding:
MainActivity has a ...
ViewPager (3 fragments showing the tab content) has a ...
1st TabFragment has a ...
ViewPager (3 fragments showing images) <--- I can't see these, but I can swipe on them for some reason.
Unfortunately... None of the Fragments are visible, HOWEVER... I can still swipe for some reason to the last fragment...Just nothing is visibly shown...
Layout with the ViewPager inside:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<android.support.v4.view.ViewPager
android:id="#+id/view_pager"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
Code with ViewPager
final List<Fragment> imageFragments = new ArrayList<>();
for (final UserImage userImage : user.getImages()) {
final SizeImage processed = userImage.getProcessed();
imageFragments.add(UserImageFragment.newInstance(processed.getFullsize()));
}
final ImagesPagerAdapter imagesAdapter = new ImagesPagerAdapter(getFragmentManager(), imageFragments);
viewPager.setAdapter(imagesAdapter);
The PagerAdapter:
public class ImagesPagerAdapter extends FragmentStatePagerAdapter {
#NonNull
private List<Fragment> fragments;
public ImagesPagerAdapter(#NonNull final FragmentManager fragmentManager,
#NonNull final List<Fragment> fragments) {
super(fragmentManager);
this.fragments = fragments;
}
#Override
public Fragment getItem(final int position) {
return fragments.get(position);
}
#Override
public int getCount() {
return fragments.size();
}
}
The Fragment:
public class UserImageFragment extends Fragment {
#BindView(R.id.image_view)
ImageView imageView;
public static UserImageFragment newInstance(final String url) {
final UserImageFragment fragment = new UserImageFragment();
Bundle args = new Bundle();
args.putString("url", url);
fragment.setArguments(args);
return fragment;
}
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
final View view = inflater.inflate(R.layout.fragment_user_image, container, false);
ButterKnife.bind(this, view);
final Bundle arguments = getArguments();
if (arguments != null) {
final String url = arguments.getString("url", null);
if (url != null) {
final Uri uri = Uri.parse(url);
Picasso.with(getActivity()).load(url).into(imageView, new Callback() {
#Override
public void onSuccess() {
Toast.makeText(getActivity(), "Success", Toast.LENGTH_SHORT).show();
}
#Override
public void onError() {
Toast.makeText(getActivity(), "Fail", Toast.LENGTH_SHORT).show();
}
});
}
}
return view;
}
}
And here is xml code:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/md_red_900">
<ImageView
android:id="#+id/image_view"
android:layout_width="200dp"
android:layout_height="200dp"
android:background="#color/md_yellow_500" />
</RelativeLayout>
To show Fragments inside another Fragment, use getChildFragmentManager() instead of getFragmentManager()
final ImagesPagerAdapter imagesAdapter = new
ImagesPagerAdapter(getChildFragmentManager(), imageFragments);
viewPager.setAdapter(imagesAdapter);
It may not be obvious at first sight but don't try to cache or prepare fragments for a fragment adapter. Fragment(State)PagerAdapter.getItem has to return a new item and when the method is called is managed internally by the adapter.
Let the fragment adapter have data it needs to construct fragments at the right time:
final List<UserImage> userImages = user.getImages();
final ImagesPagerAdapter imagesAdapter = new ImagesPagerAdapter(getFragmentManager(), userImages);
viewPager.setAdapter(imagesAdapter);
And implement the fragment adapter so that getItem returns a new fragment:
public class ImagesPagerAdapter extends FragmentStatePagerAdapter {
#NonNull
private List<UserImage> userImages;
public ImagesPagerAdapter(#NonNull final FragmentManager fragmentManager,
#NonNull final List<UserImage> userImages) {
super(fragmentManager);
this.userImages = userImages;
}
#Override
public Fragment getItem(final int position) {
final UserImage userImage = userImages.get(position);
final SizeImage processed = userImage.getProcessed();
return UserImageFragment.newInstance(processed.getFullsize())
}
#Override
public int getCount() {
return userImages.size();
}
}
Here I see two options for improvement:
Replace List<UserImage> with List<SizeImage> or List<whatever getFullSize returns>. The less implementation details you leak to the adapter the better.
If userImage.getProcessed().getFullsize() takes time (accesses disk, performs computation) do that asynchronously in one of UserImageFragment lifecycle methods - defer to when it's needed and don't block UI thread.
Neither FragmentPagerAdapter nor FragmentStatePagerAdapter are built with updates in mind so
Don't try to modify the userImages list.
If the images change replace and reattach the whole adapter.
I want to get the last fragment in the backstack, or the current displayed it's the same for me, in the tab b_1. As you can see in the following image, I have a ViewPager, and another one inner tab b. Thus there are four current fragments displayed.
Question: How can I get the Fragment 2 instance?
I have seen another solutions, but none works for this scenario.
Annotation: The fragment to return is not necessary the hosted in the ViewPager. I can have opened two more fragments in a tab.
With this method I get all the current visible fragments, but not the one specific I want.
public ArrayList<Fragment> getVisibleFragment() {
List<Fragment> fragments = getSupportFragmentManager().getFragments();
ArrayList<Fragment> visibleFragments = new ArrayList<>();
if (fragments != null) {
for (Fragment fragment : fragments) {
if (fragment != null && fragment.isVisible())
visibleFragments.add(fragment);
}
}
return visibleFragments;
}
Some interesting code
activity_main.xml
<android.support.design.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="#style/ThemeOverlay.AppCompat.Dark.ActionBar">
<android.support.design.widget.TabLayout
android:id="#+id/tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:tabMode="fixed"
app:tabGravity="fill"/>
</android.support.design.widget.AppBarLayout>
<android.support.v4.view.ViewPager
android:id="#+id/viewpager"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="#string/appbar_scrolling_view_behavior"/>
</android.support.design.widget.CoordinatorLayout>
MainActivity.java
public class MainActivity extends AppCompatActivity {
private static ViewPagerAdapter adapter;
private static ViewPager viewPager;
private TabLayout tabLayout;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
viewPager = (ViewPager) findViewById(R.id.viewpager);
setupViewPager();
tabLayout = (TabLayout) findViewById(R.id.tabs);
tabLayout.setupWithViewPager(viewPager);
setupTabIcons();
}
private void setupViewPager() {
adapter = new ViewPagerAdapter(getSupportFragmentManager());
// Wrap with HostFragment to get separate tabbed nagivation.
adapter.addFrag(HostFragment.newInstance(new Fragment1()), null);
adapter.addFrag(HostFragment.newInstance(new RootFragment2()), null);
adapter.addFrag(HostFragment.newInstance(new Fragment4()), null);
viewPager.setAdapter(adapter);
viewPager.setOffscreenPageLimit(2);
}
public void openNewFragment(Fragment fragment) {
HostFragment hostFragment = (HostFragment) adapter.getItem(viewPager.getCurrentItem());
hostFragment.replaceFragment(fragment, true);
}
}
fragment_host.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/hosted_fragment"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
HostFragment.java
/**
* This class implements separate navigation for a tabbed viewpager.
*
* Based on https://medium.com/#nilan/separate-back-navigation-for-
* a-tabbed-view-pager-in-android-459859f607e4#.u96of4m4x
*/
public class HostFragment extends BackStackFragment {
private Fragment fragment;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
View view = inflater.inflate(R.layout.fragment_host, container, false);
if (fragment != null) {
replaceFragment(fragment, false);
}
return view;
}
public void replaceFragment(Fragment fragment, boolean addToBackstack) {
if (addToBackstack) {
getChildFragmentManager().beginTransaction().replace(R.id.hosted_fragment, fragment).addToBackStack(null).commit();
} else {
getChildFragmentManager().beginTransaction().replace(R.id.hosted_fragment, fragment).commit();
}
}
public static HostFragment newInstance(Fragment fragment) {
HostFragment hostFragment = new HostFragment();
hostFragment.fragment = fragment;
return hostFragment;
}
public Fragment getFragment() {
return fragment;
}
}
fragment2_root.xml
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/fragment2_root"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<android.support.design.widget.TabLayout
android:id="#+id/tab2_tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:tabMode="fixed"
app:tabGravity="fill"/>
<android.support.v4.view.ViewPager
android:id="#+id/tab2_viewpager"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="#string/appbar_scrolling_view_behavior"/>
</LinearLayout>
RootFragment2.java
public class RootFragment2 extends Fragment {
private ViewPagerAdapter adapter;
private ViewPager viewPager;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Inflate the layout for this fragment.
View root = inflater.inflate(R.layout.fragment2_root, container, false);
viewPager = (ViewPager) root.findViewById(R.id.tab2_viewpager);
setupViewPager(viewPager);
TabLayout tabLayout = (TabLayout) root.findViewById(R.id.tab2_tabs);
tabLayout.setupWithViewPager(viewPager);
return root;
}
private void setupViewPager(ViewPager viewPager) {
adapter = new ViewPagerAdapter(getActivity().getSupportFragmentManager());
// Wrap with HostFragment to get separate tabbed nagivation.
adapter.addFrag(HostFragment.newInstance(new Fragment2()), null);
adapter.addFrag(HostFragment.newInstance(new Fragment3()), null);
viewPager.setAdapter(adapter);
viewPager.setOffscreenPageLimit(1);
}
public ViewPagerAdapter getAdapter() {
return adapter;
}
public ViewPager getViewPager() {
return viewPager;
}
}
First define a SparseArray in your ViewPagers' adapters like below. In this array we'll hold the instance of fragments.
SparseArray<Fragment> registeredFragments = new SparseArray<>();
And Override your Adapters' instantiateItem method.
#Override
public Object instantiateItem(ViewGroup container, int position) {
Fragment fragment = (Fragment) super.instantiateItem(container, position);
registeredFragments.put(position, fragment);
return fragment;
}
Also Override destroyItem method of your ViewPagers
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
registeredFragments.remove(position);
super.destroyItem(container, position, object);
}
And define a new method to get your ViewPager Fragments instance.
public Fragment getRegisteredFragment(int position) {
return registeredFragments.get(position);
}
And finally set add a PageChangeListener to your ViewPagers:
viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
#Override
public void onPageSelected(int position) {
// Here's your instance
YourFragment fragment =(YourFragment)yourPagerAdapter.getRegisteredFragment(position);
}
#Override
public void onPageScrollStateChanged(int state) {
}
});
I hope this'll help you. Good luck.
Edit: I'm sorry i cannot understand exactly what you're planning to do but if you need to keep sub fragment (b_1, b_2) instance you can define a method to your activity such as
public void setCurrentFragment(Fragment fragment){
this.currentFragment = fragment;
}
and in your sub view pager's adapter you can call this method like below:
subViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
#Override
public void onPageSelected(int position) {
// Here's your instance
YourFragment fragment =(YourFragment)yourSubPagerAdapter.getRegisteredFragment(position);
((MyActivity)getActivity).setCurrentFragment(fragment);
}
#Override
public void onPageScrollStateChanged(int state) {
}
});
With this way you can keep one instance and your top fragment.
After some comments with DEADMC, I decided to implement it with extra memory, saving in a list the open fragments.
I explain my solution. I hve two types of special fragments, host and root. Host Fragments are those which are the init of a tab. Root Fragments are those which hold a ViewPager. In this scenario, tab_b has a Root Fragment with an inner ViewPager.
For each tab, this is, for each HostFragment, I have added a list fragments to save the fragments opened in this tab. Also a method getCurrentFragment to get the last displayed in this tab.
public class HostFragment extends BackStackFragment {
private ArrayList<Fragment> fragments = new ArrayList<>();
public Fragment getCurrentFragment() {
return fragments.get(fragments.size() - 1);
}
Also a method to remove the last fragment displayed in this tab is needed, to maintain a true state. Back button needs to be redirect to this method.
public void removeCurrentFragment() {
getChildFragmentManager().popBackStack();
fragments.remove(fragments.size() - 1);
}
Also previous methods need to be changed, to insert the new fragments opened into the list.
public void replaceFragment(Fragment fragment, boolean addToBackstack) {
// NEW: Add new fragment to the list.
fragments.add(fragment);
if (addToBackstack) {
getChildFragmentManager().beginTransaction().replace(R.id.hosted_fragment, fragment).addToBackStack(null).commit();
} else {
getChildFragmentManager().beginTransaction().replace(R.id.hosted_fragment, fragment).commit();
}
}
public static HostFragment newInstance(Fragment fragment) {
HostFragment hostFragment = new HostFragment();
// NEW: Add first fragment to the list.
hostFragment.fragments.add(fragment);
return hostFragment;
}
} // HostFragment.
RootFragment is an interface implemented by those fragments which holds a ViewPager.
public interface RootFragment {
/**
* Opens a new Fragment in the current page of the ViewPager held by this Fragment.
*
* #param fragment - new Fragment to be opened.
*/
void openNewFragment(Fragment fragment);
/**
* Returns the fragment displayed in the current tab of the ViewPager held by this Fragment.
*/
Fragment getCurrentFragment();
}
And then, the implementation would be:
MainActivity is not a Fragment, but I implement RootFragment interface for meaning.
public class MainActivity extends AppCompatActivity implements RootFragment {
private void setupViewPager() {
adapter = new ViewPagerAdapter(getSupportFragmentManager());
// Wrap with HostFragment to get separate tabbed nagivation.
adapter.addFrag(HostFragment.newInstance(new Fragment1()), null);
adapter.addFrag(new RootFragment2(), null); // This is a Root, not a Host.
adapter.addFrag(HostFragment.newInstance(new Fragment4()), null);
viewPager.setAdapter(adapter);
// 2 because TabLayout has 3 tabs.
viewPager.setOffscreenPageLimit(2);
}
#Override
public void openNewFragment(Fragment fragment) {
Fragment hosted = adapter.getItem(viewPager.getCurrentItem());
// Replace the fragment of current tab to [fragment].
if (hosted instanceof HostFragment) {
((HostFragment) hosted).replaceFragment(fragment, true);
// Spread action to next ViewPager.
} else {
((RootFragment) hosted).openNewFragment(fragment);
}
}
#Override
public Fragment getCurrentFragment() {
Fragment hosted = adapter.getItem(viewPager.getCurrentItem());
// Return current tab's fragment.
if (hosted instanceof HostFragment) {
return ((HostFragment) hosted).getCurrentFragment();
// Spread action to next ViewPager.
} else {
return ((RootFragment) hosted).getCurrentFragment();
}
}
}
and RootFragment2
public class RootFragment2 extends Fragment implements RootFragment {
private void setupViewPager(ViewPager viewPager) {
adapter = new ViewPagerAdapter(getActivity().getSupportFragmentManager());
// Wrap with HostFragment to get separate tabbed nagivation.
adapter.addFrag(HostFragment.newInstance(new Fragment2()), null);
adapter.addFrag(HostFragment.newInstance(new Fragment3()), null);
viewPager.setAdapter(adapter);
// 1 because TabLayout has 2 tabs.
viewPager.setOffscreenPageLimit(1);
}
#Override
public void openNewFragment(Fragment fragment) {
((HostFragment) adapter.getItem(viewPager.getCurrentItem())).replaceFragment(fragment, true);
}
#Override
public Fragment getCurrentFragment() {
return ((HostFragment) adapter.getItem(viewPager.getCurrentItem())).getCurrentFragment();
}
}
And then, I just need to call:
((MainActivity) getActivity()).getCurrentFragment();
You can create something like CustomFragment class which contains getClassName method and extends from Fragment class. Then extend all your fragments such as RootFragment2 from CustomFragment class instead of just Fragment.
So you can get fragment like this:
public CustomFragment getNeededFragment(String fragmentName) {
List<Fragment> fragments = getSupportFragmentManager().getFragments();
CustomFragment result = null;
if (fragments != null) {
for (Fragment fragment : fragments) {
if (fragment != null && fragment.isVisible()) {
try {
CustomFragment customFragment = (CustomFragment) customFragment;
if (customFragment.getClassName().equals(fragmentName)))
result = customFragment;
} catch (ClassCastException e) {
}
}
}
}
return result ;
}
I would do something like that:
1. Create a interface like this.
public interface ReturnMyself {
Fragment returnMyself();
}
2. All fragments inside ViewPagers should implements this one.
3. Add OnPageChangeListener to your main VP. So You will always know current position.
4. Add OnPageChangeListener your inner(s) VP so you will know which one is there on screen.
5. Add method to your adapter (both main and inner) which returns your fragment from list passed to it.
public ReturnMyself getReturnMyselfAtPosition()
6. All Fragments should return this in returnMyself()
7. Fragment that has inner fragments should return in returnMyself something like.
Fragment returnMyself() {
return this.myInnerFragmentAdapter.getReturnMyselfAtPosition().returnMyself();
}
8. From main fragment/activity you just call.
this.adapter.getReturnMyselfAtPosition().returnMyself();
And you got your current fragment.
I added ViewPager for views(not fragments) located in the middle of the fragment, but for some reason it's not showing.
There is ViewGroup parent container for it (FrameLayout), viewpager added dynamically (also tried to insert it directly in layout).
May be I miss something.
Will be glad for any tip, thanks!
ViewPager:
public class TabsPagerAdapter extends PagerAdapter {
List<View> pages = null;
public TabsPagerAdapter(List<View> pages) {
this.pages = pages;
}
#Override
public int getCount() {
return pages.size();
}
#Override
public boolean isViewFromObject(View view, Object object) {
return view == object;
}
#Override
public Object instantiateItem(ViewGroup collection, int position) {
View v = pages.get(position);
collection.addView(v, 0);
return v;
}
#Override
public void destroyItem(ViewGroup collection, int position, Object view) {
collection.removeView((View) view);
}
}
Fragment:
...
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View root = inflater.inflate(R.layout.fragment_product, container, false);
FrameLayout pagerPlaceholder = (FrameLayout) root.findViewById(R.id.viewpager_placeholder);
List<View> pages = new ArrayList<>();
View page = LayoutInflater.from(getDashboardActivity()).inflate(R.layout.test_layout, null);
TextView textView = (TextView) page.findViewById(R.id.text_view);
textView.setText("Test1");
pages.add(page);
View page2 = LayoutInflater.from(getDashboardActivity()).inflate(R.layout.test_layout, null);
TextView textView2 = (TextView) page2.findViewById(R.id.text_view);
textView2.setText("Test2");
pages.add(page);
TabsPagerAdapter tabsPagerAdapter = new TabsPagerAdapter(pages);
ViewPager viewPager = new ViewPager(getDashboardActivity());
viewPager.setAdapter(tabsPagerAdapter);
pagerPlaceholder.addView(viewPager, 0,
new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
return root;
}
fragment_product.xml
...
<LinearLayout
android:id="#+id/fragment_catalog_content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:visibility="visible">
...
<FrameLayout
android:id="#+id/viewpager_placeholder"
android:layout_width="match_parent"
android:layout_height="match_parent">
</FrameLayout>
</LinearLayout>
I tried your code and found two clues, hope this will help:
Did you forget to commit the transaction of FragmentManager?
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction transaction = fragmentManager.beginTransaction();
transaction.add(R.id.frament_container, new MyFragment());
transaction.commit();
The View page is added to the ArrayList pages twice, you need to add page2 at second page.add(page).
I just push the demo to github.
Please using getChildFragmentManager(); if you use NestedFragment.
Simple :
FragmentManager fragmentManager = getChildFragmentManager();
FragmentTransaction transaction = fragmentManager.beginTransaction();
transaction.add(R.id.frament_container, new MyFragment());
transaction.commit();
So I have implemented a ViewPager with a FragmentStatePagerAdapter and I have 2 fragments. I have called the setOffscreenPageLimit() with arguments 1 and 2 in the hope that the fragments won't be re-created but the 1st one always gets re-created while the second one gets created ones.
The first fragment always goes through these steps, as long as it goes out of view ( slide to right to the other fragment, or hit home button, etc.. )
onPause
onStop
onAttach
onCreate
onCreateView
onStart
onResume
For some reason the second fragment only gets created once and if I swipe to the first fragment, it behaves as expected - it doesn't get re-created unless I hit the home button or the power button. The behavior I want is that the two fragments are created once for the duration of the app.
My fragment structure is the following:
I have an activity with a FrameLayout. I add my SlidingTabFragment to that FrameLayout.
The SlidingTabFragment contains two fragments itself ( 2 Tabs )
The FragmentStatePagerAdapter
public class TabsPagerAdapter extends FragmentStatePagerAdapter {
/** Constructor of the class */
public TabsPagerAdapter(FragmentManager fm, Context context) {
super(fm);
this.mContext = context;
}
/** This method will be invoked when a page is requested to create */
#Override
public Fragment getItem(int arg0) {
switch(arg0){
/** tab1 is selected */
case 0:
Fragment1 frag1 = new Fragment1();
return frag1;
/** tab2 is selected */
case 1:
Fragment2 frag2 = new Fragment2();
return frag2;
}
return null;
}
#Override
public CharSequence getPageTitle(int position) {
return Settings.TAB_TITLES[position];
}
/** Returns the number of pages */
#Override
public int getCount() {
return Settings.TAB_TITLES.length;
}
}
The parent Fragment:
public class SlidingTabFragment extends Fragment {
ViewPager pager = null;
TabsPagerAdapter adapter = null;
SlidingTabLayout tabs = null;
public static SlidingTabFragment newInstance() {
SlidingTabFragment fragment = new SlidingTabFragment();
return fragment;
}
public SlidingTabFragment() {
// Required empty public constructor
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
}
#Override
public void onViewCreated(View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
adapter = new TabsPagerAdapter(getChildFragmentManager(), getActivity());
pager = (ViewPager) view.findViewById(R.id.pager);
pager.setAdapter(adapter);
pager.setOffscreenPageLimit(2);
// Assigning the Sliding Tab Layout View
tabs = (SlidingTabLayout) view.findViewById(R.id.sliding_layout);
tabs.setCustomTabView(R.layout.custom_tab_view, 0);
tabs.setDistributeEvenly(true);
// Setting indicator color to white
tabs.setCustomTabColorizer(new SlidingTabLayout.TabColorizer() {
#Override
public int getIndicatorColor(int position) {
return getResources().getColor(R.color.White);
}
});
tabs.setViewPager(pager);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_sliding_tab, container, false);
}
}
R.layout.fragment_sliding_tab
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/activity_series_details"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.myapp.views.SlidingTabLayout
android:id="#+id/sliding_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:elevation="2dp"
android:background="#color/tab_bar_background_color"
/>
<android.support.v4.view.ViewPager
android:id="#+id/pager"
android:layout_height="0dp"
android:layout_width="match_parent"
android:layout_weight="1" />
</LinearLayout>
Add this to your fragment:
#Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if (isVisibleToUser) {
// load data here
}else{
// fragment is no longer visible
}
}
I tried out the sample code from the API and it didn't really work so I implemented my own:
FragmentPagerSupport
public class FragmentPagerSupport extends FragmentActivity {
static final int NUM_ITEMS = 10;
MyAdapter mAdapter;
ViewPager mPager;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mAdapter = new MyAdapter(getSupportFragmentManager());
Log.i("Pager", "mAdapter = " + mAdapter.toString());
mPager = (ViewPager)findViewById(R.id.pager);
if (mPager == null)
Log.i("Pager", "mPager = null");
else
Log.i("Pager", "mPager = " + mPager.toString());
Log.i("Pager", "Setting Pager Adapter");
mPager.setAdapter(mAdapter);
}
public static class MyAdapter extends FragmentPagerAdapter {
public MyAdapter(FragmentManager fm) {
super(fm);
Log.i("Pager", "MyAdapter constructor");
}
#Override
public int getCount() {
Log.i("Pager", "MyAdapter.getCount()");
return NUM_ITEMS;
}
#Override
public Fragment getItem(int position) {
Log.i("Pager", "MyAdapter.getItem()");
return TestFragment.newInstance(position);
}
}
public static class TestFragment extends Fragment {
public static TestFragment newInstance(int position) {
Log.i("Pager", "TestFragment.newInstance()");
TestFragment fragment = new TestFragment();
Bundle args = new Bundle();
args.putInt("position", position);
fragment.setArguments(args);
return fragment;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// TODO Auto-generated method stub
Log.i("Pager", "TestFragment.onCreateView()");
LinearLayout layout = (LinearLayout)inflater.inflate(R.layout.fragment_item, null);
int position = getArguments().getInt("position");
TextView tv = (TextView)layout.findViewById(R.id.text);
tv.setText("Fragment # " + position);
tv.setTextColor(Color.WHITE);
tv.setTextSize(30);
return layout;
}
}
}
main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<android.support.v4.view.ViewPager
android:id="#+id/pager"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</LinearLayout>
fragment_item.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<TextView
android:id="#+id/text"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:text="#string/hello"
/>
</LinearLayout>
My question is, instead of creating a new Fragment instance each time the user swipes left and right, how do I save a fragment's state (to some data structure) and then restore it?
The API demo doesn't seem to have any state information saving code at all.
Use ViewPager.setOffscreenPageLimit() in FragmentActivity
ViewPager pager = (ViewPager) findViewById(R.id.viewPager);
pager.setOffscreenPageLimit(2);
See more at How do I tell my custom FragmentPagerAdapter to stop destroying my fragments?
just override this method in FragmentpagerAdapter
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
// TODO Auto-generated method stub
super.destroyItem(ViewGroup container, int position, Object object);
}
remove super.destroyItem(ViewGroup container, int position, Object object);
from your code
Update:
In the getItem of MyAdapter you return a new instance of your Fragment. Android will not call getItem on every swipe. It will only call getItem to get a new instance, but will reuse the existing instances as long as they are availible.
About the state part of the Demo. I can't help you. I think the normal techniques for restoring state in Fragements/Activities apply here, so nothing special when loading it in a ViewPager (but I might be wrong).
If you override
public void destroyItem(View container, int position, Object object)
without calling super, the fragment will not be destroyed and will be reused.
The adapter should be extended from FragmentStatePagerAdapter instead of FragmentPageAdapter and the adapter will keep reference from position to fragment item. On the activity or fragment parent, set listener OnPageChangeListener for PageIndicator to detect the position of fragment item has been activated and update related data's state.
About the data state of fragment item, I think should save/restore state from Activity or Fragement parent.
The adapter can add some code as following:
public static class MyAdapter extends FragmentStatePagerAdapter {
private SparseArray<TestFragment> mPageReferenceMap
= new SparseArray<TestFragment>();
...
#Override
public Object instantiateItem(ViewGroup viewGroup, int position) {
Object obj = super.instantiateItem(viewGroup, position);
//Add the reference when fragment has been create or restore
if (obj instanceof TestFragment) {
TestFragment f= (TestFragment)obj;
mPageReferenceMap.put(position, f);
}
return obj;
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
//Remove the reference when destroy it
mPageReferenceMap.remove(position);
super.destroyItem(container, position, object);
}
public TestFragment getFragment(int key) {
return mPageReferenceMap.get(key);
}
...
}
Hope this help.
I tripped on some similar ground...
I wanted to keep my fragments saved within my adapter to avoid useless reloading since I only had 5 fragments in the FragmentPagerAdapter.
I basically had to create an array and override the getItem(position) method:
private StoryListFragment[] fragmentsArray
= new StoryListFragment[getCount()];
public SectionsAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
if(fragmentsArray[position]!=null)
return fragmentsArray[position];
StoryListFragment storyListFragment = null;
switch(position){
case(0):
storyListFragment = StoryListFragment.newInstance(StoriesBank.NEWS);
break;
case(1):
storyListFragment = StoryListFragment.newInstance(StoriesBank.FEATURES);
break;
case(2):
storyListFragment= StoryListFragment.newInstance(StoriesBank.ARTS);
break;
}
fragmentsArray[position] = storyListFragment;
return storyListFragment;
}
#Override
public Object instantiateItem(ViewGroup container, int position) {
StoryListFragment fragment = (StoryListFragment) super.instantiateItem(container, position);
fragmentsArray[position] = fragment;
return fragment;
}