17 min read

In this article by Kyle Mew author of the book, Android 5 Programming by Example, we will learn how to:

  • Add a GestureDetector to a view
  • Add an OnTouchListener and an OnGestureListener
  • Detect and refine fling gestures
  • Use the DDMS Logcat to observe the MotionEvent class
  • Edit the Logcat filter configuration
  • Simplify code with a SimpleOnGestureListener
  • Add a GestureDetector to an Activity
  • Edit the Manifest to control launch behavior
  • Hide UI elements
  • Create a splash screen
  • Lock screen orientation

(For more resources related to this topic, see here.)

Adding a GestureDetector to a view

Together, view.GestureDetector and view.View.OnTouchListener are all that are required to provide our ImageView with gesture functionality. The listener contains an onTouch() callback that relays each MotionEvent to the detector. We are going to program the large ImageView so that it can display a small gallery of related pictures that can be accessed by swiping left or right on the image.

There are two steps to this task as, before we implement our gesture detector, we need to provide the data for it to work on.

Adding the gallery data

As this app is for demonstration and learning purposes, and so we can progress as quickly as possible, we will only provide extra images for one or two of the ancient sites in the project. Here is how it’s done:

  1. Open the Ancient Britain project.
  2. Open the MainData.java file.
  3. Add the following arrays:
    static Integer[] hengeArray = {R.drawable.henge_large,
    R.drawable.henge_2, R.drawable.henge_3,
    R.drawable.henge_4};
    static Integer[] horseArray = {};
    static Integer[] wallArray = {R.drawable.wall_large,
    R.drawable.wall_2};
    static Integer[] skaraArray = {};
    static Integer[] towerArray = {};
    static Integer[][] galleryArray = {hengeArray, horseArray,
    wallArray, skaraArray, towerArray};
  4. Either download the project files from the Packt website or find four of your own images (around 640 x 480 px). Name them henge_2, henge_3, henge_4, and wall_2 and place them in your res/drawable directory.

This is all very straightforward, and the code that will accompany it allows you to have individual arrays of any length. This is all we need to add to our gallery data. Now, we need to code our GestureDetector and OnTouchListener.

Adding the GestureDetector

Along with the OnTouchListener that we will define for our ImageView, the GestureDetector has its own listeners. Here we will use GestureDetector.OnGestureListener to detect a fling gesture and collect the MotionEvent that describe it.

Follow these steps to program your ImageView to respond to fling gestures:

  1. Open the DetailActivity.java file.
  2. Declare the following class fields:
    private static final int MIN_DISTANCE = 150;
    private static final int OFF_PATH = 100;
    private static final int VELOCITY_THRESHOLD = 75;
    private GestureDetector detector;
    View.OnTouchListener listener;
    private int ImageIndex;
  3. In the onCreate() method assigns both the detector and listener like this:
    detector = new GestureDetector(this, new
    GalleryGestureDetector());
    listener = new View.OnTouchListener() {
    @Override
    public boolean onTouch(View v, MotionEvent event) {
    return detector.onTouchEvent(event);
    }
    };
  4. Beneath this, add the following line:
    ImageIndex = 0;
  5. Beneath the line detailImage = (ImageView) findViewById(R.id.detail_image);, add the following line:
    detailImage.setOnTouchListener(listener);
  6. Create the following inner class:
    class GalleryGestureDetector implements 
     GestureDetector.OnGestureListener { }
  7. Before dealing with the errors this generates, add the following field to
    the class:

    private int item;
    {
    item = MainActivity.currentItem;
    }
  8. Click anywhere on the line registering the error and press Alt + Enter. Then select Implement Methods, making sure that you have the Copy JavaDoc and Insert @Override boxes checked.
  9. Complete the onDown() method like this:
    @Override
    public boolean onDown(MotionEvent e) {
    return true;
    }
  10. Fill in the onShowPress() method:
    @Override
    public void onShowPress(MotionEvent e) {
    detailImage.setElevation(4);
    }
  11. Then fill in the onFling() method:
    @Override
    public boolean onFling(MotionEvent event1, MotionEvent
    event2, float velocityX, float velocityY) {
    if (Math.abs(event1.getY() - event2.getY()) > OFF_PATH)
    return false;
    if (MainData.galleryArray[item].length != 0) {
    // Swipe left
    if (event1.getX() - event2.getX() > MIN_DISTANCE &&
    Math.abs(velocityX) > VELOCITY_THRESHOLD) {
    ImageIndex++;
    if (ImageIndex ==
    MainData.galleryArray[item].length)
    ImageIndex = 0;
    detailImage.setImageResource(MainData
    .galleryArray[item][ImageIndex]);
    } else {
    // Swipe right
    if (event2.getX() - event1.getX() >
    MIN_DISTANCE && Math.abs(velocityX) >
    VELOCITY_THRESHOLD) {
    ImageIndex--;
    if (ImageIndex < 0) ImageIndex =
    MainData.galleryArray[item].length - 1;
    detailImage.setImageResource(MainData
    .galleryArray[item][ImageIndex]);
    }
    }
    }
    detailImage.setElevation(0);
    return true;
    }
  12. Test the project on an emulator or handset.

