I am trying to use ViewPager and TabLayout in different ways than the usual.
I have 5 tabs, and each tabs have ViewPager which has two fragment pages (A Fragment and B Fragment)each.
For each tab, ViewPager needs to display same fragment pages with different information. ( Such as Tab's title is displayed in A Fragment, and Tab 1's title is "Android", and Tab 2's title is "IOS" )
Since FragmentPagerAdapter sets the Fragments in getItem(int position) method like the below, I tried to send these information in that method (information about the tab).
I found the problem,when i launch the ViewPager and set the adapter to it, the adapter is only stetted once So, when Tab 1's ViewPager already set to A Fragment the title of "Android", it won't change to "IOS" when I turn it to second tab Tab2.
Is there any way sending different informations each tab to the ViewPager to receive the information differently?
I kind of wrote it too broad maybe? if you need more specific informations, or do not get the question please tell me I will explain you more.
Thanks for helping
My FragmentPagerAdapter
public class ManagerPagerAdapter extends FragmentPagerAdapter{
....
#Override
public Fragment getItem(int position){
...
/* I received informations about the tab, and setted to bundle.
So thought each tab gives different bundle data to AFragment.
But since the Adapter is setted only once, this method is not
called more than once*/
Bundle bundle = new Bundle();
switch(position){
case 0:
AFragment fragment_a = new AFragment();
fragment_a.setArguments(bundle);
return fragment_a;
case 1: //Not really matters
BFragment fragment_b = new BFragment();
return fragment_b;
}
}
}
My TabLayout code is not so different from other examples.
Thank you again!!!
If the Activity needs to be able to listen for changes to the page selected or other events surrounding the ViewPager, then we just need to hook into the ViewPager.OnPageChangeListener on the ViewPager to handle the events:
pager.addOnPageChangeListener(new OnPageChangeListener() {
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
// This method will be invoked when the current page is scrolled
}
public void onPageSelected(int position) {
// set your title here for specic fagment
//This method will be invoked when a new page becomes selected.
}
public void onPageScrollStateChanged(int state) {
// Called when the scroll state changes:
// SCROLL_STATE_IDLE, SCROLL_STATE_DRAGGING, SCROLL_STATE_SETTLING
}
});
Related
I have one fragment where are three (default) images and when user click on them, they will change to another. But when i swipe to another fragment and back to fragment with images there are not default ones as on the start. When I swipe two times so I will pass to another fragment (distance from original with images is 2 fragments) images are resetted to default. I was trying to implement setOffscreenPageLimit() from ViewPager and set it to 1, but minimum "length" when views in fragments are resetted is 2. How can I change that images to default manually after swipe action? Thank you.
Edit: I think that issue why onResume() is not working here: Fragment onResume not called
but i dont know what that means :/ I have three classes FragmentController.class, PagerAdapter.class and class of specific fragment for example FirstFragment.class. I don't know how connect these classes together.
Check that you create the fragments in the getItem() method of the adapter and do not hold any reference to that fragments (only WeakReferences if necessary), otherwise the fragments could not be destroyed.
EDIT:
The first fragment is unloaded only when you are in the third one because setOffscreenPageLimit is at least 1 so a viewpager allways loads the fragments that are at both sides of the selected one.
What you could do is to update your adapter with this code to provide a getFragment(position) method:
private HashMap<Integer, WeakReference<Fragment>> fragmentReferences = new HashMap<>();
#Override
public Fragment getItem(int position) {
Fragment fragment;
switch (position) {
case 0:
fragment = FirstFragment.newInstance();
break;
// etc
}
fragmentReferences.put(position, new WeakReference<>(fragment));
return fragment;
}
public Fragment getFragment(int position) {
WeakReference<Fragment> ref = fragmentReferences.get(position);
return ref == null ? null : ref.get();
}
After then you can get the selected fragment and call the method you want from the first fragment when a page is selected:
viewPager.setOnPageChangeListener(new OnPageChangeListener() {
#Override
public void onPageSelected(int currentPage) {
if (currentPage == 0) {
Fragment firstFragment = adapter.getFragment(0);
if (firstFragment != null) {
// This method resets the images of the first fragment
((FirstFragment) firstFragment).reset();
}
}
}
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
// Do nothing
}
#Override
public void onPageScrollStateChanged(int state) {
// Do nothing
}
});
I'm not quite sure if there's a "best way" to tackle the following design issue.
I have a Tablayout with 2 Tabs in my MainActivity. Each Tab is a different Fragment. I go to Tab1 and see Fragment1. I need to launch a new Fragment (1A) from Fragment 1 and am not sure the best way to do it? I was thinking about one of these.
A) Take the Tabs out of my MainActivity and place them in a separate MainFragment, which gets launched with the app. That way when the user launches Fragment 1A, it replaces just the 1 MainFragment with the Tabs.
or
B) Keep the Tabs in the MainActivity and find a way to replace Fragment 1 with Fragment 1A when under Tab1.
Any suggestions would be appreciated. Thank you.
I think you shouldn't do both of points... Frag1 visible under Tab1 should contain all the layout (including initialy hidden) and logic for this view. If you need to show smth new it may be smaller (then popup, dialog etc.) or expand some layout, maybe with some animation (you may still use ViewPager inside Fragment inside ViewPager, disable touch events and manipulate scrolling programmatically...).
When Action picked by user is intended to show smth so-much-improtant that previous screen is not needed at all then you should probably open new Activity
PS. If you insist to replace current "screen" (in fact Activitys content) note that title of Tab1 may not representing what contains Frag1A. It very depends what kind of content you have there. You may consider move TabLayout/RecyclerView to e.g. FrameLayout container and add to it you Frag1A covering whole previous view including Tabs. In current design guidelines you can even find suggested solution for way of showing new fragment - with circular reveal animation
I do not understand very well what you want, but possibly this.
Use FragmentPagerAdapter..
public class TabsPagerAdapter extends FragmentPagerAdapter {
private static final int NUM_TABS = 3;
public TabsPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
switch(position){
case 0:
return Tab1Fragment.newInstance();
case 1:
return Tab2Fragment.newInstance();
default:
return Tab3Fragment.newInstance();
}
}
#Override
public int getCount() {
return NUM_TABS;
}
#Override
public CharSequence getPageTitle(int position) {
if (position == 0){
return "Tab 1";
}
if (position == 1){
return "Tab 2";
}
return "Tab 3";
}
In your activity...
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Tabs
TabLayout tabs = (TabLayout) findViewById(R.id.tabs);
ViewPager pager = (ViewPager) findViewById(R.id.pager);
TabsPagerAdapter adapter = new TabsPagerAdapter(getSupportFragmentManager());
pager.setAdapter(adapter);
tabs.setupWithViewPager(pager);
}
I have an application running on an embedded system with 4 tabs in a SectionsPagerAdapter and in the Fragment of each tab I have code that updates the display every 0.5 seconds via a timer.
I find that when I create 4 tabs, the first 2 are created and 3 and 4 are only created if I scroll to the right which is expected.
The issue for me is that after the view for each tab is created and the timer is started, the timer fires and the calls to update the views gets processed even if the tab is not visible.
I am trying to find a way to know if the tab is visible or not and if it's hidden, I simply skip the re-drawing of the views. Only when the tab is visible do I do the updates.
I have tried using isShown() and hasWindowFocus() from the the view that getView() returns but they always return true;
I've also tried to use onPause and onResume for each fragment but they only get called when I move to the 2rd tab from it. Eg, on tab1, move to tab2, not called, moved to tab3, onPause() called.
For now I have used onTabSelected() to store the current tab in my singleton class that all the fragments have access to the system data from. When I create the fragment, I pass in the tab position.
BUT , how can I check for this view being visible in a more elegant way?
You can check if the Fragment you want to update is selected in an OnPageListener attached to your ViewPager. In the listener you can check if the page you want to update your timer on is selected. Here is an example:
SectionsPagerAdapter mSectionsPagerAdapter = new SectionsPagerAdapter(getSupportFragmentManager());
ViewPager mViewPager = (ViewPager) findViewById(R.id.pager);
mViewPager.setAdapter(mSectionsPagerAdapter);
mViewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
#Override
public void onPageSelected(int page) {
/*If the page is the page you want to update your timer on, enable it.*/
if ( page == myTimerPageId ){
FragmentManager fragmentManager = getSupportFragmentManager();
Fragment timerFragment = fragmentManager.findFragmentByTag("android:switcher:" + mViewPager.getId() + ":" + myTimerPageId);
timerFragment.startTimer();
}
/*If it is not the page you update your timer on, disable it.*/
else{
FragmentManager fragmentManager = getSupportFragmentManager();
Fragment timerFragment = fragmentManager.findFragmentByTag("android:switcher:" + mViewPager.getId() + ":" + myTimerPageId);
timerFragment.stopTimer();
}
}
#Override
public void onPageScrolled(int arg0, float arg1, int arg2) {}
#Override
public void onPageScrollStateChanged(int arg0) {}
});
You have to get the Fragment through the FragmentManager and then do whatever you want to that Fragment, implement startTimer() or something. myTimerPageId must be the identifier of the page where you are incrementing your timer. The findFragmentByTag part is stolen from this answer. The OnPageChangeListener part is grabbed from here.
As a side note, the ViewPager has an setOffscreenPageLimit that sets how many pages beside the active one that is kept in memory. This can be minimum 1, though, so it is not possible to use this together with Fragment.onPause to achieve what is asked here. If this could be of use in another sitatuation, take a look here.
I have ActionBar Tabs setup. It consists of 4 tabs. Everything is fine until I navigate away from TabbedFragment and returning back.
I create tabs like this:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final ActionBar actionBar = getActionBar();
tabs = Lists.newArrayList();
tabs.add(new TabDefinition<>("Tab 1"));
tabs.add(new TabDefinition<>("Tab 2"));
tabs.add(new TabDefinition<>("Tab 3"));
tabs.add(new TabDefinition<>("Tab 4"));
for (TabDefinition tab : tabs) {
actionBar.addTab(actionBar.newTab()
.setText(tab.text)
.setTag(tab.tag)
.setTabListener(this));
}
}
And initialize adapter like this:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.paging_tab_container, container, false);
}
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
viewPager = (ViewPager) view.findViewById(R.id.pager);
viewPager.setAdapter(new FragmentStatePagerAdapter(getFragmentManager()) {
#Override
public Fragment getItem(int position) {
return tabs.get(position).fragment;
}
#Override
public int getCount() {
return tabs.size();
}
});
viewPager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
#Override
public void onPageSelected(int position) {
getActionBar().setSelectedNavigationItem(position);
}
});
viewPager.setCurrentItem(getActionBar().getSelectedNavigationIndex(), true);
}
When returning back to TabbedFragment selected tab and 1 next to it would not have any content. Just empty view. But if I select current + 2 fragment content is loaded. And then returning to that first fragment content is reloaded.
For example I have A, B, C, D tabs. Before leaving TabbedFragment I had selected tab A.
When returning to TabbedFragment I still am at tab A, but it's empty. So is tab B.
But when selecting tab C it is created and loaded. Returning to tab A it is recreated.
What could be the problem here?
After a while ran into the same problem again, so updating this question.
If you're using FragmentStatePagerAdapter you should provide FragmentManager via getChildFragmentManager() instead of getFragmentManager(). See Issue 55068: ViewPager doesn't refresh child fragments when back navigation via backstack
Okay so When using a FragmentStatePagerAdapter your fragments will be destroyed when you navigate anymore than one fragment Away since by default offScreenPageLimit is set to 1 by default just as mentioned above.
Typically this Class is used for an activity that has a very large set of Fragments, i.e have to scroll through a large amount of views. If your application does not need more than say 3-4 tabs I would suggest using FragmentPagerAdapter instead, and then specifying your offScreenPageLimit to something like 3, so if you get to the 4th Tab, all 3 tabs before will still be in memory.
Here is some Sample Code for a project on github that i created illustrating how to dynamically load the fragments if you don't want to add this offScreenPageLimit.
https://github.com/lt-tibs1984/InterfaceDemo/blob/master/src/com/divshark/interfacedemo/InterfaceDemoMain.java
Walk through all this code in this Class, and you will see how I'm dynamically loading the fragments, each time my ViewPager is slid over. Most notably at the bottom.
You can download this code, and use it as a test base for what you want to do.
Try adding the setOffScreenPageLimit(2) in the onCreate() method for the viewPager and notice the different behavior. To check the behavior, edit the text in fragment 1. Navigate Away and navigate back, with this set or not. You will see when it is set, the fragment's text remains what you change it to, since the fragment is never recreated.
Please provide additional questions if you have them.
GoodLuck
UPDATE
private static final String [] fragmentClasses = {"com.example.project.YourFragment1","com.example.project.YourFragment2","com.example.project.YourFragment3"};
viewPager.setAdapter(new FragmentStatePagerAdapter(getFragmentManager()) {
#Override
public Fragment getItem(int position) {
Fragment fragmentAtPosition = null;
// $$$$ This is the Important Part $$$$$
// Check to make sure that your array is not null, size is greater than 0 , current position is greater than equal to 0, and position is less than length
if((fragmentClasses != null) && (fragmentClasses.length > 0)&&(position >= 0)&& (position < fragmentClasses.length))
{
// Instantiate the Fragment at the current position of the Adapter
fragmentAtPosition = Fragment.instantiate(getBaseContext(), fragmentClasses[position]);
fragmentAtPosition.setRetainInstance(true);
}
return fragmentAtPosition;
}
#Override
public int getCount() {
return fragmentClasses.length;
}
});
The problem exists in the Fragments you use as tabs, I think. They seem to not show anything when they are resumed (see Fragment lifecycle). The "weird" issue that only the currently selected +/-1 tab is empty, is because the offScreenPageLimit of your ViewPager is 1 by default. All tabs above this threshold are re-created.
Therefore, increasing the value will -- in your case -- cause all your tabs to appear empty after resuming. Check in your Fragment code which lifecycle methods you use to inflate your layout, set adapters and so forth, because that's what's causing your trouble.
I guess this happens because while loading fragment android loads current and current+1, if you debug you would not see onPause getting called for the immediate next fragment.
You can reload content programmatically in onTabChanged() method of TabHost.OnTabChangeListener.
After doing much research, this worked for me.
I have a complex layout with 3 tabs in a fragment, that gets switched out for other fragments. I realized that the ViewpagerAdapter will retain state, even if you press the home button. My problem was switching back and forth would null out the child fragment UI view elements and crash. The key is to not new out your ViewPagerAdapter. Adding the null check for the Adapter worked for me. Also, be sure to allocate setOffscreenPageLimit() for your needs. Also, from what I understand setRetainInstance(true); should not be used for fragments that have UI, it is designed for headless fragments.
In the fragment that holds your Tabs:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.fragment_tab, container, false);
tabLayout = (TabLayout) view.findViewById(R.id.tablayout);
viewPager = (ViewPager) view.findViewById(R.id.viewPager);
//Important!!! Do not fire the existing adapter!!
if (viewPagerAdapter == null) {
viewPagerAdapter = new ViewPagerAdapter(getChildFragmentManager());
viewPagerAdapter.addFragments(new AFragment(), "A");
viewPagerAdapter.addFragments(new BFragment(), "B");
viewPagerAdapter.addFragments(new CFragment(), "C");
}
//Allocate retention buffers for three tabs, mandatory
viewPager.setOffscreenPageLimit(3);
tabLayout.setupWithViewPager(viewPager);
viewPager.setAdapter(viewPagerAdapter);
return view;
}
Or more simply when navigating back to tabbedfragment (assuming you use an intent and the fragment is within an activity) use:
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
This keeps the original activity and moves it to the top of the stack rather than recreating it, thus you never need to recreate the viewPager.
My app has a ListView on startup. The user can either manually select an item in the ListView to go to a details screen or swipe using a ViewPager between the different details screens. The ViewPager's fragments are setup like this:
Listing
Detail 1
Detail 2
Detail 3
Detail 4
...
It's my understanding, when the Listing fragment is loaded, the ViewPager will execute Detail 1's code, for performance. The same when Detail 1 is loaded, Detail 2's code will execute.
The problem I'm running into is that I'm setting the title of each detail fragment in onActivityCreated, however, when the Listing fragment is loaded, it is displaying Detail 1's title. So I moved the code to onPageSelected of the ViewPager, which works if the user is swiping, but if the user manually selects an item in the ListView the title is never set.
I'm not sure if there is an event that is only fired when a user manually selects an item in the ListView and not when they are swiping or if I need to rethink my apps' setup. For example, instead of using this code in the Listing fragment's onListItemClick event:
final Intent listing = new Intent(getActivity().getApplicationContext(), Details.class);
startActivity(listing);
I need to somehow use the ViewPager.
mViewPager = (ViewPager)findViewById(R.id.viewpager);
mMyFragmentPagerAdapter = new MyFragmentPagerAdapter(getSupportFragmentManager());
mViewPager.setAdapter(mMyFragmentPagerAdapter);
mViewPager.setSaveEnabled(false);
mViewPager.setOnPageChangeListener(new OnPageChangeListener() {
#Override
public void onPageSelected(int position) {
String title = GetTitle(position);
getSupportActionBar().setTitle(title);
}
#Override
public void onPageScrolled(int position, float offset, int offsetPixel) {
}
#Override
public void onPageScrollStateChanged(int state) {
}
});
You'll probably want to override the method getPageTitle(int) in your MyFragmentPagerAdapter class. The documentation states:
This method may be called by the ViewPager to obtain a title string to
describe the specified page. This method may return null indicating no
title for this page. The default implementation returns null.
So rather than returning null, make sure you return the actual page title. You get passed in the position/index of the page the title is requested for, so a simple switch-case statement should suffice. Alternatively, you could set up an interface for your pages and query the relevant page for its title.