Creating a Google TV Sliding Navigation Menu
The standard design pattern for Google TV apps is generally to use the LeftNavBar library. Unfortunately there isn’t an apklib and it isn’t included as a google tv addon library, so one has to compile and work with the library itself. The LeftNavBar library is basically the action bar flipped vertically.
The problem I have with it it is that the bar is always visible on the screen, and it doesn’t implement the Drawer Layout pattern, which lets the menu slide all the way in. It allows shows the icons that are available and it is always visible on the screen. So, I’ve created an alternative that will be used with Serenity starting with 1.4.0. This uses the MenuDrawer library, and the following custom layout.
<?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" android:background="#50000000" > <LinearLayout android:id="@+id/title_bar_layout" android:layout_width="match_parent" android:layout_height="59dp" android:background="@android:color/background_dark" android:gravity="center_vertical" android:paddingLeft="8dip" android:paddingRight="8dip" > <ImageView android:id="@+id/left_icon" android:layout_width="36dip" android:layout_height="36dip" android:layout_marginRight="8dip" android:scaleType="fitCenter" android:src="@drawable/serenity_bonsai_logo_small" android:visibility="visible" /> <LinearLayout android:layout_width="0dip" android:layout_height="59dp" android:layout_marginRight="8dip" android:layout_weight="1" android:gravity="center_vertical" android:orientation="vertical" > <TextView android:id="@+id/title" android:layout_width="match_parent" android:layout_height="0dip" android:layout_weight="1" android:ellipsize="end" android:gravity="center_vertical" android:singleLine="true" android:text="@string/app_label" /> <TextView android:id="@+id/subtitle" android:layout_width="match_parent" android:layout_height="wrap_content" android:ellipsize="end" android:singleLine="true" android:visibility="gone" /> </LinearLayout> <ProgressBar android:id="@+id/progress_circular" style="?android:attr/progressBarStyle" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginRight="8dip" android:max="10000" android:visibility="gone" /> <ImageView android:id="@+id/right_icon" android:layout_width="36dip" android:layout_height="36dip" android:layout_marginRight="8dip" android:scaleType="fitCenter" android:visibility="gone" /> </LinearLayout> <ListView android:id="@+id/menu_list_options" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_below="@+id/title_bar_layout" android:descendantFocusability="afterDescendants" android:focusable="false" android:nextFocusDown="@+id/menu_help_text" android:nextFocusUp="@+id/menu_settings" android:scrollingCache="true" > </ListView> <LinearLayout android:id="@+id/menu_help" android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center_vertical" android:orientation="horizontal" android:layout_above="@+id/menu_settings" android:layout_alignParentBottom="true" android:visibility="visible" android:addStatesFromChildren="true" > <TextView android:id="@+id/menu_help_text" android:layout_width="0dp" style="@android:style/TextAppearance.Holo.Medium" android:text="Help" android:textColor="@android:color/white" android:layout_height="wrap_content" android:layout_weight="1" android:gravity="center_vertical" android:background="@drawable/menu_item_selector" android:drawableLeft="@drawable/ic_action_action_help" android:focusable="true" android:focusableInTouchMode="true" android:nextFocusUp="@+id/menu_list_options" android:clickable="true" /> </LinearLayout> <LinearLayout android:id="@+id/menu_settings" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@android:color/background_dark" android:gravity="center_vertical" android:orientation="horizontal" android:layout_alignParentBottom="true" android:focusable="true" android:focusableInTouchMode="true" android:visibility="gone" > <TextView android:id="@+id/menu_settings_text" android:layout_width="0dp" style="@android:style/TextAppearance.Holo.Medium" android:text="Help" android:textColor="@android:color/white" android:layout_height="wrap_content" android:layout_weight="1" android:gravity="center_vertical" android:background="@drawable/menu_item_selector" android:drawableLeft="@drawable/leftnav_bar_option_icon_normal_dark" android:focusable="true" android:focusableInTouchMode="true" android:nextFocusUp="@+id/menu_options" android:clickable="true" /> </LinearLayout> </RelativeLayout>
This borrows a few design elements from the Google TV LeftNavBar (mainly the app header, progress items, and the Settings option in the menu). The rest contains a ListView named menu_list_options, which can be used to populate a list of options for the menu. In may case I use a TextView CompoundDrawable to create the options with their icons. You can populate the list however you want, and if you combine MenuDrawer with say SlidingMenu you could implement the multilevel sliding drawer that you see in apps like YouTube.
The results are similar to the following:
By tying things further into the Google TV remote’s Menu key, and an on screen Drawer icon that is clickable, you can have the drawer slide out when needed and be hidden when it isn’t. It is important to make sure that users have some sort of visual indicator like standard 3 dots or dashes you see in the ActionBar on most apps.
To tie this into an activity is similar to the following:
protected void createSideMenu() { mainContext = this; menuDrawer = MenuDrawer.attach(this, MenuDrawer.Type.OVERLAY); menuDrawer.setMenuView(R.layout.menu_drawer); menuDrawer.setContentView(R.layout.activity_plex_app_main); menuDrawer.setDrawerIndicatorEnabled(true); View menuButton = findViewById(R.id.menu_button); menuButton.setOnClickListener(new MenuDrawerOnClickListener(menuDrawer)); populateMenuOptions(); } protected void populateMenuOptions() { List<MenuDrawerItem> drawerMenuItem = new ArrayList<MenuDrawerItem>(); drawerMenuItem.add(new MenuDrawerItemImpl(getResources().getString(R.string.options_main_about), R.drawable.ic_action_action_about)); drawerMenuItem.add(new MenuDrawerItemImpl(getResources().getString(R.string.options_main_clear_image_cache), R.drawable.ic_action_content_remove)); drawerMenuItem.add(new MenuDrawerItemImpl(getResources().getString(R.string.tutorial), R.drawable.ic_action_tutorial)); drawerMenuItem.add(new MenuDrawerItemImpl("Empty Video Queue", R.drawable.ic_action_content_remove)); ListView listView = (ListView)menuDrawer.getMenuView().findViewById(R.id.menu_list_options); hideMenuItems(listView); listView.setAdapter(new MenuDrawerAdapter(this, drawerMenuItem)); listView.setOnItemClickListener(new MainMenuDrawerOnItemClickedListener(menuDrawer, mainGallery)); }
If you want to tie this into the Google TV Menu key, here is a sample:
if (keyCode == KeyEvent.KEYCODE_MENU && !menuDrawer.isMenuVisible()) { menuDrawer.toggleMenu(); return true; } if (keyCode == KeyEvent.KEYCODE_BACK && menuDrawer.isMenuVisible()) { menuDrawer.toggleMenu(); return true; }
In general using MenuDrawer vs SlidingMenu I found the former to be easier to use and worked well with the remote control. I experienced focus issues when trying to use SlidingMenu with a remote control, and not so with MenuDrawer. One thing to note, with MenuDrawer layouts is that items in the drawer even when they are not visible can gain focus with the D-Pad navigation. So make sure you are using the nextFocus* options in your layouts to help control things. Also you may want to hide options like the menu_list_options view when it isn’t actually showing.
So there you have it, a basic navigation drawer which hides when it isn’t needed, and can be made to slide out when it is. With a TV experience, screen space is at a premium, so freeing up much as possible can be a necessary thing at times.