Showing posts with label options menu. Show all posts
Showing posts with label options menu. Show all posts

Saturday, August 15, 2015

Custom options menu in Android application.

First of all I need to say, that I was cunning with title. This post is not about menu customization. It's about creating pseudo-menu.
So, requirement was to create screen-wide menu with icons. Also from image you can see, that menu appears below toolbar and doesn't overlay it.

Of course I googled this question. And appears, that Google want us to keep menu grey, flat and simple. Here is interesting article about this problem:
Android: How can you Implement a Custom Menu class.
I can't resist to quote it:
My question is to Google engineers.  Why?  Why is this so?  Why can’t I pass in a custom Theme to my Menus?  Why can’t I choose how to Style them?  Yes, I can understand you not wanting some devious developer taking control of a phone by disabling the actions of all the phone’s buttons but the Menu button?
My idea is pretty simple. Instead of menu I use Fragment, that I show in every activity on press on menu icon. I use Relative layout to place this MenuFragment below Toolbar. And yes, I use Toolbar from support library instead of action bar. This gives more flexibility.

But let's explore some code.

 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
   android:layout_width="match_parent"  
   android:layout_height="match_parent">  
   <include  
     android:id="@+id/app_bar"  
     layout="@layout/app_bar" />  
   <fragment xmlns:tools="http://schemas.android.com/tools"  
     android:id="@+id/fragment"  
     android:name="com.shakenbeer.customoptionsmenu.MainActivityFragment"  
     android:layout_width="match_parent"  
     android:layout_height="match_parent"  
     android:layout_below="@id/app_bar"  
     tools:layout="@layout/fragment_main" />  
   <FrameLayout  
     android:id="@+id/fragment_menu_container"  
     android:layout_width="match_parent"  
     android:layout_height="wrap_content"  
     android:layout_below="@id/app_bar"></FrameLayout>  
 </RelativeLayout>  

This is how activity could look like. I used the same layout for every activity in my project. FrameLayout is a container for menu.
Also here is BaseActivity classm that contains logic for calling menu and choosing menu item:
public class BaseActivity extends AppCompatActivity implements MenuFragment.MenuListener {
    protected Toolbar toolbar;

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

        toolbar = (Toolbar) findViewById(R.id.app_bar);
        setSupportActionBar(toolbar);
        getSupportActionBar().setDisplayShowTitleEnabled(true);
        getSupportActionBar().setDisplayShowHomeEnabled(true);
        getSupportActionBar().setElevation(0f);
    }

    @Override    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.menu, menu);
        return true;
    }

    @Override    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case R.id.action_menu: {
                triggerFragmentMenu();
                return true;
            }
        }
        return super.onOptionsItemSelected(item);
    }

    private void triggerFragmentMenu() {
        FragmentManager fragmentManager = getFragmentManager();
        FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
        Fragment menuFragment = fragmentManager.findFragmentById(R.id.fragment_menu_container);
        if (menuFragment == null) {
            MenuFragment fragment = MenuFragment.newInstance();
            fragmentTransaction.replace(R.id.fragment_menu_container, fragment);
        } else {
            fragmentTransaction.remove(menuFragment);
        }
        fragmentTransaction.commit();
    }

    @Override    public void onMenuItemSelected(int menuPosition) {
        removeMenuFragment();
        MenuContent.MenuItem menuItem = MenuContent.ITEMS.get(menuPosition);
        Intent intent = new Intent(this, menuItem.activityClass);
        startActivity(intent);
    }

    @Override    public void onBackPressed() {
        if (!removeMenuFragment())
            super.onBackPressed();
    }

    private boolean removeMenuFragment() {
        FragmentManager fragmentManager = getFragmentManager();
        Fragment menuFragment = fragmentManager.findFragmentById(R.id.fragment_menu_container);
        if (menuFragment != null) {
            FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
            fragmentTransaction.remove(menuFragment);
            fragmentTransaction.commit();
            return true;
        } else {
            return false;
        }
    }}

Menu contains only one item and this item is always shown as action. This simple trick removes standard overflow menu icon from toolbar.

When we click on menu icon - we add MenuFragment to activity or remove it if it's already here.

When we click on menu item - we remove MenuFragment and start corresponding activity.
Here is sources of MenuFragment:
 public class MenuFragment extends Fragment {

    public interface MenuListener {
        public void onMenuItemSelected(int menuPosition);
    }

    private MenuListener listener;

    public static MenuFragment newInstance() {
        MenuFragment fragment = new MenuFragment();
        return fragment;
    }

    public MenuFragment() {
        // Required empty public constructor    }

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

    @Override    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_menu, container, false);

        ListView menuListView = (ListView) view.findViewById(R.id.fragment_menu_listview);

        menuListView.setAdapter(new MenuAdapter(getActivity()));

        menuListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override 
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                listener.onMenuItemSelected(position);
            }
        });

        return view;
    }

    @Override    public void onAttach(Activity activity) {
        super.onAttach(activity);
        try {
            listener = (MenuListener) activity;
        } catch (ClassCastException e) {
            throw new ClassCastException(activity.toString()
                    + " must implement MenuListener");
        }
    }
} 



MenuFrament contains MenuListener, any activity should implements it and could override BaseActivity implementation do perform whatever you want. Starting new activity from menu - it's just an example.

Full sources: https://github.com/Shakenbeer/CustomOptionsMenu