MyFlexRadio App Released on the Android Market

No Comments

MyFlexRadio has been published on the Android Market and is officially affiliated with MyFlexRadio.com. Go get it!

MyFlexRadio
MyFlexRadio

Lock Screen
Lock Screen Replacement

Custom Menu Bar (tabs) – How to hook the menu button to show/hide a custom tab bar

No Comments

I want to begin this tutorial by crediting Josh Clemm for the underlying Custom Tabs implementation found in this example. The way he accomplished customization is very clean and the best method I have seen to date.

The subject of this tutorial is how to create your own custom menu bar that will be shown and hidden when the user clicks the menu button on his/her device. There are many other uses for this example and it just isn’t limited to a menu or tab bar. For example, you can use this to show and hide a panel that contains whatever content you want to be shown. It could be used for contextual help or MediaPlayer controls, etc.

Custom Menu Panel
Custom Menu Bar

The basis of this example is the layout. I have found that the RelativeLayout is superior for creating great layouts that perform well across devices. If you want to learn more about what a RelativeLayout can do for you I highly recommend that you check out Working with Multiple Android Screens in the Motorola Android Technical Library. For the purpose of this tutorial… I haven’t followed everything that I should have as far as creating a good cross device application by defining my layouts using dip (Density Independent Pixels)… but this is just an example. Now let’s get to it.

The main layout for the application contains 2 RelativeLayouts, a TextView, a TabHost, a TabWidget and a View. In particular, I want to point out the layout_alignParentBottom attribute of the panel RelativeLayout. This attribute is one of 4 separate attributes that allow you to place a RelativeLayout on any side of the parent layout. I also want to bring your attentions to the visibility attribute which allows us to set the layout as hidden. Using “gone” makes the layout to be completely hidden, as if the view had not been added.

/res/layout/main.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:background="@drawable/background"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">
    <TextView
        android:background="#000000"
        android:id="@+id/content"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="">
    </TextView>    
    <RelativeLayout
        android:layout_alignParentBottom="true"
        android:id="@+id/menuPopup"
        android:background="@drawable/tl_bg"
        android:layout_width="fill_parent"
        android:layout_height="90px"
        android:visibility="gone">
            <TabHost
                android:id="@android:id/tabhost"
                android:layout_width="fill_parent"
                android:layout_height="fill_parent">
            <LinearLayout
                android:orientation="vertical"
                android:layout_width="fill_parent"
                android:layout_height="fill_parent">
                <View
                    android:layout_width="fill_parent"
                    android:layout_height="0.5dip"
                    android:background="#000">
                </View>
                <TabWidget
                    android:id="@android:id/tabs"
                    android:layout_width="fill_parent"
                    android:layout_height="fill_parent"
                    android:layout_marginLeft="0dip"
                    android:layout_marginRight="0dip">
                </TabWidget>
                <View
                    android:layout_width="fill_parent"
                    android:layout_height="2dip"
                    android:background="#696969">
                </View>
                <View
                    android:layout_width="fill_parent"
                    android:layout_height="2dip"
                    android:background="#000">
                </View>
                <FrameLayout
                    android:id="@android:id/tabcontent"
                    android:layout_width="fill_parent"
                    android:layout_height="fill_parent">
                </FrameLayout>
            </LinearLayout>
        </TabHost>
    </RelativeLayout>
</RelativeLayout>

The second layout is for each individual tab. I am not going to go into how to customize the tabs in this tutorial. You can check out Josh Clemm’s tutorial from the link above to learn more.
/res/layout/tabs_layout.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/tabsLayout"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:background="@drawable/tab_bg_selector"
    android:padding="5px"
    android:gravity="center"
    android:orientation="vertical">
    <ImageView
        android:id="@+id/tabsIcon"
        android:layout_width="36px"
        android:layout_height="36px">
    </ImageView>
    <TextView
        android:id="@+id/tabsText"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="5px"
        android:text=""
        android:textSize="12px"
        android:textColor="@drawable/tab_text_selector">
    </TextView>
</LinearLayout>

The first activity is called CustomMenuBar.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
package com.androidworkz.example.custommenubar;

/* Proper credit where credit is due
 * The custom tabs implementation contained within is adapted from an example
 * by Josh Clemm @ http://joshclemm.com/blog/?p=136
 * His custom tabs example is also located at http://code.google.com/p/android-custom-tabs/
 * The icons are from an icon pack that I found some time ago and can't remember where
 */


