Chapter 6. Graphical User Interface

When Android was being invented, its designers faced many choices whose outcome would determine the success or failure of their project. Once they had rejected all the other smartphone operating systems, both closed and open source, and decided to build their own atop the Linux kernel, they were faced with somewhat of a blank canvas. One important choice was which programming language to use; they wisely chose Java. But once that choice was made, there was the choice of user interface technology to deploy: Java ME, Swing, the Standard Widget Toolkit (SWT), or none of the above.

Java ME is the Java Micro Edition, Sun/Oracle’s official standard API for cell phones and other small devices. Java ME was once a pretty big success story: tens if not hundreds of millions of cell phones have a Java ME runtime inside. Also, all BlackBerry devices made from around 2000 to around 2010, and all BlackBerry smartphone applications for BlackBerry OSes 5, 6 and 7 (but not including BlackBerry OS 10), were based on Java ME. But the Java ME GUI was regarded as too limiting by the Android team, having been designed for the days when cell phones had really tiny screens and limited functionality (it’s noteworthy that BlackBerry came to a similar conclusion, dropping Java ME when it came time to produce BlackBerry 10).

Swing is the Java Standard Edition (desktop Java, Java SE, a.k.a. JDK or JRE) GUI. It is based on Java’s earlier widget toolkit, the Abstract Window Toolkit (AWT). Swing can make some beautiful GUI music in the right hands, but is rather large and uses too much overhead for Android.

SWT is the GUI layer developed for use in the Eclipse IDE itself and in Eclipse rich clients. It is an abstraction layer, and depends on the underlying operating system–specific toolkit (e.g., Win32 in the Microsoft arena, GTK under Unix/Linux, etc.).

The final option, and the one ultimately chosen, was to go it alone. The Android designers thus built their own GUI toolkit designed specifically for smartphones. But they took many good ideas from the other toolkits, and learned from the mistakes that had been made along the way.

To learn any new GUI framework is, necessarily, a lot of work. Making your apps work well in the community of apps for that UI is even more work. Recognizing this, Google has set up the Android Design site. Another set of guidelines that can help is the Android Patterns site, which is not about coding but about showing designers how the Android visual experience is supposed to work. Illustrated, crowdsourced, and recommended!

One word of terminological warning: the term “widget” has two distinct meanings. All GUI controls such as buttons, labels, and the like are widgets and appear in the android.widget package. This package also contains the “layout containers” (ViewGroup subclasses), which are rather like a combination of JPanel and LayoutManager in Swing. Simple widgets and layouts are subclassed from View, so collectively they are often referred to as a view. The other kind of widget is one that can appear on an Android Home screen; these are now called “app widgets” to distinguish them from the basic ones and are in their own package, android.appwidget. This type of widget is commonly used for status displays such as news and weather updates, updates from friends/social streams, and the like. We have a recipe on app widgets (Recipe 6.30), at the end of this chapter. While we’ll try to use the terms widget and app widget correctly, you sometimes have to infer from the context which meaning is intended.

This chapter covers the main GUI elements in Android. The following chapter covers the “things that go bump in your device”: menus, dialogs, toasts, and notifications. The one after that treats the all-important topic of list views (ListView and RecyclerView).

6.1 Understanding and Following User Interface Guidelines

Ian Darwin

Problem

Lots of developers, even good ones, are very bad at user interface design.

Solution

Use the user interface guidelines. But which ones?

Discussion

UI guidelines have been around almost since Xerox PARC invented GUIs in the 1980s and showed them to Microsoft and Apple. A given set of guidelines must be appropriate to the platform. General guidelines for mobile devices are available from several sources. Android.com publishes advice too.

The official Android UI Guidelines are probably as good a starting place as any, especially if you already have some background in UI design. If not, some of the other works discussed in this recipe may help you understand UI design issues.

For some thoughtful UI pattern notes, see the Android Developers blog.

One of the oldest GUI guides is Microsoft’s The Gui Guide: International Terminology for the Windows Interface. This was less about UI design than about internationalization; it came with a floppy disk (remember those?) containing recommended translations for common Microsoft Windows GUI element names into a dozen or so common languages. This book is rather dated today.

In the 1980s and 1990s Sun’s user interface development was heavily influenced by Xerox PARC, in XView, Sun’s long-defunct Unix implementation of OPEN LOOK, and in the “Java Look and Feel,” respectively. A classic but technology-specific work from this time and place is the Java Look and Feel Design Guidelines (Addison-Wesley).

A more general work is Designing Visual Interfaces: Communication-Oriented Techniques by Kevin Mullet and Darrell Sano (Prentice Hall). This is a thorough discussion of the design issues, mostly from a desktop perspective (Mac, Unix, Windows), but the principles spelled out here are useful in dealing with human–computer interaction issues.

Concluding the desktop front is the more recent Microsoft-oriented book About Face: The Essentials of Interaction Design (Wiley). Now in its third edition, this book was originally written by Alan Cooper, known as the “Father of Visual Basic.”

6.2 Looking Good with Material Design

Ian Darwin

Problem

You want your app to look like a modern Android application.

Solution

Use Material Design, Android’s new visual paradigm for application development—or, as Android puts it, “a comprehensive guide for visual, motion, and interaction design across platforms and devices.” You can make your apps look great using this modern set of visual approaches.

Discussion

The main steps you need to implement in creating or updating an app for Material Design include the following:

  • Read the official Material Design specification.

  • Apply the material theme to your app.

  • Create or update your layouts following Material Design guidelines.

  • Add elevation to your View objects, causing them to cast shadows.

  • Consider using new features, such as card widgets, and new versions of widgets such as the RecyclerView in place of the older ListView.

  • Add or customize animations in your app.

  • And do all this while maintaining backward compatibility!

Google introduced the material design approach in Android 50. It is also used in Google web applications, supported by a web toolkit called Material Design Lite. The name comes from the analogy with physical material, e.g., either “physical stuff” or “fabric.” Material Design is a largely two-dimensional framework, except that objects (such as View objects in Android) have elevation, which causes them to cast shadows (drop-shadow effects have been with us for decades, but this formalizes them a bit), and can be animated on entry, activation, or exit.

The basic step in using a material theme is to base your application’s theme on material rather than the older Holo-based themes. You will typically tailor the colors used in your theme. For example, in the AndroidManifest.xml file, you might have:

<application
           android:name=".AndroidApplication"
           android:icon="@drawable/icon"
           android:label="@string/app_name"
           android:theme="@style/AppTheme" >
           android:allowBackup="true">
           ...
</application>

You would then have a styles.xml file defining AppTheme, based on android:Theme.Material, as per Example 6-1. The five main colors you can set are shown in Figure 6-1.

Example 6-1. res/values/styles.xml
<resources>
   <style name="AppTheme" parent="android:Theme.Material">
    <item name="android:colorPrimary">@color/primary</item>
    <item name="android:colorPrimaryDark">@color/primary_dark</item>
    <item name="android:colorAccent">@color/accent</item>
   </style>
</resources>
material 02
Figure 6-1. Material Design colors

The actual values of these colors must be defined in an XML file. We’ll choose colors from the palettes listed in the specification—here we’re using the 300, 500, and 700 colors of the Orange palette:

<resources>
   <color name="primary">#FF9800</color>
   <color name="primary_dark">#F57C00</color>
   <color name="accent">#FFCC80</color>
</resources>

Creating your layouts is a matter of applying the design prescriptions as you go along, paying attention to sizes, distances, and the like.

Adding elevation is partly a matter of using the new android:elevation attribute:

<Button
   ...
   android:elevation="5sp"/>

Elevation is used to represent the fact that material has thickness and, for example, if you place a book on a table, the book will cast a shadow. Material Design recommends that the action bar (see Recipe 6.5) always have an elevation of 4dp. Purely for demonstration purposes (we don’t recommend ever doing this in a real app), Example 6-2 shows code that produces various elevations; this code allows you, by dragging the slider, to view the effects of different elevations on the drop shadow (see Figure 6-2).

Example 6-2. Elevation/displacement example
public void onCreate(Bundle b) {

    Button raisable = (Button) findViewById(R.id.elevator);
    SeekBar control = (SeekBar) findViewById(R.id.elevatorControl);

    control.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
        public void onProgressChanged(SeekBar seekBar,
        int progress, boolean fromUser) {
            raisable.setElevation(progress);
            raisable.setText(getString(R.string.raise_me) + " " + progress);
        }
        // Two methods from SeekBar.OnSeekBarChangeListener interface omitted
    });
}
ack2 0601
Figure 6-2. Elevation variation and drop shadow

Please refer to other recipes on using new features such as Card widgets (Recipe 6.13) and new versions of widgets such as the RecyclerView (Recipe 8.1) in place of the older ListView.

There are even new icons for Material Design. Quoting its download page:

Material design system icons are simple, modern, friendly, and sometimes quirky. Each icon is created using our design guidelines to depict in simple and minimal forms the universal concepts used commonly throughout a UI. Ensuring readability and clarity at both large and small sizes, these icons have been optimized for beautiful display on all common platforms and display resolutions.

As if to emphasize that Material isn’t just for Android, the icons are available in several forms, for web view (as images or as a web font), for Android, and even for iOS. See https://design.google.com/icons/ for information on the Material Icons package.

See Also

Google’s Android team has published, well, a lot of “material” on Material Design, including the following:

As well, several good third-party discussions have been published, including these by Greg Nudelman:

6.3 Choosing a Layout Manager (a.k.a. ViewGroup) and Arranging Components

Ian Darwin

Problem

You want to know how to arrange your GUI components within your view.

Solution

Use one of the many layout managers or ViewGroups that are available.

Discussion

As in the case of Java SE and most other GUI packages, there are multiple components you can use to control the layout of individual GUI components. Java SE’s AWT and Swing provide two classes that work together: Container and LayoutManager. A Container has a LayoutManager instance to perform layout calculations on its behalf. Android, having been conceived for smaller devices, combines these two functions into a single class, android.view.ViewGroup. There are many subclasses of ViewGroup intended for you to use. While LinearLayout is the most well-known, there are many others. There are also some subclasses that are not intended for use as arbitrary layout managers, such as the drop-down–like Spinner (see Recipe 6.14). The following table should help you get a handle on which one(s) to use.

Name Basic idea AbsoluteLayout

Absolute positioning; almost never the right choice!

FrameLayout

Multiple Views in a stack

GridLayout

Equal-sized Views in rows and columns

LinearLayout

Views in a row or column

RelativeLayout

Complex layouts, like HTML tables; more efficient than nesting

TableLayout

A set of rows, each with some number of columnsa

TabHost

Tabbed view

SlidingDrawer(deprecated)

Vertical divide of the screen

a See also Recipe 1.25.

All modern IDEs for Android come with a built-in drag-and-drop visual layout editor that lets you drag and drop GUI components to arrange the layout as you want it. There also used to be a standalone GUI builder tool called DroidDraw, but it seems to have been abandoned by its original author, left behind when Google shut down GoogleCode. There have been multiple attempts to revive DroidDraw; you can find them with a GitHub search if you have some reason for not using the tools that come with your IDE.

6.4 Handling Configuration Changes by Decoupling the View from the Model

Alex Leffelman

Problem

When your device’s configuration changes (most frequently due to an orientation change), your Activity is destroyed and re-created, making state information difficult to maintain.

Solution

Decouple your user interface from your data model so that the destruction of your Activity doesn’t affect your state data.

Discussion

It’s a situation that all Android developers (except those who read this part of this book in time) run into with their very first applications: “My application works great, but when I change my phone’s orientation everything resets!”

By design, when a device’s configuration (read: orientation) changes, the Android UI framework destroys the current Activity and re-creates it for the new configuration. This enables the designer to optimize the layout for different screen orientations and sizes. However, this causes a problem for the developer who wishes to maintain the state of the Activity as it was before the orientation change destroyed the screen. Attempting to address this problem can lead to many complicated solutions, some more graceful than others. But if we take a step back and design our applications wisely, we can write cleaner, more robust code that makes life easier for everyone.

A graphical user interface is exactly what its name describes. It is a graphical representation of an underlying data model that allows the user to interface with and manipulate the data. It is not the data model itself. Let’s talk our way through an example to illustrate why that is an important point to make.

Consider a tic-tac-toe application. A simple main Activity for this would most likely include at bare minimum a GridView (with appropriate Adapter) to display the board and a TextView to tell the user whose turn it is. When the user clicks a square in the grid, an appropriate X or O is placed in that grid cell. As new Android developers, we find it logical to also include a two-dimensional array containing a representation of the board to store its data so that we can determine if the game is over, and if so, who won (see Example 6-3).

Example 6-3. First version of the TicTacToe Activity class
public class TicTacToeActivity extends Activity {

    private TicTacToeState[][] mBoardState;

    private GridView mBoard;
    private TextView mTurnText;

    @Override
    public void onCreate(Bundle savedInstanceState) {

        setContentView(R.layout.main);

        mBoardState = new TicTacToeState[3][3];

        mBoard = (GridView)findViewById(R.id.board);
        mTurnText = (TextView)findViewById(R.id.turn_text);

        // Set up Adapter, OnClickListeners, etc., for mBoard
    }
}

