Ojonugwa Jude Ochalifu
Ojonugwa Jude Ochalifu

Reputation: 27257

Switch between Fragments with onNavigationItemSelected in new Navigation Drawer Activity template (Android Studio 1.4 onward)

IntelliJ has made changes to the Navigation Drawer template Activity in Android Studio with fewer lines of code in the Activity class. The new Activity class looks like this:

public class MainActivity extends AppCompatActivity
    implements NavigationView.OnNavigationItemSelectedListener {

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
    setSupportActionBar(toolbar);

    FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
    fab.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
                    .setAction("Action", null).show();
        }
    });

    DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
    ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(
            this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close);
    drawer.setDrawerListener(toggle);
    toggle.syncState();

    NavigationView navigationView = (NavigationView) findViewById(R.id.nav_view);
    navigationView.setNavigationItemSelectedListener(this);
}

@Override
public void onBackPressed() {
    DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
    if (drawer.isDrawerOpen(GravityCompat.START)) {
        drawer.closeDrawer(GravityCompat.START);
    } else {
        super.onBackPressed();
    }
}


@SuppressWarnings("StatementWithEmptyBody")
@Override
public boolean onNavigationItemSelected(MenuItem item) {
    // Handle navigation view item clicks here.
    int id = item.getItemId();

    if (id == R.id.nav_camara) {
        // Handle the camera action
    } else if (id == R.id.nav_gallery) {

    } else if (id == R.id.nav_slideshow) {

    } else if (id == R.id.nav_manage) {

    } else if (id == R.id.nav_share) {

    } else if (id == R.id.nav_send) {

    }

    DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
    drawer.closeDrawer(GravityCompat.START);
    return true;
}
}

One of the most notable change here is the method:

onNavigationItemSelected(MenuItem item)

The old Navigation Drawer template's definition of this method was:

onNavigationItemSelected(int position, long itemId)

You could modify that old template by deleting the inner PlaceHolderFragment class, creating your own fragments and layouts and doing something like this:

Fragment fragment = null;
switch (position) {
    case 0:
        fragment = new FragmentA();
        break;
    case 1:
        fragment = new FragmentB();
        break;
    default:
        break;
}

if (fragment != null) {
    FragmentManager fragmentManager = getSupportFragmentManager();
    fragmentManager.beginTransaction()
            .replace(R.id.frame_container, fragment).commit();

}

But this doesn't work with the new template (at least not from my little knowledge). I have tried:

 public boolean onNavigationItemSelected(MenuItem item) {
    // Handle navigation view item clicks here.
    int id = item.getItemId();
    Snackbar snackbar = Snackbar.make(findViewById(android.R.id.content), item.getTitle() + " clicked", Snackbar.LENGTH_SHORT);
    Fragment fragment = null;
    switch (id) {
        case R.id.nav_home:
            fragment = HomeFragment.getFragInstance();
            break;

        case R.id.nav_news:

            fragment = NewsFragment.getFragInstance();
            break;


        default:
            break;
    }

    if (fragment != null) {
        FragmentTransaction transaction = fragmentManager.beginTransaction();
        transaction.addToBackStack(null);
        transaction.replace(R.id.drawer_layout, fragment);
        transaction.commit();


        DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
        drawer.closeDrawer(GravityCompat.START);

    }
    return true;
}

but the layout for the home layout is also shown in the news layout. This is probably happening because of the line:

transaction.replace(R.id.drawer_layout, fragment);

Fragments are supposed to be replaced in a FrameLayout and the old Navigation Drawer layout looked like this:

<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">

<!-- Framelayout to display Fragments -->
<FrameLayout
    android:id="@+id/frame_container"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

<!-- Listview to display slider menu -->


<ListView
    android:id="@+id/list_sliderMenu"
    android:layout_width="240dp"
    android:layout_height="match_parent"
    android:layout_gravity="start"
    android:choiceMode="singleChoice"
    android:divider="@color/white"
    android:dividerHeight="1dp"
    android:listSelector="@drawable/list_selector"
    android:background="@color/list_background"/>

