📜 ⬆️ ⬇️

How to easily make Navigation Drawer and tabs used in popular applications from Google

When using the popular Play Store app, many people noticed tabs for switching content. This use of tabs can be found in other applications from Google, such as Play Music, Play Press.



On this basis there is interest, and sometimes the need (if the customer asks) to realize what he saw. I was no exception when designing a new application, the design of which was sketched on a draft, there was a very similar design, although I had only a few tabs. It would seem that difficult? We will now open the official documentation, review the necessary sections and get down to business. But, having studied the documentation, I could not find relevant examples - and a new question immediately arose. Why Android developers from Google, by default, do not provide examples with the necessary functionality to make it quite simple, because it is implemented in each of their applications? Also, googling, there were similar questions on Stack Overflow. Based on this, it turned out that there is a problem, or at least an unresolved question, which should be dealt with.
')
Below I want to talk about how you can still implement the Navigation Drawer pattern along with tabs, as in popular applications from Google.

The example will use the Eclipse integrated development environment, but all actions can be reproduced using other environments, for example, Google’s recently released and increasingly popular Android Studio, based on IntelliJ IDEA.

Project creation


Create a new project. To do this, go to File> New> Android Application Project. Fill in the fields, such as the name of the application, package and SDK version. Next, follow the screens by pressing the Next button and leaving everything by default.



The development environment for us will create a new project with a standard structure.

The example will work starting with API version 8. This is justified by the fact that users still use devices with the old version of Android. Below are data on the number of devices running different versions of the platform on 08/12/2014.



Action Bar for API 8


But ActionBar, combining the title and menu, appeared since Android 3.0 (API 11). In order to use it, you need to connect to the project the Android-Support-v7-Appcompat library, kindly provided by Google. Detailed instructions for adding a library to a project can be found at: developer.android.com/tools/support-library/setup.html

There are two ways to add a library to a project — without using resources and using. The project will use a library using resources. After the library is added to the project tree, you need to go to Properties, right-click on the project and select in the Android categories, then press the Add key. In the list that appears, select android-support-v7-appcompat and click OK> Apply> OK. The library is added to the project. But if you try to start the application, the ActionBar will not be visible yet. It is necessary to change the line in res / values ​​/ styles.xml:

<style name="AppBaseTheme" parent="android:Theme.Light"> 

in res / values-v11 / styles.xml change the line:
 <style name="AppBaseTheme" parent="android:Theme.Holo.Light "> 

in res / values-v14 / styles.xml change the line:
 <style name="AppBaseTheme" parent="android:Theme.Holo.Light.DarkActionBar"> 

on
 <style name="AppBaseTheme" parent="@style/Theme.AppCompat.Light"> 


Also in the main activity it is necessary to inherit not from the Activity, but from ActionBarActivity (android.support.v7.app.ActionBarActivity). After the done actions and launching the application, you can see the ActionBar, including on earlier versions of the API.



Go to the Menu folder and edit the main.xml file to look like this:

 <menu xmlns:android="http://schemas.android.com/apk/res/android" xmlns:sabd="http://schemas.android.com/apk/res-auto" > <item android:id="@+id/action_search" android:icon="@android:drawable/ic_menu_search" android:orderInCategory="100" android:title="@string/action_search" sabd:showAsAction="ifRoom"/> </menu> 


It is necessary to pay attention to the following line: xmlns: sabd = http: //schemas.android.com/apk/res-auto. Now you need to set the showAsAction attribute as follows: sabd: showAsAction = "". You should also go to strings.xml and change the line:

 <string name="action_settings">Settings</string> 

on
 <string name="action_search">Search</string> 


Now the menu will have a familiar look.



Sidebar implementation


Our next step is to introduce the sidebar menu (Navigation Drawer). This is a panel that displays the main navigation parameters of the application in the left edge of the screen. It opens with a gesture from the left edge of the screen or by clicking on the application icon in the action bar.



Let's change the main resource, activity_main.xml:

 <android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/drawer_layout" android:layout_width="match_parent" android:layout_height="match_parent"> <!-- The main content view --> <FrameLayout android:id="@+id/content_frame" android:layout_width="match_parent" android:layout_height="match_parent" /> <!-- The navigation drawer --> <ListView android:id="@+id/left_drawer" android:layout_width="240dp" android:layout_height="match_parent" android:layout_gravity="start" android:choiceMode="singleChoice" android:divider="@android:color/transparent" android:dividerHeight="0dp" android:background="#111"/> </android.support.v4.widget.DrawerLayout> 


The side menu will be populated in the ListView, for this we add a string array of names to the string.xml:

 <string-array name="screen_array"> <item>Screen 1</item> <item>Screen 2</item> <item>Screen 3</item> </string-array> 