This is easy enough to imagine and implement, and everything works great—except that when you turn your phone sideways in the middle of an intense round of tic-tac-toe, you have a fresh board staring you in the face and your inevitable victory is postponed. As described earlier, the UI framework just destroyed your Activity and re-created it, calling onCreate() and resetting the board data.

While reading the code in Example 6-3, you might have said to yourself, “Hey, that Bundle savedInstanceState looks promising!” And you’d be right. For this painfully simple example, you could stick your board data into a Bundle and use it to reload your screen. There’s even a pair of methods, onRetainNonConfigurationInstance() and getLastNonConfigurationInstance(), that let you pass any Object you want from your old, destroyed Activity to your newly created one. For this example you could just pass your mBoardState array to your new Activity, and you’d be all set. But you’re going to be writing big, successful, amazing apps any day now, and that just doesn’t scale well with complicated interfaces. We can do better!

This is why separating your GUI from your data model is so handy. Your GUI can be destroyed, re-created, and changed, but the underlying data can survive unharmed through as many UI changes as you can throw at it. Let’s separate our game state out into a separate data class (see Example 6-4).

Example 6-4. The TicTacToe class divided
public class TicTacToeGame {

    private TicTacToeState[][] mBoardState;

    public TicTacToeGame() {
        mBoardState = new TicTacToeState[3][3];
        // Initialize
    }

    public TicTacToeState getCellState(int row, int col) {
        return mBoardState[row][col];
    }
    public void setCellState(int row, int col, TicTacToeState state) {
        mBoardState[row][col] = state;
    }

    // Other methods to determine whose turn it is, if the game is over, etc.
}

This will not only help us maintain our application state but is generally just good object-oriented design.

Now that we have our data safely outside of the volatile Activity, how do we access it to build our interface? There are two common approaches: we can declare all variables in TicTacToeGame as static and access them through static methods; or we can design TicTacToeGame as a singleton, allowing access to one global instance to be used throughout our application.

I prefer the second option purely from a design perspective. We can turn TicTacToeGame into a singleton by making the constructor private and adding the following lines to the top of the class:

private static TicTacToeGame instance = new TicTacToeGame();
public static TicTacToeGame getInstance() {
    return instance;
};

Now all we have to do is obtain the game data and set our UI elements to appropriately display the data. It’s most useful to wrap this in its own function—refreshUI(), perhaps—so that it can be used whenever our Activity makes a change to the data. For example, when a user clicks a cell of the board, there need only be two lines of code in the listener: one call to modify the data model (via our TicTacToeGame singleton), and one call to refresh the UI.

It may be obvious, but it is worth mentioning that your data classes survive only as long as your application’s process is running. If it is killed by the user or the system, naturally the data is lost. That situation necessitates more persistent storage through the filesystem or databases and is outside the scope of this recipe.

This approach very effectively decouples your visual representation of the data from the data itself, and makes orientation changes trivial. Simply calling refreshUI() in your onCreate(Bundle) method is enough to ensure that whenever your Activity is destroyed and re-created, it can access the data model and display itself correctly. And as an added bonus, you’re now practicing better object-oriented design and will see your code base become cleaner, more scalable, and easier to maintain.

6.5 Controlling the Action Bar

Ian Darwin

Problem

The action bar is an important part of most modern Android applications. You need to know how to create, configure, and, when necessary, hide the action bar.

Solution

Use either a Toolbar or the older ActionBar in your layout, and use a mixture of XML and code to control the action bar’s functionality.

Discussion

The action bar was introduced in Android Honeycomb (3.0, API 11) as a standard component for most normal applications. The one common use case for an app that doesn’t have an action bar is full-screen applications such as cameras, photo display/editing apps, or video games.

The action bar consists of a full-width rectangle about 5% of the vertical size of the screen, with an icon and program name at the left, an “overflow menu” icon (replacing the hard Menu button present on older Android hardware) at the right (three dots in a vertical row), and optionally actions to the left of the overflow menu. A typical layout is shown in Figure 6-3.

There are several ways to get an ActionBar. Assuming you are targeting modern versions (4.0 and later), you will automatically have an action bar unless you provide an application theme that is not derived directly or indirectly from the Holo theme. This will be implemented by the “standard” android.app.ActionBar class. One downside of this is that some features have been added since the time the ActionBar was introduced (Android 3.0), in modern versions such as Android 7.0; thus you may be calling methods that exist in modern versions but will not exist in the library on the user’s device if the device is running an older version of Android. This would, of course, end badly.

ack2 0602
Figure 6-3. Action bar general layout

Accordingly, the current recommendation is to use the “v7 appcompat” library. This library provides two different ActionBar implementations, the appcompat ActionBar and the recommended appcompat ToolBar class. The ToolBar is more general than the ActionBar and can be used in different places, though we’ll only consider its use as an action bar here.

The recommended steps for your action bar are thus:

  1. Ensure that v7 appcompat is included; the coordinates for this library as of API 24 are com.android.support:appcompat-v7:24.1.1. But use the latest one for the API you are targeting.

  2. Make your Activities extend AppCompatActivity, not Activity.

  3. In the AndroidManifest.xml file, set the application element to use a NoActionBar theme so you don’t get a native ActionBar added (two action bars will not coexist well).

  4. In the layout file, use an android.support.v7.widget.Toolbar (as in Example 6-5) and position it at the top of your layout so it will look like a standard action bar.

  5. In your Activity startup code, set this toolbar as the action bar using setCompatActionBar().

Example 6-5 is the layout file with a ToolBar, as generated by Android Studio.

Example 6-5. XML layout using appcompat ToolBar as action bar
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    tools:context="com.androidcookbook.actionbarcompat.MainActivity">

    <android.support.design.widget.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:theme="@style/AppTheme.AppBarOverlay">

        <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:background="?attr/colorPrimary"
            app:popupTheme="@style/AppTheme.PopupOverlay" />

    </android.support.design.widget.AppBarLayout>

    <LinearLayout
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:layout_behavior="@string/appbar_scrolling_view_behavior">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Hello World!"
            android:padding="5dp"/>
    </LinearLayout>

</android.support.design.widget.CoordinatorLayout>
Note

The CoordinatorLayout is part of the appcompat library, and requires use of the app:layout_behavior attribute on its immediate children; without it, they are ignored.

Example 6-6 is the Activity code for this sample application.

Example 6-6. Activity code for appcompat ToolBar
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);
    }
    ...
}

Once you have created your action bar, you can control it in various ways. To make your action bar disappear or reappear, you can use:

getCompatActionBar().hide()
getCompatActionBar().show()

In fact, any of the standard appcompat ActionBar methods can be used. The getCompatActionBar() method doesn’t return the toolbar directly, but returns it wrapped in a ToolbarActionBar which, as the name implies, provides all the ActionBar methods.

In the first paragraphs of this recipe, I referred to the “overflow menu” without explaining that. The traditional Android Options menu (described in Recipe 7.3) appears in response to the “Menu button,” but modern Android devices are not required to have a physical button for this purpose. Instead, the three dots at the right of the action bar (see Figure 6-3) call up the menu. However—and this is one of the original uses for the action bar—you can “promote” menu items from the Activity’s menu to the action bar! All you have to do is add the showAsAction attribute in the menu.xml file. This attribute can take the value never (which is the default), ifRoom (whose meaning is obvious), or always (which is also obvious, but whose use is not recommended; you should use ifRoom in preference to this last choice).

Here is a Help menu item added to the default menu file:

<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    tools:context="com.androidcookbook.actionbarcompat.MainActivity">
    <item
        android:id="@+id/action_settings"
        android:orderInCategory="100"
        android:title="@string/action_settings"
        app:showAsAction="never"
        />
    <item
        android:id="@+id/action_help"
        android:title="Help"
        app:showAsAction="ifRoom"
        />
</menu>

This produces the layout shown in Figure 6-4.

ack2 0603
Figure 6-4. Action bar with menu item

As with other menu items, you can use text, icons, or both.

See Also

You will find more information in the official documentation. The “Share action” in the action bar is treated specially, in Recipe 6.6.

Source Download URL

The source code for this example is in the Android Cookbook repository, in the subdirectory ActionBarCompat (see “Getting and Using the Code Examples”).

6.6 Adding a Share Action to Your Action Bar

Ian Darwin

Problem

You want to add the standard Share icon to your action bar and have it handle an application-provided Intent.

Solution

Use the actionProviderClass attribute in a menu item, set up an Intent for it to process, and pass the Intent into the ActionProvider. It really is that simple!

Discussion

Sharing information is one of the canonical uses of mobile and computing devices. Having one application use another to handle data is a prime feature of the Android platform. Android offers a “Share” menu that lets you pass text, images, or almost anything else off to any of a number of applications to be handled.

For example, let’s examine how to export (share) a short string as “plain text”; this will be sharable to (acceptable by) quite a few applications, but Android will choose the “most popular” to put at the top of the Share menu.

We start by adding a menu item that will go into the ActionBar:

<menu xmlns:android="http://schemas.android.com/apk/res/android" >

    <item android:id="@+id/menu_item_share"
        android:showAsAction="ifRoom"
        android:title="@string/action_share"
        android:actionProviderClass="android.widget.ShareActionProvider" />
...
</menu>

In our onCreate() method, we create an Intent with an action of ACTION_SEND, a content type of plain text, and an Extra of the string we want to share. There is nothing special about text—this mechanism can share almost any kind of data, as long as there’s at least one app that has registered with an Intent filter for the given content type:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    mShareIntent = new Intent();
    mShareIntent.setAction(Intent.ACTION_SEND);
    mShareIntent.setType("text/plain");
    mShareIntent.putExtra(Intent.EXTRA_TEXT,
    "From me to you, this text is new.");
}

Finally, in our menu creation method, we find the MenuItem by its ID and ask it for its ActionProvider (this is where API level 14 is required!). If we find that, we just add the share Intent to it:

    @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);

        // Find the MenuItem that we know has the ShareActionProvider
        MenuItem item = menu.findItem(R.id.menu_item_share);

        // Get its ShareActionProvider
        mShareActionProvider = (ShareActionProvider) item.getActionProvider();

        // Connect the dots: give the ShareActionProvider its Share Intent
        if (mShareActionProvider != null) {
            mShareActionProvider.setShareIntent(mShareIntent);
        }

        // Return true so Android will know we want to display the menu
        return true;
    }

That really is all there is to it.

When you first run the app, it looks like Figure 6-5.

ack2 06in03
Figure 6-5. Share action in action

Tap the Share icon and the Share menu appears—all courtesy of the ShareActionProvider! As mentioned, the most likely apps are at the top of the list; the rest are delegated to the “See all” section (Figure 6-6).

I picked the Messaging app and, just as a quick reality check, sent it to myself, as in Figure 6-7.

ack2 06in04
Figure 6-6. Share menu
ack2 06in05
Figure 6-7. Sending a message to myself

The message arrives, as in Figure 6-8!

ack2 06in06
Figure 6-8. Message arrives

Note that if you later go back to the app that started the sharing, if there is room in the action bar, the app you chose to share with (in my case, Messaging) appears beside the Share icon—a neat optimization!

6.7 Building Modern UIs with the Fragment API

Ian Darwin

Problem

You want a more flexible arrangement of parts of your screen. Or, you need to use some newer APIs that only work with Fragments.

Solution

Use the FragmentManager and layouts to arrange Fragment views.

Discussion

Fragments were introduced with Android 3.0 and have become increasingly common since. It’s possible to use them in a simple Activity, as in Example 6-7.

Example 6-7. Portion of MainActivity.java
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
}

Surprisingly, for the simplest use of a single Fragment, there is no code needed. The work is done in the layout, as in Example 6-8.

Example 6-8. layout/activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    tools:context="com.androidcookbook.fragmentsimple.MainActivity">

    <android.support.design.widget.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:theme="@style/AppTheme.AppBarOverlay">

        <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:background="?attr/colorPrimary"
            app:popupTheme="@style/AppTheme.PopupOverlay" />
    </android.support.design.widget.AppBarLayout>

    <fragment
        android:id="@+id/fragment"
        android:name=".MainActivityFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior"
        tools:layout="@layout/fragment_main" />

</android.support.design.widget.CoordinatorLayout>

Notice that unlike with regular Views (such as Button and TextView), where the name is the simple name of the class, the fragment element is not capitalized, but treated specially; you always subclass Fragment, so the actual class name must be provided by the android:name attribute.

The Fragment itself is a class in our application, so it exists as a Java class, shown in Example 6-9. And a Fragment has a View, so it has a layout XML file, shown in Example 6-10.

Example 6-9. Simple Fragment code
public class MainActivityFragment extends Fragment {

    public MainActivityFragment() {
        // Constructor with arguments may be needed in more sophisticated apps
    }

    /** Like Menus, Fragments must be inflated by the developer */
    @Override
    public View onCreateView(
                LayoutInflater inflater, ViewGroup container,
                Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment_main, container, false);
    }
}
Example 6-10. Simple Fragment layout
<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"
    tools:context="com.androidcookbook.fragmentsdemos.MainActivityFragment"
    tools:showIn="@layout/activity_main">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!" />

</RelativeLayout>

In more sophisticated Activities, one will typically use the FragmentManager to load Fragments into the Activity dynamically. The FragmentManager is transaction-based; a simple use might be something like:

Fragment frag = new MyDemoFragment();
FragmentTransaction tx = getFragmentManager();
tx.beginTransaction();
tx.add(container, frag); // Or: tx.replace(container, newFragment);
tx.commit();

This can also be written in a fluent API style, as:

getFragmentManager().beginTransaction()
    .add(container, new MyDemoFragment())
    .commit();

In a support library–based Activity, use getSupportFragmentManager() instead of getFragmentManager().

One main use of the Fragment subclasses is to have multiple views in place concurrently. Consider a list-detail application. In a small-screen device, or in a device that is narrow when in portrait mode, it makes sense to have a single view—either the list of items or the details of one item—onscreen at a time. However, on a tablet in landscape mode, where you have lots of width available, it makes sense to have the list and one item’s details side-by-side (see Figure 6-9).

ack2 0604
Figure 6-9. Portrait or landscape layout

As you can only have one Activity onscreen in an application, this would require a lot of shuffling around of View objects if you were to try to implement it using a single Activity. Using Fragments, however, makes it much easier.

Fragments can be thought of as mini-Activities in some ways, but they must reside within an actual Activity. In our list-detail example, we will have a DisplayActivity, with both a ListFragment and a DetailFragment contained within it. In portrait mode, only the ListFragment will be shown, but in landscape mode on any device of reasonable size, the two Fragments will display side-by-side. This is commonly performed by using different view configurations based on the resources system, and triggering on this in the code to either use the FragmentManager to install the second Fragment, or to start the detail view as an Activity so it appears in front of the List. In our demo we have a TaskListActivity, which is launched from the Tasks menu item in the action bar; TaskListActivity contains the code in Example 6-11 (slightly simplified).

Example 6-11. Showing the detail in either a Fragment or an Activity
holder.mView.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        // This view will be created (but empty) on wide devices; show DetailFragment
        if (findViewById(R.id.task_detail_container) != null) {
            Bundle arguments = new Bundle();
            arguments.putString(TaskDetailFragment.ARG_ITEM_ID, holder.mItem.id);
            TaskDetailFragment fragment = new TaskDetailFragment();
            fragment.setArguments(arguments);
            // Replace the empty container with the actual DetailFragment
            getSupportFragmentManager().beginTransaction()
                    .replace(R.id.task_detail_container, fragment)
                    .commit();
        } else {
            // Not on a wide device, show the DetailActivity
            Context context = v.getContext();
            Intent intent = new Intent(context, TaskDetailActivity.class);
            intent.putExtra(TaskDetailFragment.ARG_ITEM_ID, holder.mItem.id);
            context.startActivity(intent);
        }
    }
});

The layout configuration for the container is as follows:

res/layout/task_list.xml

Contains the List (RecyclerView; see Recipe 8.1) with no ViewGroup around it.

res/layout-w900dpi/task_list.xml

Contains the List in a horizontal LinearLayout, which also has an empty FrameLayout with android:id="@+id/task_detail_container" and android:layout_width="0dp", the latter causing it not to show until it is replaced by the Fragment. The fact that task_detail_container only exists in the wide view is tested in the code to control the switch between Fragment and Activity, so the code will display correctly if the user switches the device orientation while the app is running.

Note that a Fragment is not an Activity. All the same life-cycle methods (Recipe 1.2) are available, but there are several additional ones. The following should be kept in mind:

  • Call getActivity() in the Fragment for API calls that need a Context or Activity reference.

  • Implement onCreateView() to connect your View with that of the Activity.

  • Implement onActivityCreated() to be notified when your owning Activity is completely set up.

Note that onCreateView() (rather than onCreate()) is where you should create your View, and that you are responsible for inflating the View yourself. For example:

public View onCreateView(LayoutInflater inf, ViewGroup container) {
    return inf.inflate(R.layout.This_Fragment's_Layout, container, false);
}

Source Download URL

The source code for this example is in the Android Cookbook repository, in the subdirectory FragmentsDemos (see “Getting and Using the Code Examples”).

6.8 Creating a Button and Its Click Event Listener

Ian Darwin

Problem

You need to do something when the user presses a button.

Solution

Create a button in your layout, and use an OnClickListener implementation to make it perform the relevant action when clicked.

Discussion

Creating a button in your layout is simple. In the XML layout, you can create a button like so:

<Button android:id="@+id/start_button"
    android:text="@string/start_button_label"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"/>

In your Activity’s onCreate() method, find the button by its ViewID (in this example, R.id.start_button). Call its setOnClickListener() method with an OnClickListener.

In the OnClickListener implementation, check for the ViewID and perform the relevant action:

public class MainActivity extends Activity implements OnClickListener {
    public void onCreate() {
        startButton = findViewById(R.id.start_button);
        startButton.setOnClickListener(this);
        ...
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
        case R.id.start_button:
            // Start whatever it is the start button starts...
            ...
        case R.id.some_other_button:
            // etc.
        }
    }
}

An experienced Java programmer would expect to use an anonymous inner class for the onClickListener, as has been done in AWT and Swing since Java 1.1. For performance reasons, early Android documentation recommended against this, suggesting instead that you have the Activity implement OnClickListener and check the ViewID (i.e., analogous to the Java 1.0 way of doing things). As with Swing, as the power of devices has increased such old-style ways of doing things are becoming less popular, though you will likely still see both styles in use for some time.

6.9 Enhancing UI Design Using Image Buttons

Rachee Singh

Problem

You want to enhance your UI design, but without adding a lot of descriptive text.

Solution

Use an image button. This requires less effort than a text view with descriptive text, since an image can explain the scenario much better than a lot of words can.

Discussion

Making your own image button requires defining the characteristics of the button in an XML file that should be placed in /res/drawable. This file specifies the three states of an image button:

  • Pressed state

  • Focused state

  • Other states (optional)

For instance:

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:drawable="@drawable/play_pressed"
          android:state_checked="true" />
    <item android:drawable="@drawable/play" />
</selector>

So, for each of these states, the ID of an image is specified (the image present in /res/drawable as a .png file). When the button is pressed, the play_pressed image is displayed. There are two such buttons in the sample application: the Play button and the Settings button. In the .java file of the application, the onClick aspect of the buttons can be taken care of. In this recipe, a toast is displayed with some appropriate text. Programmers can start a new Activity from here, or broadcast an Intent, or do many other things based on their requirements.

In Figure 6-10, the left screenshot shows the Play button not pressed, and the right screenshot shows the Play button pressed.

ack2 0605
Figure 6-10. Play button not pressed (left) and pressed (right)

Source Download URL

The source code for this example is in the Android Cookbook repository, in the subdirectory ImageButtonDemo (see “Getting and Using the Code Examples”).

6.10 Using a FloatingActionButton

Ian Darwin

Problem

You want a round graphic button that will appear in front of your application (similar to the “Add” button found on many Google apps, such as the GMail app in Figure 6-11), and you want to respond to this button being pressed.

ack2 0607
Figure 6-11. GMail “Add” floating action button

Solution

Use a FloatingActionButton.

Discussion

The FloatingActionButton appears in the lower right of your application window and is often used for a rounded “+” button with an associated action such as adding a contact, creating a new message to be sent, and so on. While there have always been ways to provide this functionality, it is now available as a supported component (in the support library; see Recipe 1.21) in Android. It is as easy to use as a regular button; just add it to your XML layout, like so:

<android.support.design.widget.FloatingActionButton
    android:id="@+id/floatingButton"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="bottom|end"
    android:layout_margin="@dimen/fab_margin"
    android:src="@android:drawable/ic_dialog_info" />

Because this button is in the support library rather than in android.widget, we have to list its full class in the layout file. We give it an id so we can refer to it. The gravity and padding are recommended for the button to appear in the lower right. We use src to indicate the drawable to display inside the circular button (it’s named as it is to remind us that we’re not providing the complete drawable, unlike with an ImageButton), and use either android:onClick in the XML or findViewById() and setOnclickListener() in code to specify what to do when the button is tapped. This example uses android:onClick="runMe" in the XML and the following code:

public void runMe(View v) {
    final String msg = "You pressed my button";
    Log.d(TAG, msg);
    Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
}

The result is shown in Figure 6-12.

ack2 0608
Figure 6-12. FloatingActionButton in action

See Also

The new Snackbar component, introduced at around the same time as the FloatingActionButton, is discussed in Recipe 7.1.

Note

Android Studio’s New Activity wizard includes a “Basic Activity” choice that provides a preconfigured FloatingActionButton, which out of the box shows a Snackbar.

Source Download URL

The source code for this example is in the Android Cookbook repository, in the subdirectory FloatingButtonSnackbarDemo (see “Getting and Using the Code Examples”).

6.11 Wiring Up an Event Listener in Many Different Ways

Daniel Fowler

Problem

You need to be familiar with the different ways to code event handlers, both to know when to use which approach and because you will come across the various methods in this Cookbook and elsewhere.

Solution

When writing software, very rarely is there only one way to do things. This holds true when wiring up View events; half a dozen techniques are shown in this recipe.

Discussion

When a View fires an event, an application will not respond to it unless the app is listening for it. To detect the event, a listener is instantiated and assigned to the View. Take, for example, the onClick event, the most widely used event in Android apps. Nearly every View that can be added to an app screen will fire the event when the user taps it (on touch screens) or presses the trackpad/trackball when the View has focus. This event is listened to by a class implementing the OnClickListener interface. The class instance is then assigned to the required View using the View’s setOnClickListener() method. In the following section, the HandleClick inner class sets the text of a TextView (textview1) when a Button (button1) is pressed.

Technique 1. The Member class

In Example 6-12, a nested class called HandleClick implementing OnClickListener is declared as a member of the Activity (main). This is useful when several listeners require similar processing that can be handled by a single class.

Example 6-12. The Member class
public class MainActivity extends Activity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        // Attach an instance of HandleClick to the Button
        findViewById(R.id.button1).setOnClickListener(new HandleClick());
    }
    private class HandleClick implements OnClickListener{
        public void onClick(View arg0) {
            Button btn = (Button)arg0;    // Cast view to a button
            // Get a reference to the TextView
            TextView tv = (TextView) findViewById(R.id.textview1);
            // Update the TextView text
            tv.setText("You pressed " + btn.getText());
        }
    }
}

A variation on this would involve making the inner class public and putting it in its own source file. In theory, any Activity that needed a copy of such a class could create its own instance thereof. It is relatively rare, however, that action listeners are generic enough to be shared in this way.

Technique 2. The interface type

In Java an interface can be used as a type. A variable is declared as an OnClickListener and assigned using new OnClickListener() {}, while behind the scenes Java is creating an object (an anonymous class) that implements OnClickListener. This has similar benefits to the first technique (see Example 6-13). When an instance is being saved, many Java developers (including this Cookbook’s main author) consider it a good practice to use interface variables rather than the specific implementation type for the variable.

Example 6-13. The interface type
public class MainActivity extends Activity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        // Use the handleClick variable to attach the event listener
        findViewById(R.id.button1).setOnClickListener(handleClick);
    }
    private OnClickListener handleClick = new OnClickListener() {
            public void onClick(View arg0) {
            Button btn = (Button)arg0;
            TextView tv = (TextView) findViewById(R.id.textview1);
            tv.setText("You pressed " + btn.getText());
        }
    };
}

Technique 3. The anonymous inner class

Declaring the OnClickListener within the call to the setOnClickListener() method is common. This method is useful when each listener does not have functionality that could be shared with other listeners, though some novice developers find this type of code difficult to understand. Again, Java is creating an object that implements the interface behind the scenes for new OnClickListener() {} (see Example 6-14).

Example 6-14. The anonymous inner class
public class MainActivity extends Activity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        findViewById(R.id.button1).setOnClickListener(new OnClickListener() {
            public void onClick(View arg0) {
                Button btn = (Button)arg0;
                TextView tv = (TextView) findViewById(R.id.textview1);
                tv.setText("You pressed " + btn.getText());
            }
        });
    }
}

Technique 4. Implementation in Activity

The Activity itself can implement the OnClickListener (see Example 6-15). Since the Activity object (main) already exists, this saves a small amount of memory by not requiring another object to host the onClick() method. It does make public a method that is unlikely to be used elsewhere, however, and implementing multiple events will make the declaration of main long.

Example 6-15. Implementation in Activity
public class MainActivity extends Activity implements OnClickListener{
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        findViewById(R.id.button1).setOnClickListener(this);
    }
    public void onClick(View arg0) {
        Button btn = (Button)arg0;
        TextView tv = (TextView) findViewById(R.id.textview1);
        tv.setText("You pressed " + btn.getText());
    }
}

Technique 5. Lambda expression (requires the Java 8 toolchain)

In Android 7 and later, using an up-to-date SDK which should include the Java 8 tooling, you can use a lambda expression to shorten the code. Lambdas can be recognized by the -> syntax, introduced into the Java language with Java SE version 8. An example use of a lambda expression is shown in Example 6-16.

Example 6-16. Lambda expression
public class MainActivity extends Activity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        View v = findViewById(R.id.button1);
        v.setOnClickListener(v->{
            Button btn = (Button)arg0;
            TextView tv = (TextView) findViewById(R.id.textview1);
            tv.setText("You pressed " + btn.getText());
        });
    }
}