The process of gesture detection in the preceding code begins when the OnTouchListener listener’s onTouch() method is called. It then passes that MotionEvent to our gesture detector class, GalleryGestureDetector, which monitors motion events, sometimes stringing them together and timing them until one of the recognized gestures is detected. At this point, we can enter our own code to control how our app responds as we did here with the onDown(), onShowPress(), and onFling() callbacks. It is worth taking a quick look at these methods in turn.

It may seem, at the first glance, that the onDown() method is redundant; after all, it’s the fling gesture that we are trying to catch. In fact, overriding the onDown() method and returning true from it is essential in all gesture detections as all the gestures begin with an onDown() event.

The purpose of the onShowPress() method may also appear unclear as it seems to do a little more than onDown(). As the JavaDoc states, this method is handy for adding some form of feedback to the user, acknowledging that their touch has been received. The Material Design guidelines strongly recommend such feedback and here we have raised the view’s elevation slightly.

Without including our own code, the onFling() method will recognize almost any movement across the bounding view that ends in the user’s finger being raised, regardless of direction or speed. We do not want very small or very slow motions to result in action; furthermore, we want to be able to differentiate between vertical and horizontal movement as well as left and right swipes. The MIN_DISTANCE and OFF_PATH constants are in pixels and VELOCITY_THRESHOLD is in pixels per second. These values will need tweaking according to the target device and personal preference. The first MotionEvent argument in onFling() refers to the preceding onDown() event and, like any MotionEvent, its coordinates are available through its getX() and getY() methods.

The MotionEvent class contains dozens of useful classes for querying various event properties—for example, getDownTime(), which returns the time in milliseconds since the current onDown() event.

In this example, we used GestureDetector.OnGestureListener to capture our gesture. However, the GestureDetector has three such nested classes, the other two being SimpleOnGestureListener and OnDoubleTapListener. SimpleOnGestureListener provides a more convenient way to detect gestures as we only need to implement those methods that relate to the gestures we are interested in capturing. We will shortly edit our Activity so that it implements the SimpleOnGestureListener instead, allowing us to tidy our code and remove the four callbacks that we do not need. The reason for taking this detour, rather than applying the simple listener to begin with, was to get to see all of the gestures available to us through a gesture listener and demonstrate how useful JavaDoc comments can be, particularly if we are new to the framework. For example, take a look at the following screenshot:

Another very handy tool is the Dalvik Debug Monitor Server (DDMS), which allows us to see what is going on inside our apps while they are running. The workings of our gesture listener are a good place to do this as most of its methods operate invisibly.

Viewing gesture activity with DDMS

