Saturday, December 20, 2014

Using the Android Toolbar Widget

    Introduced with Android Lollipop, the Toolbar is meant to be a replacement for the ActionBar when additional customization is required. In this tutorial I will go over some of the basic customizations for the Toolbar and how to customize it with some styles. All code for this tutorial can be found on GitHub.

    The first thing we're going to want to do is import the AppCompat library in build.gradle, as this'll provide the support version of the Toolbar back to Android API 7.

compile 'com.android.support:appcompat-v7:21.0.3'

    Once we have the library imported, we can add the Toolbar to our activity layout file. One of the biggest advantages to using the Toolbar widget is that you can place the view anywhere in your layout. For this example we'll just keep it at the top of the layout, like the standard ActionBar. One thing to note is that the Toolbar extends ViewGroup, so the layout file can also contain additional views if needed.

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_height="wrap_content"
        android:layout_width="match_parent"
        style="@style/AppTheme.Toolbar" />

    <TextView
        android:id="@+id/content"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/hello_world"/>

</LinearLayout>

    Now that the activity layout is built, we need to adjust the theme used for the activity to exclude the default ActionBar, as it is not compatible with a Toolbar. This same theme can be used to customize some general features of the Toolbar, such as the background color, text color and overflow popup theme. The following styles will provide us with a green Toolbar and overflow dropdown, white text and white overflow button color.

    <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">

        <!-- Used for Toolbar background color -->
        <item name="colorPrimary">#005500</item>
        <!-- Toolbar + Overflow menu text color -->
        <item name="android:textColorPrimary">#FFFFFF</item>
        <!-- Overflow menu button color -->
        <item name="android:textColorSecondary">#FFFFFF</item>

    </style>

    <style name="AppTheme.Toolbar" parent="Widget.AppCompat.Toolbar">
        <item name="android:background">?attr/colorPrimary</item>
        <item name="android:minHeight">?attr/actionBarSize</item>
        <item name="popupTheme">@style/AppTheme.ActionBar.Popup</item>
    </style>

    <!-- Overflow drop down style -->
    <style name="AppTheme.ActionBar.Popup" parent="Widget.AppCompat.Light.PopupMenu.Overflow">
        <item name="android:background">?attr/colorPrimary</item>
    </style>


    With the layout and styles applied, we can start applying the logic behind our Toolbar. To start, we declare the Toolbar and a custom view that will be added to the Toolbar, in this case an AnalogClock (mainly because it's really obvious/visible for this example), at the top of our activity

    private Toolbar mToolbar;
    private AnalogClock mCustomView;

    After the ContentView has been set to our layout file, we can initialize the Toolbar and call setSupportActionBar() to assign the Toolbar to our activity.

        mToolbar = (Toolbar) findViewById( R.id.toolbar );
        setSupportActionBar( mToolbar );

    The overflow menu is populated in the same way that you would populate the ActionBar, by inflating a menu xml file in onCreateOptionsMenu() and handling the actions in onOptionsItemSelected(). For this example we'll use the actions in the overflow menu to call methods for customizing the Toolbar.


    From this point, the rest of the Toolbar actions are fairly straight forward. The Toolbar title, subtitle and logo can be set with a few method calls from getSupportActionBar().

    private void showCustomTitleAndSubtitle() {
        getSupportActionBar().setTitle( "Custom Title");
        getSupportActionBar().setSubtitle( "subtitle" );
        getSupportActionBar().setDisplayShowTitleEnabled( true );
    }

    private void hideTitleAndSubtitle() {
        getSupportActionBar().setDisplayShowTitleEnabled( false );
    }

    private void showDefaultLogo() {
        getSupportActionBar().setLogo( R.drawable.ic_launcher );
        getSupportActionBar().setDisplayUseLogoEnabled( true );
    }

    private void hideLogo() {
        getSupportActionBar().setDisplayUseLogoEnabled( false );
    }


     Adding and removing a custom view to the Toolbar is equally straight forward. The biggest thing to remember is that we need to attempt to remove the custom view from our Toolbar first, otherwise the app will crash if we attempt to add the same view to the Toolbar more than once. Once we're certain that the custom view is not attached to our Toolbar, we can the view and our Toolbar will automatically update.

    private void showCustomView() {
        if( mCustomView == null )
            mCustomView = (AnalogClock) LayoutInflater.from( this ).inflate( R.layout.ab_clock, null );

        mToolbar.removeView( mCustomView );
        mToolbar.addView( mCustomView );
    }

    private void hideCustomView() {
        mToolbar.removeView( mCustomView );
        setSupportActionBar( mToolbar );
    }


    With that, we have a Toolbar that is easily customizable, able to be placed anywhere in our layout and easily styled to fit our app color scheme.

Tuesday, December 9, 2014

Building Data Driven Hierarchical Views

    One common scenario for applications is displaying a UI that is driven by event data. In this tutorial I will go over a very useful technique that I work with almost daily, which I learned from one of the incredibly talented engineers that taught me early on, for building multiple custom views that can be used in various layouts and updated by passing data to one container view per layout. All code for this tutorial can be found on GitHub.

    The first thing we need to do for this example is create an interface that will be used by all of our updateable views. This interface only has one method, update, which is used by our views in order to update their state.

public interface Updateable {
    public void update( Weather weather );
}

    Next we'll implement a simple data model to house all of our data that will drive the views

public class Weather {
    private int temperature;
    private int windSpeed;
    private WindDirection windDirection;
    private WeatherCondition condition;

    public Weather( int temperature, int windSpeed, WindDirection windDirection, WeatherCondition condition ) {
        this.temperature = temperature;
        this.windSpeed = windSpeed;
        this.windDirection = windDirection;
        this.condition = condition;

    }

    public void setTemperature( int temperature ) {
        this.temperature = temperature;
    }

    public int getTemperature() {
        return temperature;
    }
...

    where WindDirection and WeatherCondition simply are enums defined like so

public enum WeatherCondition {
    RAIN,
    SNOW,
    LIGHTNING,
    SUN,
    CLOUDY,
    FOG
}

    Now that we have our data models and interface set up, it's time to start creating the updateable views for our project. To start, we'll go over the standard child views, in this case modified TextView and ImageViews, and then we'll move on to the container view that triggers the state update in all of its child views. Our first example of an updateable view is WeatherImage, which simply implements our Updateable interface and extends ImageView.

public class WeatherImage extends ImageView implements Updateable

    In the update method that comes from the Updateable interface, we check the Weather object for data pertaining to the weather condition, and then display an image based on that data.

@Override
public void update( Weather weather ) {
    Log.e( "WeatherImage", "update!" );
    if( weather == null || weather.getWeatherCondition() == null )
        return;

    switch( weather.getWeatherCondition() ) {
        case CLOUDY: {
            setImageResource( R.drawable.cloudy );
            break;
        }
        case FOG: {
            setImageResource( R.drawable.fog );
            break;
        }
        case LIGHTNING: {
            setImageResource( R.drawable.lightning );
            break;
        }
        case RAIN: {
            setImageResource( R.drawable.rain );
            break;
        }
        case SNOW: {
            setImageResource( R.drawable.snow );
            break;
        }
        case SUN: {
            setImageResource( R.drawable.sun );
            break;
        }
    }
}

    We use this technique for children views for WindWeatherTextView, WeatherTextView and WeatherTemperatureTextView as well, though instead of extending an ImageView, we extend a TextView, as the class names imply.

    Where the magic really happens is in the container view for these updateable child views, UpdateableLinearLayout. Our container view implements Updateable as well, however instead of adjusting itself during update, it calls update on all of its child views.

public class UpdateableLinearLayout extends LinearLayout implements Updateable

@Override
public void update( Weather weather ) {
    Log.e("UpdateableLinearLayout", "Update!" );
    if( weather != null && mUpdateableViews != null && !mUpdateableViews.isEmpty() ) {
        for( Updateable view : mUpdateableViews ) {
            view.update( weather );
        }
    }
}

    mUpdateableViews is simply a list of Updateable objects, and it is populated in onFinishInflate() by looping and recursing through all of the children views under UpdateableLinearLayout and its children in order to create a comprehensive list of views in the current layout that implement Updateable

@Override
protected void onFinishInflate() {
    super.onFinishInflate();
    mUpdateableViews = findTopLevelUpdateables( this );
}

public List<Updateable> findTopLevelUpdateables( ViewGroup view ) {
    ArrayList<Updateable> results = new ArrayList<Updateable>();

    int childCount = view.getChildCount();
    for( int i = 0; i < childCount; i++ ) {
        results = findTopLevelUpdateables( view.getChildAt( i ), results );
    }
    return results;
}

protected ArrayList<Updateable> findTopLevelUpdateables( View view,
                                                        ArrayList<Updateable> results ) {

    if( ( view instanceof ViewGroup ) && !( view instanceof Updateable ) ) {
        ViewGroup viewGroup = (ViewGroup) view;
        int childCount = viewGroup.getChildCount();
        for( int i = 0; i < childCount; i++ ) {
            findTopLevelUpdateables( viewGroup.getChildAt( i ), results );
        }
    }

    Updateable result = ( view != null && view instanceof Updateable ) ? (Updateable) view : null;
    if( result != null ) {
        results.add( result );
    }
    results.trimToSize();
    return results;
}

    These same methods could easily be used for a RelativeLayout class, or any other ViewGroup that a developer would want to use for their event driven layout.

    Once our view classes are created, we can start to implement them in a custom layout file, as shown in activity_main.xml

<com.ptrprograms.eventdrivenhierarchicalviews.view.UpdateableLinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/root"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <com.ptrprograms.eventdrivenhierarchicalviews.view.WeatherImage
        android:layout_width="match_parent"
        android:layout_height="300dp" />

    <com.ptrprograms.eventdrivenhierarchicalviews.view.WeatherTextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textSize="24sp" />

    <com.ptrprograms.eventdrivenhierarchicalviews.view.WeatherTemperatureTextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textSize="24sp" />

    <com.ptrprograms.eventdrivenhierarchicalviews.view.WindWeatherTextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textSize="24sp" />

</com.ptrprograms.eventdrivenhierarchicalviews.view.UpdateableLinearLayout>

    As you can see, our UpdateableLinearLayout is our root ViewGroup for this layout, and all of its children are custom views that implement our Updateable interface. It is good to note that our UpdateableLinearLayout can support other children views that do not implement Updateable, though they will not automatically update when new data is present.

    For this example we implement this layout in our MainActivity and save a reference to the root UpdateableLinearLayout in onCreate.

setContentView(R.layout.activity_main);
mRootView = (UpdateableLinearLayout) findViewById( R.id.root );

    For this example, since we aren't using a live API in order to drive our data, I have created a method that simply goes through random Weather data every three seconds and calls update on the root view in order to simulate fresh data changing our view.

private void startSimulation() {
    mHandler = new Handler();
    Runnable runnable = new Runnable() {
        @Override
        public void run() {
            if( mCurrentItem >= mWeather.size() )
                mCurrentItem = 0;

            mRootView.update( mWeather.get( mCurrentItem++ ) );

            if( mHandler != null )
                mHandler.postDelayed( this, 3000 );
        }
    };

    runnable.run();
}

   And with that we now have custom views that change how they are displayed based on updating data, and can be reused in multiple places across our app by simply building multiple layout files. This technique has been invaluable in my every day development, and I hope it'll be useful for other Android developers as well.