One restriction is that a lambda expression can only be used to implement a functional interface—that is, an interface that has only a single abstract method in it—since the name of the method (as well as the name of the interface itself) is inferred by the compiler. Fortunately, most “action event” listener interfaces are functional.

Technique 6. Attribute in View layout for onClick events

The name of a method defined in the Activity can be assigned to the android:onClick attribute in a layout file (see Example 6-17). This can save you from having to write a lot of boilerplate code.

Example 6-17. Class named in manifest
public class MainActivity extends Activity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
    }
    public void handleClick(View arg0) {
        Button btn = (Button)arg0;
        TextView tv = (TextView) findViewById(R.id.textview1);
        tv.setText("You pressed " + btn.getText());
    }
}

In the layout file the Button would be declared with the android:onClick attribute:

<Button android:id="@+id/button1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button 1"
        android:onClick="handleClick"/>

The first five techniques for handling events can be used with other event types (onLongClick, onKey, onTouch, onCreateContextMenu, onFocusChange). The technique described in this subsection only applies to the onClick event. The layout file in Example 6-18 declares an additional two buttons. Using the android:onClick attribute, you don’t need an additional code is required than that defined earlier; that is, no additional findViewById() and setOnClickListener() method for each button. This should appear as in Figure 6-13.

Example 6-18. Multiple uses of android:onClick
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">
  <TextView android:id="@+id/textview1"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:text="Click a button."
            android:textSize="20dp"/>
  <LinearLayout android:orientation="horizontal"
                android:layout_width="fill_parent"
                android:layout_height="wrap_content">
    <Button android:id="@+id/button1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Button 1"
            android:onClick="HandleClick"/>
    <Button android:id="@+id/button2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Button 2"
            android:onClick="HandleClick"/>
    <Button android:id="@+id/button3"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Button 3"
            android:onClick="HandleClick"/>
  </LinearLayout>
</LinearLayout>

Deciding which technique to use to wire up a listener will depend on the functionality required, how much code is reusable across Views, and how easy the code will be for future maintainers to understand. Ideally the code should be succinct and easy to read.

ack2 0609
Figure 6-13. onClick event from android:onClick

6.12 Using CheckBoxes and RadioButtons

Blake Meike

Problem

You want to offer the user a set of choices that is more limited than a list.

Solution

Use CheckBoxes or RadioButtons as appropriate.

Discussion

These views are probably familiar to you from other user interfaces. They allow the user to choose from multiple options. CheckBox is typically used when you want to offer multiple selections with a yes/no or true/false choice for each. RadioButton is used when only one choice is allowed at a time. Android has adapted these familiar components to make them more useful in a touch-screen environment. Figure 6-14 shows these multiple-choice views laid out in an Android application.

ack2 0610
Figure 6-14. A checkbox and three radio buttons

The layout XML file that created the screen in Figure 6-14 looks like this:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >

    <CheckBox
        android:id="@+id/cbxBox1"
        android:layout_width="32dp"
        android:layout_height="32dp"
        android:checked="false" />

    <TextView
        android:id="@+id/txtCheckBox"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="CheckBox: Not checked" />

    <RadioGroup
        android:id="@+id/rgGroup1"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical" >

        <RadioButton
            android:id="@+id/RB1"
			android:layout_width="fill_parent"
			android:layout_height="wrap_content"
            android:text="Button1" />

        <RadioButton
            android:id="@+id/RB2"
			android:layout_width="fill_parent"
			android:layout_height="wrap_content"
            android:text="Button2" />

        <RadioButton
            android:id="@+id/RB3"
			android:layout_width="fill_parent"
			android:layout_height="wrap_content"
            android:text="Button3" />

    </RadioGroup>

    <TextView
        android:id="@+id/txtRadio"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="RadioGroup: Nothing picked" />

</LinearLayout>

The XML file just lists each view we want on the screen along with the attributes we want. A RadioGroup is also a ViewGroup, so it can contain the appropriate RadioButton views.

Example 6-19 is the Java file that responds to user clicks.

Example 6-19. The Chooser examples
package com.androidcookbook.checkboxradiobutton;

import android.app.Activity;
import android.os.Bundle;
import android.view.Menu;
import android.view.View;
import android.widget.CheckBox;
import android.widget.RadioButton;
import android.widget.RadioGroup;
import android.widget.TextView;

public class SelectExample extends Activity {
    private CheckBox checkBox;
    private TextView txtCheckBox, txtRadio;

