Robin Chutaux

"Think out of the box"

How to create a menu like Hello SMS

Posted on 27 August 2014.

HelloSMS is an awesome app with a design concept that I like very much ! So I wanted to know how to create the same menu and after some digging I found a way to do it. It was a little bit difficult so I will explain here how to achieve the same menu with interaction.

In this tutorial you will use supportV13, supportV4 is great but if you want to create an app for API 14+ you don’t need fragment from supportV4. Also with the supportV13 you can use the same layout and widget available in supportV4 with the fragment from the app package. I advise you to use if you can the fragment from app package.

You can find an example on Github if you have any problem to understand how to achieve this great layout :).

Custom view

To create this menu from HelloSMS app you need to use a widget from support V4 but you want to use fragment from app package also you need to add some library to your build.gradle. Moreover you will need to have Otto library to manage states of our menu.

1
2
compile 'com.squareup:otto:+'
compile 'com.android.support:support-v13:18.+'

After sync gradle files to fetch these libraries, you will start to create the custom view which will manage your two framelayout. Before that you need to set the width of the framelayout that will always be visible and the maximum width that you want to expand your left menu. In dimens.xml you have to add this two values,

1
2
<dimen name="menu_width_closed">70dp</dimen>
<dimen name="menu_width_expand">170dp</dimen>

Next, create a new class called SlidingPanel.java that extends SlidingPaneLayout and implements SlidingPaneLayout.PanelSlideListener. This class is your custom view; you don’t need a layout file like in my other post.

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
public class SlidingPanel extends SlidingPaneLayout implements SlidingPaneLayout.PanelSlideListener
{
    private Integer menuWidthClosed;
    private Integer menuWidthExpand;

    public SlidingPanel(Context context)
    {
        super(context);
        init();
        setPanelSlideListener(this);
    }

    public SlidingPanel(Context context, AttributeSet attrs)
    {
        super(context, attrs);
        init();
        setPanelSlideListener(this);
    }

    public SlidingPanel(Context context, AttributeSet attrs, int defStyle)
    {
        super(context, attrs, defStyle);
        init();
        setPanelSlideListener(this);
    }

    private void init()
    {
        menuWidthClosed = getResources().getDimensionPixelOffset(R.dimen.menu_width_closed);
        menuWidthExpand = getResources().getDimensionPixelOffset(R.dimen.menu_width_expand);

        setSliderFadeColor(getResources().getColor(android.R.color.transparent));
    }

    @Override
    public void onPanelSlide(View panel, float slideOffset)
    {
        Log.e("Menu offset", "" + slideOffset);
    }

    @Override
    public void onPanelOpened(View panel)
    {
        Log.e("Is menu opened ?", "Yeah !");
    }

    @Override
    public void onPanelClosed(View panel)
    {
        Log.e("Is menu opened ?", "Nooo !");
    }
}

I had some Logs to know the state of my menu. Moreover you can see that I retrieve values from our dimens file with getDimensionPixelOffset that convert your value from dp to pixel. Now you need to know where the user click to open or not the menu because if you have buttons on the visible part of your menu, users can click on it without opening your menu but you want your users to be able to open it so you need two variables: the first one to know where the user clicks and slides and the second one to know if the menu is open or not.

1
2
private Boolean isOpened = false;
private float actiondown = 0;

Here you might say “Hey! How can I know when users are clicking or sliding my menu ?”. Don’t worry you can override onInterceptTouchEvent and manage all interactions in it. Also you just have to set actiondown when the user touch the menu and check if he is sliding it or not.

1
2
3
4
5
6
7
8
9
10
11
12
13
@Override
public boolean onInterceptTouchEvent(MotionEvent ev)
{
    if (ev.getAction() == MotionEvent.ACTION_DOWN)
    {
        actiondown = ev.getX();
        return super.onInterceptTouchEvent(ev);
    }

    return (ev.getAction() == MotionEvent.ACTION_MOVE &&
          ((isOpened && actiondown > ev.getX() && actiondown > menuWidthExpand) ||
          (!isOpened && actiondown < ev.getX() && actiondown < menuWidthClosed)));
}

Here you check if user are sliding menu and in which way. If he just click down or up nothing happens. The next part is a bonus: you don’t need to have a basic sliding menu but it can be great to be notified when your menu is opened or closed or the offset of your menu, if you want to add an animation.

Bonus

You have to create three classes : OnDragMenu that takes a float in parameter of its constructor, and OnPanelStateChanged that take a boolean of its constructor. You will use them with Otto to send a message through the event bus. So your three overridden methods will look like this

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Override
public void onPanelSlide(View panel, float slideOffset)
{
    Log.e("Offset", "" + slideOffset);
    App.getBus().post(new OnDragMenu(slideOffset));
}

@Override
public void onPanelOpened(View panel)
{
    Log.e("panel", "opened");
    App.getBus().post(new OnPanelStateChanged(true));
    isOpened = true; // Mandatory to know the slidingpanel state
}

@Override
public void onPanelClosed(View panel)
{
    Log.e("panel", "closed");
    isOpened = false; // Mandatory to know the slidingpanel state
    App.getBus().post(new OnPanelStateChanged(false));
}

Now you have to handle these messages, you have to create methods taken one of these classes in parameters. Don’t forget to register your class in the event bus otherwise your message will be lost.

Main layout

This part is very easy to understand and to code. You just have to create a new variable in dimens.xml that will be the maximum width of your menu when it’s open.

1
<dimen name="menu_width_opened">240dp</dimen>

You can see that this value is the sum of two previous dimens. Now, you can create your main layout with our two framelayout into our SlidingPanelLayout

1
2
3
4
5
6
7
8
9
10
11
12
13
<FrameLayout
    android:id="@+id/activity_container_left"
    android:layout_width="240dp"
    android:layout_height="match_parent"
    android:layout_gravity="left"/>

<FrameLayout
    android:id="@+id/activity_container_right"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_marginLeft="@dimen/menu_width_closed"
    android:layout_gravity="left"
    android:background="@color/light_green"/>

You have all the pieces to create a slidingPanel like Hello SMS. Fill free to post a comment if there is some ambiguous parts :D

Comments