How to get the current fragment displayed in a specific tab of a viewpager?
Asked Answered
E

4

21

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.

scenario

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;
    }
}
Egypt answered 25/8, 2016 at 10:40 Comment(0)
A
46

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.

Apparel answered 1/9, 2016 at 14:22 Comment(12)
1. Can you explain what am I doing (and which effects has) if I override instantiateItem and destroyItem? 2. Also, should I save YourFragment fragment from onPageSelected into a global attribute currentFragment to have access to it whenever I want? 3. And, as I have an inner Viewpager, would I have two currentFragments? I mean, one RootFragment2 in MainActivity and one Fragment2 in RootFragment2.Egypt
1- You'll just keep instances of your fragments which you'll use in your view pagers. When you create a new Fragment in your ViewPager adapter you add it's reference to a sparse array and get it when you need. 2- Yes you can do that. 3- Yes you can use 2 current fragments or you can keep a single instance this depends on you.Apparel
Okey, I have implemented it, but doesn't work as desired. I save just a fragment; this means, the onPageSelected of the main and inner VP save the fragment into a global attribute. When Fragment2 is being displayed, currentFragment is RootFragment2.Egypt
Ok, it was because last page selected was to go to tab_b. However, it is being displayed tab_b1 although it has not been selected (by default, there is always an open page in a viewpager).Egypt
@SantiGil how is it going? does my solution works for you?Apparel
Your solution works, but only for a main VP. When you have inner VPs, it fails, because when tab_b is selected, currentFragment is RootFragment2 (this doesn't keep in mind the inner ViewPager). This happens although I init manual the first time with the correct value. I solved this in another way, let me post the answer so you can see it.Egypt
Yes, that is what I did. The two VPs, main and inner, set this value when page selected. However, when tab_b is selected, tab_b1 and tab_b2 haven't been selected but one of them is being displayed. Thus currentFragment is the root which holds the inner viewpager, because last page selected was tab_b.Egypt
i think there's something wrong with your implementation.Apparel
What about holding fragment references? maybe you should use WeakReferences arrayFilomenafiloplume
we remove fragment reference from sparse array onDestroyItem method. so i think no need to use wekreferences array.Apparel
Is there any possibility of memory leaks here? How many Fragment references will be held in SparseArray at one time?Jointed
there're no possible memory leaks here because when we destroy item we remove it from sparse array. fragment count will be held in SparseArray at one time depends on your viewpager's offScreenPageLimit.Apparel
E
3

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.

Hierarchy

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();

Egypt answered 2/9, 2016 at 9:0 Comment(0)
I
0

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 ;
}
Iapetus answered 1/9, 2016 at 11:9 Comment(6)
I had in mind this solution, but as I have commented The fragment to return is not necessary the hosted in the ViewPager. I can have opened two more fragments in a tab. I mean, I don't know the fragmentName to search for it, because there can be 5-6 fragments in the stack, and I don't know which one is the current displayed.Egypt
I have also a similar solution, but I think there has to be a better one because I don't consider it smart. The solution is: each fragment that can be used in a tab, implements a interface Tab_x_Fragment where x is the number of the tab where it can be displayed. Thus, I just have to get the fragment which is instanceof Tab_b1_Fragment from all the current displayed fragments.Egypt
Almost, you can keep it in another lists in MainActivity and pass it by special method. So you have current state of every tab already in main Activity, For example keep them in hashMap with key as tabNameIapetus
Yes, I also though about that. But I need then special memory, and I'm sure I can get this done using Android features.Egypt
You spend special memory only for pointer for your fragments. It requires only 4 bytes for every fragment. I dont think it will really slow down your app.Iapetus
Haha I know! But I mean, it is not only special memory, but also duplicated. I have all that information in the backstack.Egypt
A
0

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.

Afterburner answered 1/9, 2016 at 12:13 Comment(7)
What does exactly getReturnMyselfAtPosition() do? Do you refer to the getItem(int position) method of FragmentStatePagerAdapter?Egypt
It could be. But getRetunMyselfAtPosition should return ReturnMyself instead of fragment, so you don't need to cast it :-)Afterburner
But this solution returns me the Fragment hosted in the ViewPager tab. As I said, The fragment to return is not necessary the hosted in the ViewPager. I can have opened two more fragments in a tab.Egypt
thats why, as i wrote in point 7, the fragment that has inner fragmnets, should not return himself, but one of his child fragment.Afterburner
Yes, but with that you only manage to return the hosted fragment in the inner ViewPager, instead of the RootFragment2. For example if Fragment2 opens a Fragment5 inside tab_b1, this would return Fragment2 as current displayed because is the hosted in the inner ViewPager.Egypt
No it won't. Sorry that you don't understand thatAfterburner
It returned me the HostFragment which hosts the tab (see my answer), and not the fragment in the hosted_fragment container of this HostFragment.Egypt

© 2022 - 2024 — McMap. All rights reserved.