You need to determine what the position in ListView will look like. To do this, create a new resource in the layout folder with the name drawer_list_item.xml:

 <TextView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@android:id/text1" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@drawable/activated_background" android:gravity="center_vertical" android:minHeight="?attr/listPreferredItemHeightSmall" android:paddingLeft="16dp" android:paddingRight="16dp" android:textAppearance="?android:attr/textAppearanceMedium" android:textColor="#fff" /> 


For the operation of the created resource to work, we will additionally create in the res folder a new drawable folder and in it create the activated_background.xml selector, placing in it:

 <selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:drawable="@color/blue" android:state_activated="true"/> <item android:drawable="@color/blue" android:state_selected="true"/> <item android:drawable="@color/blue" android:state_pressed="true"/> <item android:drawable="@color/blue" android:state_checked="true"/> <item android:drawable="@android:color/transparent"/> </selector> 


In the values ​​folder, create a resource for the color.xml colors and place a color there that will be responsible for selecting the list item in the side menu:

 <?xml version="1.0" encoding="utf-8"?> <resources> <item name="blue" type="color">#FF33B5E5</item> </resources> 


The next step is to add icons to the application, namely the side menu icon. You can download the archive of icons via a direct link from the official Google website at: developer.android.com/downloads/design/Android_Design_Icons_20130926.zip . The archive will have a large number of icons for different events, but you need icons from the Navigation_Drawer_Indicator folder. Each graphic object with the name ic_drawer.png should be placed into the project with the correct drawable view type - ??? ..

To notify that the menu is open or closed, add more entries to string.xml:

 <string name="drawer_open">Open navigation drawer</string> <string name="drawer_close">Close navigation drawer</string> 


At the same time, we will delete the next line from the resources, since we will not need it anymore:

 <string name="hello_world">Hello world!</string> 


Main Activity


Now you need to rewrite the class MainActivity. To support old devices, we use the Android Support Library (v4), when creating a project, it is automatically added to the libs folder. There are comments in the listing that will give you the opportunity to understand how the code works. For more information, you can use the official documentation: developer.android.com/training/implementing-navigation/nav-drawer.html . Below is a listing.

