Hi, Habr!
In the
previous article, I talked about adding a Support Library to your project and gave a simple example of SupportActionBar. But very often ActionBar is used not only as a substitute for the menu, but also as a way to navigate the application. Under the cut it is written how to implement it.
Navigation methods
ActionBar has 3 ways to navigate:
NAVIGATION_MODE_STANDART - in fact, not navigation at all, just an ActionBar with elements;
NAVIGATION_MODE_LIST - instead of the title drop-down list;
NAVIGATION_MODE_TABS - tabs under the ActionBar.
Drop-down list
Let's not create anything, but take the project from the
previous article . Let's create a new class -
ScreenFragment , it will be analogous to different screens of the application:
')
import android.os.Bundle; import android.support.v4.app.Fragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; public class ScreenFragment extends Fragment { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { TextView tv = new TextView(getActivity()); tv.setText("Screen " + getArguments().getInt(MainActivity.key_screen_number)); tv.setTextSize(30); return tv; } }
I did not create a separate xml markup file, it is not really needed here. We take the screen number from the arguments and insert it into the programmatically created TextView, which we then show.
Change the
onCreate () method code and add another one to the
MainActivity :
public static final String key_screen_number = "key_screen_number"; ActionBar ab; FragmentTransaction ft; ScreenFragment screen_fragment; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ab = getSupportActionBar(); ab.setNavigationMode(ActionBar.NAVIGATION_MODE_LIST); String[] screens = new String[] {"Screen 1", "Screen 2", "Screen 3"}; ArrayAdapter<String> sp_adapter = new ArrayAdapter<String>(this, android.R.layout.simple_spinner_item, screens); sp_adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); ab.setListNavigationCallbacks(sp_adapter, this); selected_list_item_position = -1; ab.setSelectedNavigationItem(0); } public boolean onNavigationItemSelected(int position, long id) { ft = getSupportFragmentManager().beginTransaction(); screen_fragment = new ScreenFragment(); Bundle args = new Bundle(); args.putInt(key_screen_number, position + 1); screen_fragment.setArguments(args); ft.replace(android.support.v7.appcompat.R.id.action_bar_activity_content, screen_fragment); ft.commit(); return true; }
In onCreate, we tell ActionBar that we will use the navigation method - a list, and prepare an adapter for it, and assign an event handler. It has only one method -
onNavigationItemSelected (int position, long id) . It is called when the user selects any item in the drop-down list. Here we create a new
ScreenFragment and give it a screen number so that it can show it. Then we start
FragmentTransaction and add this fragment to the View with id = android.support.v7.appcompat.R.id.action_bar_activity_content. This is the FrameLayout where our layout from setContentView () is added. Run the application and select various screens:



As a markup for the elements of the drop-down list, I use the system layout, but it does not look very nice. Therefore, it is better to use your own. The
Adapter.setDropDownViewResource () method is responsible for
adding it.Tabs
To change the way tabs are navigated,
let's tweak
MainActivity :
public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ab = getSupportActionBar(); ab.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS); Tab tab = ab.newTab(); tab.setText("Screen 1"); tab.setTabListener(this); ab.addTab(tab, 0, true); tab = ab.newTab(); tab.setText("Screen 2"); tab.setTabListener(this); ab.addTab(tab, 1, false); tab = ab.newTab(); tab.setText("Screen 3"); tab.setTabListener(this); ab.addTab(tab, 2, false); }
You also need to make the
MainActivity ... implements ... TabListener . This is a tab clicker. He has as many as 3 methods:
onTabUnselected (Tab tab, FragmentTransaction ft) - called when the current tab is closed;
onTabSelected (Tab tab, FragmentTransaction ft) - called when a new tab opens (triggered immediately after the previous one);
onTabReselected (Tab tab, FragmentTransaction ft) - when the user clicks on an already open tab:
public void onTabUnselected(Tab tab, FragmentTransaction ft) { } public void onTabSelected(Tab tab, FragmentTransaction ft) { screen_fragment = new ScreenFragment(); Bundle args = new Bundle(); args.putInt(key_screen_number, tab.getPosition() + 1); screen_fragment.setArguments(args); ft.replace(android.support.v7.appcompat.R.id.action_bar_activity_content, screen_fragment); } public void onTabReselected(Tab tab, FragmentTransaction ft) { }
Here we no longer need to create FragmentTransaction, it is given to us initially (it is assumed that we will work with fragments). But for this FragmentTransaction, you cannot call the addToBackStack () and commit () methods. We also have a pressed tab, from which we can pull out everything we need - text, icon, position, etc.
Tabs can be assigned to your View, if the system does not suit you -
setCustomView (int layoutResId)Run the application, click on the tabs:


By the way, if there are a lot of tabs, then their titles can be scrolled horizontally (as on Google Play), but the svayp doesn’t work below the titles.
Addition to the “Drop-down list”
Most likely, when you click on an already selected navigation item, there is no need to change anything on the screen. Well, with tabs everything is clear - do not touch the
onTabReselected () method and that's it. And what about the list? It's very simple: add a variable to
MainActivity private int selected_list_item_position;
And change the code
onNavigationItemSelected (int position, long id) :
public boolean onNavigationItemSelected(int position, long id) { if (position != selected_list_item_position) { ft = getSupportFragmentManager().beginTransaction(); screen_fragment = new ScreenFragment(); Bundle args = new Bundle(); args.putInt(key_screen_number, position + 1); screen_fragment.setArguments(args); ft.replace(android.support.v7.appcompat.R.id.action_bar_activity_content, screen_fragment); ft.commit(); selected_list_item_position = position; return true; } return false; }
Now a new screen will open only if you select a non-open navigation item
Menu
Different tabs usually contain different content, and the menu for it should be different. The guys from google made this opportunity. Further I will show everything on the example of tabs. Add the following code to
ScreenFragment :
public static final String key_menu_resource = "key_menu_resource"; @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { super.onCreateOptionsMenu(menu, inflater); inflater.inflate(getArguments().getInt(key_menu_resource), menu); } @Override public boolean onOptionsItemSelected(MenuItem item) { super.onOptionsItemSelected(item); Log.d("MENU", "Cliced MenuItem is " + item.getTitle() + " (from ScreenFragment)"); return true; }
Create three files in the
res / menu / folder:
screen_1.xml :
<menu xmlns:android="http://schemas.android.com/apk/res/android" > <item android:id="@+id/item1" android:title="Item 1" android:icon="@android:drawable/ic_menu_add"/> <item android:id="@+id/settings" android:title="Settings" android:icon="@android:drawable/ic_menu_edit"/> </menu>
screen_2.xml :
<menu xmlns:android="http://schemas.android.com/apk/res/android" > <item android:id="@+id/item2" android:title="Item 2" android:icon="@android:drawable/ic_menu_camera"/> <item android:id="@+id/settings" android:title="Settings" android:icon="@android:drawable/ic_menu_edit"/> </menu>
screen_3.xml :
<menu xmlns:android="http://schemas.android.com/apk/res/android" > <item android:id="@+id/item3" android:title="Item 3" android:icon="@android:drawable/ic_menu_call" /> </menu>
Change
onTabSelected () :
private int[] menu_resources = new int[] {R.menu.screen_1, R.menu.screen_2, R.menu.screen_3}; public void onTabSelected(Tab tab, FragmentTransaction ft) { screen_fragment = new ScreenFragment(); Bundle args = new Bundle(); args.putInt(key_screen_number, tab.getPosition() + 1); args.putInt(ScreenFragment.key_menu_resource, menu_resources[tab.getPosition()]); screen_fragment.setArguments(args); screen_fragment.setHasOptionsMenu(true); ft.replace(android.support.v7.appcompat.R.id.action_bar_activity_content, screen_fragment); }
Now we need to remove (or better comment out) the
onCreateOptionsMenu method - it will only interfere with us now. And the
onOptionsItemSelected () in MainActivity will also be corrected:
@Override public boolean onOptionsItemSelected(MenuItem item) { if (item.getItemId() != R.id.settings) { return false; } else { Log.d("MENU", "Cliced MenuItem is " + item.getTitle() + " (from MainActivity)"); return true; } }
Now I’ll explain that I’m here. The fact is that in the fragment, you can also create a menu. To make it visible, you need to call the Fragment.setHasOptionsMenu (true) method. If we create a menu not in the Activity, but in a fragment, the
onOptionsItemSelected () method is called first in the
MainActivity , and only then in the
ScreenFragment if
false is returned in the Activity. Here, instead of
if there should be a
switch / case , at the end of each
case -
return true; This means that we have already processed the click and do not need to call onOptionsItemSelected in the fragment. For example, on each tab there is a menu item "Settings". In order not to type the code in each fragment, when you click on this item, we return
true . Then onOptionsItemSelected () is called only in Activity, where we can open a new SettingsActivity, for example. If you start the program and press the Menu button on the device on different tabs, different elements will be displayed.
When you click on the menu items in the logs will be not only their name, but in which class the clicks were processed. And you can generally create a separate xml file in the
res / menu / folder with this very element, Settings, and in the
MainActivity in the
onCreateOptionsMenu () method, create a menu from this file. Then the 2 menus will merge, and both items will be visible.
State saving
It often happens that when switching between tabs, the status of the content on them should be maintained. For this, fragments have a special method -
setRetainInstance (boolean retain) . If you pass it
true in the parameter, the fragment will not be re-created. To check this, rewrite the
onTabSelected () method in
MainActivity :
private int[] menu_resources = new int[] {R.menu.screen_1, R.menu.screen_2, R.menu.screen_3}; private ScreenFragment[] screens = new ScreenFragment[] {new ScreenFragment(), new ScreenFragment(), new ScreenFragment()}; public void onTabSelected(Tab tab, FragmentTransaction ft) { screen_fragment = screens[tab.getPosition()]; Bundle args = new Bundle(); args.putInt(key_screen_number, tab.getPosition() + 1); args.putInt(ScreenFragment.key_menu_resource, menu_resources[tab.getPosition()]); screen_fragment.setArguments(args); screen_fragment.setHasOptionsMenu(true); screen_fragment.setRetainInstance(true); ft.replace(android.support.v7.appcompat.R.id.action_bar_activity_content, screen_fragment); }
Afterword
Well, in general, and all that I wanted to say. The article turned out great, but hopefully useful)
Part 1 - Adding Support Library to a project, simple example, search
Part 3 - Additional Functions