5

I had an application with quite a few activities before it was decided that we will be using a Navigation Drawer or a hamburger menu. I did not want to redo the whole app using fragments so I decided to go with the approach used in this answer: Same Navigation Drawer in different Activities

EDIT: And now, this one https://stackoverflow.com/a/23477100/1371585

And I created a base activity called NavDrawerBaseActivity. Here is the code:

    public class NavDrawerBaseActivity extends MyBaseActivity {

    public DrawerLayout mNavDrawerLayout;
    public ListView mDrawerList;
    public String[] mMenuItems;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        setContentView(R.layout.navdrawer_activity);

        mNavDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
        mMenuItems = getResources().getStringArray(R.array.optionsmenu_array);
        mDrawerList = (ListView) findViewById(R.id.left_drawer);

        ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
                R.layout.drawer_list_item, mMenuItems);
        mDrawerList.setAdapter(adapter);
        mDrawerList.setOnItemClickListener(new DrawerItemClickListener(this));

        super.onCreate(savedInstanceState);
    }

    private class DrawerItemClickListener implements
            ListView.OnItemClickListener {

        private DrawerItemClickListener(Context context) {
            mContext = context;
        }

        @Override
        public void onItemClick(AdapterView<?> parent, View view, int position,
                long id) {

            String textClicked = ((TextView) view).getText().toString();
            view.setSelected(true);

            if (textClicked.equalsIgnoreCase(mContext
                    .getString(R.string.optionsmenu_library))) {


                Intent libraryIntent = new Intent(mContext,
                        LibraryActivity.class);
                libraryIntent.putExtra("navdrawerposition", position);
                startActivity(libraryIntent);
                mNavDrawerLayout.closeDrawers();
                mDrawerList.setItemChecked(position, true); //Not working


            } else if (textClicked.equalsIgnoreCase(mContext
                    .getString(R.string.optionsmenu_settings))) {
                // TODO: open activity and close the drawer
            } else if (textClicked.equalsIgnoreCase(mContext
                    .getString(R.string.optionsmenu_logout))) {
                // TODO: open activity and close the drawer
            } 
        }

        private Context mContext;
    }
}

Here is the layout file navdrawer_activity.xml

    <?xml version="1.0" encoding="utf-8"?>
<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
        android:id="@+id/activity_frame"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent" />

    <!-- The navigation drawer -->

    <ListView
        android:id="@+id/left_drawer"
        android:layout_width="240dp"
        android:layout_height="match_parent"
        android:layout_gravity="start"
        android:background="#111"
        android:choiceMode="singleChoice"
        android:divider="@android:color/transparent"
        android:dividerHeight="0dp" 
        />


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

Every activity extends the NavDrawerBaseActivity and does not use setContentView, like so:

 public class LibraryActivity extends NavDrawerBaseActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Not setting content view here, since its already set in
        // NavDrawerBaseActivity
        FrameLayout frameLayout = (FrameLayout) findViewById(R.id.activity_frame);
        // Inflating the Camera activity layout
        LayoutInflater layoutInflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        View activityView = layoutInflater
                .inflate(R.layout.library_page, null, false);
        // Adding the custom layout of this activity to frame layout set in
        // NavDrawerBaseActivity.
        frameLayout.addView(activityView);


//      Only this part of the code is doing what I want.

//      int drawerSelectedPosition = getIntent().getIntExtra(mNavDrawerPosExtraName, -1);
//      if(drawerSelectedPosition > -1){
//          mDrawerList.setItemChecked(drawerSelectedPosition, true);
//      }
    }

}

My problem: How do I correctly highlight the current activity in the NavDrawer View? The mDrawerList.setItemChecked(position, true); before launching Intent or after launching it is not working.

The weird part is: If I am currently in Activity1, Open the NavDrawer and select Activity2. I land in Activity2, open the NavDrawer and see that "Activity2" is not selected. I click the Back button, land in Activity1, open the NavDrawer and see that "Activity2" is selected.

Which means setItemChecked works, but not in the new activity that gets launched.

Currently I am passing the position as an Intent extra and specifically setting the checked position, like the commented section in LibraryActivity. This works but seems like a work around. Please tell me if there is a correct/better way of doing that in NavDrawerBaseActivity class instead of in each Activity that extends it.

