The problem I'm having is the Action Bar will not show on Android 2.3.7, but will work fine on 4.x+. The rest of my application works fine with the support v7 and v4 libraries, it's just this one area which is giving me trouble.
Here is what it should look like, as seen on 4.3:
And here is what it looks like on 2.3.7:
Inside my onCreate method (of the class which inherits from ActionBarActivity), I have this:
// setup action bar for tabs
ActionBar actionBar = getSupportActionBar();
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
actionBar.setDisplayHomeAsUpEnabled(true);
actionBar.setDisplayShowTitleEnabled(true);
Tab tab = actionBar.newTab()
.setText(R.string.details)
.setTabListener(new TabListener<DetailsFragmentOne>(
this, "one", DetailsFragmentOne.class));
actionBar.addTab(tab);
tab = actionBar.newTab()
.setText(R.string.grades)
.setTabListener(new TabListener<DetailsFragmentTwo>(
this, "one", DetailsFragmentTwo.class));
actionBar.addTab(tab);
And here is my TabListener, an inner class:
/**
* This is copied almost verbatim from the ActionBar Tabs API Guide.
* #param <T>
*/
public class TabListener<T extends Fragment> implements ActionBar.TabListener {
private Fragment mFragment;
private final Activity mActivity;
private final String mTag;
private final Class<T> mClass;
/** 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 TabListener(Activity activity, String tag, Class<T> clz) {
mActivity = activity;
mTag = tag;
mClass = clz;
}
/* The following are each of the ActionBar.TabListener callbacks */
public void onTabSelected(Tab tab, FragmentTransaction ft) {
FragmentTransaction sft = ((FragmentActivity) mActivity).getSupportFragmentManager().beginTransaction();
mFragment = getSupportFragmentManager().findFragmentByTag(mTag);
// 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());
// calling commit() here because we're not using the provided FragmentTransaction
sft.replace(android.R.id.content, mFragment, mTag).commit();
} else {
// If it exists, simply attach it in order to show it
// calling commit() here because we're not using the provided FragmentTransaction
sft.replace(android.R.id.content, mFragment).commit();
}
}
public void onTabUnselected(Tab tab, FragmentTransaction ft) {
FragmentTransaction sft = ((FragmentActivity) mActivity).getSupportFragmentManager().beginTransaction();
mFragment = getSupportFragmentManager().findFragmentByTag(mTag);
if (mFragment != null) {
// calling commit() here because we're not using the provided FragmentTransaction
sft.replace(android.R.id.content, mFragment).commit();
}
}
public void onTabReselected(Tab tab, FragmentTransaction ft) {
FragmentTransaction sft = ((FragmentActivity) mActivity).getSupportFragmentManager().beginTransaction();
mFragment = getSupportFragmentManager().findFragmentByTag(mTag);
if (mFragment != null) {
// calling commit() here because we're not using the provided FragmentTransaction
sft.replace(android.R.id.content, mFragment).commit();
}
}
}
I have seen these two other questions and attempted to implement the answers, but am still having the issue.
Implementing a TabListener using the Support Library
Implementing ActionBar tabs with v4 Fragments API
edit:
As requested, the theme which is being applied is simply the support library's AppCompat.Light.DarkActionBar theme with no overrides, seen below:
<style name="Theme.MyTheme" parent="#style/Theme.AppCompat.Light.DarkActionBar">
</style>
If you remove the background of your DetailFragment, the ActionBar and Tabs actually appear behind the DetailFragment. Instead of android.R.id.content, create your own container in your main layout and use R.id.yourcontent when calling replace in your FragmentTransaction. By making this change, it worked for me on 2.3.3 and 4+.
It appears that 2.3.3 adds the ActionBar to the root view element where 4+ is adding it outside of the root view.
sft.replace(R.id.yourcontent, mFragment).commit();
You should read the offcial document.
You must not call commit() for the fragment transaction in each of these callbacks—the system calls it for you and it may throw an exception if you call it yourself. You also cannot add these fragment transactions to the back stack.
Related
I'm using the Support Package v7 for adding Action Bar Tabs to my app.
When the tab's Fragment has a root view with height="fill_parent" the Fragment is covering my tabs.
When I’m changing the Fragment's root view to height="wrap_content" I can see the tab but my Fragment styling is affected.
My code for loading Fragments is the same as in Android Docs:
public class PicoTabListener<T extends Fragment> implements TabListener {
private Fragment mFragment;
private final Activity mActivity;
private final String mTag;
private final Class<T> mClass;
/** 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 PicoTabListener(Activity activity, String tag, Class<T> clz) {
mActivity = activity;
mTag = tag;
mClass = clz;
}
/* The following are each of the ActionBar.TabListener callbacks */
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(android.R.id.content, mFragment, mTag);
} else {
// If it exists, simply attach it in order to show it
ft.attach(mFragment);
}
}
public void onTabUnselected(Tab tab, FragmentTransaction ft) {
if (mFragment != null) {
// Detach the fragment, because another one is being attached
ft.detach(mFragment);
}
}
#Override
public void onTabReselected(Tab arg0, FragmentTransaction arg1) {
// do nothing
}
Isn't tab should always be in the top of the screen as the first layer?
Isn't tab should always be in the top of the screen as the first layer ?
No.
If you were using ActionBarSherlock, I think what you are doing would work. And if you use the native action bar implementation, I think what you are doing would work. However, you cannot do this with AppCompat:
ft.add(android.R.id.content, mFragment, mTag);
Use some container inside of your layout, like a FrameLayout, as the target of the transaction, not android.R.id.content. See this issue for more.
I am working on an app that has an "artist list", a list of a good chunk of music bands, performers and/or composers. They are sorted by genre and relative popularity.
From the main activity you go to a "band list" activity that has two tabs in it. One of them is a "genres" tab that has a list of the general music genres and when you click on each genre you get sent to the respective activity that also has two tabs in it. The first one is the "All" tabs and the other one's the "Popular" tab. Each of the tabs holds a fragment that contains a filtered sqlite database (by "filtered" I mean I have one big database that gets filtered to show what I want in different fragments rather than using a new database for each fragment/genre). So now I have 7 genres and 14 classes; 2 ("all" and "popular") for each genre.
What I would like to do now is reduce the number of classes to only two (all/popular) or, if possible, even one, by recycling the same fragment for all lists via filtering the database again. In order to do this I need to send a string from the "genres" fragment, containing the position of the pressed list item (genre) to the activity which contains the "All" and "Popular" tabs (fragments), which will then send that string further, to the fragments, which will use it to filter the sqlite database.
I need to add that I have, of course, read the Android Developers guide on fragment communication, but I do not know how to use their approach in my code, since I have created the fragments a little differently, including not defining them in the xml, so because of that I don't know how to use their getSupportFragmentManager().findFragmentById(R.id.article_fragment); method and some more things like that.
Here is the TabListener via which I am creating all fragments:
public class TabListener<T extends Fragment> implements ActionBar.TabListener {
private Fragment mFragment;
private final Activity mActivity;
private final String mTag;
private final Class<T> mClass;
public TabListener(Activity activity, String tag, Class<T> clz) {
mActivity = activity;
mTag = tag;
mClass = clz;
}
public void onTabSelected(Tab tab, FragmentTransaction ft) {
if (mFragment == null) {
mFragment = Fragment.instantiate(mActivity, mClass.getName());
ft.add(android.R.id.content, mFragment, mTag);
} else {
ft.attach(mFragment);
}
}
public void onTabUnselected(Tab tab, FragmentTransaction ft) {
if (mFragment != null) {
ft.detach(mFragment);
}
}
public void onTabReselected(Tab tab, FragmentTransaction ft) {
}
}
Thanks,
Aleksa
If you created the fragments programmatically and added them with a tag, u can use the support FragmentManager method: findFragmentByTag(String tag)
After some more trying I eventually gave up on setArguments() and getArguments() and used this in the parent activity:
public int getPosition(){ return position; }
...and this in the child fragment:
position=((ParentActivity) getActivity()).getPosition();
I don't know if it is the most efficient method around but it works great and makes sending some simple data to a fragment a trivial thing for newcomers like me as it should be. :)
My problem is about fragments with tabs. I have got one activity which basically has an empty layout and I add fragments to it dynamically. As soon as I start the activity I add a fragment with a listview to it and when I click on an item of the list I remove the fragment with the listview and add another fragment with details about it. Also some tabs are visible now. Clicking on one of these tabs shows other details about the item from the listview.
I do have working code but it seems that I'm hacking it altogether.
Here is some bits of it:
Activity:
// In onCreate:
#Override
protected void onCreate(Bundle savedInstanceState) {
// Check that the activity is using the layout version with
// the fragment_container FrameLayout
if (findViewById(R.id.fragment_container) != null) {
// However, if we're being restored from a previous state,
// then we don't need to do anything and should return or else
// we could end up with overlapping fragments.
if (savedInstanceState != null) {
return;
}
// Create an instance of FList
fList = new FList();
// Add the fragment to the 'fragment_container' FrameLayout
getFragmentManager().beginTransaction().add(R.id.fragment_container,fList).commit();
}
}
// After clicking on an item of the listview changeFragment() is called
public void changeFragment() {
FragmentTransaction ft = getFragmentManager().beginTransaction();
fSpeed = new FSpeed();
ft.add(R.id.fragment_container, fSpeed);
// this might be necessary
if (actionBar != null)
actionBar.selectTab(tabSpeed);
ft.commit();
}
// In the activity I also setup the ActionBar with tabs
private void setupActionBar() {
tabSpeed = actionBar.newTab();
// and a few more tabs...
tabSpeed.setTabListener(this);
actionBar.addTab(tabSpeed);
// But the tabs are not visible because of the navigation mode
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD);
}
// Here is the tabListener - very hacky!
#Override
public void onTabSelected(Tab tab, FragmentTransaction ft)
if (fm == null)
fm = getFragmentManager();
Fragment f = null;
switch (tab.getPosition()) {
case 0:
if (fSpeed == null)
fSpeed = new FSpeed();
f = findFragment(fSpeed);
if (f != null) {
ft.setCustomAnimations(R.anim.fragment_alpha_0_1, R.anim.fragment_alpha_1_0);
ft.remove(f);
ft.add(R.id.fragment_container, fSpeed);
} else {
Log.d(TAG, "fSpeed is " + f);
return;
}
break;
case 1:
// more tabs - similar code
}
// the findFragment() method
private Fragment findFragment(Fragment selectedFragment) {
if (fm.findFragmentById(R.id.fragment_container) == fSpeed && selectedFragment != fSpeed)
return fSpeed;
// more ifs for other fragments
else
return null;
}
This all worked fine for the minute but after switching x times between the tabs things started looking funny, e.g. fList was visible, tab icons disappeared, ... I could track the bugs down and add some null checks but I think I went the wrong way. This is why I changed the TabListener with the following one:
// Still in activity
public static class MyTabListener<T extends Fragment> implements TabListener {
private Fragment mFragment;
private final Activity mActivity;
private final String mTag;
private final Class<T> mClass;
/**
* * 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 MyTabListener(Activity activity, String tag, Class<T> clz) {
mActivity = activity;
mTag = tag;
mClass = clz;
}
/* The following are each of the ActionBar.TabListener callbacks */
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.fragment_container, mFragment, mTag);
} else {
// If it exists, simply attach it in order to show it
ft.attach(mFragment);
}
}
public void onTabUnselected(Tab tab, FragmentTransaction ft) {
if (mFragment != null) {
ft.detach(mFragment);
}
}
// The tabs are now assigned to MyTabListener
tabSpeed.setTabListener(new MyTabListener<FSpeed>(this, "speed", FSpeed.class));
Here is where the problem started. After clicking on an item from the listview with the new TabListener onTabSelected and onTabUnSelected are called in turns endlessly.
Question:
My question is how do you go from a fragment with a listview to another fragment after clicking on an item of the listview (and still use the same activity). Is my own written method changeFragment() the wrong approach? Still I would like to use the MyTabListener.
Note 1: I bought Commonsware's book to learn more about fragments but I couldn't find a more complex example with different fragments working together and also couldn't find how the back button is handled, or overridden. For example after clicking on the back button if fragment1, 2 or 3 are visible always show fragment4. If anybody found one in the book could you please tell me the chapter (name)/ page? If there isn't it would be very kind of the Common guys to provide one in the next update or so.
Note 2: Global variables were used in the project like fSpeed, tabSpeed, ...
Note 3: If you need more code or explanation please let me know in the comments. Thanks for helping!
Is my own written method changeFragment() the wrong approach?
You are adding a fragment, not replacing one. To replace a fragment, use replace(), not add().
The Problem
Before I describe my current setup I will describe my problem. I need to have hierarchal navigation between fragments inside separate tabs. Say I drill down on level in a tab, I then switch tabs and then go back to the first, original tab, I need it to be on the drilled down level and then have the ability to go back up to the top level of that tab.
Now onto my setup.
I have an application with one activity called MainActivity. This activity is what controls my tabbed action bar. I have followed many different tutorials to handle the switching of Fragments when a tab is selected and here is the code below that works drawing your attention to the onTabSelected and onTabUnselected methods.
MainActivity -- Tab Listener
public static class MyTabListener<T extends Fragment> implements ActionBar.TabListener {
private final Activity mActivity;
private final String mTag;
private final Class<T> mClass;
private final Bundle mArgs;
private Fragment mFragment;
public MyTabListener(Activity activity, String tag, Class<T> clz) {
this(activity, tag, clz, null);
}
public MyTabListener(Activity activity, String tag, Class<T> clz, Bundle args) {
mActivity = activity;
mTag = tag;
mClass = clz;
mArgs = args;
// Check to see if we already have a fragment for this tab, probably
// from a previously saved state. If so, deactivate it, because our
// initial state is that a tab isn't shown.
mFragment = mActivity.getFragmentManager().findFragmentByTag(mTag);
if (mFragment != null && !mFragment.isDetached()) {
FragmentTransaction ft = mActivity.getFragmentManager().beginTransaction();
ft.detach(mFragment);
ft.commit();
}
}
public void onTabSelected(Tab tab, FragmentTransaction ft) {
Log.e("FRAGMENT","Selected Fragment With Tag " + mTag);
if (mFragment == null) {
mFragment = Fragment.instantiate(mActivity, mClass.getName(), mArgs);
ft.add(android.R.id.content, mFragment, mTag);
} else {
ft.attach(mFragment);
}
}
public void onTabUnselected(Tab tab, FragmentTransaction ft) {
if (mFragment != null) {
ft.detach(mFragment);
}
}
public void onTabReselected(Tab tab, FragmentTransaction ft) {
Toast.makeText(mActivity, "Reselected!", Toast.LENGTH_SHORT).show();
}
}
When I am on Tab1 viewing Fragment1', when a button is pressed I then show anotherFragmentcalledFragment2` which I need the ability to go back from. The code below works if I just stay on that tab however when navigating away from the tab and back to it, everything messes up.
MainActivity -- Showing second fragment
Fragment tdFrag = Fragment.instantiate(this, SecondFragment.class.getName());
FragmentTransaction ft = getFragmentManager().beginTransaction();
ft.replace(android.R.id.content, tdFrag, "secondfrag");
ft.addToBackStack(null);
ft.commit();
My Thoughts
I think it must be the way I detach the fragments and handle the switching over when a new tab is pressed. Maybe I need to implement some way of storing each individual backstack for each tab and loading the fragments from that instead of the system back stack?
Any help would be great. I have tried to explain it as thoroughly and simply as possible.
Thanks in advance.
Ok so after a bit of experimentation I decided to implement a custom backstack using enumerated types.
I have two types of enumerated types: a ViewType and CurrentTab type. The CurrentTab type tells me what tab I'm currently viewing and the ViewType tells me what view is being viewed in these tabs. I have an instance of a ViewType for each tab I have to store where I am in the hieerarchy.
To implement a backstack I override the method thats called when the back button is pressed.
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
return true;
}
return super.onKeyDone(keyCode,event);
}
So in where it checks for the back button I put my code to check the current tab and the separate view types. That way I know where I am and if I push back I know where I need to go.
I hope this helps any questions just post a comment.
I have an app with three tabs (ActionBar Tabs), each one with one fragment at a time.
TabListener
TabsActivity
Tab1 -> ListFragment1 -> ListFragment2 -> Fragment3
Tab2 -> Tab2Fragment
Tab3 -> Tab3Fragment
The problem is when I create the FragmentTransaction (inside OnListItemClicked) from ListFragment1 to ListFragment2, the fragments inside the other tabs also change to ListFragment2.
In the end, I want to change fragments only inside on tab and preserve the state of the other tabs.
I'm already saving the state (OnSavedInstance()).
Do you know what I'm missing here?
Some of the code:
public class TabsActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.tabs);
// setup Action Bar for tabs
ActionBar actionBar = getActionBar();
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
// instantiate fragment for the tab
Fragment networksFragment = new NetworksFragment();
// add a new tab and set its title text and tab listener
actionBar.addTab(actionBar.newTab().setText("Tab1")
.setTabListener(new TabsListener(ListFragment1)));
// instantiate fragment for the tab
Fragment historyFragment = new HistoryFragment();
// add a new tab and set its title text and tab listener
actionBar.addTab(actionBar.newTab().setText("Tab2")
.setTabListener(new TabsListener(Tab2Fragment)));
// instantiate fragment for the tab
Fragment settingsFragment = new SettingsFragment();
// add a new tab and set its title text and tab listener
actionBar.addTab(actionBar.newTab().setText("Tab3")
.setTabListener(new TabsListener(Tab3Fragment)));
}
}
public class TabsListener implements ActionBar.TabListener {
private Fragment frag;
// Called to create an instance of the listener when adding a new tab
public TabsListener(Fragment networksFragment) {
frag = networksFragment;
}
#Override
public void onTabReselected(Tab arg0, FragmentTransaction arg1) {
// TODO Auto-generated method stub
}
#Override
public void onTabSelected(Tab tab, FragmentTransaction ft) {
ft.add(R.id.fragment_container, frag, null);
}
#Override
public void onTabUnselected(Tab tab, FragmentTransaction ft) {
ft.remove(frag);
}
}
public class ListFragment1 extends ListFragment {
#Override
public void onListItemClick(ListView l, View v, int position, long id) {
getListView().setItemChecked(position, true);
ListFragment2 fragment2 = ListFragment2.newInstance(position);
FragmentTransaction ft = getFragmentManager().beginTransaction();
ft.replace(R.id.fragment_container, fragment2);
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
ft.addToBackStack(null);
ft.commit();
}
}
You're not missing anything (or I'm missing it too).
I searched long and hard for a way to do this "properly" but I couldn't find anything. What I ended up doing is writing my own backstack logic.
Unfortunately my employer owns my code so I can't share any of that verbatim, but here was my approach:
Create an enum with one entry for each of your tabs. Let's call it TabType.
Now create an instance variable tabStacks of type HashMap<TabType, Stack<String>>. Now you can instantiate one stack for each tab - each stack is a list of tags, as specified by Fragment.getTag(). This way you don't have to worry about storing references to Fragments and whether they're going to disappear on you when you rotate the device. Any time you need a reference to a Fragment, grab the right tag off the stack and use FragmentManager.findFragmentByTag().
Now whenever you want to push a Fragment onto a tab, generate a new tag (I used UUID.randomUUID().toString()) and use it in your call to FragmentTransaction.add(). Then push the tag on top of the stack for the currently displayed tab.
Be careful: when you want to push a new fragment on top of an old one, don't remove() the old one, since the FragmentManager will consider it gone and it will be cleaned up. Be sure to detach() it, and then attach() it later. Only use remove() when you're permanently popping a Fragment, and only use add() the first time you want to show it.
Then, you'll have to add some relatively simple logic to your TabListener. When a tab is unselected, simply peek() at its stack and detatch() the associated Fragment. When a tab is selected, peek() at the top of that stack and attach() that fragment.
Lastly, you'll have to deal with Activity lifecycle quirks (like orientation changes). Persist your Map of Stacks as well as the currently selected tab, and unpack it again in your onCreate(). (You don't get this packing and unpacking for free, but it's pretty easy to do.) Luckily your TabType enum is Serializable so it should be trivial to put into a Bundle.