To view the workings of our OnGestureListener with DDMS, we need to first create a tag to identify our messages and then a filter to view them. The following steps demonstrate how to do this:

  1. Open the DetailActivity.java file.
  2. Declare the following constant:
    private static final String DEBUG_TAG = "tag";
  3. Add the following line inside the onDown() method:
    Log.d(DEBUG_TAG, "onDown");
  4. Add the line Log.d(DEBUG_TAG, “onShowPress”); to the onShowPress() method and do the same for each of our OnGestureDetector methods.
  5. Add the following lines to the appropriate clauses in onFling():
    Log.d(DEBUG_TAG, "left");
    Log.d(DEBUG_TAG, "right");

    Open the Android DDMS pane from the Android tab at the bottom of the window or by pressing Alt + 6.

  6. If logcat is not visible, it can be opened with the icon to the right of the top-right drop-down menu.
  7. Click on this drop-down menu and select Edit Filter Configuration.
  8. Complete the dialog as shown in the following screenshot:
  9. You can now run the project on a handset or emulator and view, in the Logcat, which gestures are being triggered and how. Your output should resemble the one here:
    02-17 14:39:00.990 1430-
    1430/com.example.kyle.ancientbritain D/tag﹕ onDown
    02-17 14:39:01.039 1430-
    1430/com.example.kyle.ancientbritain D/tag﹕ onSingleTapUp
    02-17 14:39:03.503 1430-
    1430/com.example.kyle.ancientbritain D/tag﹕ onDown
    02-17 14:39:03.601 1430-
    1430/com.example.kyle.ancientbritain D/tag﹕ onShowPress
    02-17 14:39:04.101 1430-
    1430/com.example.kyle.ancientbritain D/tag﹕ onLongPress
    02-17 14:39:10.484 1430-
    1430/com.example.kyle.ancientbritain D/tag﹕ onDown
    02-17 14:39:10.541 1430-
    1430/com.example.kyle.ancientbritain D/tag﹕ onScroll
    02-17 14:39:11.091 1430-
    1430/com.example.kyle.ancientbritain D/tag﹕ onScroll
    02-17 14:39:11.232 1430-
    1430/com.example.kyle.ancientbritain D/tag﹕ onFling
    02-17 14:39:11.680 1430-
    1430/com.example.kyle.ancientbritain D/tag﹕ right

    02-17 14:39:01.039   1430-
    1430/com.example.kyle.ancientbritain D/tag﹕ onSingleTapUp

DDMS is an invaluable tool when it comes to debugging our apps and seeing what is going on beneath the hood. Once a Log Tag has been defined in the code, we can then create a filter for it so that we see only the messages we are interested in. The Log class contains several methods to report information based on its level of importance. We used Log.d, which stands for debug. All these methods work with the same two parameters: Log.[method](String tag, String message). The full list of these methods is as follows:

  • Log.v: Verbose
  • Log.d: Debug
  • Log.i: Information
  • Log.w: Warning
  • Log.e: Error
  • Log.wtf: Unexpected error

It is worth noting that most debug messages will be ignored during the packaging for distribution except for the verbose messages; thus, it is essential to remove them before your final build.

Having seen a little more of the inner workings of our gesture detector and listener, we can now strip our code of unused methods by implementing GestureDetector.SimpleOnGestureListener.

Implementing a SimpleOnGestureListener