Listing
 package com.nikosamples.develop.smp0001navigationdrawertabs; import com.nikosamples.develop.smp0001navigationdrawertabs.fragments.ScreenOne; import com.nikosamples.develop.smp0001navigationdrawertabs.fragments.ScreenThree; import com.nikosamples.develop.smp0001navigationdrawertabs.fragments.ScreenTwo; import android.content.res.Configuration; import android.os.Bundle; import android.support.v4.app.ActionBarDrawerToggle; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; import android.support.v4.widget.DrawerLayout; import android.support.v7.app.ActionBarActivity; import android.util.Log; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.ListView; import android.widget.Toast; public class MainActivity extends ActionBarActivity { private String[] mScreenTitles; private DrawerLayout mDrawerLayout; private ListView mDrawerList; private ActionBarDrawerToggle mDrawerToggle; private CharSequence mDrawerTitle; private CharSequence mTitle; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mTitle = mDrawerTitle = getTitle(); mScreenTitles = getResources().getStringArray(R.array.screen_array); mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout); mDrawerList = (ListView) findViewById(R.id.left_drawer); // Set the adapter for the list view mDrawerList.setAdapter(new ArrayAdapter<String>(this, R.layout.drawer_list_item, mScreenTitles)); // Set the list's click listener mDrawerList.setOnItemClickListener(new DrawerItemClickListener()); getSupportActionBar().setDisplayHomeAsUpEnabled(true); getSupportActionBar().setHomeButtonEnabled(true); mDrawerToggle = new ActionBarDrawerToggle( this, /* host Activity */ mDrawerLayout, /* DrawerLayout object */ R.drawable.ic_drawer, /* nav drawer icon to replace 'Up' caret */ R.string.drawer_open, /* "open drawer" description */ R.string.drawer_close /* "close drawer" description */ ) { /** Called when a drawer has settled in a completely closed state. */ public void onDrawerClosed(View view) { getSupportActionBar().setTitle(mTitle); supportInvalidateOptionsMenu(); // creates call to onPrepareOptionsMenu() } /** Called when a drawer has settled in a completely open state. */ public void onDrawerOpened(View drawerView) { getSupportActionBar().setTitle(mDrawerTitle); supportInvalidateOptionsMenu(); // creates call to onPrepareOptionsMenu() } }; // Set the drawer toggle as the DrawerListener mDrawerLayout.setDrawerListener(mDrawerToggle); // Initialize the first fragment when the application first loads. if (savedInstanceState == null) { selectItem(0); } } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.main, menu); return super.onCreateOptionsMenu(menu); } /* Called whenever we call invalidateOptionsMenu() */ @Override public boolean onPrepareOptionsMenu(Menu menu) { // If the nav drawer is open, hide action items related to the content view boolean drawerOpen = mDrawerLayout.isDrawerOpen(mDrawerList); menu.findItem(R.id.action_search).setVisible(!drawerOpen); return super.onPrepareOptionsMenu(menu); } @Override public boolean onOptionsItemSelected(MenuItem item) { // Pass the event to ActionBarDrawerToggle, if it returns // true, then it has handled the app icon touch event if (mDrawerToggle.onOptionsItemSelected(item)) { return true; } // Handle action buttons switch(item.getItemId()) { case R.id.action_search: // Show toast about click. Toast.makeText(this, R.string.action_search, Toast.LENGTH_SHORT).show(); return true; default: return super.onOptionsItemSelected(item); } } /* The click listener for ListView in the navigation drawer */ private class DrawerItemClickListener implements ListView.OnItemClickListener { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { selectItem(position); } } /** Swaps fragments in the main content view */ private void selectItem(int position) { // Update the main content by replacing fragments Fragment fragment = null; switch (position) { case 0: fragment = new ScreenOne(); break; case 1: fragment = new ScreenTwo(); break; case 2: fragment = new ScreenThree(); break; default: break; } // Insert the fragment by replacing any existing fragment if (fragment != null) { FragmentManager fragmentManager = getSupportFragmentManager(); fragmentManager.beginTransaction() .replace(R.id.content_frame, fragment).commit(); // Highlight the selected item, update the title, and close the drawer mDrawerList.setItemChecked(position, true); setTitle(mScreenTitles[position]); mDrawerLayout.closeDrawer(mDrawerList); } else { // Error Log.e(this.getClass().getName(), "Error. Fragment is not created"); } } @Override public void setTitle(CharSequence title) { mTitle = title; getSupportActionBar().setTitle(mTitle); } /** * When using the ActionBarDrawerToggle, you must call it during * onPostCreate() and onConfigurationChanged()... */ @Override protected void onPostCreate(Bundle savedInstanceState) { super.onPostCreate(savedInstanceState); // Sync the toggle state after onRestoreInstanceState has occurred. mDrawerToggle.syncState(); } @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); // Pass any configuration change to the drawer toggles mDrawerToggle.onConfigurationChanged(newConfig); } } 



Add fragments


Add a new fragments package that will contain fragments. And we put in it three classes of fragment. For example, I will lay out the code of one fragment, the rest must be done by analogy, changing the name of the class, constructor and markup.

 package com.nikosamples.develop.smp0001navigationdrawertabs.fragments; import android.os.Bundle; import android.support.v4.app.Fragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import com.nikosamples.develop.smp0001navigationdrawertabs.R; public class ScreenOne extends Fragment { public ScreenOne() { } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View rootView = inflater.inflate(R.layout.screen_first, container, false); return rootView; } } 


We also add markup for these classes to resources. For an example, I will lay out a marking of one fragment screen_one.xml. In the rest, you only need to change the text of the android: text attribute:

 <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" > <TextView android:id="@+id/textView1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerHorizontal="true" android:layout_centerVertical="true" android:text="@string/screen_one" android:textAppearance="?android:attr/textAppearanceMedium" /> </RelativeLayout> 


Add a few more lines to string.xml, which will inform you of which screen is open from the menu:

 <string name="screen_one">Screen one</string> <string name="screen_two">Screen two</string> <string name="screen_three">Screen three</string> 


At this stage, the implementation of the Navigation Drawer is completed. If you run the application, we will see the work of the side menu. Like on older devices:



So on new:



Tab implementation


Now proceed to adding tabs. First of all, in order for us to add tabs correctly, it is necessary to understand the principle of the arrangement of navigation elements. There is a strict hierarchy in which the tabs should be located:

0 - Navigation Drawer occupies the highest level of navigation.
1 - Action Bar second level.
2 - Tabs lower level.



For implementation, you must use two additional classes, which Google kindly provides again. These are the SlidingTabLayout and SlidingTabStrip classes . Add them to the project. To do this, create a new package view, create new classes with the appropriate name, and move the code into them. If errors occur in the createDefaultTabView (Context context) method of the SlidingTabLayout class, suppress the warning by writing over the @SuppressLint ("NewApi") method