    /** Called when the Activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_select_example);
        checkBox = (CheckBox) findViewById(R.id.cbxBox1);
        txtCheckBox = (TextView) findViewById(R.id.txtCheckBox);

        // React to events from the CheckBox
        checkBox.setOnClickListener(new CheckBox.OnClickListener() {
            public void onClick(View v) {
                if (checkBox.isChecked()) {
                    txtCheckBox.setText("CheckBox: Box is checked");
                } else {
                    txtCheckBox.setText("CheckBox: Not checked");
                }
            }
        });

        final RadioGroup rg = (RadioGroup) findViewById(R.id.rgGroup1);
        // React to events from the RadioGroup
        rg.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(RadioGroup group, int checkedId) {
                RadioButton rb =
                    (RadioButton) findViewById(rg.getCheckedRadioButtonId());
                txtRadio.setText(rb.getText() + " picked");
            }
        });
        txtRadio = (TextView) findViewById(R.id.txtRadio);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.activity_select_example, menu);
        return true;
    }
}

These views work as follows:

CheckBox

The CheckBox view takes care of flipping its state back and forth and displaying a checkmark when the state is true, but not when it is false. All you have to do is create an OnClickListener to catch click events, and you can add whatever code you want to react.

RadioGroup

As mentioned earlier, the RadioGroup view is really a ViewGroup that contains any number of RadioButton views. The user can select only one of the buttons at a time, and you capture the selection either by setting a RadioGroup.OnCheckedChangeListener on the group or by setting OnClickListeners for each RadioButton.

RadioButton

A RadioButton is one of the buttons to be placed into a RadioGroup; it displays its given text as well as a circle to indicate which one of the buttons in the group is currently selected.

Taken together, these views let you provide a short set of choices and have the user select one or multiple choices from those offered.

6.13 Using Card Widgets

Ian Darwin

Problem

Card widgets are a relatively new form of UI control, and you want to learn when and how to use them.

Solution

Use a Card when you want self-contained ViewGroups with a nice border, commonly many of them in a ListView or a RecyclerView.

Discussion

Card is a new ViewGroup, subclassing FrameLayout, that provides a border and shadow. It is part of the compatibility package, so that it can work with old as well as new versions of Android. Card is easy to use as long as you remember that it is a FrameLayout (see Recipe 6.3). Items placed directly in a FrameLayout appear in a stack, and if they are different sizes, parts of various ones will be visible. In our example, which is drawn from a hypothetical real estate home listing project, we place a RelativeLayout with a photograph (ImageView) and some descriptive text (TextView) together in a Card, and place the Card in our main layout. We are using default shadow and border settings, but overriding the size and corner radius. The Card’s layout file is shown in Example 6-20.

Example 6-20. Layout file for Card
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:card_view="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:id="@+id/card_view"
        android:layout_gravity="center"
        android:layout_width="300dp"
        android:layout_height="340dp"
        card_view:cardCornerRadius="6dp"
        tools:context="com.androidcookbook.carddemo.CardActivity">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <ImageView
            android:id="@+id/house_front_view"
            android:layout_alignParentTop="true"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />

        <TextView
            android:id="@+id/house_descr"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_alignParentBottom="true" />

    </RelativeLayout>

</android.support.v7.widget.CardView>

The code that shows this Card’s layout only populates one Card (see Example 6-21), but it would be easy to turn that into an adapter for use on a ListView or RecyclerView, and the layout file is also already suitable for such use.

Example 6-21. Java setup for Card
public class CardActivity extends AppCompatActivity {

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

        // Dynamically set the image and text

        ImageView img = (ImageView) findViewById(R.id.house_front_view);
        Drawable d = ContextCompat.getDrawable(this,R.drawable.fixer_upper_1);
        img.setImageDrawable(d);

        TextView descr = (TextView) findViewById(R.id.house_descr);
        descr.setText("Beautiful fixer-upper! Only used on weekends
                       by respectable Hobbit couple!");
    }
}

The results of these few lines of code can be seen in Figure 6-15.

ack2 0611
Figure 6-15. CardDemo in action

See Also

The developer documentation on creating lists and cards.

Source Download URL

The source code for this example is in the Android Cookbook repository, in the subdirectory CardDemo (see “Getting and Using the Code Examples”).

6.14 Offering a Drop-Down Chooser via the Spinner Class

Ian Darwin

Problem

You want to offer a drop-down choice.

Solution

Use a Spinner object; you can pass the list of selections as an adapter.

Discussion

Generally known as a combo box, the spinner is the analog of the HTML select element or the Swing JComboBox. It provides a drop-down chooser whose values appear to float over the screen when the spinner is clicked. One item can be selected and the floating version will pop down, displaying the selection in the spinner (see Figure 6-16).

Like all standard components, the spinner can be created and customized in XML or in code. In this example, the term “context” or “reading context” is used to indicate when a patient’s blood pressure reading was taken (after breakfast, after lunch, etc., as shown in Figure 6-16), so that the health care practitioner can understand the value in context of the patient’s day. Here is an excerpt from res/layout/main.xml:

<Spinner  android:id="@+id/contextChooser"
          android:layout_height="wrap_content"
          android:layout_width="wrap_content"
          android:prompt="@string/context_choice"/>
ack2 0612
Figure 6-16. Spinner (drop-down) demonstration

Ideally the list of values will come from a resource file, so as to be internationalizable. Here is the file res/values/contexts.xml containing the XML values for the list of times to choose:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="context_choice">When Reading Taken</string>
    <string-array name="context_names">
        <item>Breakfast</item>
        <item>Lunch</item>
        <item>Dinner</item>
        <item>Snack</item>
    </string-array>
</resources>

To tie the list of strings to the spinner at compile time, just use <Spinner android:entries="@array/context_names" …> in the layout file. This is the simplest way to hook the list into the spinner, and is used for “Chooser1” in the example code.

To tie the list of strings to the spinner at runtime, just look up the Spinner object and set the values, as shown in Example 6-22. You might want to do so if you need to modify any of the list entries at runtime, for example, or if the list is coming from a Java language enum. “Chooser2” in Example 6-22 gets the list from the XML, and “Chooser3” gets it from the Java enum.

Example 6-22. The Spinner code
// Spinner 1 gets its labels automatically from an XML array
Spinner contextChooser1 = (Spinner) findViewById(R.id.contextChooser1);
contextChooser1.setOnItemSelectedListener(listener);

// Spinner 2 gets its labels programmatically from the XML array
Spinner contextChooser2 = (Spinner) findViewById(R.id.contextChooser2);
ArrayAdapter<CharSequence> adapter2 = ArrayAdapter.createFromResource(this,
    R.array.reading_context_names, android.R.layout.simple_spinner_item);
contextChooser2.setAdapter(adapter2);
contextChooser2.setOnItemSelectedListener(listener);

// Spinner 3 gets its labels programmatically from a Java language enum
Spinner contextChooser3 = (Spinner) findViewById(R.id.contextChooser3);
ArrayAdapter<ReadingContext> adapter3 = new ArrayAdapter<ReadingContext>(
    this, android.R.layout.simple_spinner_item, ReadingContext.values());
contextChooser3.setAdapter(adapter3);
contextChooser3.setOnItemSelectedListener(listener);

That is all you need in order for the spinner to appear, and to allow the user to select items (see Figure 6-16). If you want to know the chosen value as soon as the user has selected it, you can send an OnItemSelectedListener instance to the Spinner class’s setOnItemSelectedListener() method. This interface has two callback methods, setItemSelected() and setNothingSelected(). Both are called with the Spinner (but the argument is declared as a ViewAdapter); the former method is called with two integer arguments, the list position and the identity of the selected item, like so:

OnItemSelectedListener listener = new OnItemSelectedListener() {

    @Override
    public void onItemSelected(AdapterView<?> spinner, View arg1,
            int pos, long id) {
        Toast.makeText(SpinnerDemoActivity.this,
            "You selected " + spinner.getSelectedItem(),
            Toast.LENGTH_SHORT).show();
    }

    @Override
    public void onNothingSelected(AdapterView<?> spinner) {
        Toast.makeText(SpinnerDemoActivity.this,
            "Nothing selected.", Toast.LENGTH_SHORT).show();
    }
};

On the other hand, you may not need the value from the spinner until the user fills in multiple items and clicks a button. In this case, you can simply call the Spinner class’s getSelectedItem() method, which returns the item placed in that position by the Adapter. Assuming you placed strings in the list, you can just call toString() to get back the given String value.

Source Download URL

The source code for this project is in the Android Cookbook repository, in the subdirectory SpinnerDemo (see “Getting and Using the Code Examples”).

6.15 Handling Long-Press/Long-Click Events

Ian Darwin

Problem

You want to listen for long-press/long-click events and react to them, without having to manually check for multiple events.

Solution

Use the View class’s setLongClickable() and setOnLongClickListener() methods, and provide an OnLongClickListener.

Discussion

The View class has a method to enable/disable long-click support, set​LongClickable(boolean), and the corresponding setOnLongClickListener(On​LongClickListener) method. In Example 6-23 we listen for long-clicks on a View and respond by popping up a PopupMenu, which will be modal and will appear in front of the ListView.

Example 6-23. A LongClickListener
final View myView = findViewById(R.id.myView);
...
myView.setOnLongClickListener(new OnLongClickListener() {
        @Override
        public boolean onLongClick(View view) {
            PopupMenu p = new PopupMenu(Main.this, view);
            p.getMenuInflater().inflate(
                R.layout.main_popup_menu, p.getMenu());
            p.show();
            return true;
        }
});

The pop-up menu will be dismissed when you click one of its items; the list of menu items comes from the XML file res/menu/main_popup_menu.xml, which just contains a series of item elements with the text for the menu items.

Note that calling setOnLongClickListener() has the side effect of calling setLongClickEnabled(true).

Note also that adding an OnClickListener to a ListView (or other multi-item view) does not work as you might expect; the list items simply get dispatched as per a normal click. Instead, you must use the setOnItemLongClickListener() method that takes, unsurprisingly, an instance of OnItemLongClickListener(), which will be invoked when you long-press on an item in the list.

In fact, you can even simplify this for a ListView by preinflating your menu and passing it to the Activity’s setContextMenu(view, menu) method.

6.16 Displaying Text Fields with TextView and EditText

Ian Darwin

Problem

You want to display text on the screen, either read-only or editable.

Solution

Use a TextView when you want the user to have read-only access to text; this includes what most other GUI API packages call a Label, there being no explicit Label class in android.widget. Use an EditText when you want the user to have read/write access to text; this includes what other packages may call a TextField or a TextArea.

Discussion

EditText is a direct subclass of TextView. Note that EditText has many direct and indirect subclasses, many of which are GUI controls in their own right, such as CheckBox, RadioButton, and the like. A further subclass is the AutoCompleteTextView, which, as the name implies, allows for auto-completion when the user types the first few letters of some data item. As with the recipes in Chapter 8, there is an adapter to provide the completable text items.

Placing an EditText or TextView is trivial using the XML layout. Assigning the initial value to be displayed is also simple using XML. It is possible to set the value directly using the following:

<TextView android:text="Welcome!"/>

However, it is preferable to use a value like "@string/welcome_text" and define the string in strings.xml so that it can be changed and internationalized more readily.

Since TextView and EditText are used throughout this book, we do not have a sample application that uses them. One is provided with the Android API Examples, called LabelView, if you need it.

6.17 Constraining EditText Values with Attributes and the TextWatcher Interface

Daniel Fowler

Problem

You need to limit the range and type of values that users can input.

Solution

Use appropriate attributes on the EditText views in the layout XML and enhance them by implementing the TextWatcher interface.

Discussion

When an application needs input from a user, sometimes only a specific type of value is required; maybe a whole number, a decimal number, a number between two values, or words that are capitalized. When defining an EditText in a layout, attributes such as android:inputType can be used to constrain what the user is able to type. This automatically reduces the amount of code required later on because there are fewer checks to perform on the data that was entered. The TextWatcher interface is also useful for restricting values. In the following example an EditText only allows a value between 0 and 100—for example, to represent a percentage. There is no need to check the value because it is all done as the user types.

Here is a simple layout with one such EditText:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
            android:orientation="vertical"
            android:layout_width="fill_parent"
            android:layout_height="fill_parent">
  <EditText android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:id="@+id/percent"
            android:text="0"
            android:maxLength="3"
            android:inputType="number"/>
</LinearLayout>

The EditText is given a starting value of zero with android:text="0", and the number of characters that can be typed has been limited to three with android:maxLength="3" because the largest number we need, 100, only has three digits. Finally, the user is restricted to only positive numbers with android:inputType="number". It is a good idea to review the attributes that Android views support, because defining views in the XML layout can reduce the amount of code you need to write. For further details on the attributes supported by EditText see the Android documentation on the TextView, from which EditText is subclassed.

Within Example 6-24’s Activity class, an inner class can be used to implement the TextWatcher interface (or, for example, the Activity class itself could implement the interface). The afterTextChanged() method is overridden and will be called when the text changes as the user types. In this method the value being typed is checked to see if it is greater than 100. If so, it is set to 100. There is no need to check for values less than zero because they cannot be entered, because of the XML attributes. The try-catch is needed for when all the numbers are deleted, in which case the test for values greater than 100 would cause an exception (trying to parse an empty string).

Tip

TextWatcher also has a beforeTextChanged() and an onTextChanged() method that you can override, but they are not used in this example.

Example 6-24. The TextWatcher implementation
class CheckPercentage implements TextWatcher{
    @Override
    public void afterTextChanged(Editable s) {
        try {
            Log.d("Percentage", "input: " + s);
            if(Integer.parseInt(s.toString())>100)
                s.replace(0, s.length(), "100");
        }
        catch(NumberFormatException nfe) {
            // Empty
        }
    }
    @Override
    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
        // Not used, details on text just before it changed
        // Used to track in detail changes made to text, e.g. to implement an undo
    }
    @Override
    public void onTextChanged(CharSequence s, int start, int before, int count) {
        // Not used, details on text at point change made
    }
}

Finally, in the onCreate() method for the Activity, the inner class implementing TextWatcher is connected to the EditText using its addTextChangedListener() method:

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);
    EditText percentage=(EditText) findViewById(R.id.percent);
    percentage.addTextChangedListener(new CheckPercentage());
}

Note that it is fine to change the EditText value in afterTextChanged() as its internal Editable class is passed in. However, you cannot change it by altering the CharSequence passed into beforeTextChanged() and onTextChanged().

Running this example, with Logcat running, should show the values being set, as shown in Figure 6-17.

Also remember that if you change the value in the EditText, it will cause the afterTextChanged() method to be called again. Care must be taken to ensure that the code using TextWatcher does not result in endless looping.

ack2 0613
Figure 6-17. TextWatcher in action

See Also

The developer documentation on the TextView class, EditText class, and TextWatcher interface.

6.18 Implementing AutoCompleteTextView

Rachee Singh

Problem

You want to save the user from typing entire words, and instead auto-complete entries based on the first few characters the user enters.

Solution

Use the AutoCompleteTextView widget that acts as a cross between an EditText and a Spinner, enabling auto-completion.

Discussion

The demo layout here includes a TextView that supports auto-completion. Auto-completion is done using an AutoCompleteTextView widget. Example 6-25 shows the layout XML code.

Example 6-25. The auto-completion layout
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
    <TextView
    android:id="@+id/field"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    />
    <AutoCompleteTextView
    android:id="@+id/autocomplete"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:completionThreshold="2"/>

</LinearLayout>

The completionThreshold field in the AutoCompleteTextView sets the minimum number of characters that the user has to enter in the TextView so that auto-completion options corresponding to his input to show up.

The Activity in which we are implementing auto-completion should implement TextWatcher so that we can override the onTextChanged() method:

public class AutoComplete extends Activity implements TextWatcher {

We need to override the unimplemented methods: onTextChanged(), beforeTextChanged(), and afterTextChanged(). We also require three fields:

  • A handle onto the TextView

  • A handle onto the AutoCompleteTextView

  • A list of String items from which the auto-completion will choose

These three items are shown here:

private TextView field;
private AutoCompleteTextView autocomplete;
String autocompleteItems [] = {"apple", "banana", "mango", "pineapple","apricot",
    "orange", "pear", "grapes"};

Our onTextChanged() method, shown next, simply copies the current value of the text field into another text field—this is not mandatory, but in this demo it will show you what values are being set in the auto-completion component:

@Override
public void onTextChanged(CharSequence arg0, int arg1, int arg2, int arg3) {
    field.setText(autocomplete.getText());
}

In the onCreate() method of the same Activity, we get a handle on the TextView and the AutoCompleteTextView components of the layout. We also set a String adapter for the AutoCompleteTextView:

setContentView(R.layout.main);
field = (TextView) findViewById(R.id.field);
autocomplete = (AutoCompleteTextView)findViewById(R.id.autocomplete);
autocomplete.addTextChangedListener(this);
autocomplete.setAdapter(new ArrayAdapter<String>(this,
    android.R.layout.simple_dropdown_item_1line, autocompleteItems));

Source Download URL

You can download the source code for this example from Google Docs.

6.19 Feeding AutoCompleteTextView Using a SQLite Database Query

Jonathan Fuerth

Problem

Although the Android documentation contains a complete working example of using AutoCompleteTextView with an ArrayAdapter, just substituting a SimpleCursorAdapter into the example does not work.

Solution

There are two extra twists to using SimpleCursorAdapter instead of ArrayAdapter:

  • You need to tell the Adapter which column to use to fill the TextView after the user selects a completion.

  • You need to tell the Adapter how to requery based on the user’s latest input in the text field. Otherwise, it shows all rows returned by the cursor and the list never shrinks to include the items of actual interest.

Discussion

The following example code would typically be found in the onCreate() method of the Activity that contains the AutoCompleteTextView. It retrieves the AutoCompleteTextView from the Activity’s layout, creates a SimpleCursorAdapter, configures that SimpleCursorAdapter to work with the AutoCompleteTextView, and then assigns the Adapter to the View.

The two important differences from the ArrayAdapter example in the Android Developers Guide are marked in Example 6-26.

Example 6-26. The onCreate() code
final AutoCompleteTextView itemName =
    (AutoCompleteTextView) findViewById(R.id.item_name_view);

SimpleCursorAdapter itemNameAdapter = new SimpleCursorAdapter(
        this, R.layout.completion_item, itemNameCursor, fromCol, toView);

itemNameAdapter.setStringConversionColumn(                         1
itemNameCursor.getColumnIndexOrThrow(GroceryDBAdapter.ITEM_NAME_COL));

itemNameAdapter.setFilterQueryProvider(new FilterQueryProvider() { 2

    public Cursor runQuery(CharSequence constraint) {
        String partialItemName = null;
        if (constraint != null) {
            partialItemName = constraint.toString();
        }
        return groceryDb.suggestItemCompletions(partialItemName);
    }
});

itemName.setAdapter(itemNameAdapter);
1

With ArrayAdapter, there is no need to specify how to convert the user’s selection into a String. However, SimpleCursorAdapter supports using one column for the text of the suggestion, and a different column for the text that’s fed into the text field after the user selects a suggestion. Although the most common case is to use the same text for the suggestion as you get in the text field after picking it, this is not the default. The default is, as is often the case in Java, to use toString(), e.g., to fill the text view with the toString() representation of your cursor—something like android.database.sqlite.SQLiteCursor@f00f00d0. That is not what you want!

2

With ArrayAdapter, the system takes care of filtering the alternatives to display only those strings that start with what the user has typed into the text field so far. The SimpleCursorAdapter is more flexible, but again, the default behavior is not useful. If you fail to write a FilterQueryProvider for your adapter, the AutoCompleteTextView will simply show the initial set of suggestions no matter what the user types. With the FilterQueryProvider, the suggestions work as expected.

6.20 Turning Edit Fields into Password Fields

Rachee Singh

Problem

You need to designate an EditText as a password field so that characters the user types will not be visible to “shoulder surfers.”

Solution

Android provides the password attribute on the EditText class, which provides the needed behavior.

Discussion

If your application requires the user to enter a password, the EditText being used should be special. It should hide the characters entered. This can be done by adding this property to the EditText in XML:

android:inputType="textPassword"

Figure 6-18 shows how the password EditText would look.

ack2 0614
Figure 6-18. EditText with password

6.21 Changing the Enter Key to “Next” on the Soft Keyboard

Jonathan Fuerth

Problem

Several apps, including the Browser and Contacts apps, replace the Enter key on the onscreen keyboard with a Next key that gives focus to the next data entry view. You want to add this kind of polish to your own apps.

Solution

Set the appropriate input method editor (IME) attribute on the views in question.

Discussion

Figure 6-19 shows a simple layout with three text fields (EditText views) and a Submit button.

ack2 0615
Figure 6-19. Three text fields and a Submit button

Note the Enter key at the bottom right. Pressing it causes the currently focused text field to expand vertically to accommodate another line of text. This is not what you normally want!

Here is the code for the layout in Figure 6-19:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">
<EditText
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:text="Field 1" />
<EditText
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:text="Field 2" />
<EditText
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:text="Field 3" />
<Button
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="center_horizontal"
    android:text="Submit" />
</LinearLayout>

Figure 6-20 shows a better version of the same UI, with a Next key where Enter was.

ack2 0616
Figure 6-20. Improved UI: Next key

Besides being more convenient for users, this also prevents people from entering multiple lines of text into a field that was only intended to hold a single line.

Here’s how to tell Android to display a Next button on your keyboard. Note the android:imeOptions attributes on each of the three EditText views:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">
<EditText
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:text="Field 1"
    android:singleLine="true"
    android:imeOptions="actionNext" />
<EditText
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:text="Field 2"
    android:singleLine="true"
    android:imeOptions="actionNext" />
<EditText
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:text="Field 3"
    android:singleLine="true"
    android:imeOptions="actionDone" />
<Button
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="center_horizontal"
    android:text="Submit" />
</LinearLayout>

Finally, notice the actionDone on the third text field: the button that follows is not focusable in touch mode, and if it were, it wouldn’t display a keyboard anyway. As you might guess, actionDone puts a Done button where the Enter key normally goes. Pressing the Done button simply hides the keyboard.

The android:singleLine attribute is required for the imeOptions to take effect. The official Android documentation says that this “constrains the text to a single horizontally scrolling line instead of letting it wrap onto multiple lines, and advances focus instead of inserting a newline when you press the enter key.” This is marked as deprecated in the current SDK, but the replacement (android:maxLines="1") does not cause the imeOptions to take effect.

There are a number of refinements you can make to the appearance of the software keyboard, including hints about the input type, suggested capitalization, and even select-all-on-focus behavior. They are all worth investigating. Every little touch can make your app more pleasurable to use.

See Also

The Android API documentation for TextView, especially the section on imeOptions.

Source Download URL

The source code for this project is in the Android Cookbook repository, in the subdirectory SoftKeyboardEnterNext (see “Getting and Using the Code Examples”).

6.22 Processing Key-Press Events in an Activity

Rachee Singh

Problem

You want to intercept the keys pressed by the user and perform actions corresponding to them.

Solution

Override the onKeyDown() method in an Activity.

Discussion

If the application must react differently to different key-presses, you need to override the onKeyDown() method in the Activity’s Java code. This method takes the KeyCode as an argument so that, within a switch-case block, different actions can be carried out (see Example 6-27).

Example 6-27. The onKeyDown() method
public boolean onKeyDown(int keyCode, KeyEvent service) {
    switch(keyCode) {
        case KeyEvent.KEYCODE_HOME:
            keyType.setText("Home Key Pressed!");
            break;
        case KeyEvent.KEYCODE_DPAD_CENTER :
            keyType.setText("Center Key Pressed!");
            break;
        case KeyEvent.KEYCODE_DPAD_DOWN :
            keyType.setText("Down Key Pressed!");
            break;
        // And so on...
    }
}

Source Download URL

You can download the source code for this example from Google Docs.

6.23 Let Them See Stars: Using RatingBar

Ian Darwin

Problem

You want the user to choose from a number of identical GUI elements in a group to indicate a value such as a “rating” or “evaluation.”

Solution

Use the RatingBar widget; it lets you specify the number of stars to appear and the default rating, notifies you when the user changes the value, and lets you retrieve the rating.

Discussion

RatingBar provides the now-familiar “rating” user interface experience, where a user is asked to rank or rate something using star classification (the RatingBar doesn’t display the thing to be rated; that’s up to the rest of your app). RatingBar is a subclass of ProgressBar, extended to display a whole number of icons (the stars) in the bar. Its primary properties are:

numStars

The number of stars to display (int)

rating

The user’s chosen rating (float, because of stepSize)

stepSize

The increment for selection (float; common values are 1.0 and 0.5, depending on how fine-grained you want the rating to be)

isIndicator

A boolean, set to true to make this read-only

These properties are normally set in the XML:

<RatingBar
    android:id="@+id/serviceBar"
    android:gravity="center"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:numStars="5"
    android:rating="3"
    android:stepSize="1.0"
    android:isIndicator='false'
    />

The RatingBar maintains its rating value internally. You can find out how the user has rated the item in two ways:

  • Invoke the getRating() method.

  • Provide a change notification listener of type OnRatingBarChangeListener.

The OnRatingBarChangeListener has a single method, onRatingChanged(), called with three arguments:

RatingBar rBar

The event source, a reference to the particular RatingBar

float fRating

The rating that was set

boolean fromUser

true if set by a user, false if set programmatically

Example 6-28 simulates a customer survey; it creates two RatingBars, one to rate service and another to rate price (the XML for both is identical except for the android:id). In the main program, an OnRatingBarChangeListener is created to display touchy-feely–sounding feedback for the given rating (the rating is converted to an int and a switch statement is used to generate a message for the toast).

Example 6-28. The RatingBar demo app
public class MainActivity extends Activity {
    /** Called when the Activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        OnRatingBarChangeListener barChangeListener =
            new OnRatingBarChangeListener() {
            @Override
            public void onRatingChanged(RatingBar rBar,
                float fRating, boolean fromUser) {
                int rating = (int) fRating;
                String message = null;
                switch(rating) {
                case 1: message = "Sorry you're really upset with us"; break;
                case 2: message = "Sorry you're not happy"; break;
                case 3: message = "Good enough is not good enough"; break;
                case 4: message = "Thanks, we're glad you liked it."; break;
                case 5: message = "Awesome - thanks!"; break;
                }
                Toast.makeText(Main.this,
                    message,
                    Toast.LENGTH_LONG).show();
            }
        };
        final RatingBar sBar = (RatingBar) findViewById(R.id.serviceBar);
        sBar.setOnRatingBarChangeListener(barChangeListener);
        final RatingBar pBar = (RatingBar) findViewById(R.id.priceBar);
        pBar.setOnRatingBarChangeListener(barChangeListener);

        Button doneButton = (Button) findViewById(R.id.doneButton);
        doneButton.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View arg0) {
                String message = String.format(
                        "Final Answer: Price %.0f/%d, Service %.0f/%d%nThank you!",
                        sBar.getRating(), sBar.getNumStars(),
                        pBar.getRating(), pBar.getNumStars()
                        );
                // Thank the user
                Toast.makeText(Main.this,
                        message,
                        Toast.LENGTH_LONG).show();
                // And upload the numbers to a database, hopefully...

                // That's all for this Activity, and hence this App
                finish();
            }
        });
    }
}

There is more than one RatingBar, so we don’t save the value in the listener, because an incomplete survey is not useful; in the Done button action listener we fetch both values and display them, and this would be the place to save them. Your mileage may vary: it may make more sense to save them in the OnRatingBarChangeListener.

If you’re not used to printf-like formatting, the String.format call uses %.0f to format the float as an int, instead of casting it (since we have to do nice formatting anyway). Ideally the format message should be from the XML strings, but this is only a demo program.

The main UI is shown in Figure 6-21.

When the user clicks the Done button, she will see the Farewell message displayed in the desktop window (see Figure 6-22).

ack2 0617
Figure 6-21. Displaying a feedback rating
ack2 0618
Figure 6-22. Completion of the rating/survey

When you wish to both display the current “average” or similar measure ratings from a community and allow users to enter their own ratings, it is customary to display the current ratings read-only and to create a pop-up dialog where a user can enter her particular rating. This is described on the Android Patterns website.

Source Download URL

The source code for this project is in the Android Cookbook repository, in the subdirectory RatingBarDemo (see “Getting and Using the Code Examples”).

6.24 Making a View Shake

Ian Darwin

Problem

You want a View component to shake for a few seconds to catch the user’s attention.

Solution

Create an animation in the XML, then call the View object’s startAnimation() method, using the convenience routing loadAnimation() method to load the XML.

Discussion

The animation specification is created in XML files in the anim directory. In this example, we want the text entry field to be able to shake either left to right (to emulate a person shaking his head from side to side, which means “no” or “I disagree” in many parts of the world) or up and down (a person nodding in agreement). So, we create two animations, horizontal.xml and vertical.xml. Here is horizontal.xml:

<?xml version="1.0" encoding="utf-8"?>
<translate
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:fromXDelta="0"
    android:toXDelta="10"
    android:duration="1000"
    android:interpolator="@anim/cycler"
    />

The file vertical.xml is identical except it uses fromYDelta and toYDelta.

The Interpolator—the function that drives the animation—is contained in another file, cycler.xml, shown here:

<?xml version="1.0"?>
<cycleInterpolator
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:cycles="5"/>

To apply one of the two animations to a View component, you need a reference to it. You can, of course, use the common findViewById(R.id.*). You can also use the Activity method getCurrentFocus() if you are dealing with the current input (focused) View component; this avoids coupling to the name of a particular component, if you know that your animation will always apply to the current input object. In my code I know this is true because the animation startup is done in an onClick() method. Alternatively, you could use the View that is passed into the onClick() method, but that would make the button shake, not the text field.

I won’t show the whole application, but here is the onClick() method that contains all the animation code (see Example 6-29).

Example 6-29. The animation code
@Override
public void onClick(View v) {
    String answer = answerEdit.getText().toString();
    if ("yes".equalsIgnoreCase(answer)) {
        getCurrentFocus().startAnimation(
        AnimationUtils.loadAnimation(getApplicationContext(),
        R.anim.vertical));
        return;
    }
    if ("no".equalsIgnoreCase(answer)) {
        getCurrentFocus().startAnimation(
        AnimationUtils.loadAnimation(getApplicationContext(),
        R.anim.horizontal));
        return;
    }
    Toast.makeText(this, "Try to be more definite, OK?",
        Toast.LENGTH_SHORT).show();
}

The shaking effect is convenient for drawing the user’s attention to an input that is incorrect, but it can easily be overdone. Use judiciously!

6.25 Providing Haptic Feedback

Adrian Cowham

Problem

You want to provide haptic feedback with your application.

Solution

Use Android’s haptic controls to provide instant physical feedback.

Discussion

Building confidence among users that their actions had an effect is a requirement for any app on any platform. The canonical example is displaying a progress bar to let users know an action is being processed. For touch interfaces this technique still applies, but the advantage of a touch interface is that developers have the opportunity to provide physical feedback, as users are capable of actually feeling the device react to their actions.

Android has some stock haptic controls, but if these don’t satisfy your needs you can gain control of the device’s vibrator for custom feedback.

Warning

Custom control of the device’s vibrator requires permission. This is something you’ll have to explicitly list in your AndroidManifest.xml file. If you’re paranoid about asking for permission or if you already have a long list of permissions, you may want to use the stock Android haptic feedback options.

Also be aware that some devices don’t have a vibrator; when you run the examples in this recipe on such devices, you will not receive haptic feedback.

I’ll start by showing the more complicated example first, custom haptic feedback.

Custom haptic feedback using the device’s vibrator

Your first step is to request the necessary permission. Add the following line to your AndroidManifest.xml file:

<uses-permission android:name="android.permission.VIBRATE" />

Now define a listener to respond to touch events. The CustomHapticListener shown in Example 6-30 is implemented as a private nonstatic inner class of the main Activity because it needs access to the Context method getSystemService().

Example 6-30. The haptic feedback listener implementation
private class CustomHapticListener implements View.OnClickListener {

    // Duration in milliseconds to vibrate
    private final int durationMs;

    public CustomHapticListener(int ms) {
        durationMs = ms;
    }

    @Override
    public void onClick(View v) {
        Vibrator vibe = (Vibrator) getSystemService(VIBRATOR_SERVICE); 1
        vibe.vibrate(durationMs); 2
    }
}

1 and 2 are the important lines. 1 gets a reference to the Vibrator service and 2 vibrates the device. If you have not requested the vibrate permission, 2 will throw an exception.

Now register the listener. In your Activity’s onCreate() method, you’ll need to get a reference to the GUI element you want to attach haptic feedback to and then register the OnClickListener defined earlier:

@Override
public void onCreate( Bundle savedInstance ) {
    Button customBtn = ( Button ) findViewById( R.id.btn_custom );
    customBtn.setOnTouchListener( new CustomHapticListener( 100 ) );
}

That’s it; you’re in control of the haptic feedback. Now we’ll move on to using stock Android haptic feedback.

Stock haptic feedback events

First things first: to use stock Android haptic feedback events you must enable this on a view-by-view basis. That is, you must explicitly enable haptic feedback for each view. You can enable haptic feedback declaratively in your layout file or programmatically in Java. To enable haptic feedback in your layout, simply add the android:hapticFeedbackEnabled="true" attribute to your view(s). Here’s an abbreviated example:

<button android:hapticFeedbackEnabled="true">
</button>

Here’s how you do the same thing in code:

Button keyboardTapBtn = ( Button ) findViewById( btnId );
keyboardTapBtn.setHapticFeedbackEnabled( true );

Now that haptic feedback has been enabled, the next step is to register a listener in which to then perform the actual feedback. For this example we’ll use a touch listener so that touch events will invoke the feedback; Example 6-31 shows how to register an OnTouchListener and perform haptic feedback when a user touches the view.

Example 6-31. Haptic feedback demo app
// Initialize some buttons with the stock Android haptic feedback values
private void initializeButtons() {
    // Initialize the buttons with the standard haptic feedback options
    initializeButton( R.id.btn_keyboardTap, HapticFeedbackConstants.KEYBOARD_TAP ); 1
    initializeButton( R.id.btn_longPress,   HapticFeedbackConstants.LONG_PRESS );  2
    initializeButton( R.id.btn_virtualKey,  HapticFeedbackConstants.VIRTUAL_KEY ); 3
}

// Helper method to initialize single buttons and register an OnTouchListener
// to perform the haptic feedback
private void initializeButton( int btnId, int hapticId ) {
    Button btn = ( Button ) findViewById( btnId );
    btn.setOnTouchListener( new HapticTouchListener( hapticId ) );
}

// Class to handle touch events and respond with haptic feedback
private class HapticTouchListener implements OnTouchListener {

    private final int feedbackType;

    public HapticTouchListener( int type ) { feedbackType = type; }

    public int feedbackType() { return feedbackType; }

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        // Only perform feedback when the user touches the view, as opposed
        // to lifting a finger off the view
        if( event.getAction() == MotionEvent.ACTION_DOWN ) {
            // Perform the feedback
            v.performHapticFeedback( feedbackType() );4
        }
        return true;
    }
}

You’ll notice that in lines 1 through 3, I’m initializing three different buttons with three different haptic feedback constants. These are Android’s stock values; two of the three seem to provide exactly the same feedback.

Example 6-31 is part of a test app I wrote to demonstrate haptic feedback, and I could not tell the difference between HapticFeedbackConstants.LONG_PRESS and HapticFeedbackConstants.KeyboardTap. HapticFeedbackConstants.VIRTUAL_KEY, when tested, does not appear to provide any feedback. 4 is where the haptic feedback is performed. All in all, providing haptic feedback is pretty simple, but remember that if you want control of the device’s vibrator you must request permission in your AndroidManifest.xml file. If you choose to use the stock Android haptic feedback options, make sure you enable haptic feedback for your views either in the layout or programmatically.

See Also

The blog post by the author of this recipe, entitled “Android’s Haptic Feedback”.

Source Download URL

The source code for this project is in the Android Cookbook repository, in the subdirectory HapticFeedback (see “Getting and Using the Code Examples”).

6.26 Navigating Different Activities Within a TabView

Pratik Rupwal

Problem

You want to change from an Activity within a TabView to another Activity within the same tab.

Solution

Replace the content view of the tab with the new Activity you want to move to.

Discussion

When a “calling” Activity within a TabView calls another Activity through an Intent, the TabView gets replaced by the view of the called Activity. To show the called Activity within the TabView, we can replace the view of the calling Activity with the view of the called Activity so that the TabView remains stable. To achieve this we need to extend the calling Activity from ActivityGroup rather than Activity.

In Example 6-32 the Calling Activity extended from ActivityGroup has been set within a TabView.

Example 6-32. Replacing the Activity within a tab
// 'Calling' Activity
public class Calling extends ActivityGroup implements OnClickListener
{
    Button b1;
    Intent i1;
    /** Called when the Activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.calling);
        b1=(Button)findViewById(R.id.changeactivity);
        b1.setOnClickListener();
    }
    public void onClick(View view) {
        // Create an intent to call the 'Called' Activity
        i1=new Intent(this.getBaseContext(),Called.class);
        // Call the method to replace the view.
        replaceContentView("Called", i1);
    }
    // This replaces the view of the 'Calling' Activity with the 'Called' Activity.
    public void replaceContentView(String id, Intent newIntent) {
        // Obtain the view of the 'Called' Activity using its Intent 'newIntent'
        View view = getLocalActivityManager().startActivity(id,
            newIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)) .getDecorView();
        // Set the above view to the content of the 'Calling' Activity.
        this.setContentView(view);
    }
}

The “called Activity” can also call another Activity (say CalledSecond), as shown here:

// 'Called Activity'
public class Called extends Activity implements OnClickListener
{
    Button b1;
    Intent i1;
    Calling caller;
    /** Called when the Activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.called);
        b1=(Button)findViewById(R.id.changeactivity);
        b1.setOnClickListener();
    }
    public void onClick(View view) {
        // Create an Intent to call the 'CalledSecond' Activity
        i1=new Intent(this.getBaseContext(),CalledSecond.class);
        /**
         * 'CalledSecond' can be any Activity, even
         * 'Calling' (in case backward navigation is required)
         */

