I have three pages in my viewpager, each page is a fragment, And I have some EditText's in each page, I third page I have a Button called SAVE, Now in this button click event I have to the values from all EditText's. I have tried many way, but none is worked, Always I am getting NullPinterException. Any help will be highly appreciable.
Thanks,
Guna.
I have a very similar set up in my current app. What I did was create a subclass of Fragment that has the method:
public abstract String[] getForm();
the getForm method essentially returns a String[] containing the string stored in each form. Each Fragment has to implement that correctly. Now once you have that, in your Activity that contains the ViewPager initialize a list of fragments that your activity's ViewPagerAdapter should use to display. That way, now when you are in the final fragment and this button is clicked (and your fragment that contains the button click successfully informs the activity that the button click event occurred), your activity will know to iterate through the whole list of fragments, calling the fragments respective getForm method implementation.
Note that this will only work if you are not using a ViewStatePagerAdapter. The reason for this is because the ViewStatePagerAdapter is not guaranteed to keep all of your fragments in memory.
Here is a code example (In the code example, I have my view pager stored in a fragment but this design would most definitely work if you were keeping your viewpager in an activity). The real work is being done in the submit method. That is where we are collecting the fields from the other fragment (Hence this method should be called in you OnButtonClickListener code):
public class CreateAccountFragment extends RestCallExecutingFragment implements ViewPager.OnPageChangeListener {
private OnAccountCreationListener onAccountCreationListener;
public static final int VARIOUS_FRAG_POS = 2;
public static final int ACCOUNT_INFO_FRAG_POS = 0;
private static final int ADDRESS_FRAG_POS = 1;
public static final int CREATE_ACCOUNT_ID = 0;
public CreateAccountFragment() {
// Required empty public constructor
}
ArrayList<FormFragment> fragmentsToDisplay;
/**
* The {#link android.support.v4.view.PagerAdapter} that will provide
* fragments for each of the sections. We use a
* {#link android.support.v13.app.FragmentPagerAdapter} derivative, which will keep every
* loaded fragment in memory. If this becomes too memory intensive, it
* may be best to switch to a
* {#link android.support.v13.app.FragmentStatePagerAdapter}.
*/
SectionsPagerAdapter mSectionsPagerAdapter;
/**
* The {#link android.support.v4.view.ViewPager} that will host the section contents.
*/
ViewPager mViewPager;
#InjectView(R.id.rb_accountInfo)
RadioButton rb_accountInfo;
#InjectView(R.id.rb_address)
RadioButton rb_address;
#InjectView(R.id.rb_various)
RadioButton rb_various;
#InjectView(R.id.rg_createAccount)
RadioGroup rg_createAccount;
#InjectView(R.id.tv_pageTitle)
TextView tv_pageTitle;
List<RadioButton> radioButtons;
CreateAccountCommand createAccountCommand;
private static ArrayList<FormFragment> getCreateAccountFragments(){
ArrayList<FormFragment> list = new ArrayList<>();
list.add(AccountInfoFragment.newInstance());
list.add(AddressFragment.newInstance());
list.add(VariousFragment.newInstance());
return list;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.frag_create_account, container, false);
ButterKnife.inject(this, view);
fragmentsToDisplay = getCreateAccountFragments();
// Create the adapter that will return a fragment for each of the three
// primary sections of the activity.
mSectionsPagerAdapter = new SectionsPagerAdapter(getChildFragmentManager());
//todo make it easier to press the radio button
// Set up the ViewPager with the sections adapter.
mViewPager = (ViewPager) view.findViewById(R.id.pager);
mViewPager.setOnPageChangeListener(this);
mViewPager.setAdapter(mSectionsPagerAdapter);
return view;
}
public void submit() {
//todo move the create account button to this activity's view
AccountSubmissionRDTO createAccountSubmissionDTO;
try {
AccountInfoData accountInfoData = (AccountInfoData) fragmentsToDisplay.get(ACCOUNT_INFO_FRAG_POS).submitForm();
AddressData addressData = (AddressData) fragmentsToDisplay.get(ADDRESS_FRAG_POS).submitForm();
VariousData variousData = (VariousData) fragmentsToDisplay.get(VARIOUS_FRAG_POS).submitForm();
// createAccountSubmissionDTO = new CreateAccountSubmissionRDTO(CREATE_ACCOUNT_ID,0, -1, accountInfoData,addressData,variousData); //todo create actual server and local ids
}
/**
* A {#link android.support.v13.app.FragmentPagerAdapter} that returns a fragment corresponding to
* one of the sections/tabs/pages.
*/
public class SectionsPagerAdapter extends FragmentPagerAdapter {
public SectionsPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
FormFragment selectedFragment = fragmentsToDisplay.get(position);
Assert.assertNotNull("the fragment selected should be within list", selectedFragment);
return selectedFragment;
}
#Override
public int getCount() {
return fragmentsToDisplay.size();
}
#Override
public CharSequence getPageTitle(int position) {
Locale l = Locale.getDefault();
String pageTitle = fragmentsToDisplay.get(position).getPageTitle();
return pageTitle.toUpperCase(l);
}
}
}
Related
I have been trying to implement a ViewPager with different fragments.
And the problem is when i run the app, in the ViewPager, out of all the pages, only one page is visible and that page only gets changed when I slide over to the other pages in the ViewPager.
Take a look at my code,(although I checked it many times referring it with online resources).
This is what each of my fragments look like:
public class fragment1 extends Fragment {
/* Variable to store reference to the ACtivity */
Activity mCurrentActivity;
/* Variable storing reference to the ArrayList */
private ArrayList<Word> mDefaultWords;
/**
* THe empty public Constructor
*/
public fragment1(){
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
/** Getting reference to the Activity */
mCurrentActivity = getActivity();
// Populating the ArrayList here
// And later in the onActivityCreated callback I set an adapter on the ArrayList
return inflater.inflate(R.layout.activity_others, container, false);
}
#Override
public void onActivityCreated(Bundle savedStateInstance){
super .onActivityCreated(savedStateInstance);
/**
* Creating {#link ArrayAdapter} to link the {#link String}
* from {#link ArrayList} {#param
*/
MyAdapter adaptItems = new MyAdapter(mCurrentActivity, mDefaultWords);
// Getting the id of the ListView in numberActivity.xml
ListView myList = (ListView) mCurrentActivity.findViewById(R.id.theList);
//Chaning background color
myList.setBackgroundColor(ContextCompat.getColor(getContext(), android.R.color.holo_purple));
// Setting the adapter with the {#link ListView}
myList.setAdapter(adaptItems);
}
}
}
My Activity setting the adapter class extending FragmentPagerAdapter as a private inner class and setting the adapter on the ViewPager.
public class Main2Activity extends AppCompatActivity {
private ViewPager mViewPager;
private FragmentPagerAdapter mFragmentStatePagerAdapter;
private FragmentManager mFragmentManager;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
mFragmentManager = getSupportFragmentManager();
mViewPager = (ViewPager) findViewById(R.id.theViewPager);
mFragmentStatePagerAdapter = new MyFragmentStatePagerAdapter(mFragmentManager);
/* Setting the apdapter on the pager */
mViewPager.setAdapter(mFragmentStatePagerAdapter);
}
public class MyFragmentStatePagerAdapter extends FragmentPagerAdapter {
public MyFragmentStatePagerAdapter(FragmentManager fragmentManager){
super(fragmentManager);
}
#Override
public int getCount(){
return 4;
}
#Override
public Fragment getItem(int position) {
if (position == 0) {
return new fragment1();
} else if (position == 1){
return new fragment2();
} else if (position == 2) {
return new fragment3();
} else {
return new fragment4();
}
}
}
}
And here is the layout with the ViewPager
<android.support.v4.view.ViewPager
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/theViewPager"
android:visibility="visible" />
As I said, when I run the app only one page gets displayed, other pages are present in the ViewPager but they are blank and displays the default background color,
And the one page that is displayed is the one that gets changed when I swipe left or right in the ViewPager.
So what's the issue?
dont downvote the question, its a genuine problem.
So, I worked my way around, let me say how.
What happened is, I was working on a cloned project that had a old gradle version and sdktools version was also not updated and was quite old.
and the min API targetted was API 15
And I was testing my application on API 21.
So, what I did is I used a different layouts for each of my fragments.
That is for each fragment I created its own XML layout.
And that worked perfectly.
Odd problem, so I updated the gradle and sdktools, to avoid such weird problems.
I have a situation I need some advice on. I have an app that has an expandable list view. Each child click sends the user to the next activity, which is a tab layout with a list view. The tab layout has 3 tabs. I'm trying to figure out how to send data to the 3 tabs listviews when child is clicked on expandable listview.
Originally I was going to set it up in the setOnChildClickListener like so:
expandableListView.setOnChildClickListener(new ExpandableListView.OnChildClickListener() {
#Override
public boolean onChildClick(ExpandableListView parent, View v, int groupPosition, int childPosition, long id) {
if (groupPosition == 0) {
if (childPosition == 0) {
mCustomListViewAdapter.addAdapterItem(new CustomObject("Squats", "60%", "6", "150", false));
listViewFri.setAdapter(mCustomListViewAdapter);
EDIT: this is my tablayout activity that sets up the 3 tabs. I'm not sure where to access the bundled extras.
public class WorkoutDaysActivity extends BaseActivity {
ListView listViewFri = (ListView) findViewById(R.id.listViewFri);
ListView listViewMon = (ListView) findViewById(R.id.listViewMon);
ListView listViewWed = (ListView) findViewById(R.id.listViewWed);
/**
* The {#link android.support.v4.view.PagerAdapter} that will provide
* fragments for each of the sections. We use a
* {#link FragmentPagerAdapter} derivative, which will keep every
* loaded fragment in memory. If this becomes too memory intensive, it
* may be best to switch to a
* {#link android.support.v4.app.FragmentStatePagerAdapter}.
*/
private SectionsPagerAdapter mSectionsPagerAdapter
/**
* The {#link ViewPager} that will host the section contents.
*/
private ViewPager mViewPager;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.workout_days);
Bundle extras = getIntent().getBundleExtra("args");
mToolBar = activateToolbarWithHomeEnabled();
setUpNavigationDrawer();
// Create the adapter that will return a fragment for each of the three
// primary sections of the activity.
mSectionsPagerAdapter = new SectionsPagerAdapter(getSupportFragmentManager());
// Set up the ViewPager with the sections adapter.
mViewPager = (ViewPager) findViewById(R.id.container);
mViewPager.setAdapter(mSectionsPagerAdapter);
TabLayout tabLayout = (TabLayout) findViewById(R.id.tabs);
tabLayout.setupWithViewPager(mViewPager);
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_workout_days, menu);
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
//noinspection SimplifiableIfStatement
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
/**
* A placeholder fragment containing a simple view.
*/
public static class PlaceholderFragment extends Fragment {
/**
* The fragment argument representing the section number for this
* fragment.
*/
private static final String ARG_SECTION_NUMBER = "section_number";
public PlaceholderFragment() {
}
/**
* Returns a new instance of this fragment for the given section
* number.
*/
public static PlaceholderFragment newInstance(int sectionNumber) {
PlaceholderFragment fragment = new PlaceholderFragment();
Bundle args = new Bundle();
args.putInt(ARG_SECTION_NUMBER, sectionNumber);
fragment.setArguments(args);
return fragment;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
switch (getArguments().getInt(ARG_SECTION_NUMBER)) {
case 1:
View rootView = inflater.inflate(R.layout.fragment_workout_days, container, false);
Bundle extras = getArguments();
CustomObject objects = (CustomObject) extras.getSerializable("w29w1");
return rootView;
case 2: View rootView2 = inflater.inflate(R.layout.fragment_sub_page1, container, false);
TextView textView2 = (TextView) rootView2.findViewById(R.id.textView2);
textView2.setText("Workout 29 Week 1");
return rootView2;
case 3:
View rootView3 = inflater.inflate(R.layout.fragment_sub_page2, container, false);
TextView textView3 = (TextView) rootView3.findViewById(R.id.txtFrag3);
textView3.setText(getString(R.string.section_format, getArguments().getInt(ARG_SECTION_NUMBER)));
return rootView3;
default: View rootView4 = inflater.inflate(R.layout.fragment_workout_days, container, false);
TextView textView4 = (TextView) rootView4.findViewById(R.id.txtFrag1);
textView4.setText(getString(R.string.section_format, getArguments().getInt(ARG_SECTION_NUMBER)));
return rootView4;
}
}
}
/**
* A {#link FragmentPagerAdapter} that returns a fragment corresponding to
* one of the sections/tabs/pages.
*/
public class SectionsPagerAdapter extends FragmentPagerAdapter {
public SectionsPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
// getItem is called to instantiate the fragment for the given page.
// Return a PlaceholderFragment (defined as a static inner class below).
return PlaceholderFragment.newInstance(position + 1);
}
#Override
public int getCount() {
// Show 3 total pages.
return 3;
}
#Override
public CharSequence getPageTitle(int position) {
switch (position) {
case 0:
return "Monday";
case 1:
return "Wednesday";
case 2:
return "Friday";
}
return null;
}
}
}
I thought it would go in this part like so:
switch (getArguments().getInt(ARG_SECTION_NUMBER)) {
case 1:
View rootView = inflater.inflate(R.layout.fragment_workout_days, container, false);
Bundle extras = getArguments();
CustomObject objects = (CustomObject) extras.getSerializable("w29w1");
CustomListViewAdapter customListViewAdapter = new CustomListViewAdapter(this, objects);
But im getting an error under (this, objects) saying "cannot be applied to .placeholderfragment .custom object" "PlaceholderFragment cannot be converted to Context"
This is what I put in the onChildClick:
Bundle extras = new Bundle();
final ArrayList<CustomObject> objects = new ArrayList<CustomObject>();
objects.add(new CustomObject("Squat", "65%", "6", "150", false));
extras.putSerializable("w29w1", objects);
Intent intent = new Intent(getApplicationContext(), WorkoutDaysActivity.class);
intent.putExtra("args", extras);
startActivity(intent);
Thanks for the help! please let me know if you need to see any of my code to better understand what I'm trying to do!
I'm having some difficulty understanding the question completely, but here's my thoughts so far.
Are you attempting to set the same instance of the list adapter on each list? If you want the same general behaviour, but to tie different data to a list, be sure you are instantiating new adapter instances for each list.
i.e.
myList1.setAdapter(new MyAdapter());
myList2.setAdapter(new MyAdapter());
rather than
MyAdapter myAdapter = new MyAdapter();
myList1.setAdapter(myAdapter);
myList2.setAdapter(myAdapter);
Additionally, it is wise to move data through intents when communicating with another activity. In your initial activity, when an action prompts launching the next activity, put the extras into the intent before calling startActivity(). Then, inside the next activity, you will call getIntent().getXXXExtra("...") where the XXX is type-matched to the extra you are extracting, and ... is the correct key you placed on the extra in the previous activity.
If you are using fragments contained in activities, make use of Fragment.setArguments() (in activity2) and Fragment.getArguments() (in fragment2) to pass the extras from fragment1 to activity2 to fragment2.
Passing Extras Around
When you click an item and want to launch a new activity (a new view to display), a good approach is to bundle up the information needed, pass it to the new activity, unwrap all that information and then apply it to the views as you desire.
For example...
#Override
public boolean onChildClick(ExpandableListView parent, View v, int groupPosition, int childPosition, long id) {
if (groupPosition == 0) {
if (childPosition == 0) {
Bundle extras = new Bundle();
// Put all the extras you need. This can be primitives like int, boolean, double, or complex objects using the Serializable interface
// For example, say your CustomObject implements Serializable
CustomObject customObject = new CustomObject("Squats", "60%", "6", "150", false);
extras.putSerializable("object_key_here", customObject);
Intent intent = new Intent(getContext(), NextActivityHere.class);
intent.putExtra("args", extras);
startActivity(intent);
}
}
In the activity containing the fragment which contains the list:
// Likely in onCreate()
Bundle extras = getIntent.getBundleExtra("args");
MyFragment myFragment = new MyFragment();
myFragment.setArguments(extras);
// Use fragment manager to add the fragment to your view
In the fragment (myFragment) containing the list:
// Likely in onCreate()
Bundle extras = getArguments();
CustomObject customObject = (CustomObject) extras.getSerializable("object_key_here");
// Now you can use the object to set up the adapter, etc...
Hopefully this helps at least show how to get data from the original activity to the fragment containing the list. Usually I see myself passing an ArrayList of data if I'm going to be populating a list. Without knowing too much about your scenario, it's difficult to be more specific.
Layouts with Tabs
You may want to do some reading on TabLayouts and ViewPagers. These are the models used to create paged layouts with tabs.
Implementing these views should greatly reduce the complexity of the Activity. Inside your activity's onCreate() method, you would set up the TabLayout and ViewPager to host your Monday, Wednesday, and Friday fragments.
Each of Monday, Wednesday, and Friday fragment would be an instantiation of the same fragment class which contains the list you wish to fill (if I'm getting the correct vision of the app's layout).
List of MY Assumptions
Assume the XML file is named activity_tab_host.xml
Assume the TabLayout has an id="tab_layout"
Assume the ViewPager has an id="view_pager"
Assume the activity file is named TabHostActivity.java
Assume the fragment with the list is called WorkoutDetailFragment.java
Assume your fragment receives the data through its constructor. You can either do this or pass it through the arguments as you've done. I'm just attempting to minimize code while communicating the general idea. The fragment just needs to get the data! Try not to get hung up on specific methods and code shown here; you may find it easier to pass data through a constructor, etc.
Assume the list of data is ready in the ArrayLists of CustomObjects, simply referred to as mondayData, wednesdayData, and fridayData
Assume you've created your custom adapter called SectionsPagerAdapter
Guidance Code (Not 100% of what is needed)
Get the TabLayout and ViewPager views from the XML view source file:
// TabHostActivity.java::onCreate()
setContentView(R.layout.activity_tab_host);
TabLayout tabLayout = (TabLayout) findViewById(R.id.tab_layout);
ViewPager viewPager = (ViewPager) findViewById(R.id.view_pager);
Set up your ViewPager:
// TabHostActivity.java::onCreate()
SectionsPagerAdapter adapter = new SectionsPagerAdapter(getFragmentManager());
// Create fragment to populate the tabs
WorkoutDetailFragment mondayFragment = new WorkoutDetailFragment(mondayData);
WorkoutDetailFragment wednesdayFragment = new WorkoutDetailFragment(wednesdayData);
WorkoutDetailFragment fridayFragment = new WorkoutDetailFragment(fridayData);
// Add pages (fragments) to the adapter, passing the fragment and tab title
adapter.addFragment(mondayFragment, "Monday");
adapter.addFragment(wednesdayFragment, "Wednesday");
adapter.addFragment(fridayFragment, "Friday");
// Tie the adapter to your ViewPager
viewPager.setAdapter(adapter);
Tie the ViewPager to the TabView:
// TabHostActivity.java::onCreate()
tabLayout.setupWithViewPager(viewPager);
The main takeaway regarding where to instantiate new fragments is...
The ViewPager is a device used for holding fragments, allowing the user to change between these fragments in a single activity. You should be instantiating fragments within the TabHostActivity.java activity and setting up the fragments within a ViewPager.
My above example simplifies the situation by passing list data in through the WorkoutDetailFragment constructor. This is one of two easy ways to pass data to a fragment.
Passing data through the fragment constructor:
public class WorkoutDetailFragment extends Fragment {
private ArrayList<Object> listData;
public WorkoutDetailFragment(ArrayList<Object> listData) {
this.listData = listData;
}
// Rest of the code for the fragment...
}
Passing data through fragment arguments
// TabHostActivity.java
Bundle args = new Bundle(); // May have received this bundle from extras already. If so, use Bundle args = getIntent.getExtra(...
args.putExtra("list_data", listData); // Where listData is an ArrayList<CustomObject> in your case (Monday/Wednesday/Friday data)
WorkoutDetailFragment fragment = new WorkoutDetailFragment();
fragment.setArguments(args);
// Add the fragment to the view pager...
// Inside WorkoutDetailFragment.java retrieve list data from args using getArguments().getSerializable("list_data")
Hopefully this helps!
I am using a ViewPager to show 9 fragments. In each of these fragments, I want to just show a different picture. I want to use one single fragment layout, but dynamically add in the picture. Also, would like add a "Continue" button on the last fragment that when pressed will go to another activity.
How do I go about making a fragment layout dynamic?
Main Activity
public class StoryboardPageActivity extends FragmentActivity {
// The number of pages (wizard steps) to show in this demo.
private static final int NUM_PAGES = 9;
// The pager widget, which handles animation and allows swiping horizontally to access previous and next wizard steps.
private ViewPager mPager;
// The pager adapter, which provides the pages to the view pager widget.
private PagerAdapter mPagerAdapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_storyboard_page);
// Instantiate a ViewPager and a PagerAdapter.
mPager = (ViewPager) findViewById(R.id.storyboardPager);
mPagerAdapter = new StoryboardPagerAdapter(getSupportFragmentManager());
mPager.setAdapter(mPagerAdapter);
}
#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 {
// Otherwise, select the previous step.
mPager.setCurrentItem(mPager.getCurrentItem() - 1);
}
}
// A simple pager adapter that represents 5 fragment objects, in sequence.
private class StoryboardPagerAdapter extends FragmentStatePagerAdapter {
public StoryboardPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
return StoryboardFragment.newInstance(position);
}
#Override
public int getCount() {
return NUM_PAGES;
}
}
}
Fragment
public class StoryboardFragment extends Fragment {
private static final String KEY_POSITION = "position";
static StoryboardFragment newInstance(int position) {
StoryboardFragment frag = new StoryboardFragment();
Bundle args = new Bundle();
args.putInt(KEY_POSITION, position);
frag.setArguments(args);
return(frag);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
ViewGroup rootView = (ViewGroup) inflater.inflate(R.layout.fragment_storyboard_page, container, false);
ImageView image = (ImageView)rootView.findViewById(R.id.imgStoryboard);
int position = getArguments().getInt(KEY_POSITION, -1);
int[] images = {R.drawable.storyboard1, R.drawable.storyboard2, R.drawable.storyboard3,
R.drawable.storyboard4, R.drawable.storyboard5, R.drawable.storyboard6,
R.drawable.storyboard7, R.drawable.storyboard8, R.drawable.storyboard9};
image.setImageResource(images[position]);
return rootView;
}
}
Fragment XML
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#fff" >
<ImageView
android:id="#+id/imgStoryboard"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:contentDescription="#string/storyboardSlide" />
</RelativeLayout>
How do I go about making a fragment layout dynamic?
The same way you make any other "layout dynamic". If you want to put an image in an ImageView, call setImageBitmap() or setImageDrawable() or whatever. For example, the PagerAdapter could supply the position to the fragment (via a factory method), and the fragment could then know what image to load.
This sample project demonstrates populating the hint of an EditText with a custom value based upon the page's position.
With respect to the "Continue" button, either have a separate fragment class for that (and appropriate smarts in your PagerAdapter, or always have the button in your layout, but set to android:visibility="gone" by default, toggling it via setVisibility(View.VISIBLE) for the fragment that needs it.
I am new to using a ViewPager, but the initial screen on my app is a ListView that the user can add/remove new items to, then clicking on one of the items brings them to a "details" fragment based on an id that is passed. I'd like for the user to also be able to swipe from the listing through to each of the details.
I have the ViewPager working, except the id's are always off by one. This might be my lack of understanding of ViewPagers, but if I put a breakpoint in the onCreateView of the details fragment, the breakpoint is hit when the app loads and the id that is passed is the first id. So, say the ids are 1,2,3,4, when the app loads, the id on app start-up in onCreateView is 1. When I perform the initial swipe from the listing to the first details fragment, the id is 2 (when I would expect it to be 1).
This is what I have so far:
Main.class (this initial activity on app start-up)
public class Main extends SherlockFragmentActivity
{
private static int NUMBER_OF_PAGES;
private ViewPager mViewPager;
private MyFragmentPagerAdapter mMyFragmentPagerAdapter;
private static List<Fragment> fragments;
#Override
public void onCreate(final Bundle icicle)
{
super.onCreate(icicle);
setContentView(R.layout.main);
mViewPager = (ViewPager)findViewById(R.id.viewpager);
mMyFragmentPagerAdapter = new MyFragmentPagerAdapter(getSupportFragmentManager());
mViewPager.setAdapter(mMyFragmentPagerAdapter);
final List<Integer> ids = GetIds(); //loads ids to popular the viewpager
NUMBER_OF_PAGES = ids.size();
fragments = new ArrayList<Fragment>();
fragments.add(new ListingFragment()); //initial screen
for(Integer id : ids)
fragments.add(DetailsFragment.newInstance(id));
}
private static class MyFragmentPagerAdapter extends FragmentStatePagerAdapter {
public MyFragmentPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int index) {
return fragments.get(index);
}
#Override
public int getCount() {
return NUMBER_OF_PAGES;
}
}
}
DetailsFragment.class
public class DetailsFragment extends SherlockFragmentActivity
{
private int detailId;
private List<Integer> items;
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
}
#Override
public void onActivityCreated(final Bundle icicle)
{
super.onActivityCreated(icicle);
}
public static DetailsFragment newInstance(int id) {
DetailsFragment lf = new DetailsFragment();
Bundle bundle = new Bundle();
bundle.putInt("id", id);
lf.setArguments(bundle);
return lf;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.details, container, false);
detailId = getArguments().getInt("id"); //this id is always off
items = GetItems();
return view;
}
}
I have the ViewPager working, except the id's are always off by one.
This might be my lack of understanding of ViewPagers, but if I put a
breakpoint in the onCreateView of the details fragment, the breakpoint
is hit when the app loads and the id that is passed is the first id.
So, say the ids are 1,2,3,4, when the app loads, the id on app
start-up in onCreateView is 1. When I perform the initial swipe from
the listing to the first details fragment, the id is 2 (when I would
expect it to be 1).
Maybe I don't understand the problem you're facing but the code you posted(again, if that is the full code you use(currently it will not compile as your DetailsFragments extends SherlockFragmentActivity (?!))) can't do what you say. The way you setup the fragments will make the them have the proper ids.
What you're seeing it's most likely due to the way the ViewPager works, which can be misleading. When you first start the app you'll see the ListingFragment. Now, the ViewPager, in order to provide a smooth swipe for the user will also load(by default) one additional fragment on each side of the current visible fragment(so when the app starts the ViewPager will load page 0(ListingFragment) and the next(to the right as we don't have anywhere to go left)) page, 1(the DetailsFragment with the id 1). When you swipe from the ListingFragment to the first DetailsFragment(which has the id 1) the ViewPager will automatically create the second DetailsFragment(which has the id 2) in advance(again, in order to be able to swipe right away to it). Now, if you have a Log statement in the onCreateView in the DetailsFragment(to see the id) it's normal that you see the second id as you swipe from the ListingFragment to the first DetailsFragment because that Log will not appear from the first DetailsFragment(with id 1, which is already created) being created, it will appear because the second DetailsFragment(with id 2) is being created in advance.
Also, revise your code, as you introduced a subtle bug. You set the size of the adapter to
NUMBER_OF_PAGES which is initialized with ids.size() but then you add the ListingFragment which will make the DetailsFragment with the last id(from ids) not appear at all.
Fragments seem to be very nice for separation of UI logic into some modules. But along with ViewPager its lifecycle is still misty to me. So Guru thoughts are badly needed!
Edit
See dumb solution below ;-)
Scope
Main activity has a ViewPager with fragments. Those fragments could implement a little bit different logic for other (submain) activities, so the fragments' data is filled via a callback interface inside the activity. And everything works fine on first launch, but!...
Problem
When the activity gets recreated (e.g. on orientation change) so do the ViewPager's fragments. The code (you'll find below) says that every time the activity is created I try to create a new ViewPager fragments adapter the same as fragments (maybe this is the problem) but FragmentManager already has all these fragments stored somewhere (where?) and starts the recreation mechanism for those. So the recreation mechanism calls the "old" fragment's onAttach, onCreateView, etc. with my callback interface call for initiating data via the Activity's implemented method. But this method points to the newly created fragment which is created via the Activity's onCreate method.
Issue
Maybe I'm using wrong patterns but even Android 3 Pro book doesn't have much about it. So, please, give me one-two punch and point out how to do it the right way. Many thanks!
Code
Main Activity
public class DashboardActivity extends BasePagerActivity implements OnMessageListActionListener {
private MessagesFragment mMessagesFragment;
#Override
protected void onCreate(Bundle savedInstanceState) {
Logger.d("Dash onCreate");
super.onCreate(savedInstanceState);
setContentView(R.layout.viewpager_container);
new DefaultToolbar(this);
// create fragments to use
mMessagesFragment = new MessagesFragment();
mStreamsFragment = new StreamsFragment();
// set titles and fragments for view pager
Map<String, Fragment> screens = new LinkedHashMap<String, Fragment>();
screens.put(getApplicationContext().getString(R.string.dashboard_title_dumb), new DumbFragment());
screens.put(getApplicationContext().getString(R.string.dashboard_title_messages), mMessagesFragment);
// instantiate view pager via adapter
mPager = (ViewPager) findViewById(R.id.viewpager_pager);
mPagerAdapter = new BasePagerAdapter(screens, getSupportFragmentManager());
mPager.setAdapter(mPagerAdapter);
// set title indicator
TitlePageIndicator indicator = (TitlePageIndicator) findViewById(R.id.viewpager_titles);
indicator.setViewPager(mPager, 1);
}
/* set of fragments callback interface implementations */
#Override
public void onMessageInitialisation() {
Logger.d("Dash onMessageInitialisation");
if (mMessagesFragment != null)
mMessagesFragment.loadLastMessages();
}
#Override
public void onMessageSelected(Message selectedMessage) {
Intent intent = new Intent(this, StreamActivity.class);
intent.putExtra(Message.class.getName(), selectedMessage);
startActivity(intent);
}
BasePagerActivity aka helper
public class BasePagerActivity extends FragmentActivity {
BasePagerAdapter mPagerAdapter;
ViewPager mPager;
}
Adapter
public class BasePagerAdapter extends FragmentPagerAdapter implements TitleProvider {
private Map<String, Fragment> mScreens;
public BasePagerAdapter(Map<String, Fragment> screenMap, FragmentManager fm) {
super(fm);
this.mScreens = screenMap;
}
#Override
public Fragment getItem(int position) {
return mScreens.values().toArray(new Fragment[mScreens.size()])[position];
}
#Override
public int getCount() {
return mScreens.size();
}
#Override
public String getTitle(int position) {
return mScreens.keySet().toArray(new String[mScreens.size()])[position];
}
// hack. we don't want to destroy our fragments and re-initiate them after
#Override
public void destroyItem(View container, int position, Object object) {
// TODO Auto-generated method stub
}
}
Fragment
public class MessagesFragment extends ListFragment {
private boolean mIsLastMessages;
private List<Message> mMessagesList;
private MessageArrayAdapter mAdapter;
private LoadMessagesTask mLoadMessagesTask;
private OnMessageListActionListener mListener;
// define callback interface
public interface OnMessageListActionListener {
public void onMessageInitialisation();
public void onMessageSelected(Message selectedMessage);
}
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
// setting callback
mListener = (OnMessageListActionListener) activity;
mIsLastMessages = activity instanceof DashboardActivity;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
inflater.inflate(R.layout.fragment_listview, container);
mProgressView = inflater.inflate(R.layout.listrow_progress, null);
mEmptyView = inflater.inflate(R.layout.fragment_nodata, null);
return super.onCreateView(inflater, container, savedInstanceState);
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
// instantiate loading task
mLoadMessagesTask = new LoadMessagesTask();
// instantiate list of messages
mMessagesList = new ArrayList<Message>();
mAdapter = new MessageArrayAdapter(getActivity(), mMessagesList);
setListAdapter(mAdapter);
}
#Override
public void onResume() {
mListener.onMessageInitialisation();
super.onResume();
}
public void onListItemClick(ListView l, View v, int position, long id) {
Message selectedMessage = (Message) getListAdapter().getItem(position);
mListener.onMessageSelected(selectedMessage);
super.onListItemClick(l, v, position, id);
}
/* public methods to load messages from host acitivity, etc... */
}
Solution
The dumb solution is to save the fragments inside onSaveInstanceState (of host Activity) with putFragment and get them inside onCreate via getFragment. But I still have a strange feeling that things shouldn't work like that... See code below:
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
getSupportFragmentManager()
.putFragment(outState, MessagesFragment.class.getName(), mMessagesFragment);
}
protected void onCreate(Bundle savedInstanceState) {
Logger.d("Dash onCreate");
super.onCreate(savedInstanceState);
...
// create fragments to use
if (savedInstanceState != null) {
mMessagesFragment = (MessagesFragment) getSupportFragmentManager().getFragment(
savedInstanceState, MessagesFragment.class.getName());
StreamsFragment.class.getName());
}
if (mMessagesFragment == null)
mMessagesFragment = new MessagesFragment();
...
}
When the FragmentPagerAdapter adds a fragment to the FragmentManager, it uses a special tag based on the particular position that the fragment will be placed. FragmentPagerAdapter.getItem(int position) is only called when a fragment for that position does not exist. After rotating, Android will notice that it already created/saved a fragment for this particular position and so it simply tries to reconnect with it with FragmentManager.findFragmentByTag(), instead of creating a new one. All of this comes free when using the FragmentPagerAdapter and is why it is usual to have your fragment initialisation code inside the getItem(int) method.
Even if we were not using a FragmentPagerAdapter, it is not a good idea to create a new fragment every single time in Activity.onCreate(Bundle). As you have noticed, when a fragment is added to the FragmentManager, it will be recreated for you after rotating and there is no need to add it again. Doing so is a common cause of errors when working with fragments.
A usual approach when working with fragments is this:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
CustomFragment fragment;
if (savedInstanceState != null) {
fragment = (CustomFragment) getSupportFragmentManager().findFragmentByTag("customtag");
} else {
fragment = new CustomFragment();
getSupportFragmentManager().beginTransaction().add(R.id.container, fragment, "customtag").commit();
}
...
}
When using a FragmentPagerAdapter, we relinquish fragment management to the adapter, and do not have to perform the above steps. By default, it will only preload one Fragment in front and behind the current position (although it does not destroy them unless you are using FragmentStatePagerAdapter). This is controlled by ViewPager.setOffscreenPageLimit(int). Because of this, directly calling methods on the fragments outside of the adapter is not guaranteed to be valid, because they may not even be alive.
To cut a long story short, your solution to use putFragment to be able to get a reference afterwards is not so crazy, and not so unlike the normal way to use fragments anyway (above). It is difficult to obtain a reference otherwise because the fragment is added by the adapter, and not you personally. Just make sure that the offscreenPageLimit is high enough to load your desired fragments at all times, since you rely on it being present. This bypasses lazy loading capabilities of the ViewPager, but seems to be what you desire for your application.
Another approach is to override FragmentPageAdapter.instantiateItem(View, int) and save a reference to the fragment returned from the super call before returning it (it has the logic to find the fragment, if already present).
For a fuller picture, have a look at some of the source of FragmentPagerAdapter (short) and ViewPager (long).
I want to offer a solution that expands on antonyt's wonderful answer and mention of overriding FragmentPageAdapter.instantiateItem(View, int) to save references to created Fragments so you can do work on them later. This should also work with FragmentStatePagerAdapter; see notes for details.
Here's a simple example of how to get a reference to the Fragments returned by FragmentPagerAdapter that doesn't rely on the internal tags set on the Fragments. The key is to override instantiateItem() and save references in there instead of in getItem().
public class SomeActivity extends Activity {
private FragmentA m1stFragment;
private FragmentB m2ndFragment;
// other code in your Activity...
private class CustomPagerAdapter extends FragmentPagerAdapter {
// other code in your custom FragmentPagerAdapter...
public CustomPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
// Do NOT try to save references to the Fragments in getItem(),
// because getItem() is not always called. If the Fragment
// was already created then it will be retrieved from the FragmentManger
// and not here (i.e. getItem() won't be called again).
switch (position) {
case 0:
return new FragmentA();
case 1:
return new FragmentB();
default:
// This should never happen. Always account for each position above
return null;
}
}
// Here we can finally safely save a reference to the created
// Fragment, no matter where it came from (either getItem() or
// FragmentManger). Simply save the returned Fragment from
// super.instantiateItem() into an appropriate reference depending
// on the ViewPager position.
#Override
public Object instantiateItem(ViewGroup container, int position) {
Fragment createdFragment = (Fragment) super.instantiateItem(container, position);
// save the appropriate reference depending on position
switch (position) {
case 0:
m1stFragment = (FragmentA) createdFragment;
break;
case 1:
m2ndFragment = (FragmentB) createdFragment;
break;
}
return createdFragment;
}
}
public void someMethod() {
// do work on the referenced Fragments, but first check if they
// even exist yet, otherwise you'll get an NPE.
if (m1stFragment != null) {
// m1stFragment.doWork();
}
if (m2ndFragment != null) {
// m2ndFragment.doSomeWorkToo();
}
}
}
or if you prefer to work with tags instead of class member variables/references to the Fragments you can also grab the tags set by FragmentPagerAdapter in the same manner:
NOTE: this doesn't apply to FragmentStatePagerAdapter since it doesn't set tags when creating its Fragments.
#Override
public Object instantiateItem(ViewGroup container, int position) {
Fragment createdFragment = (Fragment) super.instantiateItem(container, position);
// get the tags set by FragmentPagerAdapter
switch (position) {
case 0:
String firstTag = createdFragment.getTag();
break;
case 1:
String secondTag = createdFragment.getTag();
break;
}
// ... save the tags somewhere so you can reference them later
return createdFragment;
}
Note that this method does NOT rely on mimicking the internal tag set by FragmentPagerAdapter and instead uses proper APIs for retrieving them. This way even if the tag changes in future versions of the SupportLibrary you'll still be safe.
Don't forget that depending on the design of your Activity, the Fragments you're trying to work on may or may not exist yet, so you have to account for that by doing null checks before using your references.
Also, if instead you're working with FragmentStatePagerAdapter, then you don't want to keep hard references to your Fragments because you might have many of them and hard references would unnecessarily keep them in memory. Instead save the Fragment references in WeakReference variables instead of standard ones. Like this:
WeakReference<Fragment> m1stFragment = new WeakReference<Fragment>(createdFragment);
// ...and access them like so
Fragment firstFragment = m1stFragment.get();
if (firstFragment != null) {
// reference hasn't been cleared yet; do work...
}
I found another relatively easy solution for your question.
As you can see from the FragmentPagerAdapter source code, the fragments managed by FragmentPagerAdapter store in the FragmentManager under the tag generated using:
String tag="android:switcher:" + viewId + ":" + index;
The viewId is the container.getId(), the container is your ViewPager instance. The index is the position of the fragment. Hence you can save the object id to the outState:
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt("viewpagerid" , mViewPager.getId() );
}
#Override
protected void onCreate(Bundle savedInstanceState) {
setContentView(R.layout.activity_main);
if (savedInstanceState != null)
viewpagerid=savedInstanceState.getInt("viewpagerid", -1 );
MyFragmentPagerAdapter titleAdapter = new MyFragmentPagerAdapter (getSupportFragmentManager() , this);
mViewPager = (ViewPager) findViewById(R.id.pager);
if (viewpagerid != -1 ){
mViewPager.setId(viewpagerid);
}else{
viewpagerid=mViewPager.getId();
}
mViewPager.setAdapter(titleAdapter);
If you want to communicate with this fragment, you can get if from FragmentManager, such as:
getSupportFragmentManager().findFragmentByTag("android:switcher:" + viewpagerid + ":0")
I want to offer an alternate solution for perhaps a slightly different case, since many of my searches for answers kept leading me to this thread.
My case
- I'm creating/adding pages dynamically and sliding them into a ViewPager, but when rotated (onConfigurationChange) I end up with a new page because of course OnCreate is called again. But I want to keep reference to all the pages that were created prior to the rotation.
Problem
- I don't have unique identifiers for each fragment I create, so the only way to reference was to somehow store references in an Array to be restored after the rotation/configuration change.
Workaround
- The key concept was to have the Activity (which displays the Fragments) also manage the array of references to existing Fragments, since this activity can utilize Bundles in onSaveInstanceState
public class MainActivity extends FragmentActivity
So within this Activity, I declare a private member to track the open pages
private List<Fragment> retainedPages = new ArrayList<Fragment>();
This is updated everytime onSaveInstanceState is called and restored in onCreate
#Override
protected void onSaveInstanceState(Bundle outState) {
retainedPages = _adapter.exportList();
outState.putSerializable("retainedPages", (Serializable) retainedPages);
super.onSaveInstanceState(outState);
}
...so once it's stored, it can be retrieved...
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (savedInstanceState != null) {
retainedPages = (List<Fragment>) savedInstanceState.getSerializable("retainedPages");
}
_mViewPager = (CustomViewPager) findViewById(R.id.viewPager);
_adapter = new ViewPagerAdapter(getApplicationContext(), getSupportFragmentManager());
if (retainedPages.size() > 0) {
_adapter.importList(retainedPages);
}
_mViewPager.setAdapter(_adapter);
_mViewPager.setCurrentItem(_adapter.getCount()-1);
}
These were the necessary changes to the main activity, and so I needed the members and methods within my FragmentPagerAdapter for this to work, so within
public class ViewPagerAdapter extends FragmentPagerAdapter
an identical construct (as shown above in MainActivity )
private List<Fragment> _pages = new ArrayList<Fragment>();
and this syncing (as used above in onSaveInstanceState) is supported specifically by the methods
public List<Fragment> exportList() {
return _pages;
}
public void importList(List<Fragment> savedPages) {
_pages = savedPages;
}
And then finally, in the fragment class
public class CustomFragment extends Fragment
in order for all this to work, there were two changes, first
public class CustomFragment extends Fragment implements Serializable
and then adding this to onCreate so Fragments aren't destroyed
setRetainInstance(true);
I'm still in the process of wrapping my head around Fragments and Android life cycle, so caveat here is there may be redundancies/inefficiencies in this method. But it works for me and I hope might be helpful for others with cases similar to mine.
My solution is very rude but works: being my fragments dynamically created from retained data, I simply remove all fragment from the PageAdapter before calling super.onSaveInstanceState() and then recreate them on activity creation:
#Override
protected void onSaveInstanceState(Bundle outState) {
outState.putInt("viewpagerpos", mViewPager.getCurrentItem() );
mSectionsPagerAdapter.removeAllfragments();
super.onSaveInstanceState(outState);
}
You can't remove them in onDestroy(), otherwise you get this exception:
java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
Here the code in the page adapter:
public void removeAllfragments()
{
if ( mFragmentList != null ) {
for ( Fragment fragment : mFragmentList ) {
mFm.beginTransaction().remove(fragment).commit();
}
mFragmentList.clear();
notifyDataSetChanged();
}
}
I only save the current page and restore it in onCreate(), after the fragments have been created.
if (savedInstanceState != null)
mViewPager.setCurrentItem( savedInstanceState.getInt("viewpagerpos", 0 ) );
What is that BasePagerAdapter? You should use one of the standard pager adapters -- either FragmentPagerAdapter or FragmentStatePagerAdapter, depending on whether you want Fragments that are no longer needed by the ViewPager to either be kept around (the former) or have their state saved (the latter) and re-created if needed again.
Sample code for using ViewPager can be found here
It is true that the management of fragments in a view pager across activity instances is a little complicated, because the FragmentManager in the framework takes care of saving the state and restoring any active fragments that the pager has made. All this really means is that the adapter when initializing needs to make sure it re-connects with whatever restored fragments there are. You can look at the code for FragmentPagerAdapter or FragmentStatePagerAdapter to see how this is done.
If anyone is having issues with their FragmentStatePagerAdapter not properly restoring the state of its fragments...ie...new Fragments are being created by the FragmentStatePagerAdapter instead of it restoring them from state...
Make sure you call ViewPager.setOffscreenPageLimit() BEFORE you call ViewPager.setAdapter(fragmentStatePagerAdapter)
Upon calling ViewPager.setOffscreenPageLimit()...the ViewPager will immediately look to its adapter and try to get its fragments. This could happen before the ViewPager has a chance to restore the Fragments from savedInstanceState(thus creating new Fragments that can't be re-initialized from SavedInstanceState because they're new).
I came up with this simple and elegant solution. It assumes that the activity is responsible for creating the Fragments, and the Adapter just serves them.
This is the adapter's code (nothing weird here, except for the fact that mFragments is a list of fragments maintained by the Activity)
class MyFragmentPagerAdapter extends FragmentStatePagerAdapter {
public MyFragmentPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
return mFragments.get(position);
}
#Override
public int getCount() {
return mFragments.size();
}
#Override
public int getItemPosition(Object object) {
return POSITION_NONE;
}
#Override
public CharSequence getPageTitle(int position) {
TabFragment fragment = (TabFragment)mFragments.get(position);
return fragment.getTitle();
}
}
The whole problem of this thread is getting a reference of the "old" fragments, so I use this code in the Activity's onCreate.
if (savedInstanceState!=null) {
if (getSupportFragmentManager().getFragments()!=null) {
for (Fragment fragment : getSupportFragmentManager().getFragments()) {
mFragments.add(fragment);
}
}
}
Of course you can further fine tune this code if needed, for example making sure the fragments are instances of a particular class.
To get the fragments after orientation change you have to use the .getTag().
getSupportFragmentManager().findFragmentByTag("android:switcher:" + viewPagerId + ":" + positionOfItemInViewPager)
For a bit more handling i wrote my own ArrayList for my PageAdapter to get the fragment by viewPagerId and the FragmentClass at any Position:
public class MyPageAdapter extends FragmentPagerAdapter implements Serializable {
private final String logTAG = MyPageAdapter.class.getName() + ".";
private ArrayList<MyPageBuilder> fragmentPages;
public MyPageAdapter(FragmentManager fm, ArrayList<MyPageBuilder> fragments) {
super(fm);
fragmentPages = fragments;
}
#Override
public Fragment getItem(int position) {
return this.fragmentPages.get(position).getFragment();
}
#Override
public CharSequence getPageTitle(int position) {
return this.fragmentPages.get(position).getPageTitle();
}
#Override
public int getCount() {
return this.fragmentPages.size();
}
public int getItemPosition(Object object) {
//benötigt, damit bei notifyDataSetChanged alle Fragemnts refrehsed werden
Log.d(logTAG, object.getClass().getName());
return POSITION_NONE;
}
public Fragment getFragment(int position) {
return getItem(position);
}
public String getTag(int position, int viewPagerId) {
//getSupportFragmentManager().findFragmentByTag("android:switcher:" + R.id.shares_detail_activity_viewpager + ":" + myViewPager.getCurrentItem())
return "android:switcher:" + viewPagerId + ":" + position;
}
public MyPageBuilder getPageBuilder(String pageTitle, int icon, int selectedIcon, Fragment frag) {
return new MyPageBuilder(pageTitle, icon, selectedIcon, frag);
}
public static class MyPageBuilder {
private Fragment fragment;
public Fragment getFragment() {
return fragment;
}
public void setFragment(Fragment fragment) {
this.fragment = fragment;
}
private String pageTitle;
public String getPageTitle() {
return pageTitle;
}
public void setPageTitle(String pageTitle) {
this.pageTitle = pageTitle;
}
private int icon;
public int getIconUnselected() {
return icon;
}
public void setIconUnselected(int iconUnselected) {
this.icon = iconUnselected;
}
private int iconSelected;
public int getIconSelected() {
return iconSelected;
}
public void setIconSelected(int iconSelected) {
this.iconSelected = iconSelected;
}
public MyPageBuilder(String pageTitle, int icon, int selectedIcon, Fragment frag) {
this.pageTitle = pageTitle;
this.icon = icon;
this.iconSelected = selectedIcon;
this.fragment = frag;
}
}
public static class MyPageArrayList extends ArrayList<MyPageBuilder> {
private final String logTAG = MyPageArrayList.class.getName() + ".";
public MyPageBuilder get(Class cls) {
// Fragment über FragmentClass holen
for (MyPageBuilder item : this) {
if (item.fragment.getClass().getName().equalsIgnoreCase(cls.getName())) {
return super.get(indexOf(item));
}
}
return null;
}
public String getTag(int viewPagerId, Class cls) {
// Tag des Fragment unabhängig vom State z.B. nach bei Orientation change
for (MyPageBuilder item : this) {
if (item.fragment.getClass().getName().equalsIgnoreCase(cls.getName())) {
return "android:switcher:" + viewPagerId + ":" + indexOf(item);
}
}
return null;
}
}
So just create a MyPageArrayList with the fragments:
myFragPages = new MyPageAdapter.MyPageArrayList();
myFragPages.add(new MyPageAdapter.MyPageBuilder(
getString(R.string.widget_config_data_frag),
R.drawable.ic_sd_storage_24dp,
R.drawable.ic_sd_storage_selected_24dp,
new WidgetDataFrag()));
myFragPages.add(new MyPageAdapter.MyPageBuilder(
getString(R.string.widget_config_color_frag),
R.drawable.ic_color_24dp,
R.drawable.ic_color_selected_24dp,
new WidgetColorFrag()));
myFragPages.add(new MyPageAdapter.MyPageBuilder(
getString(R.string.widget_config_textsize_frag),
R.drawable.ic_settings_widget_24dp,
R.drawable.ic_settings_selected_24dp,
new WidgetTextSizeFrag()));
and add them to the viewPager:
mAdapter = new MyPageAdapter(getSupportFragmentManager(), myFragPages);
myViewPager.setAdapter(mAdapter);
after this you can get after orientation change the correct fragment by using its class:
WidgetDataFrag dataFragment = (WidgetDataFrag) getSupportFragmentManager()
.findFragmentByTag(myFragPages.getTag(myViewPager.getId(), WidgetDataFrag.class));
A bit different opinion instead of storing the Fragments yourself just leave it to the FragmentManager and when you need to do something with the fragments look for them in the FragmentManager:
//make sure you have the right FragmentManager
//getSupportFragmentManager or getChildFragmentManager depending on what you are using to manage this stack of fragments
List<Fragment> fragments = fragmentManager.getFragments();
if(fragments != null) {
int count = fragments.size();
for (int x = 0; x < count; x++) {
Fragment fragment = fragments.get(x);
//check if this is the fragment we want,
//it may be some other inspection, tag etc.
if (fragment instanceof MyFragment) {
//do whatever we need to do with it
}
}
}
If you have a lot of Fragments and the cost of instanceof check may be not what you want, but it is good thing to have in mind that the FragmentManager already keeps account of Fragments.
add:
#SuppressLint("ValidFragment")
before your class.
it it doesn´t work do something like this:
#SuppressLint({ "ValidFragment", "HandlerLeak" })