Community
  • 1
  • 1
ForeverNoob
  • 141
  • 1
  • 14

1 Answers1

1

Do not put a nav drawer in every activity, this defeats the purpose of extending the NavDrawerBaseActivity class. Because all your other activities extend this base class they should automatically inherit all its functionality. Hence, only put the drawer in the NavDrawerBaseActivity. Then, in your xml for the drawer you can specify each buttons action like:

<Button
        style="@style/drawerBtn"
        android:id="@+id/activity1Btn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:onClick="onClick">

Or simply set an onClick listener on your drawer, something like:

myDrawer.setOnClickListener().........etc

Then in your onClick handler (which is inside the NavDrawerBaseActivity class) you can simply check which button was pressed and open the associated activity, something like:

//remember, if you used the onClickListener you have to use @Override at the top of this function
public void onClick(View item) {

    //lets see which button on the drawer was pressed....item is the item that triggered the click
    switch (item.getId()) {
        case R.id.activity1Btn:
            Intent intent1 = new Intent(this, Activity1.class);
            startActivity(intent1);
            break;
        case R.id.activity2Btn:
            Intent intent2 = new Intent(this, Activity2.class);
            startActivity(intent2);
            break;
        case R.id.activity3Btn:
            Intent intent3 = new Intent(this, Activity3.class);
            startActivity(intent3);
            break;
    }
}

Remember that Activity1, Activity2, Activity3 will have to extend NavDrawerBaseActivity which then either have to extend Activity or extend another class which in turn extends Activity...... and so on and so forth. You can then also set things like mDrawerList.setItemChecked(position, true) in this switch. So in short, make all drawer things happen in this class only and simply "implement" this class by extending it, remember this class contains all functionality that is common across all the "child" classes/activities, they all inherit this behaviour

EDIT:

If you want to highlight the items in your drawer and you must rather use setSelected(true) on your item. You can define custom selection states if you want to by creating a selection style in your drawables folder. You then set this style as the background of your list items example:

<!-- drawable/myStyles.xml-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_enabled="true" android:state_pressed="true" android:drawable="@color/blue"/> <!--item selected state--> 
    <item android:state_enabled="true" android:state_focused="true" android:drawable="@color/blue"/> <!--item selected state--> 
    <item android:state_enabled="true" android:state_selected="true" android:drawable="@color/blue"/> <!--item selected state--> 
    <item android:state_focused="false" android:state_pressed="false" android:drawable="@color/gray"/> <!--item NOT selected state--> 
</selector>

and your drawer item:

<LinearLayout
    android:id="@+id/my_drawer_item"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="fill_parent"
    android:background="@drawable/myStyles">

Then in your switch/if-else statement you set the selected item as myNewSelectedView.setSelected(true). You might have to manually deselect the old one like myOldSelectedItem.setSelected(false)

Then click/selected listener where you have you switch/if-else statement:

 @Override
public void onItemClick(AdapterView<?> parent, View view, int position,long id) {
     view.setSelected(true)  

     //....the rest of your code here
}

Lastly I recommend you try this first as this will be the easiest route to follow if you use a normal listView:

<ListView android:id="@+id/my_list"
    android:choiceMode="singleChoice" 
    android:listSelector="@android:color/blue" />

EDIT2:

Now to retain the selected items state...So it seems that each action on the drawer reinstantiates the base class which isnt ideal. We need to somehow retain the instance state so it acts almost as a singleton. I've tried overriding the the onSaveInstanceState and using the singleInstance launcher state but they did not work. So, for the interim I've come up with the solution of saving the current selection in memory as a static variable:

private static int mCurrentSelectionIndex = 0; //this is defined at the top of your class with your default selected screen ie the first item in the list.

//then in setContentView after assigning the drawer layout you set the currentSelection
@Override
public void setContentView(final int layoutResID) {
    //...... first assign the layouts ie mDrawerList = findViewByLayout(R.id.myDrawerList) etc

    mDrawerList.setSelection(mCurrentSelectionIndex); 
      // OR:
    mDrawerList.setItemChecked(mCurrentSelectionIndex, true);
}

