Robin Chutaux

"Think out of the box"

How to create a custom actionbar

Posted on 26 August 2014.

Manage actionbar might be very difficult when you want to have some custom behavior. If you want to change the color, the visibility or to add animations with a standard actionbar it can be done in a very simple, by following a few steps and by combining some tricks with the Otto library. Do not forget that this tutorial is made for applications using API 14+.

Setup Otto library

Otto is an event bus designed to decouple different parts of your application while still allowing them to communicate efficiently.
Forked from Guava, Otto adds unique functionality to an already refined event bus as well as specializing it to the Android platform.

In fact Otto may be compared to the event bus in Objectiv-c. With it you can send a message, here the message is an object that is send to the method which has subscribed to the bus . Each method will be called if its context still exists. Your activity or your fragments have to register to the bus, if the activity or the fragments are still alive, the methods which subscribe will be called but if the fragments or the activity are not visible, the message will be lost and there will be no NPE. After this we have finish the boring part and we can go to the fun one :)

The first thing to do is to add Otto library to your build.gradle

1
compile 'com.squareup:otto:+'

Next, you will have to create a new class for override Application class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class App extends Application
{
    private static Bus bus;

    @Override
    public void onCreate()
    {
        super.onCreate();

        bus = new Bus(); // Instantiate a new Bus
    }

    public static Bus getBus()
    {
        return bus;
    }
}

Do not forget to specify that your application has to use this new class as main application.

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"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.application" >

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme"
        android:name=".app.App"> <!-- This line specify that we have to use the new class -->

        <activity
            android:name=".ui.activity.MainActivity"
            android:label="@string/app_name"
            android:windowSoftInputMode="adjustPan"
            android:screenOrientation="portrait">

            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

Now you can use the new Bus instance. In your activity/fragment you can register, post events and subscribe, the only thing you need to create is a custom actionbar. On Otto you can find another annotations like: @Produce but you don’t need it for this tutorial ;)

Fragments and views

First, we need to create a custom view for the actionbar with its layout. It will be a Relativelayout with several Imageviews and a textview for title. This is the view_actionbar.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
<?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="?android:actionBarSize"
                android:background="#34495e">

    <ImageView
        android:id="@+id/view_actionbar_first"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@android:drawable/ic_menu_camera"
        android:layout_centerVertical="true"
        android:layout_alignParentRight="true"
        android:layout_margin="5dp"/>

    <ImageView
        android:id="@+id/view_actionbar_second"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@android:drawable/ic_menu_preferences"
        android:layout_centerVertical="true"
        android:layout_toLeftOf="@+id/view_actionbar_first"
        android:layout_margin="5dp"/>

    <TextView
        android:id="@+id/view_actionbar_title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerVertical="true"
        android:text="AwesomeApp"
        android:textColor="@android:color/white"
        android:layout_marginLeft="30dp"
        android:textSize="20sp"/>
</RelativeLayout>

We have a great layout for the actionbar, you can add whatever you want (and remember that this is just an example ;). Now we can create the class that inherits from RelativeLayout which has this layout.

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
public class ActionbarView extends RelativeLayout
{
    private ImageView firstImageView;
    private ImageView secondImageView;
    private TextView titleTextView;

    public ActionbarView(Context context)
    {
        super(context);
        init(context);
    }

    public ActionbarView(Context context, AttributeSet attrs)
    {
        super(context, attrs);
        init(context);
    }

    public ActionbarView(Context context, AttributeSet attrs, int defStyle)
    {
        super(context, attrs, defStyle);
        init(context);
    }

    private void init(final Context context)
    {
        final View rootView = inflate(context, R.layout.view_actionbar, this);
        firstImageView = (ImageView) rootView.findViewById(R.id.view_actionbar_first);
        secondImageView = (ImageView) rootView.findViewById(R.id.view_actionbar_second);
        titleTextView = (TextView) rootView.findViewById(R.id.view_actionbar_title);
    }
}

Now you have a complete custom view and you can customize the custom actionbar as you like it. Next, you have to use this layout, in the main layout, above the framelayout that contains all your fragments.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
             xmlns:tools="http://schemas.android.com/tools"
             android:layout_width="match_parent"
             android:layout_height="match_parent">

    <com.example.myapplication.view.ActionbarView
        android:id="@+id/activity_actionbar"
        android:layout_width="match_parent"
        android:layout_height="?android:actionBarSize"/>

    <FrameLayout
        android:id="@+id/container"
        android:layout_below="@+id/activity_actionbar"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity"
        tools:ignore="MergeRootFrame" />

</RelativeLayout>

In Android Studio you can see, on the right, an actionbar, just above the main framelayout. Now there are two more steps to follow and this will be done ;) You have to put this layout in your main activity and you have to connect the actionbar to the activity.

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
public class MainActivity extends Activity {

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

    @Override
    protected void onStart()
    {
        super.onStart();
        App.getBus().register(this); // Here we register this activity in bus.
    }

    @Override
    protected void onDestroy()
    {
        super.onDestroy();
        App.getBus().unregister(this); // Here we unregister this acitivity from the bus.
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();
        if (id == R.id.action_settings) {
            return true;
        }
        return super.onOptionsItemSelected(item);
    }
}

In the OnStart and in the OnDestroy methods we register and unregister from the bus. You have to do it in every class that has a subscribing method. Also you have to add it into the fragments and into the activity, because only the current class is added to the bus. Your message in Otto is a class, also for each type of message we need a different class. For this example I created 2 dummie classes which are FirstItemClicked and SecondItemClicked. Now we just have to send messages from our custom view to our activity/fragment, if your fragment has its own actionbar. Also you have to add this code in your init method in your custom view.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
firstImageView.setOnClickListener(new OnClickListener()
{
    @Override
    public void onClick(View v)
    {
        App.getBus().post(new FirstItemClicked());
    }
});

secondImageView.setOnClickListener(new OnClickListener()
{
    @Override
    public void onClick(View v)
    {
        App.getBus().post(new SecondItemClicked());
    }
});

Finally you just have to subscribe to this class if you want to be notified.

1
2
3
4
5
6
7
8
9
10
11
@Subscribe
public void OnFirstItemClicked(FirstItemClicked firstItemClicked)
{
    Log.d("FirstItem", "Clicked !!");
}

@Subscribe
public void OnSecondItemClicked(SecondItemClicked secondItemClicked)
{
    Log.d("SecondItem", "Clicked !!");
}

You can run your project and TA-DAH ! :) You have a custom actionbar, that you can change as you want, and the certitude that you will never get a NPE. If you want you can add in paramater of your dummie class constructor some value and get it in your subscribe method ;)

Comments