import android.app.TabActivity;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TabHost;
import android.widget.TabHost.TabContentFactory;
import android.widget.TabHost.TabSpec;
import android.widget.TextView;

public class CustomMenuBar extends TabActivity {
   
    private TabHost mTabHost;
    private static RelativeLayout mMenuPanel;
    private TextView content;

    private final static String WEBSITE = "http://www.androidworkz.com";
   
    // This isn't necessary but it makes it nice to determine which tab I am on in the switch statement below
    private static class TabItem {
        public final static int SEARCH = 0;
        public final static int SHARE = 1;
        public final static int WEBSITE = 2;
        public final static int SETTINGS = 3;
        public final static int QUIT = 4;
    }
   
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        setupViews();
    }
   
    private void setupViews() {
       
        content = (TextView) findViewById(R.id.content);
        content.setText(getString(R.string.search));
       
        mMenuPanel = ((RelativeLayout) findViewById(R.id.menuPopup));
        mMenuPanel.setVisibility(View.GONE);
       
        mTabHost = (TabHost) findViewById(android.R.id.tabhost);
        mTabHost.setup();
       
        mTabHost.getTabWidget().setDividerDrawable(R.drawable.tab_divider);
       
        addActivityTab(new TextView(this), "Search", R.drawable.search, new Intent(CustomMenuBar.this, Search.class));
        addActivityTab(new TextView(this), "Share", R.drawable.heart, new Intent(CustomMenuBar.this, Share.class));
        addMethodTab(new TextView(this), "Website", R.drawable.globe);
        addActivityTab(new TextView(this), "Settings", R.drawable.tools, new Intent(CustomMenuBar.this, Settings.class));
        addMethodTab(new TextView(this), "Quit", R.drawable.power);

        mTabHost.setOnTabChangedListener(new TabHost.OnTabChangeListener() {
            @Override
            public void onTabChanged(String arg0) {
                if (mMenuPanel != null) {
                    if (mMenuPanel.getVisibility() == View.VISIBLE) {                  
                        toggleMenu();
                    }
                    switch (mTabHost.getCurrentTab()) {
                        case TabItem.SEARCH:
                            content.setText(getString(R.string.search));
                            break;
                        case TabItem.SHARE:
                            content.setText(getString(R.string.share));
                            break;
                        case TabItem.WEBSITE:
                            content.setText(getString(R.string.website));
                            final Intent visit = new Intent(Intent.ACTION_VIEW);
                            visit.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                            visit.setData(android.net.Uri.parse(WEBSITE));
                           
                            // Use a thread so that the menu is responsive when clicked
                            new Thread(new Runnable() {
                                public void run() {
                                    startActivity(visit);
                                }
                            }).start();                
                            break;
                        case TabItem.SETTINGS:
                            content.setText(getString(R.string.settings));
                            break;
                        case TabItem.QUIT:
                            content.setText(getString(R.string.quit));
                            new Thread(new Runnable() {
                                public void run() {                        
                                    CustomMenuBar.this.finish();
                                   
                                    // The following makes the Android Gods frown upon me
                                    android.os.Process.killProcess(android.os.Process.myPid());
                                    System.exit(0);
                                }
                            }).start();
                            break;
                        default:
                            break;
                    }
                   
                    // Handle click on currently selected tab - hide menu bar
                    // IMPORTANT: This listener has to appear AFTER the tabs are added
                    // Unfortunately, This doesn't work when the current tab contains an activity (except for tab 0)
                    // If you only have method tabs then it works perfect
                    mTabHost.getTabWidget().getChildAt(mTabHost.getCurrentTab()).setOnClickListener(new View.OnClickListener() {
                       
                        @Override
                        public void onClick(View v) {
                            toggleMenu();
                        }
                    });
                   
                    //if you want to reset the current tab
                    // mTabHost.setCurrentTab(0);
                }
            }
        });
    }
   
    // Use this method to add an activity or intent to the tab bar
    private void addActivityTab(final View view, final String tag, int iconResource, Intent intent) {
        View tabview = createTabView(mTabHost.getContext(), tag, iconResource);
         
        TabSpec setContent = mTabHost.newTabSpec(tag).setIndicator(tabview)
                .setContent(intent);
        mTabHost.addTab(setContent);

    }
   
    // Use this method if you only want the tab to execute a method
    private void addMethodTab(final View view, final String tag, int iconResource) {
        View tabview = createTabView(mTabHost.getContext(), tag, iconResource);

        TabSpec setContent = mTabHost.newTabSpec(tag).setIndicator(tabview)
                .setContent(new TabContentFactory() {
                    public View createTabContent(String tag) {
                        return view;
                    }
                });
        mTabHost.addTab(setContent);

    }
   
    private static View createTabView(final Context context, final String text,
            int iconResource) {
        View view = LayoutInflater.from(context)
                .inflate(R.layout.tabs_layout, null);
        TextView tv = (TextView) view.findViewById(R.id.tabsText);
        tv.setText(text);

        ImageView icon = (ImageView) view.findViewById(R.id.tabsIcon);
        icon.setImageResource(iconResource);

        return view;
    }
   
    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if (keyCode == KeyEvent.KEYCODE_MENU) {
            toggleMenu();
            return true;
        } else {
            return super.onKeyDown(keyCode, event);
        }
       
    }
   
    public static void toggleMenu() {
        if (mMenuPanel.getVisibility() == View.GONE) {
            mMenuPanel.setVisibility(View.VISIBLE);
        } else {
            mMenuPanel.setVisibility(View.GONE);
        }      
    }
}

