I have an activity that uses ActionBarSherlock to show an action bar with navigation tabs. I need one of those tabs to open another activity (taking over the whole screen) when it is tapped. I built a custom TabListener to achieve this, but it fires when the tab contents are initially rendered, not just when it's manually selected.
I can't see a way to catch a traditional onClick event, so I assume I need to handle this in the TabListener and somehow distinguish the initial selection from a later manual selection, but how? I could use a counter each time the tab is selected and only start the secondary activity if the counter is above zero, but that looks like an ugly hack. I was hoping there was a way to handle a "click" as opposed to a "selection" as the latter includes both the initial display of the views, as well as later manual clicks.
I attach the listener to the action bar navigation tab like this:
tab.setTabListener(new TabDiseaseSelectorListener(this));
And then the listener looks like this:
public class TabDiseaseSelectorListener implements ActionBar.TabListener {
private final Activity mActivity;
public TabDiseaseSelectorListener(Activity activity) {
mActivity = activity;
}
public void onTabSelected(ActionBar.Tab tab, FragmentTransaction unused) {
Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
intent.setClass(mActivity, DiseaseSelector.class);
startActivity(intent);
}
public void onTabUnselected(ActionBar.Tab tab, FragmentTransaction unused) {
}
public void onTabReselected(ActionBar.Tab tab, FragmentTransaction ft) {
}
}
I tried using onTabReselected() but that only fires if the tab is already selected and the user taps it again.
Related
I am running an app with 3 tabs that support both swiping and action bar for tab navigation. i set up a validation check so that it when tab 2 is selected, if certain requirements arent met, it returns to tab 1.
It works well with swiping (if swiping from tab1 to tab2, it displays the error message and returns to tab 1) but with the action bar, if an action bar button is pressed for tab2, the error message is displayed and tab1's view displayed but the action bar button remains on the tab 2.
I had tried the following script but with no luck of changing the active tab button back to the first tab. This is especially a problem since data is supposed to be saved to sqlite when whenever a new tab is selected.
public void onTabSelected(Tab tab, FragmentTransaction ft) {
check = GlobalApp.data().value;
if(tab.getTag() == "Product")
{
if(check == "Select Client")
{
actionBar.setSelectedNavigationItem(0);
viewPager.setCurrentItem(0);
alert.showAlertDialog(Invoice2.this,
"Error",
"Client name not selected", true);
}
else
{
viewPager.setCurrentItem(tab.getPosition());
}
}
else if ((tab.getTag() == "Confirm"))
{
viewPager.setCurrentItem(tab.getPosition());
}
First bind the tabs to the ViewPager:
ActionBar.TabListener tabListener = new ActionBar.TabListener() {
public void onTabSelected(ActionBar.Tab tab, FragmentTransaction ft) {
viewPager.setCurrentItem(tab.getPosition());
//Do the criteria check here; AFTER setting current item.
}
then Viewpager to the tabs:
viewPager.setOnPageChangeListener(
new ViewPager.SimpleOnPageChangeListener() {
#Override
public void onPageSelected(int position) {
getActionBar().setSelectedNavigationItem(position);
//Do the criteria check here; AFTER setting current item.
}
});
After that you can perform check for your criteria after commented lines. Regardless of you changing the tab or page, your tabs and your pages should change synchronously. For more info check this Android Developers training page
I have an android application where I use the ActionBar in NAVIGATION_MODE_TABS mode.
Currently, I am heavily using Fragments. So, that worked quite well for the displayed tabs. I am facing a design issue where I think I am going in the wrong direction.
MainActivity has three tabs. One tab in particular have bunch of navigations on it.
MainActivity
Tab1
Tab2
Tab3 (This has buttons that should display different fragments based on what the user clicked)
The requirement is to keep the tabs always visible with the same text. So, I ended up creating bunch of activities that inherit from the MainActivity where I made the third tab content based on what the user clicked on.
This allowed me to sustain the content in a good flow and keep the look consistent. However, It seems an overhead to create an activity.
I tried to leverage the FragmentManager and pushToStack. However, that route didn't seem to work.
I am relying on this interface ActionBar.TabListener to properly attach and detach the fragments based when the user clicks on the tab. That is good. However, the minute I introduce a different fragment that seemed problematic.
Hope That is clear and I am looking for the best advice.
I solved it by relying on the the following logic:
public class FragmentTabListener implements ActionBar.TabListener
{
private String _fragmentClassName;
private Context _context;
private Fragment _fragment;
private Boolean _cleanStack = true;
public FragmentTabListener(Context context, String fragmentClassName,
Boolean cleanStack)
{
_context = context;
_fragmentClassName = fragmentClassName;
_cleanStack = cleanStack;
}
public FragmentTabListener(Context context, String fragmentClassName)
{
this(context, fragmentClassName, false);
}
#Override
public void onTabReselected(Tab tab, FragmentTransaction ft)
{
cleanFragmentManagerStack();
if (this._cleanStack)
{
ft.attach(_fragment);
}
}
private void cleanFragmentManagerStack()
{
if (this._cleanStack)
{
FragmentManager mgr = ((Activity) _context).getFragmentManager();
int backStackCount = mgr.getBackStackEntryCount();
for (int i = 0; i < backStackCount; i++)
{
// Get the back stack fragment id.
int backStackId = mgr.getBackStackEntryAt(i).getId();
mgr.popBackStack(backStackId,
FragmentManager.POP_BACK_STACK_INCLUSIVE);
}
}
}
#Override
public void onTabSelected(Tab tab, FragmentTransaction ft)
{
if (_fragment == null)
{
_fragment = Fragment.instantiate(_context, _fragmentClassName);
ft.add(android.R.id.content, _fragment);
}
else
{
ft.attach(_fragment);
}
}
#Override
public void onTabUnselected(Tab tab, FragmentTransaction ft)
{
this.cleanFragmentManagerStack();
// TODO Auto-generated method stub
ft.detach(_fragment);
}
}
Any action on my original fragment, I was adding a fragment to the current stack.
This worked. Hope it helps someone.
From what I understand you need 3 fragments, each one of them in one tab, and in the 3rd tab you can have, inside that fragment, based on what user clicked, a different fragment inside (nested fragments are supported now). Maybe I didn't understand your problem correctly, if that's the case tell me.
Cheers!
How are you Mark? I have a question regarding the program you have written in the book "CommonsWare The Busy Coders Guide to Android Development".
I am talking about Fragments/EU4You_6 on Chapter 28 page 377. I want to expand this by adding an ActionBar Tab.
Without making any changes on your original program, what I did was that I copied EU4You.java to EU5You.java, which represents Tab2. EU4You.java will be the default Tab1.
The following are my approach:
I created a java program called EU4Main.java, which represents the MAIN program instead of the original EU4You. Of course, I changed the manifest to android:name=".EU4Main"
The EU4Main.java is where I put the ActionBar Tab. The trouble is and making me frustrated is in the TabListener setup. I have this setup .setTabListener(new TabListener(EU4You.class))); , which passes a Class. It did not work. Do you have any advise on this instead of passing a class?
Also, the onTabSelected on the code snippets below, did not work properly. If I clicked Tab2, it will show the list for Tab2 but it will automatically returns to Tab1. It won't stay at Tab2. I don't know why?
I would appreciate of any help if you can provide a better and working approach for both .setTabListener and onTabSelected
I have also extended FragmentActivity to EU4Main or shall I just use extends Activity instead?
Thanks in advance.
I have included EU4Main below with incorrect and incomplete codes( I just can't make it work...)
public class EU4Main extends FragmentActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final ActionBar bar = getActionBar();
bar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
bar.setDisplayOptions(0, ActionBar.DISPLAY_SHOW_TITLE);
bar.addTab(bar
.newTab()
.setText("Countries")
.setTabListener(new TabListener(EU4You.class)));
bar.addTab(bar
.newTab()
.setText("Artists")
.setTabListener(new TabListener(EU5You.class)));
.
.
.
private class TabListener implements ActionBar.TabListener {
public TabListener(Activity activity) {
mActivity = activity;
}
public void onTabSelected(ActionBar.Tab tab, android.app.FragmentTransaction unused) {
if (tab.getPosition() == 0) {
Intent intent = new Intent();
String packageName = "com.commonsware.android.eu4you";
String className = "com.commonsware.android.eu4you.EU4You";
intent.setClassName(packageName, className);
startActivity(intent);
else{
Intent intent = new Intent();
String packageName = "com.commonsware.android.eu4you";
String className = "com.commonsware.android.eu4you.EU5You";
intent.setClassName(packageName, className);
startActivity(intent);
}
}
public void onTabUnselected(ActionBar.Tab tab, android.app.FragmentTransaction unused) {
FragmentManager fragMgr = getSupportFragmentManager();
FragmentTransaction xaction=fragMgr.beginTransaction();
}
public void onTabReselected(ActionBar.Tab tab,
android.app.FragmentTransaction xaction) {
// NO-OP
}
}
Your TabListener has a constructor that takes an Activity. You are calling the constructor with a Class. A Class is not an Activity.
Furthermore, you are using two separate TabListener instances, but your code for TabListener does not do anything different based upon the supplied parameter.
And, you are starting activities when tabs are selected, which is not going to be especially useful.
When a TabListener is called with onTabSelected(), it needs to affect a change to the existing UI. Starting a whole new activity does not constitute a change to the existing UI. Rather, TabListener should do something like:
execute a FragmentTransaction
set a fresh ListAdapter in the ListFragment managed by the tabs
update ordinary widgets in the current activity
etc.
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.
I have an app (for Honeycomb) with a main activity that shows a sort of dashboard, with three buttons and a title. When the user clicks a button they are taken to a screen where they can enter data and do a calculation. I would like to have two approaches to the calculation in this second ('calculator') activity, and would like to implement this through having two tabs in the action bar (only when you are in this calculator activity).
I haven't used a tabhost widget or tabs ever before, so how do I go about having a tab widget in the action bar and changing the rest of the screen (everything but the action bar and system bar) when the other tab is selected?
If someone could point me towards some source code specifically for Honeycomb action bar tabs, that would be great.
Thanks for any help, and have a great day.
See Honycomb Gallery which makes use of action bar tabs.
Tabs in the action bar is a very neat feature. To make this question complete here on SO, I'll provide an example; This code goes in your Activity's onCreate
final ActionBar actionBar = getActionBar();
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
// remove the activity title to make space for tabs
actionBar.setDisplayShowTitleEnabled(false);
// instantiate some fragments for the tabs
Fragment fragment1 = new Fragment1();
Fragment fragment2 = new Fragment2();
// add a new tab and set its title text and tab listener
actionBar.addTab(actionBar.newTab().setText(R.string.title_tab1)
.setTabListener(new MyTabListener(fragment1)));
actionBar.addTab(actionBar.newTab().setText(R.string.title_tab2)
.setTabListener(new MyTabListener(fragment2)));
You can put the MyTablListener as an inner class of your activity, It could look something like this;
class MyTabListener implements ActionBar.TabListener {
private Fragment fragment;
public MyTabListener(Fragment fragment) {
this.fragment = fragment;
}
public void onTabSelected(ActionBar.Tab tab, FragmentTransaction ft) {
ft.replace(R.id.activity_new_formula_fragment_content, fragment, null);
}
public void onTabUnselected(ActionBar.Tab tab, FragmentTransaction ft) {
ft.remove(fragment);
}
public void onTabReselected(ActionBar.Tab tab, FragmentTransaction ft) {
}
}