In a simple tabLayout view viewPager setup I am unable to correctly pass through the selected tab to the fragment within it. As tabs are selected left to right (i.e tab 1 -> tab x) the tab is correctly interpreted, but as they are selected right to left, 0 is returned.
Here is the important code:
Activity including tabLayout:
TabLayout tabLayout = (TabLayout) findViewById(R.id.tab_layout);
ViewPager viewPager = (ViewPager) findViewById(R.id.viewpager);
viewPager.setAdapter(new OwnPagerAdapter(getSupportFragmentManager(), NavDrawer.this));
tabLayout.setupWithViewPager(viewPager);
PagerAdapter:
public class OwnPagerAdapter extends FragmentPagerAdapter {
private int TAB_COUNT = 7;
private String tabTitles[] = new String[] {"Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday" };
private Context context;
public OwnPagerAdapter(FragmentManager fm, Context context) {
super(fm);
this.context = context;
}
#Override
public Fragment getItem(int position) {
return OwnTimetableFragment.newInstance(position - 1);
}
#Override
public int getCount() {
return TAB_COUNT;
}
#Override
public CharSequence getPageTitle(int position) {
return tabTitles[position];
}
}
Fragment:
public class OwnTimetableFragment extends Fragment {
public static final String ARG_DAY = "ARG_DAY";
private int mDay;
public static OwnTimetableFragment newInstance(int day) {
Bundle args = new Bundle();
args.putInt(ARG_DAY, day);
OwnTimetableFragment fragment = new OwnTimetableFragment();
fragment.setArguments(args);
return fragment;
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mDay = getArguments().getInt(ARG_DAY);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View tabView = inflater.inflate(R.layout.fragment_own_timetable, container, false);
Log.e("mDay", mDay + "");
return tabView;
}
The log method call in the onCreateView of the fragment will correctly return numbers 0->6 as the tabs are swiped through left to right, but as soon as a tab left of the current tab is selected, it will return 0, which will become the new origin (i.e one tab right will become 1)
Seems as though there is something going on with fragments coming in and out of focus that might be causing this?
First of all, you are passing pos - 1 in your new instance, because of which, your first tab will display log as -1. If you want correct value, only pass pos in newInstance.
Other than that, make sure you are not passing any setOffscreenPageLimit in viewPager. Everthing else in your code seems to be okay.
Let me know your exact issue if you are still facing it.
Related
I have googled everything during a day about the subject and nothing seems to work around for my project. I have added a viewpager to my main activity. There is two fragments attached to it thanks to a page adapter. I initialized everything on the OnCreate method from my main activity. The view pager work well. The problem is when I want to access from my MainActivity the textviews included in my fragments. I created a Set method in each fragment which modify the textviews accordingly to the arguments. So in order to call this method from my MainActivity I have to instantiate the fragments on my MainActivity and the problem is that this line of code return null and the fragments can not be instantiated. Here is the code used for testing this purpose.
Main Activity
public class MainActivity extends AppCompatActivity{
private SecondActivitySwipeAdapter adapterViewPager;
private FirstFragmentSwipe fragment1;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.v(TAG, "onCreate");
setContentView(R.layout.main_activity_test);
ViewPager vpPager = (ViewPager) findViewById(R.id.viewpager);
adapterViewPager = new SecondActivitySwipeAdapter(getSupportFragmentManager());
vpPager.setAdapter(adapterViewPager);
fragment1 = (FirstFragmentSwipe) adapterViewPager.getRegisteredFragment(vpPager.getCurrentItem());
fragment1.setTextViews(1,2,3,4,5);
}
...
}
SecondActivitySwipeAdapter
public class SecondActivitySwipeAdapter extends FragmentStatePagerAdapter {
SparseArray<Fragment> registeredFragments = new SparseArray<Fragment>();
private static int NUM_ITEMS = 2;
private FragmentManager mFragmentManager;
public SecondActivitySwipeAdapter(FragmentManager fragmentManager) {
super(fragmentManager);
mFragmentManager = fragmentManager;
}
// Returns total number of pages
#Override
public int getCount() {
return NUM_ITEMS;
}
// Returns the fragment to display for that page
#Override
public Fragment getItem(int position) {
switch (position) {
case 0:
return FirstFragmentSwipe.newInstance();
case 1:
return SecondFragmentSwipe.newInstance();
default:
return null;
}
}
#Override
public Object instantiateItem(ViewGroup container, int position) {
Fragment fragment = (Fragment) super.instantiateItem(container, position);
registeredFragments.put(position, fragment);
return fragment;
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
registeredFragments.remove(position);
super.destroyItem(container, position, object);
}
public Fragment getRegisteredFragment(int position) {
return registeredFragments.get(position);
}
}
FirstFragmentSwipe
public class FirstFragmentSwipe extends Fragment {
private TextView avg1;
private TextView avg2;
private TextView avg3;
private TextView avg4;
private TextView avg5;
public static FirstFragmentSwipe newInstance(){
return new FirstFragmentSwipe();
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.swipelayout1, container, false);
avg1 = view.findViewById(R.id.pwrAvgNmbview1);
avg2 = view.findViewById(R.id.pwrAvgNmbview2);
avg3 = view.findViewById(R.id.pwrAvgNmbview3);
avg4 = view.findViewById(R.id.pwrAvgNmbview4);
avg5 = view.findViewById(R.id.pwrAvgNmbview5);
return view;
}
public void setTextViews(int avg1, int avg2, int avg3, int avg4, int avg5){
this.avg1.setText(Integer.toString(avg1));
this.avg2.setText(Integer.toString(avg2));
this.avg3.setText(Integer.toString(avg3));
this.avg4.setText(Integer.toString(avg4));
this.avg5.setText(Integer.toString(avg5));
}
}
The second fragment is basically the same as fragment for the moment. I am using some ideas found by googling. The last line of the onCreate method make the app crash. Thanks for you precious help.
So the problem here is that registeredFragments doesn't have the fragments instantiated yet.
First you can check if fragment1 is null to prevent crashes.
Furthermore you'd rather put initialization methods in getItem() of adapter or in onViewCreated() of the fragment and do some updates in later lifecycle methods if needed.
so I wanted a tablayout and a viewpager to be able to swipe through different fragments.
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_home, container, false);
tabLayout = (TabLayout) view.findViewById(R.id.tabLayout);
viewPager = (ViewPager) view.findViewById(R.id.viewPager);
viewPagerAdapter = new ViewPagerAdapter(getFragmentManager());
viewPagerAdapter.addFragments(new AFragment(), "Test1", getContext());
viewPagerAdapter.addFragments(new BFragment(), "Test2", getContext());
viewPagerAdapter.addFragments(new CFragment(), "Test3", getContext());
viewPager.setAdapter(viewPagerAdapter);
tabLayout.setupWithViewPager(viewPager);
return view;
}
And my ViewPagerAdapter class looks like this:
public class ViewPagerAdapter extends FragmentPagerAdapter {
ArrayList<Fragment> fragments = new ArrayList<>();
ArrayList<String> tabTitles = new ArrayList<>();
Context context;
public void addFragments(Fragment fragments, String titles, Context context) {
this.fragments.add(fragments);
this.tabTitles.add(titles);
this.context = context;
}
public ViewPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
return fragments.get(position);
}
#Override
public int getCount() {
return fragments.size();
}
#Override
public CharSequence getPageTitle(int position) {
return tabTitles.get(position);
}
}
So the adapter I set my viewPager to contains all the information such as the titles for my tabLayout and the amount of fragments. Is that correct?
So what I want to do now is replace the titles on my tabLayout with certain icons.
Am I supposed to remove the String parameter in my ViewPagerAdapter constructor and delete my Arraylist or do I have to pass an empty String?
What code do I need to add to my ViewPageAdapter class in order to be able to add icons instead of the Strings to the layout?
I found a possible solution here: How to add page title and icon in android FragmentPagerAdapter
So I changed my method to that:
#Override
public CharSequence getPageTitle(int position) {
myDrawable = ContextCompat.getDrawable(context, R.drawable.ic_mail_outline_black_24dp);
SpannableStringBuilder sb = new SpannableStringBuilder(" Page #"+ position); // space added before text for convenience
myDrawable.setBounds(0, 0, myDrawable.getIntrinsicWidth(), myDrawable.getIntrinsicHeight());
ImageSpan span = new ImageSpan(myDrawable, ImageSpan.ALIGN_BASELINE);
sb.setSpan(span, 0, 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
return sb;
}
That changed my titles to PAGE #0 #1 ... but didn't add the icon to my TabLayout.
Thank you!
EDIT:
This works too. Still don't know what I did wrong though
tabLayout.getTabAt(i).setIcon();
Please use this code for icon
mTabLayout.setupWithViewPager(mViewPager);
for (int i = 0; i < mTabLayout.getTabCount(); i++) {
mTabLayout.getTabAt(i).setIcon(R.drawable.your_icon);
}
Please replace this code will work
#Override
public CharSequence getPageTitle(int position) {
myDrawable = ContextCompat.getDrawable(context, R.drawable.ic_mail_outline_black_24dp);
title = tabTitles.get(position);
SpannableStringBuilder sb = new SpannableStringBuilder(" " + title); // space added before text for convenience
try {
myDrawable.setBounds(5, 5, myDrawable.getIntrinsicWidth(), myDrawable.getIntrinsicHeight());
ImageSpan span = new ImageSpan(myDrawable, DynamicDrawableSpan.ALIGN_BASELINE);
sb.setSpan(span, 0, 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
} catch (Exception e) {
// TODO: handle exception
}
tabTitles.set(position,sb);
return tabTitles.get(position);
}
I have an application that has an index page which has tabs for navigation. This page has multiple children pages being implemented as fragments.
Some of my children fragment views also have tabs in them for more navigation like below
The issue i am having is when i switch from one of these fragments to the other, and return to it.The tabs on that one are recreated like so
And i'm not sure how to solve this problem as it is my first time getting this serious with tabs.
Below is my code for the top-level-tab adapter
public class indexTabAdapter extends FragmentPagerAdapter
{
private List<String> titleList;
private List<Fragment> fragmentList;
public indexTabAdapter(FragmentManager fm, List<Fragment> fragmentList, List<String> titleList)
{
super(fm);
this.fragmentList = fragmentList;
this.titleList = titleList;
}
#Override
public Fragment getItem(int position)
{
return fragmentList.get(position);
}
#Override
public int getCount()
{
return fragmentList.size();
}
#Override
public CharSequence getPageTitle(int position)
{
return titleList.get(position);
}
}
Below here is the code for the activity class managing the top-level-tabs
#Override
protected void onCreate(#Nullable Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_index_page);
pager = (ViewPager) findViewById(R.id.indexViewPager);
tabLayout = (TabLayout)findViewById(R.id.indexTabs);
prepareDataResource();
indexTabAdapter ita = new indexTabAdapter(getSupportFragmentManager(),fragmentList,titleList);
pager.setAdapter(ita);
tabLayout.setupWithViewPager(pager);
ita.notifyDataSetChanged();
}
private void prepareDataResource()
{
fragmentList.add(new dash());
titleList.add("Dash");
fragmentList.add(new AcademicCalender());
titleList.add("Calender");
fragmentList.add(new IncidentReport());
titleList.add("Incident Report");
fragmentList.add(new Profile());
titleList.add("Profile");
fragmentList.add(new Pta());
titleList.add("PTA");
fragmentList.add(new timeTable());
titleList.add("Time Table");
fragmentList.add(new Results());
titleList.add("Results");
}
Lastly, this is the code for the lower-level-tab that keeps repeating
public class Profile extends Fragment
{
private List<String> titleList = new ArrayList<>();
private List<Fragment> fragmentList = new ArrayList<>();
private ViewPager pager;
private TabLayout tabLayout;
public Profile()
{
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
View view = inflater.inflate(R.layout.profile_view_activity,container,false);
pager = (ViewPager) view.findViewById(R.id.profileViewPager);
tabLayout = (TabLayout) view.findViewById(R.id.profileTabs);
prepareDataResource();
profileTabsAdapter rta = new profileTabsAdapter(getFragmentManager(),fragmentList,titleList);
pager.setAdapter(rta);
tabLayout.setupWithViewPager(pager);
return view;
}
private void prepareDataResource()
{
fragmentList.add(new index());
titleList.add("Home");
fragmentList.add(new immunization());
titleList.add("Immunization");
fragmentList.add(new information());
titleList.add("Information");
fragmentList.add(new records());
titleList.add("Health Records");
}
}
FragmentPagerAdapter instances your fragment once. So if you instantiate your list when declaring it as a field, it will always retain that same list, thus adding elements when the Fragment's onCreateView is called.
To avoid this, declare and instantiate both lists like this:
public class Profile extends Fragment {
private List<String> titleList;
private List<Fragment> fragmentList;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
//your code for views initialization
//init the lists here!
titleList = new ArrayList<>();
fragmentList = new ArrayList<>();
prepareDataResource();
//your adapter set up code
return view;
}
If you're working with ViewPager, try calling viewPager.setOffscreenPageLimit(numberOfTabs). I'm not a hundred percent sure this will work, but it won't hurt to try.
From the official Android documentation:
setOffscreenPageLimit(int limit)
Set the number of pages that should be retained to either side of the
current page in the view hierarchy in an idle state.
My EditText is in my fragment named : EquationsCalculFragment.java
I want to recover the value in my fragment named : EquationsResultat.java
I have two NullPointerException with no apparent reasons (see the comments directly in the code).
NB : For the error in the EquationsResultsFragment, I tried to cast with String.valueOf(), with Float.parseFloat(), with the two combined... but no one worked.
EquationsCalculFragment.java
public class EquationsCalculFragment extends Fragment {
private ViewPager viewPager;
private EditText champ_a;
private EditText champ_b;
private String a;
private String b;
public float getA_equa_2nd() {
return Float.parseFloat(a);
}
public float getB_equa_2nd() {
return Float.parseFloat(b);
}
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_equations_calcul, container, false);
viewPager = (ViewPager) getActivity().findViewById(R.id.viewpager);
champ_a = (EditText) view.findViewById(R.id.equa_2nd_a);
champ_b = (EditText) view.findViewById(R.id.equa_2nd_b);
Button button = (Button) view.findViewById(R.id.btn_equations_resultats);
button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
b = champ_b.getText().toString();
a = champ_a.getText().toString();
EquationsResultatFragment eqResFr = new EquationsResultatFragment();
eqResFr.getResultat(getA_equa_2nd(), getB_equa_2nd()); //NullPointerException happens here
//at com.example.slabre.applitest.EquationsCalculFragment$1.onClick(EquationsCalculFragment.java:60)
viewPager.setCurrentItem(1, true);
}
});
return view;
}
}
EquationsResultat.java
public class EquationsResultatFragment extends Fragment {
private TextView results;
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_equations_resultat, container, false);
results = (TextView) view.findViewById(R.id.equa_2nd_resultat);
return view;
}
public void getResultat(float a, float b) {
results.setText(String.valueOf((a*a)+(2*a*b)+(b*b))); // NullPointerException happens here
// at com.example.slabre.applitest.EquationsResultatFragment.getResultat(EquationsResultatFragment.java:29)
}
}
Hope you can help me, I think this is not a big problem.
Parent Activity : TabFragment.java (yes my app si working with a TabLayout and ViewPager)
public class TabFragment extends Fragment {
public static TabLayout tabLayout;
public static ViewPager viewPager;
public static int int_items = 3 ;
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
/**
*Inflate tab_layout and setup Views.
*/
View x = inflater.inflate(R.layout.tab_layout,null);
tabLayout = (TabLayout) x.findViewById(R.id.tabs);
viewPager = (ViewPager) x.findViewById(R.id.viewpager);
/**
*Set an Apater for the View Pager
*/
viewPager.setAdapter(new MyAdapter(getChildFragmentManager()));
tabLayout.post(new Runnable() {
#Override
public void run() {
tabLayout.setupWithViewPager(viewPager);
}
});
return x;
}
class MyAdapter extends FragmentPagerAdapter{
public MyAdapter(FragmentManager fm) {
super(fm);
}
/**
* Return fragment with respect to Position .
*/
#Override
public Fragment getItem(int position)
{
switch (position){
case 0 : return new EquationsCalculFragment();
case 1 : return new EquationsResultatFragment();
case 2 : return new EquationsInfosFragment();
}
return null;
}
#Override
public int getCount() {
return int_items;
}
/**
* This method returns the title of the tab according to the position.
*/
#Override
public CharSequence getPageTitle(int position) {
switch (position){
case 0 :
return "Calcul";
case 1 :
return "Résultats";
case 2 :
return "Infos";
}
return null;
}
}
}
First of all, you cannot directly communicate between different fragments. You need to go through your container activity for it.
Declare a listener interface through which your fragment will communicate with your container activity. Attach that listener to your EquationsCalculFragment. Your container activity should implement that listener.
Now when you EquationsCalculFragment wants to communicate with EquationsResultatFragment you notify your container activity using that listener. In the container activity, when you get the listener callback, grab your EquationsResultatFragment fragment instance using findFragmentById(int id) or findFragmentByTag(String tag) and check if it is already added to your container activity. If it is, then call any public method of EquationsResultatFragment fragment, using that grabbed EquationsResultatFragment fragment instance.
Check out this link for more details and sample code.
EquationsResultat's getResultat is called when you click on the EquationsCalculFragment's button, and should set EquationsResultat's TextView (results) text. Problem is, results is set on EquationsResultat's onCreateView, and it seems this method has no been called yet, and therefore results is still null.
Post your activitie's code, it will be easier to help you knowing when you are attaching the fragments, etc.
Try this!!
public void getResultat(float a, float b) {
results.setText(((a * a) + (2 * a * b) + (b * b))+"");
}
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
Log.v(TAG, "onCreateView");
View rootView = inflater.inflate(R.layout.fragment_home, container, false);
// setting up the view pager and tab layout
mViewPager = (ViewPager) rootView.findViewById(R.id.view_pager);
mTabLayout = (TabLayout) rootView.findViewById(R.id.tab_layout);
PageAdapter adapter = new PageAdapter(getFragmentManager());
mViewPager.setAdapter(adapter);
mTabLayout.setupWithViewPager(mViewPager);
mViewPager.addOnPageChangeListener(new TabLayout.TabLayoutOnPageChangeListener(mTabLayout));
return rootView;
}
My custom pager adapter looks like this:
public class PageAdapter extends FragmentStatePagerAdapter {
public PageAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
return new GroupTabFragment(position);
}
#Override
public CharSequence getPageTitle(int position) {
String title = "";
switch (position) {
case 0:
title = "Game"; // this is from a template, i have to
break; // return title dynamically
case 1:
title = "Movie";
break;
}
return title;
}
#Override
public int getCount() {
return 2; // the tabs number is a subject of change
} // i don't know it from the begining
}
The problem i have is that i have to add or remove a tab when a button is clicked, or something like this.
Each tab will display a classic list of objects.
Thank you.
There are 2 ways doing this although ViewPager is not suitable for dynamic removing:
1) Pass ArrayList<Fragment> data as parameter to PageAdapter
Then Adapter will look like this:
public class PageAdapter extends FragmentStatePagerAdapter {
private ArrayList<Fragment> data;
public PageAdapter(FragmentManager fm, ArrayList<Fragment> data) {
super(fm);
this.data = data;
}
#Override
public Fragment getItem(int position) {
return data.get(position);
}
#Override
public CharSequence getPageTitle(int position) {
String title = "";
switch (position) {
case 0:
title = "Game"; // this is from a template, i have to
break; // return title dynamically
case 1:
title = "Movie";
break;
}
return title;
}
#Override
public int getCount() {
return data.size(); // the tabs number is a subject of change
} // i don't know it from the begining
}
to remove item you do in Activity\Fragment:
data.remove(fragmentId);
adapter.notifyDataSetChanged();
2) Recreate adapter every time you want to delete item. So you basically create new adapter with new data.
data.remove(fragmentId);
PageAdapter adapter = new PageAdapter(getFragmentManager(), data);
mViewPager.setAdapter(adapter);
Instead of using mTabLayout.setupWithViewPager(mViewPager); you can setup your tabs manually:
mTabLayout.addTab(mTabLayout.newTab().setText("Movie"));
mTabLayout.addTab(mTabLayout.newTab().setText("Game"));
mTabLayout.addTab(mTabLayout.newTab().setText("Audio"));
mTabLayout.setOnTabSelectedListener(this);
Removing is also easy: removeTabAt(int position) or removeTab(Tab tab)
Here I have found some better solution to remove all the views / fragments from the current context.
Here I have used TabLayout and ViewPage within Fragment.
So we need to clear tabs from TabLayout, views from ViewPage and fragments from FragmentManager.
private final List<Fragment> mFragmentList = new ArrayList<>();
private final List<String> mFragmentTitleList = new ArrayList<>();
FragmentManager fragmentManager = null;
public TabLayout tabLayout;
public ViewPager viewPager;
public ViewPagerAdapter(FragmentManager manager, TabLayout tabLayout, ViewPager pager) {
super(manager);
this.fragmentManager= manager;
this.tabLayout = tabLayout;
this.viewPager= pager;
}
public void removeAll() {
tabLayout.removeAllTabs();
viewPager.removeAllViewsInLayout();
viewPager.removeAllViews();
viewPager.setAdapter(null);
fragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
if (manager.getFragments() != null) {
manager.getFragments().clear();
}
viewPager.setAdapter(adapterPage);
viewPager.invalidate();
viewPager.requestLayout();
mFragmentList.clear();
mFragmentTitleList.clear();
}