But the new one looks like this:

 <?xml version="1.0" encoding="utf-8"?>
  <android.support.v4.widget.DrawerLayout      
   xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:app="http://schemas.android.com/apk/res-auto"
   xmlns:tools="http://schemas.android.com/tools"
   android:id="@+id/drawer_layout"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   android:fitsSystemWindows="true"
   tools:openDrawer="start">

<include
    layout="@layout/app_bar_base"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

<android.support.design.widget.NavigationView
    android:id="@+id/nav_view"
    android:layout_width="wrap_content"
    android:layout_height="match_parent"
    android:layout_gravity="start"
    android:fitsSystemWindows="true"
    app:headerLayout="@layout/nav_header_base"
    app:menu="@menu/activity_base_drawer" />

    </android.support.v4.widget.DrawerLayout>

Long story short, how can one modify the new template to be able to switch between Fragments?

Upvotes: 44

Views: 90924

Answers (6)

Mahbubur Rahman Khan
Mahbubur Rahman Khan

Reputation: 415

Try to remove previous fragment or remove all history than add fresh fragment.

//Here we are clearing back stack fragment entries
int backStackEntry = getSupportFragmentManager().getBackStackEntryCount();
if (backStackEntry > 0) {
    for (int i = 0; i < backStackEntry; i++) {
        getSupportFragmentManager().popBackStackImmediate();
    }
}

//Here we are removing all the fragment that are shown here
if (getSupportFragmentManager().getFragments() != null && getSupportFragmentManager().getFragments().size() > 0) {
    for (int i = 0; i < getSupportFragmentManager().getFragments().size(); i++) {
        Fragment mFragment = getSupportFragmentManager().getFragments().get(i);
        if (mFragment != null) {
            getSupportFragmentManager().beginTransaction().remove(mFragment).commit();
        }
    }
}

More help from here

Upvotes: 1

L.L.
L.L.

Reputation: 679

You should not change the DrawerLayout, you only need to add a frame in the "content_main.xml".

Follow the steps below:

  1. open the "content_main.xml" file located in the "layout" folder.

  2. use the code below:

    <?xml version="1.0" encoding="utf-8"?> 
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior" tools:showIn="@layout/app_bar_main"
        tools:context=".MainActivity">    
        <FrameLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent" android:id="@+id/mainFrame">
        </FrameLayout>   
    </RelativeLayout>
    
  3. go to the onNavigationItemSelected method:

    public boolean onNavigationItemSelected(MenuItem item) {
      int id = item.getItemId();
      Fragment fragment;
    
    if (id == R.id.nav_camara) {
        fragment = new YourFragment();
        FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
        ft.replace(R.id.mainFrame, fragment);
        ft.commit();
    }
    else if (id == R.id.nav_gallery) {
    
    }
    else if (id == R.id.nav_slideshow) {
    
    }
    else if (id == R.id.nav_manage) {
    
    } else if (id == R.id.nav_share) {
    
    } else if (id == R.id.nav_send) {
    
    }
    
    DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
    drawer.closeDrawer(GravityCompat.START);
    return true;
    }
    

Upvotes: 17

Ojonugwa Jude Ochalifu
Ojonugwa Jude Ochalifu

Reputation: 27257

So, based on @L.L.'s answer I was able to solve this problem.

First of all, add your FrameLayout to your content_main.xml file:

<FrameLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent" android:id="@+id/content_frame"/>

In your MainActivity (or whatever you have named the Activity with the navigation drawer) define a method named displayView

 public void displayView(int viewId) {

    Fragment fragment = null;
    String title = getString(R.string.app_name);

    switch (viewId) {
        case R.id.nav_news:
            fragment = new NewsFragment();
            title  = "News";

            break;
        case R.id.nav_events:
            fragment = new EventsFragment();
            title = "Events";
            break;

    }

    if (fragment != null) {
        FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
        ft.replace(R.id.content_frame, fragment);
        ft.commit();
    }

    // set the toolbar title
    if (getSupportActionBar() != null) {
        getSupportActionBar().setTitle(title);
    }

    DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
    drawer.closeDrawer(GravityCompat.START);

}

Am switching between 3 custom Fragments; NewsFragment, EventsFragment and GalleryFragment.

in my menu activity_main_drawer I have changed the content to this:

<menu xmlns:android="http://schemas.android.com/apk/res/android">

