I am writing my first Android app. It's a port of an iPhone app which has 3 tabs at the bottom, call these A, B and C. Tab A has 4 child activities (A1, A2, A3, A4) , tab B has 3 child activities and Tab C has 2 child activities.
For the Android app, I don't really want to show the iPhone style tab bar at the bottom. In fact I'd rather have no tab bar at all (so I can use more of the screen) and instead use the Menu button to swap between activities A, B and C.
I'm really struggling to choose the best method to implement this which will also need to handle the back button correctly.
I've read the Android developer notes on Activities and tried this out (using the intent flags to disable the 'slide left' effect) This works for switching between A1, B1, C1, but if you've navigated A1, A2, then C1 i don't know how to make the Back button go to A2 (it goes to A1)
I've also read about using Tabs and tried a test with this. However the back button is not handled and simply exits the app. I realise I can handle the Stack myself and override the Back button and that I'll need to use ActivityGroups. But I've not found any good examples of how to handle the back button in the ActivityGroup and also read that theres a bug in handling the back button if one of the Activities in the Group is a ListActivity, which I also intend to use.
Any help or pointers would be appreciated to help get me started.
If you use TabHost it's quite hard thing to handle and I wouldn't recommend that
If you use Context Menu than calling each activity normally (!not withing activity group) should put the Activity to the Activity Stack and make standard functioning of Back button available.
Alternatively you can imitate bottom menu just by a separate menu View which you include to all the layouts which will avoid TabHost limitation but will give the same look.
This is how I go about using the menu button. In your main activity have something like this:
First, make a new android xml file and set the type to menu (its one of the dropdowns in eclipse), then the menu.xml file will automatically be made in the right place.
Open up the layout, and add your 3 items (one for each page you want to switch to)
// Menu button pressed
#Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.menu, menu);
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.page1:
startActivity(new Intent(this, page1Activity.class));
break;
case R.id.page2:
startActivity(new Intent(this, page2Activity.class));
break;
case R.id.page3:
startActivity(new Intent(this, page3Activity.class));
break;
}
return true;
Where page1Activity and the rest are the other classes.
Make sure that in your Id section of the layout you have #+id/page1, etc...
Just add those activities to your manifest and it should work. The back button works fine with this (though I havent tried with a listview. With a listview you should just have a finish(); when you select something to avoid multiple lists)
To switch subpages use something like this:
public void page1aPressed(View button) {
Intent nextPage = new Intent();
nextPage.setClassName("com.example",
"com.example.page1a");
startActivity(nextPage);
When you press back it just goes to the previous activity. Maybe I'm thinking of this wrong but it works for me.
Related
My problem is almost same like
Similar Problem
But the solution didn't work for me.
In my two different app I am using same menu class file but they are showing different result. I have set menu in which clicking one of the items directs toward "AboutActivity" . When in one app I back from the menu's "AboutActivity" it refreshes the "MainActivity" but in another doesn't. The difference between the two apps is first one has two activity's (Main+About) and second one has five activity's (Main+About+Main2+Main3+..)
The code I have used in the Manifest.
<activity android:name="school.infinity.maruf.agecalculator.about"
android:label="About"
android:parentActivityName="school.infinity.maruf.agecalculator.MainActivity"
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|uiMode|screenSize|smallestScreenSize|layoutDirection|touchscreen"/>
This is the code used in class file
public boolean onOptionsItemSelected(MenuItem item) {
// User clicked on a menu option in the app bar overflow menu
switch (item.getItemId()) {
case R.id.about:
Intent intent=new Intent(this,about.class);
startActivity(intent);
return true;
}
I have used the following code for MainActivity in the manifest
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|uiMode|screenSize|smallestScreenSize|layoutDirection|touchscreen"/>
Now how can I stop reloading/refreshing the main activity during getting back from "AboutLayout"?
I have implemented a TabHost. In one tab I have Activity1, which calls Activity2 after a button click, which calls Activity3 after a button click, which calls Activity1 after a button click, etc.. No backstack functionality is required, just 1 --> 2 --> 3 --> 1, etc. All three activities have a separate layout file.
Everything works fine, except that after the first transition from 1 --> 2 the activities grab the entire screen and the tabs are invisble forever.
Question: how can I keep these three activities within the confinement of de tab area and the tabs visible? The problem has been recognized here many times before; the solution used to be ActivityGroups, but these are deprecated and Fragments are advised instead. I have seen many examples here, but nothing that could help me.
Can I keep my three activites (Activity1 extends Activity, etc)?
Should I add fragment tags to the layout files?
Do I need to work with transactions?
Should I work with one fragment class or three?
Can you please give me a few hints how I should go about? I woud already be helped if you tell which classes I need to use and of what type they are.
Thanks in advance.
It took me more than half a day, but finally found a solution that works. Unfortunately I am still stuck with deprecated issues (Activity Group and getLocalActivityManager().startActivity(..)).
Again I have a single tab under a TabHost and several activities, all operating within that tab. Navigation from one activity to the next occurs with a buttonclick. Solution:
all Activities operating within the tab need to extend ActivityGroup
All Activity classes need to have a button handler that links to the next activity like this:
public void onBtnClicked(View view) {
Intent intent = new Intent(view.getContext(), NextActivity.class);
replaceContentView("NextActivity", intent);
}
public void replaceContentView(String id, Intent newIntent) {
View view = getLocalActivityManager().startActivity(id, newIntent.
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)).getDecorView();
this.setContentView(view);
}
By this the tabs remain visible all the time, as desired.
Hope this helps someone.
I have an app with master/detail layout (1 activity, 1 ListView fragment and 1 detail fragment). When the user clicks an item in the ListView, a fragment transaction instantiates a detail fragment on the right-pane that includes the information corresponding to that item. When the detail fragment is shown I hide the initial action bar buttons/items and show 3 new AB items (done/delete/cancel). The user can clean the right-pane and return to the initial UI state by either pressing the back button or by pressing one of the 3 AB items.
The issue I'm experiencing is that when the user selects the app's home icon (i.e. "up navigation") the activity gets re-loaded (i.e. the animation that indicates that the activity is starting can be seen as both the action bar and the UI is been redrawn). The issue only happens when the app home icon is pressed. If the user presses the back button or a cancel/done/delete action bar button, the fragment is simply remove from the right-pane and the UI returns to initial state without any "re-loading".
The XML layout for the activity is the following (inside LinearLayout; prettify is hiding that line):
<fragment class="*.*.*.ListFragment"
android:id="#+id/titles" android:layout_weight="1"
android:layout_width="0px"
android:layout_height="match_parent" />
<FrameLayout android:id="#+id/details" android:layout_weight="2"
android:layout_width="0px"
android:layout_height="match_parent" />
The DetailsFragement has the actionBar.setDisplayHomeAsUpEnabled statement in its onCreate method:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
ActionBar actionBar = getSherlockActivity().getSupportActionBar();
actionBar.setDisplayHomeAsUpEnabled(true);
}
For both the ListView fragment and the Detail fragments the onCreateOptionsMenu() and onOptionsItemSelected() method are implemented within the fragments. Below the code for the Details fragment:
#Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.edit_menu, menu);
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
// some variable statements...
switch (item.getItemId()) {
case android.R.id.home:
//Toast.makeText(getSherlockActivity(), "Tapped home", Toast.LENGTH_SHORT).show();
onHomeSelectedListener.onHomeSelected();
return true;
case R.id.menu_edit_item_done:
editedTask.setTitle(editedTaskTitle);
onTaskEditedListener.onTaskEdited(editedTask, UPDATE_TASK, true);
return true;
default:
return super.onOptionsItemSelected(item);
}
}
In the host activity I implement the onHomeSelectedListner to handle the app home icon press (i.e. "up navigation":
public void onHomeSelected(){
FragmentManager manager = getSupportFragmentManager();
FragmentTransaction ft = manager.beginTransaction();
TaskFragment taskFragment = (TaskFragment)getSupportFragmentManager().findFragmentById(R.id.details);
ft.remove(taskFragment);
ft.commit();
manager.popBackStack();
}
The activity's listener in charged of handling all other action bar buttons (i.e. done/delete/cancel) is onTaskEditedListener and, aside of other code that processes some data, it has the same fragment transactions shown above.
Update(1/24)
Based on tyczj and straya feedback I placed log statements inside onCreate(), onResume(), onPause() of the activity to determine the differences between onHomeSelected and onTaskEdited listeners. I'm able to confirm that during the "up navigation" event (i.e. onHomeSelected) onPause(), onCreate() and onResume() are called. Whereas during the onTaskEdited call (i.e. back button or done/delete/cancel press) none of those events are called.
Update (1/25)
Based on a suggestion by Mark Murphy, I commented out the onHomeSelected method call in the "case android.R.id.home" statement just to see what would the Activity do. The thinking was that the app would do nothing since the are no statements. Turns out that is not the case. Even without a call to the listener method (i.e. that removes the fragment), the activity is restarted and the detail fragment is removed from the fragment container.
Update (2/28)
I temporarily workaround the fact that my main activity was getting restarted by disabling the window animations (as highlighted in my own answer). However, through further testing I uncovered a bug. Thanks to Wolfram Rittmeyer's sample code I was able to figure out the real reason(s) why my activity was restarting (in master/detail single layout) during up navigation:
1) Although I was using this "onHomeSelectedListener" to properly remove the fragment from the backstack, I still had some remnant code in the ListView fragment's onOptionsItemSelected that was creating a new intent to start the hosting activity. That's why pressing the app's home icon was re-starting the activity.
2) In my final implementation (shown in my own answer), I got rid of the onHomeSelectedListener in the activity and replace the startActivity intent (i.e. offending code) inside the ListView's onOptionsItemSelected to use the fragment removal + popBackStack code originally in the onHomeSelectedListener.
After much research and poking around, turns out that only reason why my activity was restarting during "up navigation" for master/detail configuration was because I left some code in the ListView Fragment's onOptionsItemSelected that was creating an intent to start the main activity in addition to my full fragment transaction code elsewhere. Below is the final implementation with which I got "up navigation" to work properly on both phone (multiple activities) and tablet (single activity/multi-pane) configurations. Thanks to Wolfram Rittmeyer for a couple of hints in his code (link in the comment section) that help me pinpoint my problem!
Main Activity: Hosts the fragments and performs some other app-specific operations
ListView Fragment: Handles "up navigation in table configuration
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
if(mDualPane){
FragmentManager manager = getSherlockActivity().getSupportFragmentManager();
FragmentTransaction ft = manager.beginTransaction();
DetailFragment detailFragment = (DetailFragment)manager.findFragmentById(R.id.details);
ft.remove(detailFragment);
ft.commit();
manager.popBackStack();
getSherlockActivity().getSupportActionBar().setDisplayHomeAsUpEnabled(false);
getSherlockActivity().getSupportActionBar().setHomeButtonEnabled(false);
}
return true;
// Other case statements...
default:
return super.onOptionsItemSelected(item);
}
}
Details Fragment: Handles up navigation in phone configuration
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
// Sets "up navigation" for both phone/tablet configurations
ActionBar actionBar = getSherlockActivity().getSupportActionBar();
actionBar.setDisplayHomeAsUpEnabled(true);
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
if(!mDualPane){
Intent parentActivityIntent = new Intent(getSherlockActivity(), MainActivity.class);
parentActivityIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(parentActivityIntent);
getSherlockActivity().finish();
}
return true;
// Other case statements...
default:
return super.onOptionsItemSelected(item);
}
}
If you look at the Navigation Design Pattern you will see that you want to return to the starting activity when the home button is hit.
So say you have 2 Activities call them A1 and A2. Clicking on something in A1 takes you to A2. If the user hits the home button you should return them to A1 clearing the stack of everything up until that activity like this
Intent intent = new Intent(this, A1.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intent);
this is what the flag Intent.FLAG_ACTIVITY_CLEAR_TOP does
If set, and the activity being launched is already running in the current task, then instead of launching a new instance of that activity, all of the other activities on top of it will be closed and this Intent will be delivered to the (now on top) old activity as a new Intent.
For example, consider a task consisting of the activities: A, B, C, D. If D calls startActivity() with an Intent that resolves to the component of activity B, then C and D will be finished and B receive the given Intent, resulting in the stack now being: A, B.`
The currently running instance of activity B in the above example will either receive the new intent you are starting here in its onNewIntent() method, or be itself finished and restarted with the new intent. If it has declared its launch mode to be "multiple" (the default) and you have not set FLAG_ACTIVITY_SINGLE_TOP in the same intent, then it will be finished and re-created; for all other launch modes or if FLAG_ACTIVITY_SINGLE_TOP is set then this Intent will be delivered to the current instance's onNewIntent().
This launch mode can also be used to good effect in conjunction with FLAG_ACTIVITY_NEW_TASK: if used to start the root activity of a task, it will bring any currently running instance of that task to the foreground, and then clear it to its root state. This is especially useful, for example, when launching an activity from the notification manager.
don't: break and then return super.onOptionsItemSelected(item), rather just: return true;
UPDATE:
So you're saying the Activity is "restarted" based on what you see happen with Views, but can you confirm what may or may not happen to the Activity (and Fragments for that matter) by using logging in the various lifecycle methods? That way you can be sure of what the current (erroneous) behaviour is before moving forward with diagnosis.
UPDATE:
OK, good to be sure about behaviour :)
Now regarding your question "What is the correct way to implement "up navigation" for a master/detail layout (1 activity/2fragments)? ":
The typical way is that the 2 Fragments got added within a single FragmentTransaction and you simply popBackStack to remove them and go back to whatever previous state was. I think you're doubling up by manually removing a Fragment within a FragmentTransaction and then popping backstack. Try just popBackStack. Oh and just to be sure and consistent, since you're using ActionBarSherlock and support.v4 are you using a FragmentActivity (rather than an Activity) and SherlockFragment?
I think you should handle the Up button only inside the activity.
If youre in a phone, the up button will be handled by activity that acts as a wrapper of that fragment, in tablet (master/detail pattern) you dont want it anyways
There are two activities.
Activity A has a button that can switch to Activity B.
Activity B also has a button that can switch to Activity A.
here is my code,
#Override
public void onTabSelected(Tab tab, FragmentTransaction ft) {
Log.e("current", context.getClass().toString());
Log.e("changeto", tab.getTag().toString());
if(context.getClass()==tab.getTag())
return;
Intent intent = new Intent(new Intent(context,(Class<?>) tab.getTag()));
intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
context.startActivity(intent);
}
I want to remove the animation when i switch the activities, but it doesn't work.
However if I remove
intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
it works perfectly. Why?
Sorry for my bad English.
In the Activity that you're switching to, try using overridePendingTransition(0,0); either in onResume or in onCreate.
If you're calling startActivity in a tab switch, you're doing it wrong. Tabs are for switching views within the current activity, and switching tabs should never create navigation history. Consider switching a fragment or replacing your view hierarchy with the newly selected tab's content instead.
The more you pursue a path of switching activities for tab selection, the more you will find yourself playing whack-a-mole with subtle user experience bugs that make your app simply feel "wrong."
With your proposed implementation above, the Back button will return to the previously selected tab, breaking the, "never creates navigation history" rule. You may think that finish()ing the current Activity as you start the next can solve this, but you'll still have a host of other issues. Users expect subtle elements of state such as scroll position to persist across tabs. As of Android 4.0 there is an expectation that users should be able to swipe horizontally between tabs (http://developer.android.com/design/building-blocks/tabs.html) which you will not be able to accomplish if you are using separate activities for each tab's content.
This is only a small sample, the list just goes on. Tabs should not be used to switch between different Activities.
I stuck with one problem. actually my screen consists of two tabs. under each tab i have 4-4 activity. i m displaying each activity with the help of activity group in single tab.
Suppose i m in 1st tab which is active. Under this tab i m on 2nd activity(e.g first activity is list activity and second activity gives the result from the first activity)
I want when i click on 1st tab again it should show me the first activity again without using back button.?
I had that problem sometime ago... and that happens because people like to emulate the bottom bar of the iPhone. Android apps don't work that way and using Activity Group is always a signal of a poor UI design.
Anyway, this is what I did:
tabHost.setCurrentTabByTag(TAB_ID_MORE);
tabHost.getCurrentTabView().setOnClickListener(new OnClickListener() {
public void onClick(View v) {
if( MoreGroupActivity.self != null ) {
MoreGroupActivity.self.reset();
}
tabHost.setCurrentTabByTag(TAB_ID_MORE);
}
});
tabHost.setCurrentTabByTag(TAB_ID_HOME);
The above code is not generic, but will give you an idea of the workaround I found. Let me explain:
tabHost.setCurrentTabByTag(TAB_ID_MORE); I use this to select a current tab (in my case, the main tab was another tab, so I had to to this and then change back with tabHost.setCurrentTabByTag(TAB_ID_HOME);). I mean, in my case, the only tab with that behavior was the "More" tab.
tabHost.getCurrentTabView().setOnClickListener this allows you to put a listener to the tab. As you may have already noticed, using OnChangeTabListener is not an option in this kind of situation.
MoreGroupActivity.self Inside my group activity, I had a static field referencing the group activity it self. This kind of hacks are common while using this crappy approach.
tabHost.setCurrentTabByTag(TAB_ID_MORE); this reset the tab so that it can change back to your first activity.
When you are adding a new TabHost.TabSpec to the TabHost
use
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP| Intent.FLAG_ACTIVITY_SINGLE_TOP);
to the respective Intent