        // Initialize the object of the 'Calling' class
        caller=(Calling)getParent();
        // Call the method to replace the view.
        caller.replaceContentView("CalledSecond", i1);
    }
}

6.27 Creating a Loading Screen that Will Appear Between Two Activities

Shraddha Shravagi

Problem

You are getting a black screen before loading an Activity.

Solution

Create a simple Activity that shows a loading image instead of a black screen.

Discussion

Sometimes it takes time for an Activity to fetch user-requested data from a database or the internet, and then to load the data onto the user’s screen. In such cases, usually the screen goes black while the user waits for the data to load. The following scenario illustrates this:

  • ProfileList (the user selects one profile) → black screen → ProfileData

Instead of showing the user a black screen while she waits for the data to load, you can show an image, as illustrated in the following scenario:

  • ProfileList (the user selects one profile) → LoadingScreenActivityProfileData

In this recipe we will create a simple loading screen that appears for 2.5 seconds while the next Activity loads.

To do this, you need to start by creating a LoadingScreen layout file. This layout creates a screen that displays a “loading” message and a progress bar:

<LinearLayout
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="fill_parent"
  android:gravity="center" android:orientation="vertical"
  android:layout_height="fill_parent" android:background="#E5E5E5">

    <TextView android:text="Please wait while your data is being loaded..."
        android:layout_width="wrap_content" android:layout_height="wrap_content"
        android:textColor="#000000">
    </TextView>
    <ProgressBar android:id="@+id/mainSpinner1" android:layout_gravity="center"
        android:layout_width="wrap_content" android:layout_height="wrap_content"
        android:indeterminate="true"
        style="?android:attr/progressBarStyleInverse">
   </ProgressBar>