It is very simple to convert our gesture detector from one class of listener to another. All we need to do is change the class declaration and delete the unwanted methods. To do this, perform the following steps:

  1. Open the DetailActivity file.
  2. Change the class declaration for our gesture detector class to the following:
    class GalleryGestureDetector extends 
     GestureDetector.SimpleOnGestureListener {
  3. Delete the onShowPress(), onSingleTapUp(), onScroll(), and onLongPress() methods.

This is all you need to do to switch to the SimpleOnGestureListener. We have now successfully constructed and edited a gesture detector to allow the user to browse a series of images.

You will have noticed that there is no onDoubleTap() method in the gesture listener. Double-taps can, in fact, be handled with the third GestureDetector listener, OnDoubleTapListener, which operates in a very similar way to the other two. However, Google, in its UI guidelines, recommends that a long press should be used instead, whenever possible.

Before moving on to multitouch events, we will take a look at how to attach a GestureDetector listener to an entire Activity by adding a splash screen to our project. In the process, we will also see how to create a Full-Screen Activity and how to edit the Maniftest file so that our app launches with the splash screen.

Adding a GestureDetector to an Activity

The method we have employed so far allows us to attach a GestureDetector listener to any view or views and this, of course, applies to ViewGroups such as Layouts. There are times when we may want to detect gestures to be applied to the whole screen. For this purpose, we will create a splash screen that can be dismissed with a long press.

There are two things we need to do before implementing the gesture detector: creating a layout and editing the Manifest file so that the app launches with our splash screen.

Designing the splash screen layout

The main difference between processing gestures for a whole Activity and an individual widget, is that we do not need an OnTouchListener as we can override the Activity’s own onTouchEvent(). Here is how it is done:

  1. Create a new Blank Activity from the Project Explorer context menu called SplashActivity.java.
  2. The Activity wizard should have created an associated XML layout called activity_splash.xml. Open this and view it using the Text tab.
  3. Remove all the padding properties from the root layout so that it looks similar to this:
    <RelativeLayout

    android:layout_width=”match_parent”
    android:layout_height=”match_parent”
    tools:context=”com.example.kyle.ancientbritain
    .SplashActivity”>

    Here we will need an image to act as the background for our splash screen. If you have not downloaded the project files from the Packt website, find an image, roughly of the size and aspect of your target device’s screen, upload it to the project drawable folder, and call it splash. The file I used is 480 x 800 px.

  4. Remove the TextView that the wizard placed inside the layout and replace it with this ImageView:
    <ImageView
    android:id="@+id/splash_image"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:src="@drawable/splash"/>
  5. Create a TextView beneath this, such as the following:
    <TextView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_alignParentBottom="true"
    android:layout_centerHorizontal="true"
    android:layout_marginBottom="40dp"
    android:gravity="center_horizontal"
    android:textAppearance="?android:attr/
    textAppearanceLarge"
    android:textColor="#fffcfcbd"/>
  6. Add the following text property:
    android:text="Welcome to <b>Ancient Britain</b>npress and hold
    nanywhere on the screennto start"

    To save time adding string resources to the strings.xml file, enter a hardcoded string such as the preceding one and heed the warning from the editor to have the string extracted for you like this:

There is nothing in this layout that we have not encountered before. We removed all the padding so that our splash image will fill the layout; however, you will see from the preview that this does not appear to be the case. We will deal with this next in our Java code, but we need to edit our Manifest first so that the app gets launched with our SplashActivity.

Editing the Manifest

It is very simple to configure the AndroidManifest file so that an app will get launched with whichever Activity we choose; the way it does so is with an intent. While we are editing the Manifest, we will also configure the display to fill
the screen. Simply follow these steps:

  1. Open the res/values-v21/styles.xml file and add the following style:
    <style name="SplashTheme" parent="android:Theme.Material.
    NoActionBar.Fullscreen">
    </style>
  2. Open the AndroidManifest.xml file.
  3. Cut-and-paste the <intent-filter> element from MainActivity to SplashActivity.
  4. Include the following properties so that the entire <activity> node looks similar to this:
    <activity
    android:name=".SplashActivity"
    android:theme="@style/SplashTheme"
    android:screenOrientation="portrait"
    android:configChanges="orientation|screenSize"
    android:label="Old UK" >
    <intent-filter>
    <action android_name="android.intent.action.MAIN" />
    <category android_name="android.intent.category.LAUNCHER"
    />
    </intent-filter>
    </activity>

We have encountered themes and styles before and, here, we took advantage of a built-in theme designed for full screen activities. In many cases, we might have designed a landscape layout here but, as is often the case with splash screens, we locked the orientation with the android:screenOrientation property.

The android:configChanges line is not actually needed here, but is included as it is useful to know about it. Configuring any attribute such as this prevents the system from automatically reloading the Activity whenever the device is rotated or the screen size changed. Instead of the Activity restarting, the onConfigurationChanged() method is called. This was not needed here as the screen size and orientation were taken care of in the previous lines of code and this line was only included as a point of interest.

Finally, we changed the value of android:label. You may have noticed that, depending on the screen size of the device you are using, the name of our app is not displayed in full on the home screen or apps drawer. In such cases, when you want to use a shortened name for your app, it can be inserted here.

With everything else in place, we can get on with adding our gesture detector. This is not dissimilar to the way we did this before but, this time, we will apply the detector to the whole screen and will be listening for a long press, rather than a fling.

Adding the GestureDetector

Along with implementing a gesture detector for the entire Activity here, we will also take the final step in configuring our splash screen so that the image fills the screen, but maintains its aspect ratio. Follow these steps to complete the app splash screen.

  1. Open the SplashActivity file.
  2. Declare a GestureDetector as we did in the earlier exercise:
    private GestureDetector detector;
  3. In the onCreate() method, assign and configure our splash image and gesture detector like this:
    ImageView imageView = (ImageView) findViewById(R.id.splash_image);
    imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
    detector = new GestureDetector(this, new SplashListener());
  4. Now, override the Activity’s onTouchEvent() like this:
    @Override
    public boolean onTouchEvent(MotionEvent event) {
    this.detector.onTouchEvent(event);
    return super.onTouchEvent(event);
    }
  5. Create the following SimpleOnGestureListener class:
    private class SplashListener extends GestureDetector.
    SimpleOnGestureListener {
    @Override
    public boolean onDown(MotionEvent e) {
    return true;
    }
    @Override
    public void onLongPress(MotionEvent e) {
    startActivity(new Intent(getApplicationContext(),
    MainActivity.class));
    }
    }
  6. Build and run the app on your phone or an emulator.

The way a gesture detector is implemented across an entire Activity should be familiar by this point, as should the capturing of the long press event. The ImageView.setScaleType(ImageView.ScaleType) method is essential here; it is a very useful method in general. The CENTER_CROP constant scales the image to fill the view while maintaining the aspect ratio, cropping the edges when necessary.

There are several similar ScaleTypes, such as CENTER_INSIDE, which scales the image to the maximum size possible without cropping it, and CENTER, which does not scale the image at all. The beauty of CENTER_CROP is that it means that we don’t have to design a separate image for every possible aspect ratio on the numerous devices our apps will end up running on. Provided that we make allowances for very wide or very narrow screens by not including essential information too close to the edges, we only need to provide a handful of images of varying pixel densities to maintain the image quality on large, high-resolution devices.

The scale type of ImageView can be set from within XML with android:scaleType=”centerCrop”, for example.

You may have wondered why we did not use the built-in Full-Screen Activity from the wizard; we could easily have done so. The template code the wizard creates for a Full-Screen Activity provides far more features than we needed for this exercise. Nevertheless, the template is worth taking a look at, especially if you want a fullscreen that brings the status bar and other components into view when the user interacts with the Activity.

That brings us to the end of this article. Not only have we seen how to make our apps interact with touch events and gestures, but also how to send debug messages to the IDE and make a Full-Screen Activity.

Summary

We began this article by adding a GestureDetector to our project. We then edited it so that we could filter out meaningful touch events (swipe right and left, in this case). We went on to see how the SimpleOnGestureListener can save us a lot of time when we are only interested in catching a subset of the recognized gestures. We also saw how to use DDMS to pass debug messages during runtime and how, through a combination of XML and Java, the status and action bars can be hidden and the entire screen be filled with a single view or view group.

Resources for Article:


Further resources on this subject:


LEAVE A REPLY

Please enter your comment!
Please enter your name here