//then in onClick()
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
    mCurrentSelection = position;
    item.setSelected(true);
}
Chris
  • 4,593
  • 1
  • 33
  • 37
  • Which XML has the drawer? NavDrawerBaseActivity is never seen by a user and hence does not have a layout. So (say) Activity1 that extends NavDrawerBaseActivity is supposed to have ` ` Right? – ForeverNoob Dec 05 '14 at 13:23
  • Ahh, here is where you are wrong.... The user does see NavDrawerBaseActivity... because it is your navigation drawer! :) So essentially your Activity1, Actvity2 etc must ONLY extend NavDrawerBaseActivity AND THATS ALL. It should have absolutely nothing further to do with any form of Drawer code in xml or android code, ALL of that should be inside of NavDrawerBaseActivity, so if NavDrawerBaseActivity does not have a layout you have to create one with the drawer inside of it just like you currently do in Activity1 etc... Remember, ALL code relating to drawer must ONLY be in NavDrawerBaseActivity – Chris Dec 05 '14 at 13:50
  • Let me know if you dont understand so I can try to explain clearer or edit my answer. Do try to understand what happens with inheretance and what happens when you extend a class. Any class that extends a Base class gets that Base classes behaviours/looks etc AS WELL AS its own... – Chris Dec 05 '14 at 13:51
  • Ok, so you're saying I need to have a layout.xml for NavDrawerBaseActivity as well as a different one for Activity1. So I should call `setContentView` in the `onCreate` methods of both? Won't the latest call of `setContentView` decide the final layout? Inheritance, AFAIK, does not combine the _layout xmls_ of both NavDrawerBaseActivity as well as Activity1. – ForeverNoob Dec 08 '14 at 04:54
  • Please take another look at the code I've posted. `NavDrawerBaseActivity` already has implemented what you suggested in your answer. The method `NavDrawerBaseActivity.DrawerItemClickListener.onItemClick` has a switch-like if-else construct where I am trying to do the `mDrawerList.setItemChecked(position, true);` before launching the new activity. It is not working (and hence commented out). The NavDrawer in the new activity has nothing selected. – ForeverNoob Dec 08 '14 at 06:08
  • I think I got what you mean. I referred the accepted answer on this question - http://stackoverflow.com/questions/23476645/android-navigation-drawer-implemented-with-activities But still, the `setItemChecked` is not working in the if-else construct before or after launching the new activity. – ForeverNoob Dec 08 '14 at 07:30
  • Ok please see the info I have added – Chris Dec 08 '14 at 07:59
  • I tried everything you suggested. 1. Have the drawer xml in the NavDrawerBaseActivity and have everything else extend it. 2. Used `view.setSelected(true)` in onItemClick. 3. Changed xml of the ListView. 4. Created a custom selector. Nothing works. Shall I update my question with my current code? – ForeverNoob Dec 08 '14 at 09:54
  • Yes, i think that would be best so we can take it from there and see what we can do – Chris Dec 08 '14 at 13:59
  • Hi, I updated the question. Also, I found that `setItemChecked` works without custom selector styles, so I am using the default selector. I've described when it works in the answer. – ForeverNoob Dec 09 '14 at 07:01
  • ok see my edit, I've found a workaround but I dont like it much. It seems like the drawer base class instance is not retained but rather recreated each time it is used. my edit will explain how to bypass this – Chris Dec 09 '14 at 11:38
  • This works better than my workaround. Please update if you find the right way of doing it, I'll keep looking for a better solution too. Thank you. – ForeverNoob Dec 10 '14 at 04:41
  • Great stuff, glad we could get it to work! Will definitely have a proper look into it over the weekend and let you know if I find anything of value. – Chris Dec 10 '14 at 06:52
  • I just realised this won't update when the back button is clicked :-( – ForeverNoob Dec 12 '14 at 09:52
  • oh dear. my first thought is to override the onBack() method inside the drawer class and from there update the drawer state. you might have to create a second static variable to hold the previousSelectedIndex... does this make sense? – Chris Dec 12 '14 at 10:38
  • What if the user keeps clicking the Back button? S/he'll be stuck between CurrentSelectedItem and PreviousSelectedItem. – ForeverNoob Dec 15 '14 at 09:04