</LinearLayout>

Next, create a LoadingScreen class file (see Example 6-33).

Example 6-33. The LoadingScreen class
public class LoadingScreenActivity extends Activity {

    // Introduce a delay
    private final int WAIT_TIME = 2500;
    @Override
    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);
        System.out.println("LoadingScreenActivity  screen started");
        setContentView(R.layout.loading_screen);
        findViewById(R.id.mainSpinner1).setVisibility(View.VISIBLE);

        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                // Simulating a long-running task
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    // Can't happen
                }
                System.out.println("Going to Profile Data");
                /* Create an Intent that will start the ProfileData Activity */
                Intent mainIntent =
                    new Intent(LoadingScreenActivity.this, ProfileData.class);
                LoadingScreenActivity.this.startActivity(mainIntent);
                LoadingScreenActivity.this.finish();
            }
        }, WAIT_TIME);
    }
}

This will load the next Activity once WAIT_TIME has elapsed.

Now all you need to do is to create an Intent to launch the loading screen Activity:

protected void onListItemClick(ListView l, View v, int position, long id) {
            super.onListItemClick(l, v, position, id);

    Intent intent = new Intent(ProfileList.this, LoadingScreenActivity.class);
            startActivity(intent);
}

6.28 Adding a Border with Rounded Corners to a Layout

Daniel Fowler

Problem

You need to put a border around an area of the screen or add interest to a user interface.

Solution

Define an Android shape in an XML file and assign it to a layout’s background attribute.

Discussion

The drawable folder, under res in an Android project, is not restricted to bitmaps (PNG or JPG files); it can also hold shapes defined in XML files. These shapes can then be reused in the project. A shape can be used to put a border around a layout. This example shows how to make a rectangular border with curved corners.

Create a new file called customborder.xml in the drawable folder. Right-click on the drawable folder and select New → Drawable Resource File from the context menu (or, with that folder selected, use the File menu). In the New Drawable Resource File dialog, enter a filename of customborder. Set the “Root element” to shape, and click OK. Enter the following XML, defining the border shape, into the customborder.xml file:

<?xml version="1.0" encoding="UTF-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
       android:shape="rectangle">
    <corners android:radius="20dp"/>
    <padding android:left="10dp" android:right="10dp"
       android:top="10dp" android:bottom="10dp"/>
    <solid android:color="#CCCCCC"/>
</shape>

The attribute android:shape is set to rectangle (shape files also support oval, line, and ring). rectangle is the default value, so you can leave out this attribute if you’re defining a rectangle. For detailed information on shape files, refer to the Android documentation on shapes.

The element corners sets the rectangle corners to be rounded; it is possible to set a different radius on each corner (see the Android reference).

The padding attributes are used to move the contents of the view to which the shape is applied, to prevent the contents from overlapping the border.

