I put a FragmentTabHost into a ViewPager, but the tab content has gone after I rotate the device. The tab content will come back if I click on another tab to refresh content. Is there anything wrong in my code? Please help me know.
public class MyActivity extends FragmentActivity
{
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
LayoutInflater inflater = getLayoutInflater();
View tabView = inflater.inflate(R.layout.tab, null);
FragmentTabHost tabHost = (FragmentTabHost) tabView.findViewById(android.R.id.tabhost);
tabHost.setup(this, getSupportFragmentManager(), R.id.realtabcontent);
tabHost.addTab(tabHost.newTabSpec("tab1").setIndicator("Tab1"), TabContent1Fragment.class, null);
tabHost.addTab(tabHost.newTabSpec("tab2").setIndicator("Tab2"), TabContent2Fragment.class, null);
View page2 = inflater.inflate(R.layout.page2, null);
ViewPager viewPager = (ViewPager) findViewById(R.id.viewPager);
List<View> list = new ArrayList<View>();
list.add(page2);
list.add(tabView);
viewPager.setAdapter(new DemoPagerAdapter(list));
}
}
public class TabContent1Fragment extends Fragment
{
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
return inflater.inflate(R.layout.tab_content_1, null);
}
}
public class DemoPagerAdapter extends PagerAdapter
{
private List<View> mModeViewPageList;
public DemoPagerAdapter(List<View> modeViewPageList)
{
this.mModeViewPageList = modeViewPageList;
}
#Override
public Object instantiateItem(ViewGroup container, int position)
{
container.addView(mModeViewPageList.get(position), 0);
return mModeViewPageList.get(position);
}
#Override
public void destroyItem(ViewGroup container, int position, Object object)
{
container.removeView(mModeViewPageList.get(position));
}
#Override
public int getCount()
{
return mModeViewPageList.size();
}
#Override
public boolean isViewFromObject(View view, Object o)
{
return view == o;
}
}
Thanks to my friend, finally I found the reason was that I used PagerAdapter.
When device is rotated, all views will be recreated. But without extra codes for fragment management, PagerAdapter is not able to mange the state of the fragments inside views (tab content fragments in realtabcontent in my case) and the child fragment of a view will disappear after rotation.
So I should change all views for ViewPager into fragments and use FragmentPagerAdapter instead.
One more thing, it should be used getChildFragmentManager() from Fragment in onCreateView instead of getSupportFragmentManger() from FragmentActivity to manage the child fragments in a fragment.
I wanted to point out an important point in Light's answer. If you convert an Activity to a Fragment then you may have been using getSupportFragmentManager. When converting it, you may have changed it to getActivity().getSupportFragmentManager(). It will seemingly work until you leave and return to the fragment, and you'll find some fragments missing until you scroll the pager.
The solution is to change getActivity().getSupportFragmentManager() to getChildFragmentManager().
Related
So currently my code can move between objects from the same fragment, but I want to move between different fragments that have different layouts.What code do I need to add to viewpager to make it work? Do I need to make use of a FragentManager? Can anyone guide me on how to go about it? Thanks.
Below if my code:
ScreenSlidePagerActivity.java
public class ScreenSlidePagerActivity extends FragmentActivity {
private static final int NUM_PAGES = 5;
private ViewPager mPager;
private PagerAdapter pagerAdapter;
/**
* The pager adapter, which provides the pages to the view pager widget.
*/
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.slide_screen_viewpager);
//declare viewpager and pageradapter
mPager = findViewById(R.id.ViewPageSlide);
pagerAdapter = new ScreenSlidePagerAdapter(getSupportFragmentManager());
mPager.setAdapter(pagerAdapter);
}
#Override
public void onBackPressed() {
if (mPager.getCurrentItem() == 0){
// If the user is currently looking at the first step, allow the system to handle the
// Back button. This calls finish() on this activity and pops the back stack.
super.onBackPressed();
}
else {
mPager.setCurrentItem(mPager.getCurrentItem() -1 );
}
}
private class ScreenSlidePagerAdapter extends FragmentStatePagerAdapter {
public ScreenSlidePagerAdapter(FragmentManager fm)
{
super(fm);
}
#Override
public Fragment getItem(int position) {
return new ScreenSlidePageFragment();
}
#Override
public int getCount() {
return NUM_PAGES;
}
}
}
ScreenSlidePageFragment.java
public class ScreenSlidePageFragment extends Fragment {
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
ViewGroup rootView = (ViewGroup) inflater.inflate(
R.layout.slide_content_page, container, false
);
return rootView;
}
}
You have only one class i.e.,ScreenSlidePageFragment that extends fragments. If you want different layouts for that, its better if you create different classes that inflates different layouts. eg: if you want two layouts, create two classes and both classes should inflate different layouts. The changes need to be done are :
//inside ScreenSlidePagerAdapter
#Override
public Fragment getItem(int position) {
switch (position) {
case 0:
return new ScreenSlidePageFragment();
case 1:
return new NewClass();
//and so on
}
}
You have to create the new Class similar to ScreenSlidePageFragment. The only change is inflate a different layout.
public class NewClass extends Fragment {
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
ViewGroup rootView = (ViewGroup) inflater.inflate(
R.layout.new_layout, container, false
);
return rootView;
}
}
You can create a new_layout similar to slide_content_page and customize it as you want. You can also increase the no of fragment objects and layout as you wish.
But a new way of doing this things has come. Its better if you extend FragmentStateAdapter instead of FragmentStatePagerAdapter. This is more easy and efficient. You have to override createFragment in this case instead of getItem. Ignore of you are okay with it.
Hope this is the question you have asked and this helps. Thankyou.
so i'm using ViewPager to make my app much faster , instead of using an activity for each layout.
the problem is , in activity you can write your code inside onCreate
and it will only starts when you start the activity , right?
but when you go with fragments and fragmentPagerAdapter and use ViewPager for them.
all your fragments going to start their (onCreateView) together even if your ViewPager only showing the First Fragment!
if you are you playing a sound or animation on the start of a fragment , it will starts in the background !
here's my fragmentPagerAdapter class:
public class PagerAdapter extends FragmentStatePagerAdapter {
private final List<Fragment> mFragmentList = new ArrayList<>();
private final List<String> mFragmentTitle = new ArrayList<>();
public PagerAdapter(FragmentManager fm) {
super(fm);
}
#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);
mFragmentTitle.add(title);
}
}
and i have this (second Fragment) that i don't want it to starts without being seleceted!
public class GameFragment extends Fragment{
private View gameLayout
private Animation showBtn;
private Button button;
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
gameLayout = inflater.inflate(R.layout.game_layout, container, false);
button = gameLayout .findViewById(R.id.button);
// loading my anim xml file
showBtn = AnimationUtils.loadAnimation(getContext(),R.anim.btn_show);
button.startAnimation(showBtn);
loadLevel();
return gameLayout
}
finally this is my Activity that have ViewPager :
public class ControllerActivity extends AppCompatActivity {
public CustomPager viewPager;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_controller);
viewPager = findViewById(R.id.viewPager);
PagerAdapter adapter = new PagerAdapter(getSupportFragmentManager());
adapter.addFragment(new HomeFragment(),"Home");
adapter.addFragment(new GameFragment(),"Game");
viewPager.setAdapter(adapter);
viewPager.setCurrentItem(0);
}
i need your help to show me my mistakes here and what is the correct way
Using an OnPageChangeListener in your View Pager, you can detect which fragment is currently shown in the View Pager. You'll then need to check which fragment is shown and then call a method on that fragment's class to start any sounds for example that you don't want to start until the fragment is the fragment in view.
You should use an interface for this.
Here you can find an example of using an OnPageChangeListener.
Here you can find an example of Activity to Fragment communication using interfaces. This example has a lot of code that won't be relevant to your use case, but demonstrates the use of interfaces.
One way to do it "Might" be to just initialize the view of the fragment in onCreateView :
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
view = inflater.inflate(R.layout.fragment_media_viewer, container, false);
return view;
}
Then in the override the setUserVisibleHint() function and do the rest of the initialization there. This function is called when ever the fragment is in view.
#Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
// do the rest of the code here.
}
I haven't used setuserVisibleHint for this purpose yet. But you can try it. Let me know too.
Scenarios:
I have a Fragment with a ViewPager, that it has a FragmentPagerAdapter that provides 3 Fragments.
So the first time my application loads, the 3 tab fragments looks just fine.
When I navigate in the application, i do a FragmentManager.replace to load another fragment.
When i go back, or navigate to the Main View (the tabbed View) my first and second tabs are blank.
I debugged the app, and the Fragment.onDestroyView never gets call on the Tab fragment, so when it shows again, the Fragment.onCreateView never gets called again, so my view is blank.
If I navigate to the third Tab in the Tabbed Fragment, then the Fragment.onDestroy gets called by the PageAdapter (i supposed because of the 1 offLimit on the ViewPager)
So, my question is, once another Fragment is loaded in my Activity, and then i go back, or navigate to the Tabbed View, how can i force the ViewPager, or the PageAdapter, or even the Fragment itself to be recreated.
So far i try to do a FragmentManager.remove(fragment) with the TabView without any look, also, every time, i navigate to the Tabbed View i pass a new instance to the FragmentManager.replace method, it seems that every time, he pulls the same object from the FragmentManager.
TabView (Fragment):
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
view = inflater.inflate(R.layout.tab_view, container, false);
viewPager = (ViewPager) view.findViewById(R.id.view_iam);
final PagerAdapter pagerAdapter = new MyFragmentAdapter(getActivity().getSupportFragmentManager());
viewPager.setAdapter(pagerAdapter);
return view;
}
PageAdapter:
private static class MyFragmentAdapter extends FragmentPagerAdapter {
private Fragment[] tabs;
private String[] names;
public MyFragmentAdapter(FragmentManager fm) {
super(fm);
tabs = new Fragment[]{new FlightsView(), new MyFlightsRequestView(),
new MyFlightsView()};
names = new String[]{"VUELOS", "SOLICITUDES", "MIS VUELOS"};
}
#Override
public Fragment getItem(int position) {
return tabs[position];
}
#Override
public int getCount() {
return tabs.length;
}
#Override
public String getPageTitle(int position) {
return names[position];
}
}
FlightsView (One of the Tab Fragments)
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.view_flight, container, false);
recycle_view = (RecyclerView) view.findViewById(R.id.recycler_view);
progressBar = (ProgressBar) view.findViewById(R.id.progress_bar);
setHasOptionsMenu(true);
return view;
}
Can you please try to extend your adapter from FragmentStatePagerAdapter instead of FragmentPagerAdapter
I have a fragment, fragment A, which holds a ViewPager. The ViewPager loads different fragments which the user can swipe through "indefinitely" (I use a really high number of pages/loops to emulate this). When a user clicks on the current ViewPager fragment, then fragment A with the ViewPager is replaced by fragment B in the fragment manager. When the user returns from fragment B, the backstack is popped using popBackStackImmediate(). If the user repeats this action several times, the heap begins to fill up by about 100kb at a time until the app starts to become sloppy and malfunction as the memory fills up. I'm unsure what exactly is causing this, can anyone help?
My fragment A with the ViewPager:
public class MainFragment extends Fragment {
private MainWearActivity mMainWearActivity;
View view;
private int currentPage;
private ViewPager pager;
private ViewPagerAdapter adapter;
private LinearLayout helpIcons;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mMainWearActivity = (MainWearActivity) getActivity();
adapter = new ViewPagerAdapter(this.getChildFragmentManager());
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
view = inflater.inflate(R.layout.fragment_main, container, false);
// Scrolling menu
pager = (ViewPager) view.findViewById(R.id.watchNavPager);
pager.setAdapter(adapter);
pager.addOnPageChangeListener(adapter);
// Set current item to the middle page
pager.setCurrentItem(Consts.FIRST_PAGE);
currentPage = Consts.FIRST_PAGE;
// Set number of pages
pager.setOffscreenPageLimit(4);
// Set no margin so other pages are hidden
pager.setPageMargin(0);
return view;
}
#Override
public void onDestroyView() {
pager = null;
super.onDestroyView();
}
}
My adapter class:
public class ViewPagerAdapter extends FragmentPagerAdapter implements
ViewPager.OnPageChangeListener {
public ViewPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position)
{
position = position % Consts.PAGES;
switch(position){
case Consts.AUDIO_POS:
return new AdapterAudioFragment();
case Consts.VOICE_POS:
return new AdapterVoiceFragment();
case Consts.MAIL_POS:
return new AdapterMailFragment();
case Consts.INFO_POS:
return new AdapterInfoFragment();
default:
return null;
}
}
#Override
public int getCount()
{
return Consts.PAGES * Consts.LOOPS; // (4 * 1000)
}
#Override
public void onPageScrolled(int position, float positionOffset,
int positionOffsetPixels) {}
#Override
public void onPageSelected(int position) {}
#Override
public void onPageScrollStateChanged(int state) {}
}
One of my fragments that the adapter loads (they are all pretty much the same):
public class AdapterAudioFragment extends Fragment {
private ImageView menuImg;
private TextView menuText;
private LinearLayout rootView;
private MainWearActivity mMainWearActivity;
private View.OnClickListener imgClickListener;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mMainWearActivity = (MainWearActivity) getActivity();
imgClickListener = new View.OnClickListener() {
#Override
public void onClick(View v) {
mMainWearActivity.replaceFragment(mMainWearActivity.getFragment(Consts.FRAG_AUDIO), Consts.FRAG_AUDIO);
}
};
}
#Override
public View onCreateView(LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
// Get root view of the fragment layout
rootView = (LinearLayout) inflater.inflate(
R.layout.fragment_nav_object, container, false);
// Set the current menu image and text
menuImg = (ImageView) rootView.findViewById(R.id.fragment_image);
menuImg.setImageResource(R.mipmap.ic_audio);
menuImg.setOnClickListener(imgClickListener);
menuText = (TextView) rootView.findViewById(R.id.menuTxt);
menuText.setText(Consts.MENU_HEADER_AUDIO);
// Set the current menu selection
mMainWearActivity.setCurrentSelection(Consts.AUDIO_POS);
return rootView;
}
}
I have a feeling that the adapter's fragments are all being created but never destroyed and piling up in the heap but I can't figure out how to resolve this. Do I need to call destroyItem in the adapter and manually destroy them? Any help would be most appreciated, thanks.
Adding this to Fragment stopped leaks for me:
#Override
public void onDestroyView() {
super.onDestroyView();
viewPager.setAdapter(null);
}
Looking at the source code, the problem seems to be that when calling ViewPager#setAdapter the view will register itself as observer for the adapter. So each time onViewCreated is called your pager adapter instance will have reference of the newly created view.
There is a specific PagerAdapter for your needs - FragmentStatePagerAdapter
This version of the pager is more useful when there are a large number of pages, working more like a list view. When pages are not visible to the user, their entire fragment may be destroyed, only keeping the saved state of that fragment. This allows the pager to hold on to much less memory associated with each visited page as compared to FragmentPagerAdapter at the cost of potentially more overhead when switching between pages.
I have an activity which makes use of a FragmentTabHost, so each tab has a fragment inside it. In the first tab, I have 2 nested fragments, one on the top half of the screen, the other on the bottom half. The fragment on the bottom uses a ViewPager to scan through several LinearLayouts. It all works pretty well.
Until you move to another tab, and return to the first. The bottom nested fragment no longer appears, but the one on top does.
Here's some code demonstrating my usage:
This is how I'm using my main activity:
public class MainActivity extends SherlockFragmentActivity
{
private FragmentTabHost mTabHost;
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main_activity_layout_vert);
mTabHost = (FragmentTabHost)findViewById(android.R.id.tabhost);
mTabHost.setup(this, getSupportFragmentManager(), R.id.realtabcontent);
mTabHost.addTab(mTabHost.newTabSpec("TAB1").setIndicator("", getResources().getDrawable(R.drawable.tab1_selector)), Tab1Fragment.class, null);
mTabHost.addTab(mTabHost.newTabSpec("TAB2").setIndicator("", getResources().getDrawable(R.drawable.tab2_selector)), Tab2Fragment.class, null);
}
}
Here's how I'm using the fragment for tab1:
public class Tab1Fragment extends SherlockFragment
{
private NestedFragment1 mNestedFrag1 = null;
private NestedFragment2 mNestedFrag2 = null;
#Override
public void onCreate(Bundle inSavedInstanceState)
{
super.onCreate(inSavedInstanceState);
mNestedFrag1 = new NestedFragment1();
mNestedFrag2 = new NestedFragment2();
FragmentManager fragManager = getChildFragmentManager();
FragmentTransaction fragTransaction = fragManager.beginTransaction();
fragTransaction.add(R.id.nested_frag1_container, mNestedFrag1, "Frag1");
fragTransaction.add(R.id.nested_frag2_container, mNestedFrag2, "Frag2");
fragTransaction.commit();
}
#Override
public void onDestroy()
{
FragmentManager fragManager = getChildFragmentManager();
FragmentTransaction fragTransaction = fragManager.beginTransaction();
fragTransaction.remove(mNestedFrag1);
fragTransaction.remove(mNestedFrag2);
fragTransaction.commit();
}
}
And here is how I'm using the 2nd nested fragment (the one that disappears):
public class NestedFragment2 extends SherlockFragment
{
private MyPageAdapter mPageAdapter;
private ViewPager mViewPager;
#Override
public void onCreate(Bundle inSavedInstanceState)
{
super.onCreate(inSavedInstanceState);
mPageAdapter = new MyPageAdapter();
}
#Override
public void onDestroy()
{
super.onDestroy();
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
View toReturn = inflater.inflate(R.layout.nested_fragment1_layout_top, container, false);
mViewPager = (ViewPager)toReturn.findViewById(R.id.viewpager);
mViewPager.setAdapter(mPageAdapter);
mViewPager.setOffscreenPageLimit(5);
return toReturn;
}
private class MyPageAdapter extends PagerAdapter
{
#Override
public Object instantiateItem(ViewGroup container, int position)
{
View page = getActivity().getLayoutInflater().inflate(R.layout.view_pager_controls_layout, container, false);
container.addView(page);
return page;
}
#Override
public void destroyItem(ViewGroup container, int position, Object object)
{
container.removeView((View)object);
}
#Override
public int getCount()
{
return 5;
}
#Override
public float getPageWidth(int position)
{
return 1;
}
#Override
public boolean isViewFromObject(View view, Object object)
{
return(view == object);
}
}
}
Thoughts?
Sounds as if it is being destroyed when you move to a different tab. Try setting ViewPager.setOffscreenPageLimit() to a high number and see if it still happens.
Most likely it's because you should be using a childFragmentManager for the nested fragments, instead of the one obtained from the activity.
From a "top level" fragment whenever you gonna do the transaction to include the nested fragment use getChildFragmentManager() instead of getFragmentManager()
I figured it out. The above code was not the problem. The problem had to do with the way I was handling the PagerAdapter inflation. I wasn't doing it exactly how I showed above. The way I was actually using it was preventing an inflation of the views from happening.