We will make all new changes for the ScreenOne fragment. First we change the markup of screen_one.xml:

 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <com.nikosamples.develop.smp0001navigationdrawertabs.view.SlidingTabLayout android:id="@+id/sliding_tabs" android:layout_width="match_parent" android:layout_height="wrap_content" /> <android.support.v4.view.ViewPager android:id="@+id/viewpager" android:layout_width="match_parent" android:layout_height="0px" android:layout_weight="1" android:background="@android:color/white"/> </LinearLayout> 


It is important to use the full package name for SlidingTabLayout, as it is included in our project. Next, create a new markup in the layout folder for the pager_item.xml tabs:

 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" android:orientation="vertical" > <TextView android:id="@+id/item_subtitle" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/page"/> android:textAppearance="?android:attr/textAppearanceLarge" /> <TextView android:id="@+id/item_title" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="80sp" /> </LinearLayout> 


Let's enter string.xml and change the line:

 <string name="screen_one">Screen one</string> 


on

 <string name="page">Page:</string> 


Since we no longer need a string resource, we will immediately display the ViewPager with the tab number instead. Next, change the ScreenOne class accordingly:

ScreenOne class code
 package com.nikosamples.develop.smp0001navigationdrawertabs.fragments; import android.os.Bundle; import android.support.v4.app.Fragment; import android.support.v4.view.PagerAdapter; import android.support.v4.view.ViewPager; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; import com.nikosamples.develop.smp0001navigationdrawertabs.R; import com.nikosamples.develop.smp0001navigationdrawertabs.view.SlidingTabLayout; public class ScreenOne extends Fragment { private SlidingTabLayout mSlidingTabLayout; private ViewPager mViewPager; public ScreenOne() { } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View rootView = inflater.inflate(R.layout.screen_one, container, false); return rootView; } @Override public void onViewCreated(View view, Bundle savedInstanceState) { // Get the ViewPager and set it's PagerAdapter so that it can display items mViewPager = (ViewPager) view.findViewById(R.id.viewpager); mViewPager.setAdapter(new SamplePagerAdapter()); // Give the SlidingTabLayout the ViewPager, this must be // done AFTER the ViewPager has had it's PagerAdapter set. mSlidingTabLayout = (SlidingTabLayout) view.findViewById(R.id.sliding_tabs); mSlidingTabLayout.setViewPager(mViewPager); } // Adapter class SamplePagerAdapter extends PagerAdapter { /** * Return the number of pages to display */ @Override public int getCount() { return 10; } /** * Return true if the value returned from is the same object as the View * added to the ViewPager. */ @Override public boolean isViewFromObject(View view, Object o) { return o == view; } /** * Return the title of the item at position. This is important as what * this method returns is what is displayed in the SlidingTabLayout. */ @Override public CharSequence getPageTitle(int position) { return "Item " + (position + 1); } /** * Instantiate the View which should be displayed at position. Here we * inflate a layout from the apps resources and then change the text * view to signify the position. */ @Override public Object instantiateItem(ViewGroup container, int position) { // Inflate a new layout from our resources View view = getActivity().getLayoutInflater().inflate(R.layout.pager_item, container, false); // Add the newly created View to the ViewPager container.addView(view); // Retrieve a TextView from the inflated View, and update it's text TextView title = (TextView) view.findViewById(R.id.item_title); title.setText(String.valueOf(position + 1)); // Return the View return view; } /** * Destroy the item from the ViewPager. In our case this is simply * removing the View. */ @Override public void destroyItem(ViewGroup container, int position, Object object) { container.removeView((View) object); } } } 



Now you can start the application and see how the tabs work like on the old device:



So on the new:



You may notice that the sidebar covers the tabs, as in the rules of navigation and applications from Google:



This is a complete example and can be used.

Action Bar Tabs


I would like to mention one last, important point. Many "confused" implemented tabs with ActionBar tabs look like:



But their implementation is different, the behavior and in the horizontal orientation are transferred to the ActionBar:



If you add tabs via ActionBar, then the Navigation Drawer sidebar does not block the tabs, but leaves under them:



That's all. Thank you for your attention and pleasant coding.

Development Environment - Eclipse
The minimum version of Android -> = 8

Links


Dashboards
developer.android.com/about/dashboards/index.html?utm_source=ausdroid.net
Support library
developer.android.com/tools/support-library/index.html
Creating a Navigation Drawer
developer.android.com/training/implementing-navigation/nav-drawer.html
Android Design in Action: Navigation Anti-Patterns
plus.google.com/photos/+NickButcher/albums/5981768132040708401
SlidingTabsBasic
developer.android.com/samples/SlidingTabsBasic/index.html
Action bar
developer.android.com/guide/topics/ui/actionbar.html

You can download the finished example from GitHub.

Source: https://habr.com/ru/post/236567/


All Articles