The border color here is set to a light gray (the hexadecimal RGB value #CCCCCC).

Shapes also support gradients, but that is not used in our example; again, see the Android documentation to see how a gradient is defined.

The shape is applied using android:background="@drawable/customborder".

Within the layout other views can be added as usual. In this example a single TextView has been added, and the text is white (hexadecimal RGB value #FFFFFF). The background is set to blue, plus some transparency to reduce the brightness (hexadecimal alpha RGB value #A00000FF).

Finally, the layout is offset from the screen edge by placing it into another layout with a small amount of padding. The full layout file looks like this:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:orientation="vertical"
              android:layout_width="fill_parent"
              android:layout_height="fill_parent"
              android:padding="5dp">
    <LinearLayout android:orientation="vertical"
              android:layout_width="fill_parent"
              android:layout_height="fill_parent"
              android:background="@drawable/customborder">
    <TextView android:layout_width="fill_parent"
              android:layout_height="fill_parent"
              android:text="Text View"
              android:textSize="20dp"
              android:textColor="#FFFFFF"
              android:gravity="center_horizontal"
              android:background="#A00000FF" />
    </LinearLayout>
</LinearLayout>

This produces the result shown in Figure 6-23.

ack2 0619
Figure 6-23. Curved border

See Also

The developer documentation on shapes.

Source Download URL

The source code for this project is in the Android Cookbook repository, in the subdirectory LayoutBorder (see “Getting and Using the Code Examples”).

6.29 Detecting Gestures in Android

Pratik Rupwal

Problem

You want the user to be able to traverse through different screens using simple gestures, such as flipping/scrolling the page.

Solution

Use the GestureDetector class to detect simple gestures such as tapping, scrolling, swiping, or flipping.

Discussion

The sample application in this recipe has four views, each a different color. It also has two modes: SCROLL and FLIP. The application starts in FLIP mode. In this mode, when you perform the swipe/fling gesture in a left-to-right or top-to-bottom direction, the view changes back and forth. When a long-press is detected, the application changes to SCROLL mode, in which you can scroll the displayed view. While in this mode, you can double-tap the screen to bring the screen back to its original position. When a long-press is detected again, the application changes to FLIP mode.

This recipe focuses on gesture detection, hence the animation between those views is not discussed. Refer to Recipe 6.24 for an example of shaking a view using an animation, as well as the Android documentation for android.view.animation.

Example 6-34 provides an introduction to simple gesture detection in Android. Our GestureDetector class detects gestures using the supplied MotionEvent class. We use this class along with the onTouchEvent() method. Inside this method we call GestureDetector.onTouchEvent(). The GestureDetector class identifies the gestures or events that occurred and reports back to us using the GestureDetector.OnGestureListener callback interface. We create an instance of the GestureDetector class by passing the Context and GestureDetector.OnGestureListener listener. The double-tap event is not present in the GestureDetector.OnGestureListener callback interface; this event is reported using another callback interface, GestureDetector.OnDoubleTapListener. To use this callback interface we have to register a GestureDetector.OnDoubleTapListener for these events. The MotionEvent class contains all the values corresponding to a movement and touch event. This class holds values such as the x- and y- positions at which the event occurred, the timestamp at which the event occurred, and the mouse pointer index.

Example 6-34. Gesture detection
...
import android.view.GestureDetector;
...
import android.view.animation.OvershootInterpolator;
import android.view.animation.TranslateAnimation;

public class FlipperActivity extends Activity
        implements GestureDetector.OnGestureListener,
            GestureDetector.OnDoubleTapListener {

    final private int SWIPE_MIN_DISTANCE = 100;
    final private int SWIPE_MIN_VELOCITY = 100;

    private ViewFlipper flipper = null;
    private ArrayList<TextView> views = null;
    private GestureDetector gesturedetector = null;
    private Vibrator vibrator = null;
    int colors[] = { Color.rgb(255,128,128),
        Color.rgb(128,255,128),
        Color.rgb(128,128,255),
        Color.rgb(128,128,128) };

    private Animation animleftin = null;
    private Animation animleftout = null;

    private Animation animrightin = null;
    private Animation animrightout = null;

    private Animation animupin = null;
    private Animation animupout = null;

    private Animation animdownin = null;
    private Animation animdownout = null;

    private boolean isDragMode = false;
    private int currentview = 0;

/ ** Initializes the first screen and animation to be applied to the screen
     * after detecting the gesture. */

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

        flipper = new ViewFlipper(this);
        gesturedetector = new GestureDetector(this, this);
        vibrator = (Vibrator)getSystemService(VIBRATOR_SERVICE);
        gesturedetector.setOnDoubleTapListener(this);

        flipper.setInAnimation(animleftin);
        flipper.setOutAnimation(animleftout);
        flipper.setFlipInterval(3000);
        flipper.setAnimateFirstView(true);

        prepareAnimations();
        prepareViews();
        addViews();
        setViewText();

        setContentView(flipper);
    }

    private void prepareAnimations() {
        animleftin = new TranslateAnimation(
          Animation.RELATIVE_TO_PARENT, +1.0f, Animation.RELATIVE_TO_PARENT, 0.0f,
          Animation.RELATIVE_TO_PARENT, 0.0f, Animation.RELATIVE_TO_PARENT, 0.0f);

        animleftout = new TranslateAnimation(
          Animation.RELATIVE_TO_PARENT, 0.0f, Animation.RELATIVE_TO_PARENT, -1.0f,
          Animation.RELATIVE_TO_PARENT, 0.0f, Animation.RELATIVE_TO_PARENT, 0.0f);

        animrightin = new TranslateAnimation(
          Animation.RELATIVE_TO_PARENT, -1.0f, Animation.RELATIVE_TO_PARENT, 0.0f,
          Animation.RELATIVE_TO_PARENT, 0.0f, Animation.RELATIVE_TO_PARENT, 0.0f);

        animrightout = new TranslateAnimation(
          Animation.RELATIVE_TO_PARENT,  0.0f, Animation.RELATIVE_TO_PARENT,  +1.0f,
          Animation.RELATIVE_TO_PARENT, 0.0f, Animation.RELATIVE_TO_PARENT, 0.0f);

        animupin = new TranslateAnimation(
          Animation.RELATIVE_TO_PARENT,  0.0f, Animation.RELATIVE_TO_PARENT, 0.0f,
          Animation.RELATIVE_TO_PARENT, +1.0f, Animation.RELATIVE_TO_PARENT, 0.0f);

        animupout = new TranslateAnimation(
          Animation.RELATIVE_TO_PARENT,  0.0f, Animation.RELATIVE_TO_PARENT, 0.0f,
          Animation.RELATIVE_TO_PARENT, 0.0f, Animation.RELATIVE_TO_PARENT, -1.0f);

        animdownin = new TranslateAnimation(
          Animation.RELATIVE_TO_PARENT,  0.0f, Animation.RELATIVE_TO_PARENT, 0.0f,
          Animation.RELATIVE_TO_PARENT, -1.0f, Animation.RELATIVE_TO_PARENT, 0.0f);

        animdownout = new TranslateAnimation(
          Animation.RELATIVE_TO_PARENT, 0.0f, Animation.RELATIVE_TO_PARENT, 0.0f,
          Animation.RELATIVE_TO_PARENT, 0.0f, Animation.RELATIVE_TO_PARENT, +1.0f);

        animleftin.setDuration(1000);
        animleftin.setInterpolator(new OvershootInterpolator());
        animleftout.setDuration(1000);
        animleftout.setInterpolator(new OvershootInterpolator());

        animrightin.setDuration(1000);
        animrightin.setInterpolator(new OvershootInterpolator());
        animrightout.setDuration(1000);
        animrightout.setInterpolator(new OvershootInterpolator());

        animupin.setDuration(1000);
        animupin.setInterpolator(new OvershootInterpolator());
        animupout.setDuration(1000);
        animupout.setInterpolator(new OvershootInterpolator());

        animdownin.setDuration(1000);
        animdownin.setInterpolator(new OvershootInterpolator());
        animdownout.setDuration(1000);
        animdownout.setInterpolator(new OvershootInterpolator());
        }

        private void prepareViews() {
                TextView view = null;

                views = new ArrayList<TextView>();

                for (int color: colors) {
                        view = new TextView(this);

                        view.setBackgroundColor(color);
                        view.setTextColor(Color.BLACK);
                        view.setGravity(
                            Gravity.CENTER_HORIZONTAL | Gravity.CENTER_VERTICAL);

                        views.add(view);
                }
        }

        private void addViews() {
                for (int index=0; index<views.size(); ++index) {
                        flipper.addView(views.get(index),index,
                                        new LayoutParams(LayoutParams.FILL_PARENT,
                                        LayoutParams.FILL_PARENT));
                }
        }

        private void setViewText() {
                String text = getString(isDragMode ? R.string.app_info_drag :
                R.string.app_info_flip);
                for (int index=0; index<views.size(); ++index) {
                        views.get(index).setText(text);
                }
        }

        /** Gets invoked when a screen touch is detected. */
        @Override
        public boolean onTouchEvent(MotionEvent event) {
                return gesturedetector.onTouchEvent(event);
        }

        /** The onDown() method is called when the user first touches the screen;
         * the MotionEvent parameter represents the event that corresponds to
         * the touch event. */
        @Override
        public boolean onDown(MotionEvent e) {
                return false;
        }

        /** The onFling() method is called whenever the user swipes the screen
         * in any direction (i.e., touches the screen and immediately
         * moves the finger in any direction). */
        @Override
        public boolean onFling(MotionEvent event1, MotionEvent event2,
        float velocityX,float velocityY) {
                if(isDragMode)
                        return false;

                final float ev1x = event1.getX();
                final float ev1y = event1.getY();
                final float ev2x = event2.getX();
                final float ev2y = event2.getY();
                final float xdiff = Math.abs(ev1x - ev2x);
                final float ydiff = Math.abs(ev1y - ev2y);
                final float xvelocity = Math.abs(velocityX);
                final float yvelocity = Math.abs(velocityY);

                if (xvelocity > this.SWIPE_MIN_VELOCITY &&
                    xdiff > this.SWIPE_MIN_DISTANCE) {
                        if(ev1x > ev2x) { // Swipe left
                                --currentview;

                                if (currentview < 0) {
                                        currentview = views.size() - 1;
                                }

                                flipper.setInAnimation(animleftin);
                                flipper.setOutAnimation(animleftout);
                        }
                        else { // Swipe right
                                ++currentview;

                                if(currentview >= views.size()) {
                                        currentview = 0;
                                }

                                flipper.setInAnimation(animrightin);
                                flipper.setOutAnimation(animrightout);
                        }

                        flipper.scrollTo(0,0);
                        flipper.setDisplayedChild(currentview);
                }
                else if (yvelocity > this.SWIPE_MIN_VELOCITY &&
                    ydiff > this.SWIPE_MIN_DISTANCE) {
                        if(ev1y > ev2y) { // Swipe up
                                --currentview;

                                if(currentview < 0) {
                                        currentview = views.size() - 1;
                                }

                                flipper.setInAnimation(animupin);
                                flipper.setOutAnimation(animupout);
                        }
                        else { // swipe down
                                ++currentview;

                                if(currentview >= views.size()) {
                                        currentview = 0;
                                }
                                flipper.setInAnimation(animdownin);
                                flipper.setOutAnimation(animdownout);
                        }

                        flipper.scrollTo(0,0);
                        flipper.setDisplayedChild(currentview);
                }

                return false;
        }

        /** The onLongPress() method is called when the user touches the screen
        * and holds it for a period of time. The MotionEvent parameter represents
        * the event that corresponds to the touch event. */
        @Override
        public void onLongPress(MotionEvent e) {
                vibrator.vibrate(200);
                flipper.scrollTo(0,0);

                isDragMode = !isDragMode;

                setViewText();
        }

        /** The onScroll() method is called when the user touches the screen
        * and moves their finger to another location on the screen. */
        @Override
        public boolean onScroll(MotionEvent e1, MotionEvent e2,
                float distanceX,float distanceY) {
                if(isDragMode)
                        flipper.scrollBy((int)distanceX, (int)distanceY);

                return false;
        }

        /** The onShowPress() method is called when the user touches the screen,
         * before they lift or move their finger. This event is mostly used for
         * giving visual feedback to the user to show their action. */
        @Override
        public void onShowPress(MotionEvent e) {
        }

        /** The onSingleTapUp() method is called when the user taps the screen. */
        @Override
        public boolean onSingleTapUp(MotionEvent e) {
                return false;
        }

        /** The onDoubleTap() method is called when a double-tap event has occurred.
         * The only parameter, MotionEvent, corresponds to the double-tap
         * event that occurred. */
        @Override
        public boolean onDoubleTap(MotionEvent e) {
                flipper.scrollTo(0,0);

                return false;
        }

        /** The onDoubleTapEvent() method is called for all events that occurred
         * within the double-tap; i.e., down, move, and up events. */

        @Override
        public boolean onDoubleTapEvent(MotionEvent e) {
                return false;
        }

        /** The onSingleTapConfirmed() method is called when a single tap
        * has occurred and been confirmed, but this is not same as the
        * single-tap event in the GestureDetector.OnGestureListener. This
        * is called when the GestureDetector detects and confirms that
        * this tap does not lead to a double-tap. */
        @Override
        public boolean onSingleTapConfirmed(MotionEvent e) {
                return false;
        }
}

When the mode of the application changes the user is notified with a vibration. To use the Vibrator service, set the following permission in your application’s AndroidManifest.xml file:

<uses-permission android:name="android.permission.VIBRATE"></uses-permission>

The application uses some strings, which are declared under res/values/string.xml:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="app_info_drag">
    GestureDetector sample.

Current Mode:
        SCROLL

Drag the view using finger.
Long press to change
    the mode to FLIP.
Double tap to reposition the view to normal.</string>
    <string name="app_name">Gesture Detector Sample</string>
    <string name="app_info_flip">
    GestureDetector sample.

Current Mode: FLIP

Swipe left, right, up, down
        to change the views
Long
    press to change to mode to SCROLL</string>
</resources>

See Also

The documentation for the GestureOverlayView class for handling complex gestures in Android.

6.30 Creating a Simple App Widget

Catarina Reis

Problem

You want to enable users to more easily interact with your application.

Solution

Create an Application widget, which is a simple GUI control that appears on the Home screen and allows users to easily interact with an existing application (Activity and/or Service).

Discussion

In this recipe we will create a widget that starts a Service that updates its visual components. The widget, called CurrentMoodWidget, presents the user’s current mood in the form of a text smiley. The current mood smiley changes to a random mood smiley whenever the user clicks the smiley image button. In Figure 6-24, the screenshot on the left shows the initial view, and the screenshot on the right shows the view after a random change.

ack2 0620
Figure 6-24. Initial mood widget (left) and current mood widget (right)

Here’s how to create this simple app widget:

  1. Start by creating a new Android project (CurrentMoodWidgetProject). Use “Current Mood” as the application name and “oreillymedia.cookbook.android.spikes” as the package name. Do not create an Activity. Set the minimum SDK version to anything modern (technically the minimum API level is 8, for Android 2.2, the version that introduced app widgets).

  2. Add the string definitions for the widget in the file res/values/string.xml, according to the following name/value pairs:

    • widgettextcurrent mood

    • widgetmoodtext:)

  3. Add the images that will appear in the widget’s button (smile_icon.png). Place these under the res/drawable structure.

    ack2 06in07
  4. Create a new layout file inside res/layout, under the project structure, that will define the widget layout (widgetlayout.xml) according to the following structure:

    <TextView android:text="@string/widgettext"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="0.8"
        android:layout_gravity="center_vertical"
        android:textColor="#000000"></TextView>
    <TextView android:text="@string/widgetmoodtext"
        android:id="@+id/widgetMood" android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="0.3"
        android:layout_gravity="center_vertical"
        android:textColor="#000000"></TextView>
    <ImageButton android:id="@+id/widgetBtn" android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="0.5" android:src="@drawable/smile_icon"
        android:layout_gravity="center_vertical"></ImageButton>
  5. Provide the widget provider setup configuration by first creating the res/xml folder under the project structure and then creating an XML file (widgetproviderinfo.xml) with the following parameters:

    <appwidget-provider
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:minWidth="220dp"
        android:minHeight="72dp"
        android:updatePeriodMillis="86400000"
        android:initialLayout="@layout/widgetlayout">
    </appwidget-provider>
  6. Create the Service that reacts to the user interaction with the smiley image button (CurrentMoodService.java); see Example 6-35.

    Example 6-35. Widget’s Service implementation
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        super.onStart(intent, startId);
        updateMood(intent);
        stopSelf(startId);
        return START_STICKY;
    }
    
    private void updateMood(Intent intent) {
        if (intent != null) {
            String requestedAction = intent.getAction();
            if (requestedAction != null &&  requestedAction.equals(UPDATEMOOD)) {
              this.currentMood = getRandomMood();
              int widgetId =
                  intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, 0);
              AppWidgetManager appWidgetMan = AppWidgetManager.getInstance(this);
              RemoteViews views =
                  new RemoteViews(this.getPackageName(),R.layout.widgetlayout);
              views.setTextViewText(R.id.widgetMood, currentMood);
              appWidgetMan.updateAppWidget(widgetId, views);
            }
        }
    }
  7. Implement the widget provider class (CurrentMoodWidgetProvider.java); see Example 6-36.

    Example 6-36. Widget provider class
    @Override
    public void onUpdate(Context context, AppWidgetManager appWidgetManager,
            int[] appWidgetIds) {
        super.onUpdate(context, appWidgetManager, appWidgetIds);
    
        for (int i=0; i<appWidgetIds.length; i++) {
            int appWidgetId = appWidgetIds[i];
            RemoteViews views = new RemoteViews(context.getPackageName(),
                R.layout.widgetlayout);
            Intent intent = new Intent(context, CurrentMoodService.class);
            intent.setAction(CurrentMoodService.UPDATEMOOD);
            intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID,appWidgetId);
            PendingIntent pendingIntent =
                PendingIntent.getService(context, 0, intent, 0);
            views.setOnClickPendingIntent(R.id.widgetBtn, pendingIntent);
            appWidgetManager.updateAppWidget(appWidgetId, views);
        }
    }
  8. Finally, declare the Service and the app widget provider in the manifest file (AndroidManifest.xml):

    <service android:name=".CurrentMoodService">
    </service>
    <receiver android:name=".CurrentMoodWidgetProvider">
        <intent-filter>
          <action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
        </intent-filter>
        <meta-data android:name="android.appwidget.provider"
                   android:resource="@xml/widgetproviderinfo"/>
    </receiver>

Source Download URL

The source code for this example is in the Android Cookbook repository, in the subdirectory CurrentMoodWidget (see “Getting and Using the Code Examples”).

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
98.82.120.188