<group android:checkableBehavior="single">
    <item android:id="@+id/nav_news"  
        android:icon="@android:drawable/ic_menu_news"
        android:title="News" />
    <item android:id="@+id/nav_events" 
        android:icon="@android:drawable/ic_menu_events"
        android:title="Events" />
    <item android:id="@+id/nav_gallery" 
        android:icon="@android:drawable/ic_menu_gallery"
        android:title="Gallery" />
  </group>
</menu>

Going back to the Activity class, in your onNavigationItemSelected method do this:

@Override
public boolean onNavigationItemSelected(MenuItem item) {
    displayView(item.getItemId());
    return true;
}

Finally, the last statement in your onCreate method:

 @Override
protected void onCreate(Bundle savedInstanceState) {
    ....
    ....
    displayView(R.id.nav_news);
}

This is because I want the first view my user sees to be News.Change it to whatever you choose.

Handle back press event:

As it stands, if you press the back button from any of the Fragments, the app exits.I want my app to go back to the News Fragment (my home fragment) when the user presses the back button. So I did this:

Declared a boolean variable:

private boolean viewIsAtHome;

then in the displayView() method I did this:

 public void displayView(int viewId){
    Fragment fragment = null;
    String title = getString(R.string.app_name);

    switch (viewId) {
        case R.id.nav_news:
            fragment = new NewsFragment();
            title  = getString(R.string.news_title);
            viewIsAtHome = true;

            break;
        case R.id.nav_events:
            fragment = new EventsFragment();
            title = getString(R.string.events_title);
            viewIsAtHome = false;
            break;

        case R.id.nav_gallery:
            fragment = new GalleryFragment();
            title = getString(R.string.gallery_title);
            viewIsAtHome = false;
            break;

Finally, delete your old onBackPressed method and create a new one like this outside the onCreate() method:

@Override
public void onBackPressed() {
    DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
    if (drawer.isDrawerOpen(GravityCompat.START)) {
        drawer.closeDrawer(GravityCompat.START);
    }
    if (!viewIsAtHome) { //if the current view is not the News fragment
        displayView(R.id.nav_news); //display the News fragment
    } else {
        moveTaskToBack(true);  //If view is in News fragment, exit application
    }
}

Works for me.

Upvotes: 85

Gaurav Karia
Gaurav Karia

Reputation: 176

another way is:

public boolean onNavigationItemSelected(MenuItem item) {
    // Handle navigation view item clicks here.
    Fragment fragment;
    int id = item.getItemId();

    if (id == R.id.nav_camera) {
        // Handle the camera action
        fragment = new BlankFragment();
        FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
        ft.replace(R.id.container_new, fragment);
        ft.commit();
    } else if (id == R.id.nav_gallery) {
        fragment = new HorizontalView();
        FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
        ft.replace(R.id.container_new,fragment);
        ft.commit();
    } else if (id == R.id.nav_slideshow) {
        fragment = new FragmentVerticleView();
        FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
        ft.replace(R.id.container_new,fragment);
        ft.commit();

    } else if (id == R.id.nav_manage) {

    } else if (id == R.id.nav_share) {

    } else if (id == R.id.nav_send) {

    }

    DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
    drawer.closeDrawer(GravityCompat.START);
    return true;
}

And then you'll need to implemts all the fragments to your activity. In my case it's

MainActivity.java

    public class MainActivity extends AppCompatActivity
    implements BlankFragment.OnFragmentInteractionListener, HorizontalView.OnFragmentInteractionListener,FragmentVerticleView.OnFragmentInteractionListener,OnNavigationItemSelectedListener
     { 
    //coding stuff
    }

And to handle the Exception that is Thrown in fragment java file

    "must implement OnFragmentInteractionListener"

you simply add below method

      public void    onFragmentInteraction(Uri uri){
    //We can keep this empty
}

And there you go! All set

Thanks to this Tutorial to strike this

Upvotes: 4

Jean Y.C. Yang
Jean Y.C. Yang

Reputation: 4542

I used a template auto-created by Android Studio 1.4 too, and I had encounter same difficulties as you.

First, I changed activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" android:id="@+id/drawer_layout"
android:layout_width="match_parent" android:layout_height="match_parent"
android:fitsSystemWindows="true" tools:openDrawer="start">

<!--Main-->
<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
    <!-- The ActionBar -->
    <include
        layout="@layout/toolbar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
    <FrameLayout
        android:id="@+id/content"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    </FrameLayout>
</LinearLayout>

<!--Drawer-->
<android.support.design.widget.NavigationView
    android:id="@+id/nav_view"
    android:layout_width="wrap_content"
    android:layout_height="match_parent"
    android:layout_gravity="start"
    android:fitsSystemWindows="true"
    app:headerLayout="@layout/nav_header"
    app:menu="@menu/drawer" /></android.support.v4.widget.DrawerLayout>

A DrawerLayout that contains two parts:

  1. Main Screen (LinearLayout)
  2. the Drawer (NavigationView)

Main Screen contains the ActionBar and the FrameLayout (R.id.content) will be replaced by a fragment. The Drawer contains the header and the menu.

So, we now override onNavigationItemSelected and replace R.id.content with a fragment.

Here is my MainActivity code:

    @Override
public boolean onNavigationItemSelected(MenuItem item) {
    Fragment fragment;
    FragmentTransaction ft = getFragmentManager().beginTransaction();
    // Handle navigation view item clicks here.
    int id = item.getItemId();

    if (id == R.id.nav_A) {
        fragment = new AFragment();
        ft.replace(R.id.content, fragment).commit();        
    } else if (id == R.id.nav_B){
        fragment = new BFragment();
        ft.replace(R.id.content, fragment).commit();
    } else if (id == R.id.nav_settings) {
        Intent intent = new Intent(MainActivity.this, SettingsActivity.class);
        startActivity(intent);
    }

    DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
    drawer.closeDrawer(GravityCompat.START);
    return true;
}

Main page is also a fragment. And I set up main page in onCreate method although I am not sure if there is a better way to set up main page.

//MainPageFragment
    FragmentTransaction ft = getFragmentManager().beginTransaction();
    ft.replace(R.id.content, new MainPageFragment()).commit();

PS. here is the toolbar.xml, the ActionBar

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
android:layout_height="match_parent" android:fitsSystemWindows="true"
tools:context="jean.yang.MainActivity">
<android.support.design.widget.AppBarLayout android:layout_height="wrap_content"
    android:layout_width="match_parent" android:theme="@style/AppTheme.AppBarOverlay">
    <android.support.v7.widget.Toolbar android:id="@+id/toolbar"
        android:layout_width="match_parent" android:layout_height="?attr/actionBarSize"
        android:background="?attr/colorPrimary" app:popupTheme="@style/AppTheme.PopupOverlay" />
</android.support.design.widget.AppBarLayout>
</android.support.design.widget.CoordinatorLayout>

Upvotes: 0

jaibatrik
jaibatrik

Reputation: 7533

Not sure if I'll be able to help, but if you want to modify the layout templates for NavigationDrawer Activity, you can find it in <Path\to\Program_Files>\Android\Android Studio\plugins\android\lib\templates\activities\NavigationDrawerActivity\root\res\layout

I believe, it will be best to have a layout similar to the following -

<!-- A DrawerLayout is intended to be used as the top-level content view using match_parent for both width and height to consume the full space available. -->
<android.support.v4.widget.DrawerLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/drawer_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="${relativePackage}.${activityClass}">

    <!-- As the main content view, the view below consumes the entire
         space available using match_parent in both dimensions. -->
    <FrameLayout
        android:id="@+id/container"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <!-- android:layout_gravity="start" tells DrawerLayout to treat
         this as a sliding drawer on the left side for left-to-right
         languages and on the right side for right-to-left languages.
         If you're not building against API 17 or higher, use
         android:layout_gravity="left" instead. -->
    <!-- The drawer is given a fixed width in dp and extends the full height of
         the container. -->
    <NavigationView android:id="@+id/navigation_drawer"
        android:layout_width="@dimen/navigation_drawer_width"
        android:layout_height="match_parent"
        android:layout_gravity="<#if buildApi gte 17>start<#else>left</#if>" />

</android.support.v4.widget.DrawerLayout>

And this will work with the way you were replacing the Fragments in R.id.container earlier.

Upvotes: 0

Related Questions