Here is one of the example activities that are set as content for the activity tabs. I will only publish this one because they are identical.
Search.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package com.androidworkz.example.custommenubar;

import android.app.Activity;
import android.os.Bundle;
import android.view.KeyEvent;
import android.widget.TextView;

public class Search extends Activity {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setupViews();      
    }
   
    private void setupViews() {
        /* Search Tab Content */
        TextView textView = new TextView(this);
        textView.setText(getString(R.string.search));
        setContentView(textView);
    }
   
    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if (keyCode == KeyEvent.KEYCODE_MENU) {
            CustomMenuBar.toggleMenu();
            return true;
        } else {
            return super.onKeyDown(keyCode, event);
        }      
    }
}

The code in CustomMenuBar.java is commented and you should be able to follow what it does… I am going to concentrate on the primary subject of this tutorial which is hooking the menu button and displaying and hiding the menu panel. First, it is very easy to hook the menu button. This is accomplished by overriding the onKeyDown method of the activity and catching the KeyEvent for the menu button. If you look at the KeyEvent page in the SDK documentation you can see that there are many KeyEvent constants that allow you to hook almost any key.

1
2
3
4
5
6
7
8
9
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
    if (keyCode == KeyEvent.KEYCODE_MENU) {
        toggleMenu(); // show or hide the menu as required
        return true;
    } else {
        return super.onKeyDown(keyCode, event);
    }  
}

The next important part is the toggleMenu()_ method which just sets the visibility of the panel. Notice that the modifier of the method is static. This allows us to call this method from another activity. I will go into more detail below.

1
2
3
4
5
6
7
public static void toggleMenu() {
    if (mMenuPanel.getVisibility() == View.GONE) {
        mMenuPanel.setVisibility(View.VISIBLE);
    } else {
        mMenuPanel.setVisibility(View.GONE);
    }      
}

Now look at Search.java above. You will notice that it also has the Overriden onKeyDown method. This is because only the currently active Activity can catch KeyEvents. So we have to Override that method in each of the activities that we are using. The difference in these activities is that they don’t have access to the panel… only CustomMenuBar has access to it… so the workaround to be able to toggle the menu is to call the method directly in CustomMenuBar. This is the static call I mentioned above. Notice that in Search.java I am calling toggleMenu() like CustomMenuBar.toggleMenu();.

1
2
3
4
5
6
7
8
9
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
    if (keyCode == KeyEvent.KEYCODE_MENU) {
        CustomMenuBar.toggleMenu();
        return true;
    } else {
        return super.onKeyDown(keyCode, event);
    }      
}

I hope that this tutorial helps you design a great app. You can download a complete Eclipse Project for this tutorial here: CustomMenuBar.zip

Android Market Changes

No Comments

The Android Market is now available on the Web. Users can now find and share information about apps from their favorite browser, then purchase and download them over-the-air to their Android-powered devices. They also introduced a new feature called Buyer’s Currency that lets you set prices for your apps separately in each of the currencies on Android Market. They are deploying this feature country-by-country over the next few months, starting with developers in the U.S. When the feature is available to you, they will notify you by email. In-app Billing is also now available on Android Market.