I have a doubt about my program.
I have 3 tabs in the action bar (made with fragments), each one shows a different view. These views have a lot of information that they collect from a database to show charts. The problem is that the ammount of information is quite big for read it everytime I change from one tab to another.
The question:
Is there any way to create each view only once and then just move between them?
The Activity:
String label1 = "one day";
Tab tab = actionBar.newTab();
tab.setText(label1);
TabListener<Tab1Fragment> tl = new TabListener<Tab1Fragment>(this, label1, Tab1Fragment.class);
tab.setTabListener(tl);
actionBar.addTab(tab);
String label2 = "two days";
tab = actionBar.newTab();
tab.setText(label2);
TabListener<Tab2Fragment> tl2 = new TabListener<Tab2Fragment>(this, label2, Tab2Fragment.class);
tab.setTabListener(tl2);
actionBar.addTab(tab);
String label3 = "three days";
tab = actionBar.newTab();
tab.setText(label3);
TabListener<Tab3Fragment> tl3 = new TabListener<Tab3Fragment>(this, label3, Tab3Fragment.class);
tab.setTabListener(tl3);
actionBar.addTab(tab);
The tab listener class:
private class TabListener<T extends Fragment> implements ActionBar.TabListener{
private Fragment fragment;
private final Activity activity;
private final String tag;
private final Class<T> classT;
public TabListener(Activity activityTab, String tagTab, Class<T> classTab) {
activity = activityTab;
tag = tagTab;
classT = classTab;
}
public void onTabReselected(Tab tab, FragmentTransaction ft) {
}
public void onTabSelected(Tab tab, FragmentTransaction ft) {
if (fragment == null) {
fragment = Fragment.instantiate(activity, classT.getName());
ft.add(android.R.id.content, fragment, tag);
} else {
ft.attach(fragment); }
}
public void onTabUnselected(Tab tab, FragmentTransaction ft) {
ft.detach(fragment);
}
And one of the fragments (the 3 are the same, only change the R.layout.**):
public class Tab1Fragment extends Fragment {
public View onCreateView (LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){
setRetainInstance(true);
return (LinearLayout) inflater.inflate(R.layout.main, container, false);
Thank you!
Each fragment can retain its state. Call setRetainInstance(true) in the fragment constructor.
Then you should fetch the data only if it has not already been fetched (if your store you data in a List, set that list to null in the constructor, create and fill it when you fetch data, and in the onStart method, if the list is null, fetch the data. Else, do nothing).
See also: Persisting list items in ListAdapter on configurationChanges with setRetainInstance
Related
I want to create an app with three tabs. I have three fragments, three xml files, one main activity, one main xml and a tablistener class. The app shows the first two tabs properly, but the third tab is not shown. There is no error in the code.
Note: I'm using support library.
MainActivity:
public class MainActivity extends ActionBarActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
setupTabs();
}
// To setup tabs using ActionBar and fragments
private void setupTabs() {
// setup the ActionBar
ActionBar actionBar = getSupportActionBar();
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
actionBar.setDisplayShowTitleEnabled(true);
//define which tabs you would like to display
//and attach listeners for each tab:
ActionBar.Tab tab1 = actionBar
.newTab()
.setText("First")
.setTabListener(new SupportFragmentTabListener<FirstFragment>(R.id.main,this,
"first", FirstFragment.class));
actionBar.addTab(tab1);
actionBar.selectTab(tab1);
ActionBar.Tab tab2 = actionBar
.newTab()
.setText("Second")
.setTabListener(new SupportFragmentTabListener<SecondFragment>(R.id.main,this,
"second", SecondFragment.class));
actionBar.addTab(tab2);
ActionBar.Tab tab3 = actionBar
.newTab()
.setText("Third")
.setTabListener(new SupportFragmentTabListener<ThirdFragment>(R.id.main, this,
"third", ThirdFragment.class));
ActionBar.Tab tab4 = actionBar
.newTab()
.setText("Fourth")
.setTabListener(new SupportFragmentTabListener<FourthFragment>(R.id.main, this,
"fourth", FourthFragment.class));
}
}
TabListener:
public class SupportFragmentTabListener<T extends Fragment>
implements TabListener {
private Fragment mFragment;
private final FragmentActivity mActivity;
private final String mTag;
private final Class<T> mClass;
private final int mfragmentContainerId;
public SupportFragmentTabListener(FragmentActivity activity, String tag, Class<T> clz) {
mActivity = activity;
mTag = tag;
mClass = clz;
mfragmentContainerId = android.R.id.content;
}
public SupportFragmentTabListener(int fragmentContainerId, FragmentActivity activity, String tag, Class<T> clz) {
mActivity = activity;
mTag = tag;
mClass = clz;
mfragmentContainerId = fragmentContainerId;
}
/* The following are each of the ActionBar.TabListener callbacks */
public void onTabSelected(Tab tab, FragmentTransaction sft) {
// Check if the fragment is already initialized
if (mFragment == null) {
// If not, instantiate and add it to the activity
mFragment = Fragment.instantiate(mActivity, mClass.getName());
sft.replace(mfragmentContainerId, mFragment, mTag);
} else {
// If it exists, simply attach it in order to show it
sft.replace(mfragmentContainerId, mFragment);
}
}
public void onTabUnselected(Tab tab, FragmentTransaction sft) {
if (mFragment != null) {
// Detach the fragment, because another one is being attached
//sft.remove(mFragment);
sft.replace(mfragmentContainerId,mFragment);
}else{
// If not, instantiate and add it to the activity:
mFragment = Fragment.instantiate(mActivity, mClass.getName());
sft.add(android.R.id.content, mFragment,mTag);
}
}
public void onTabReselected(Tab tab, FragmentTransaction sft) {
// User selected the already selected tab. Usually do nothing.
}
}
ThirdFragment:
public class ThirdFragment extends Fragment {
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.grade_table, container, false);
TextView ff = (TextView) rootView.findViewById(R.id.textView2);
return rootView;
}
}
You need to add the tab after creating it. You forgot to write:
actionBar.addTab(tab3);
and
actionBar.addTab(tab4);
UPDATE : I don't understand why when tapping on each tab onTabSelected() doesn't show the correct fragment even though it's been added to the fragmentTransaction android.R.id.content.
I call this method before onTabSelected gets called to make sure fragments are not null.
protected void initTabs() {
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
if(mShowFragment3 == null) {
mShowFragment3 = EpisodeTileFragment.newInstance(getString(R.string.title_section4));
ft.add(android.R.id.content, mShowFragment3);
}
if(mShowFragment2 == null) {
mShowFragment2 = EpisodeTileFragment.newInstance(getString(R.string.title_section3));
ft.add(android.R.id.content, mShowFragment2);
}
}
#Override
public void onTabSelected(ActionBar.Tab tab,
FragmentTransaction ft) {
//this usually works, but if i try to mess with adding the tabs to the
//FragmentTransaction this won't work anymore
if(tab.getPosition() == 1) {
ft.show(mShowFragment2);
}
I have scoured the internet and this may not be possible but I wanted to give this a shot.
I currently have the app working fine, it has 4 tabs and 4 corresponding fragments. When a user opens the app onTabSelected is called and selects the first tab/fragment is added and we're all good.
A user clicks tab2 and the 2nd fragment is added and rendered. When a user clicks the 2nd tab there is an asynctask that gets data and renders this on a fragment. Etc.. this happens on tab 3 and 4 also. When a user clicks a tab the first time I instantiate the fragment and add it to the fragmentTransaction, the next time you click on the tab it's lightning fast because it's already added, and I'm hiding and showing fragments.
The question I have is , is there a way to load up all 4 tabs at the same time, vs. waiting for a user to click on a tab and then have onTabSelected firing and grabbing data etc. Please let me know if there is any questions, the code is working with no errors just not what I want, and I don't know how to instantiate all 4 fragments at the same time.
mSectionsPagerAdapter = new SectionsPagerAdapter(this.getSupportFragmentManager());
mViewPager = (ViewPager) findViewById(R.id.pager);
mViewPager.setAdapter(mSectionsPagerAdapter);
for (int i = 0; i < mSectionsPagerAdapter.getCount(); i++) {
LayoutInflater inflater = LayoutInflater.from(getApplicationContext());
View tabView = inflater.inflate(R.layout.tab_title, null);
TextView titleTV = (TextView) tabView.findViewById(R.id.action_custom_title);
titleTV.setText(mSectionsPagerAdapter.getPageTitle(i));
titleTV.setTypeface(Typeface.createFromAsset(getAssets(), getString(R.string.tab_font)));
titleTV.setSingleLine();
titleTV.setTextSize(13);
Tab t = actionBar.newTab()
.setText(mSectionsPagerAdapter.getPageTitle(i))
.setTabListener(this);
t.setCustomView(tabView);
actionBar.addTab(t);
}
this is for adding the tabs. but need to add the fragments for each tab.
Set the offscreen page limit to your number of tabs, and then your ViewPager will render all the fragments when the parent activity is created.
mViewPager.setOffscreenPageLimit(4);
You should also check out the Class Overview of ViewPager, which has a really good example of using a custom FragmentPagerAdapter called TabsAdapter with a ViewPager (instead of using the ADT generated SectionsPagerAdapter). I added the code from the official Android docs below:
public class ActionBarTabsPager extends Activity {
ViewPager mViewPager;
TabsAdapter mTabsAdapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mViewPager = new ViewPager(this);
mViewPager.setId(R.id.pager);
setContentView(mViewPager);
final ActionBar bar = getActionBar();
bar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
bar.setDisplayOptions(0, ActionBar.DISPLAY_SHOW_TITLE);
mTabsAdapter = new TabsAdapter(this, mViewPager);
mTabsAdapter.addTab(bar.newTab().setText("Simple"),
CountingFragment.class, null);
mTabsAdapter.addTab(bar.newTab().setText("List"),
FragmentPagerSupport.ArrayListFragment.class, null);
mTabsAdapter.addTab(bar.newTab().setText("Cursor"),
CursorFragment.class, null);
if (savedInstanceState != null) {
bar.setSelectedNavigationItem(savedInstanceState.getInt("tab", 0));
}
}
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt("tab", getActionBar().getSelectedNavigationIndex());
}
/**
* This is a helper class that implements the management of tabs and all
* details of connecting a ViewPager with associated TabHost. It relies on a
* trick. Normally a tab host has a simple API for supplying a View or
* Intent that each tab will show. This is not sufficient for switching
* between pages. So instead we make the content part of the tab host
* 0dp high (it is not shown) and the TabsAdapter supplies its own dummy
* view to show as the tab content. It listens to changes in tabs, and takes
* care of switch to the correct paged in the ViewPager whenever the selected
* tab changes.
*/
public static class TabsAdapter extends FragmentPagerAdapter
implements ActionBar.TabListener, ViewPager.OnPageChangeListener {
private final Context mContext;
private final ActionBar mActionBar;
private final ViewPager mViewPager;
private final ArrayList<TabInfo> mTabs = new ArrayList<TabInfo>();
static final class TabInfo {
private final Class<?> clss;
private final Bundle args;
TabInfo(Class<?> _class, Bundle _args) {
clss = _class;
args = _args;
}
}
public TabsAdapter(Activity activity, ViewPager pager) {
super(activity.getFragmentManager());
mContext = activity;
mActionBar = activity.getActionBar();
mViewPager = pager;
mViewPager.setAdapter(this);
mViewPager.setOnPageChangeListener(this);
}
public void addTab(ActionBar.Tab tab, Class<?> clss, Bundle args) {
TabInfo info = new TabInfo(clss, args);
tab.setTag(info);
tab.setTabListener(this);
mTabs.add(info);
mActionBar.addTab(tab);
notifyDataSetChanged();
}
#Override
public int getCount() {
return mTabs.size();
}
#Override
public Fragment getItem(int position) {
TabInfo info = mTabs.get(position);
return Fragment.instantiate(mContext, info.clss.getName(), info.args);
}
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
#Override
public void onPageSelected(int position) {
mActionBar.setSelectedNavigationItem(position);
}
#Override
public void onPageScrollStateChanged(int state) {
}
#Override
public void onTabSelected(Tab tab, FragmentTransaction ft) {
Object tag = tab.getTag();
for (int i=0; i<mTabs.size(); i++) {
if (mTabs.get(i) == tag) {
mViewPager.setCurrentItem(i);
}
}
}
#Override
public void onTabUnselected(Tab tab, FragmentTransaction ft) {
}
#Override
public void onTabReselected(Tab tab, FragmentTransaction ft) {
}
}
}
I have actionbar tabs and when a tab is clicked I want to add new button to my fragment.
This is my fragment code where I am adding buttons:
Button btn;
View myView;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
int numberOfButtons= getArguments().getInt("someInt",0);
LinearLayout view = new LinearLayout(getActivity());
// Inflate the layout for this fragment
view.setOrientation(LinearLayout.VERTICAL);
view.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.FILL_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT));
for (int i = 0;i<numberOfButtons;i++)
{
btn = new Button(getActivity());
view.addView(new Button(getActivity()));
}
myView = view;
return myView;
}
This my MainActivity code where I am sending number of buttons to the fragment:
int numberOfButtons=0;
public static FragmentA newInstance(int someInt) {
FragmentA myFragment = new FragmentA();
Bundle args = new Bundle();
args.putInt("someInt", someInt);
myFragment.setArguments(args);
return myFragment;
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ActionBar actionBar = getActionBar();
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
for (int i=0;i<10;i++)
{
ActionBar.Tab tab = actionBar.newTab().setText("Tab"+i).setTabListener(new ActionBar.TabListener() {
#Override
public void onTabSelected(ActionBar.Tab tab, FragmentTransaction ft) {
String tabText = (String)tab.getText();
String asd = (String)(tabText.substring(3,tabText.length()));
numberOfButtons = Integer.parseInt(asd);
FragmentA fragmentA = newInstance(numberOfButtons);
FragmentManager manager = getFragmentManager();
FragmentTransaction transaction = manager.beginTransaction();
transaction.add(R.id.mainLayout,fragmentA,"fragA");
transaction.commit();
}
#Override
public void onTabUnselected(ActionBar.Tab tab, FragmentTransaction ft) {
}
#Override
public void onTabReselected(ActionBar.Tab tab, FragmentTransaction ft) {
}
});
actionBar.addTab(tab);
}
}
This code adds buttons but there is a problem here. When Tab1 is clicked, one button is added to the fragment. When Tab2 is clicked, two buttons are added to the fragment but the first button that added by Tab1 is not removed. One of the new buttons is placed over it.
Is there any way to reset the fragment layout or remove old items of fragment before adding new ones?
From your code it seems like you're just adding the fragments' instances on top of each other.
The button added by Tab1 is not removed because Tab1 is still there in the background...
Try using the transaction.remove() method to remove the previous fragment before calling transaction.add() to add a new one...
There's also the transaction.replace() method that does both operations at the same time. Perhaps it's also worth a try.
i am using actionbar(support.v7) with 4 tabs using fragments. I want each tab to have a separate activity rather than a common activity calling fragments?
ApplicationMainTabsActivity
public class ApplicationMainTabsActivity extends ActionBarActivity {
ActionBar.Tab Tab1, Tab2, Tab3,Tab4;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ActionBar actionBar = getSupportActionBar();
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
String label1 = getResources().getString(R.string.label1);
Tab1 = actionBar.newTab();
Tab1.setText(label1);
ApplicationTabListener<Fragment1> tl = new ApplicationTabListener<Fragment1>(this,label1, Fragment1.class);
Tab1.setTabListener(tl);
actionBar.addTab(Tab1);
String label2 = getResources().getString(R.string.label2);
Tab2 = actionBar.newTab();
Tab2.setText(label2);
ApplicationTabListener<Fragment2> t2 = new ApplicationTabListener<Fragment2>(this,label2, Fragment2.class);
Tab2.setTabListener(t2);
actionBar.addTab(Tab2);
String label3 = getResources().getString(R.string.label3);
Tab3 = actionBar.newTab();
Tab3.setText(label3);
ApplicationTabListener<Fragment3> t3 = new ApplicationTabListener<Fragment3>(this,label3, Fragment3.class);
Tab3.setTabListener(t3);
actionBar.addTab(Tab3);
String label4 = getResources().getString(R.string.label4);
Tab4 = actionBar.newTab();
Tab4.setText(label4);
ApplicationTabListener<Fragment4> t4 = new ApplicationTabListener<Fragment4>(this,label4, Fragment4.class);
Tab4.setTabListener(t4);
actionBar.addTab(Tab4);
}
ApplicationTabListener.java
final class ApplicationTabListener<T extends Activity> implements ActionBar.TabListener {
private Fragment mFragment;
private final Activity mActivity;
private final String mTag;
private final Class<T> mClass;
private final Bundle mArgs;
/**
* Constructor used each time a new tab is created.
*
* #param activity
* The host Activity, used to instantiate the fragment
* #param tag
* The identifier tag for the fragment
* #param clz
* The fragment's Class, used to instantiate the fragment
*/
public ApplicationTabListener(Activity activity, String tag, Class<T> clz) {
this(activity, tag, clz, null);
}
public ApplicationTabListener(Activity activity, String tag, Class<T> clz, Bundle args) {
mActivity = activity;
mTag = tag;
mClass = clz;
mArgs = args;
}
public void onTabSelected(Tab tab, FragmentTransaction ft) {
// Check if the fragment is already initialized
if (mFragment == null) {
// If not, instantiate and add it to the activity
mFragment = Fragment.instantiate(mActivity, mClass.getName());
ft.add(R.id.action_bar_activity_content, mFragment, mTag);
} else {
// If it exists, simply attach it in order to show it
ft.replace(R.id.action_bar_activity_content,mFragment);
}
}
public void onTabUnselected(Tab tab, FragmentTransaction ft) {
if (mFragment != null) {
// Detach the fragment, because another one is being attached
ft.remove(mFragment);
}
}
public void onTabReselected(Tab tab, FragmentTransaction ft) {
// User selected the already selected tab. Usually do nothing.
}
}
Fragment1.java
public class Fragment1 extends Fragment{
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.main, container, false);
}
}
You cannot have separate activity for each Tab which was used to be as in TabActivity. And it's deprecated. You need to attach fragments in order to use it. Otherwise you need to build your own custom TabActivity.
I have 3 tabs, of which the 1st one has actual data(a listview) and rest 2 are empty. I was just trying to implement tab navigation.
in Activity's onCreate i have:
mViewPager = new ViewPager(this);
mViewPager.setId(R.id.pager);
setContentView(mViewPager);
actionBar = getSupportActionBar();
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
PagerAdapter adapter = new PagerAdapter((SlidingFragmentActivity) this,
mViewPager);
adapter.addTab(actionBar.newTab().setText("Tab-1"),
Fragment1.class, null);
adapter.addTab(actionBar.newTab().setText("Tab-2"),
Fragment2.class, null);
adapter.addTab(actionBar.newTab().setText("Tab-3"),
Fragment2.class, null);
and the PagerAdapter is:
public static class PagerAdapter extends FragmentPagerAdapter implements
ActionBar.TabListener, ViewPager.OnPageChangeListener {
private final Context mContext;
private final ActionBar mActionBar;
private final ViewPager mViewPager;
private final ArrayList<TabInfo> mTabs = new ArrayList<TabInfo>();
static final class TabInfo {
private final Class<?> clss;
private final Bundle args;
TabInfo(Class<?> _class, Bundle _args) {
clss = _class;
args = _args;
}
}
public PagerAdapter(SlidingFragmentActivity activity, ViewPager pager) {
super(activity.getSupportFragmentManager());
mContext = activity;
mActionBar = activity.getSupportActionBar();
mViewPager = pager;
mViewPager.setAdapter(this);
mViewPager.setOnPageChangeListener(this);
}
public void addTab(ActionBar.Tab tab, Class<?> clss, Bundle args) {
TabInfo info = new TabInfo(clss, args);
tab.setTag(info);
tab.setTabListener(this);
mTabs.add(info);
mActionBar.addTab(tab);
notifyDataSetChanged();
}
public int getCount() {
return mTabs.size();
}
public SherlockFragment getItem(int position) {
TabInfo info = mTabs.get(position);
return (SherlockFragment) Fragment.instantiate(mContext,
info.clss.getName(), info.args);
}
public void onPageSelected(int position) {
mActionBar.setSelectedNavigationItem(position);
}
public void onPageScrollStateChanged(int state) {
}
public void onPageScrolled(int position, float positionOffset,
int positionOffsetPixels) {
}
public void onTabSelected(Tab tab, FragmentTransaction ft) {
mViewPager.setCurrentItem(tab.getPosition());
}
public void onTabUnselected(Tab tab, FragmentTransaction ft) {
}
public void onTabReselected(Tab tab, FragmentTransaction ft) {
}
}
Here Fragment1 has a listview which gets data from a web server using an AsyncTask.
Fragment2 is just an empty fragment
public class Fragment2 extends SherlockFragment {
}
Problem:
when i select Tab-2, then Tab-3 and comeback to Tab-2, Fragment1's onCreateView is being called. Though the Tab-2 shows white screen, Fragment1's onCreateView is being called, i checked using logcat.
Didnt i implement PagerAdapter the right way?
There is nothing wrong with this behaviour.
The ViewPager tries to minimize the memory impact that the fragments have. If you use a FragmentPagerAdapter the view of your fragment can be destroyed once it leaves the screen.
Typically the ViewPager will hold the view to the left and the right of the current view in memory to enable fast tab switching.
If you have only say 5 Tabs and you want to have all the views of this tabs in memory you can modify the number of views that are kept with ViewPager.setOffscreenPageLimit. This method lets you specify how many views are kept for each side. If you have only 5 tabs you could set 4 as offScreenPageLimit to have each view kept in memory even if the user is at one of the outer tabs.
Keep in mind that the views are destroyed for a reason. This is a performance optimization and even if offScreenPageLimit of 10 may work on your device it can crash devices with fewer memory. Set the limit to an reasonable number and implement the onDestroyView and oncreateView methods of your fragments correct.