Android UI 基础教程


Jason Ostrander Android UI Fundamentals Develop and Design Jason Ostrander Android UI Fundamentals Develop and Design Android UI Fundamentals: Develop and Design Jason Ostrander Peachpit Press 1249 Eighth Street Berkeley, CA 94710 510/524-2178 510/524-2221 (fax) Find us on the Web at www.peachpit.com To report errors, please send a note to errata@peachpit.com Peachpit Press is a division of Pearson Education. Copyright © 2012 by Jason Ostrander Editor: Clifford Colby Development editor: Robyn Thomas Production editor: Myrna Vladic Copyeditor: Scout Festa Technical editor: Jason LeBrun Cover design: Aren Howell Straiger Interior design: Mimi Heft Compositor: Danielle Foster Indexer: Valerie Haynes Perry Notice of Rights All rights reserved. No part of this book may be reproduced or transmitted in any form by any means, electronic, mechanical, photocopying, recording, or otherwise, without the prior written permission of the publisher. For information on getting permission for reprints and excerpts, contact permissions@peachpit.com. Notice of Liability The information in this book is distributed on an “As Is” basis without warranty. While every precaution has been taken in the preparation of the book, neither the author nor Peachpit shall have any liability to any person or entity with respect to any loss or damage caused or alleged to be caused directly or indirectly by the instructions contained in this book or by the computer software and hardware products described in it. Trademarks Android is a trademark of Google Inc., registered in the United States and other countries. Many of the designations used by manufacturers and sellers to distinguish their products are claimed as trademarks. Where those designations appear in this book, and Peachpit was aware of a trademark claim, the designa- tions appear as requested by the owner of the trademark. All other product names and services identified throughout this book are used in editorial fashion only and for the benefit of such companies with no intention of infringement of the trademark. No such use, or the use of any trade name, is intended to convey endorsement or other affiliation with this book. ISBN 13: 978-0-321-81458-6 ISBN 10: 0-321-81458-4 9 8 7 6 5 4 3 2 1 Printed and bound in the United States of America To my lovely wife, Susan, who tirelessly supports me in all of my adventures. iv  Android UI Fundamentals: Develop and Design I could write an entire book thanking people for their help along the way. Instead, I’ll have to settle for this short paragraph: Thanks to Chris H. for pushing me to consider writing a book and giving me endless encouragement and support. To Cliff C. for giving me the chance to write this book. To Robyn T. for keeping me on schedule despite my best efforts. To JBL for fixing my code and rocking a mean bass guitar. To Scout F. and Myrna V. for working tirelessly when I was late getting chapters to them. To Lucas D. and Rob S. for reading early chapters and giving me valuable feedback. To the entire team at doubleTwist for their dedication to making great Android software. To the Android team at Google for creating a great platform. To my family for their continuing support despite my dropping off the face of the earth. To Peachpit for giving me the opportunity to write this for you. And to you, the reader, for giving me the chance to teach you in whatever small way I can. Bio Jason Ostrander is a web and mobile software developer working at Silicon Valley startup doubleTwist, where he makes syncing media to Android phones simple. Prior to that, he solved networking problems at energy management startup Sentilla and defense company Northrop Grumman. Jason holds an MS in electrical engineering from UCLA. He lives with his wife in San Francisco’s Mission District, where he spends his time searching for the perfect chile relleno. Acknowledgments Contents  v Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . viii Welcome to Android . . . . . . . . . . . . . . . . . . . . . . . . . .x Part 1  Basic Android UI Chapter 1 Getting Started. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2 Hello World . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4 Basic Structure of an Android App . . . . . . . . . . . . . . . . . . 9 Android UI Basics . . . . . . . . . . . . . . . . . . . . . . . . . . .14 Tools . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .22 Wrapping Up . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32 Chapter 2 Creating Your First Application . . . . . . . . . . . . . . . . . . . . . . . . . . . 34 Creating an App . . . . . . . . . . . . . . . . . . . . . . . . . . . .36 Getting Started with Android Views . . . . . . . . . . . . . . . . . 37 Arranging Views . . . . . . . . . . . . . . . . . . . . . . . . . . . 41 Displaying a List . . . . . . . . . . . . . . . . . . . . . . . . . . . .52 Understanding Activities . . . . . . . . . . . . . . . . . . . . . . .57 Preventing ANRs . . . . . . . . . . . . . . . . . . . . . . . . . . . 64 Finishing the TimeTracker App . . . . . . . . . . . . . . . . . . . .71 Wrapping Up . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73 Chapter 3 Going Further. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74 Supporting Multiple Screen Sizes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .76 Handling Notifications . . . . . . . . . . . . . . . . . . . . . . . . 84 Handling Events . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .92 Creating Menus . . . . . . . . . . . . . . . . . . . . . . . . . . . 96 Implementing the Time Tracker . . . . . . . . . . . . . . . . . . 102 Wrapping Up . . . . . . . . . . . . . . . . . . . . . . . . . . . . .107 Contents vi  Android UI Fundamentals: Develop and Design Part 2  The View Framework Chapter 4 Basic Views. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110 Creating a Basic Form . . . . . . . . . . . . . . . . . . . . . . . . 112 Displaying Images . . . . . . . . . . . . . . . . . . . . . . . . . . 124 Creating Maps and Displaying Websites . . . . . . . . . . . . . . .130 Wrapping Up . . . . . . . . . . . . . . . . . . . . . . . . . . . . .136 Chapter 5 Reusable UI. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 138 Abstracting Your Layouts . . . . . . . . . . . . . . . . . . . . . . 140 Abstracting Styles and Themes . . . . . . . . . . . . . . . . . . . 148 Using Fragments . . . . . . . . . . . . . . . . . . . . . . . . . . .153 Wrapping Up . . . . . . . . . . . . . . . . . . . . . . . . . . . . .162 Chapter 6 Navigation and Data Loading. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 164 Introducing the Action Bar . . . . . . . . . . . . . . . . . . . . . 166 Navigating Your App . . . . . . . . . . . . . . . . . . . . . . . . .172 Loading Data into Views . . . . . . . . . . . . . . . . . . . . . . .181 Wrapping Up . . . . . . . . . . . . . . . . . . . . . . . . . . . . .186 Chapter 7 Android Widgets. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 188 Creating a Basic Widget . . . . . . . . . . . . . . . . . . . . . . . 190 Creating a Collection Widget . . . . . . . . . . . . . . . . . . . .206 Wrapping Up . . . . . . . . . . . . . . . . . . . . . . . . . . . . .211 Contents  vii Part 3  Advanced UI Development Chapter 8 Handling Gestures . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 214 Listening to Touch Events . . . . . . . . . . . . . . . . . . . . . .216 Responding to Gestures . . . . . . . . . . . . . . . . . . . . . . .224 Wrapping Up . . . . . . . . . . . . . . . . . . . . . . . . . . . . 229 Chapter 9 Animation. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 230 Creating Drawable Animations . . . . . . . . . . . . . . . . . . . 232 Creating View Animations . . . . . . . . . . . . . . . . . . . . . .235 Creating Property Animations . . . . . . . . . . . . . . . . . . . 246 Wrapping Up . . . . . . . . . . . . . . . . . . . . . . . . . . . . 255 Chapter 10 Creating Custom Views . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 256 Understanding How Android Draws Views . . . . . . . . . . . . 258 Creating a Custom View . . . . . . . . . . . . . . . . . . . . . . 259 Adding Custom Attributes to Your Custom Views . . . . . . . . . . . . . . . . . . .267 Creating Compound Components . . . . . . . . . . . . . . . . . .274 Wrapping Up . . . . . . . . . . . . . . . . . . . . . . . . . . . . 279 Chapter 11 Creating Advanced Graphics. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 280 Using Canvas . . . . . . . . . . . . . . . . . . . . . . . . . . . . 282 Using RenderScript . . . . . . . . . . . . . . . . . . . . . . . . .289 Using OpenGL . . . . . . . . . . . . . . . . . . . . . . . . . . . . 294 Wrapping Up . . . . . . . . . . . . . . . . . . . . . . . . . . . . .301 Chapter 12 Localization and Accessibility. . . . . . . . . . . . . . . . . . . . . . . . . . . 302 Making Your App Available in Multiple Languages . . . . . . . . . . . . . . . . . 304 Making Your App Accessible . . . . . . . . . . . . . . . . . . . . 309 Wrapping Up . . . . . . . . . . . . . . . . . . . . . . . . . . . . .315 Index . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .316 viii  Android UI Fundamentals: Develop and Design There is a revolution happening in the technology industry. Touchscreen interfaces, combined with low-cost and ubiquitous smartphones, have created a perfect storm for disruptive innovation. Android is at the forefront of this change, bringing a free and open-source platform on which developers can create the next generation of applications. With free development tools and an open market, anyone can develop applications that reach a worldwide market. But why choose to develop for Android? Android now runs on the majority of smartphones in the United States. And it’s quickly expanding into new markets and device types. The last year has seen the introduction of hundreds of Android-based tablets, including the hit Kindle Fire. Google has ported Android to TVs with its Google TV platform, and many manufacturers are beginning to ship TVs with Android built in. Boeing has selected Android as the entertainment platform for its new Dreamliner jet. Ford is integrat- ing Android into its in-dash SYNC entertainment system. And Android is quickly gaining traction in the developing world, especially in Africa, where the need for low-cost handsets is greatest. Yet for all of the platform’s promise, the majority of Android applications still lack the visual polish of their iOS counterparts. This book aims to address that issue by providing developers with a solid foundation for building app UIs. It will cover the basics of UI development on Android, teach best practices for creating flexible layouts, and give you tips on how to optimize your UI for smooth, fluid performance. I created this book in the hope that it will help developers to create beautiful applications. Who am I? I’ve been developing software professionally for almost ten years, and I’ve focused on embedded and mobile software for the last five. In my day job, I work for one of the top Android development companies and write code that millions of people use every day. Android development can be difficult at times, and the challenges of supporting such a diversity of devices can be daunting. But with a good idea, a solid under- standing of the framework, and a little persistence, anyone can create a great app that is used by millions of people. I hope you’ll enjoy reading this book as much as I enjoyed writing it for you. Introduction Introduction  ix Who This Book Is For This book is aimed at beginning Android developers who are interested in creating great user interfaces. You are expected to know basic Java programming and XML syntax. The focus of this book is on UI. While you don’t need to have experience writing Android software, many basic Android concepts are only described in passing. It will help you to have a rudimentary knowledge of Android development. Who This Book Is Not For This book is not a general introduction to programming for Android. While it is intended for beginning Android developers, the focus is on user interface tools and programming. In particular, this book will not cover basic Android concepts such as intents, services, or content providers. Further, this book will not be an introduction to the Java programming language or to XML. You should know how to program and how to read XML. How You Will Learn Throughout this book, you’ll learn by creating an actual app, a simple time tracker. Each chapter includes detailed examples of real Android code that you will compile and run. All code for the book can be found at the book’s website: www.peachpit.com/androiduifundamentals. What You Will Learn You’ll learn how to create user interfaces for Android applications. From the most basic concepts, like activities and views, all the way to advanced graphics using RenderScript, this book covers everything you will use to build the user interface of your apps. A Note About Android Versions This book was written to Android version 4 APIs and best practices, but it is com- patible back to Android version 2.2. When relevant, notes and tips are included to indicate when an API is deprecated or no longer appropriate. The Android com- patibility library, a package of classes that back-port several newer features, will be used throughout the book. x  Android UI Fundamentals: Develop and Design Welcome to Android Throughout this book, you’ll be writing your code using the Eclipse integrated develop- ment environment (IDE). You’ll need to install the Android software development kit (SDK), along with the Android Developer Tools (ADT) plugin. This setup includes several other utilities to help you develop and test your application. Aside from the SDK, none of these tools are required, but they will make your application development easier. The Tools The following tools are used throughout this book to build, test, and analyze your applications. Android SDK The Android SDK is required to build and deploy Android applica- tions. The SDK contains the tools you’ll use to test and debug your applica- tion. It also contains tools for creating flexible layouts. You can download the Android SDK at http:// developer.android.com/. Eclipse Eclipse is the recom- mended IDE for Android development and is the only IDE officially sup- ported by Google. Google publishes a plugin called Android Developer Tools that integrates with Eclipse and provides features like a drag-and- drop interface builder. You are not required to use Eclipse, as the Android SDK fully supports com- mand-line development. Throughout this book, however, it is assumed you are using Eclipse. You can download Eclipse at www.eclipse.org. Welcome to Android  xi Android SDK Manager The Android SDK Manager is used to download and install the Android SDK. You will also use the SDK Manager to install add-on features like sample code, third-party emulators, and the compatibility library. The Android SDK Manager can also be used to create and launch emulated Android devices, called Android Virtual Devices. The Android SDK Manager can be found in the SDK tools/ directory as android. Hierarchy Viewer This tool displays a graphical representation of your layout hierarchy and can help you debug layout performance issues by spotting overly complex layouts. It’s also a good tool for under- standing how Android layout works. You can find this tool in the SDK tools/ directory as hierarchyviewer. DDMS The Dalvik Debug Monitor Server (DDMS) is used to debug application performance issues. It provides Java heap usage, running thread counts, and object allocation tracking. You also use it to take screen shots. The DDMS tool is built into Eclipse through the ADT or can be run standalone from the tools/ directory of the SDK. This page intentionally left blank Part 1 Basic Android UI 1 Getting Started 3 Android is a powerful mobile operating system, built using a combination of the Java programming language and XML-based layout and configuration files. This chap- ter introduces the Android development environment, walks through the basic Hello World application, and covers the Android tools, with an emphasis on the user interface (UI) tools available in the Android Developer Tools (ADT) plugin. In this chapter you’ll create a Hello World application; learn the Android project layout and purpose of each file and folder; learn basic Android UI con- cepts like the Activity class and XML layouts; and discover the tools available for creating user interfaces in Android. Before you create a basic Hello World app, you must download and install the Android developer tools available at developer.android.com. You will need to install the Android software development kit (SDK), Eclipse, and the ADT plugin. Follow the directions provided on the developer website to set up the Eclipse develop- ment environment. All examples in this book use Android SDK Release 13 and the Eclipse Helios release. When ready, create the Hello World application using the following steps: 1. Start Eclipse. 2. Open the Java perspective by choosing Window > Open Perspective > Java. 3. Choose File > New > Android Project. 4. Leave all the defaults. Name the project Example and click Next (Figure 1.1). Figure 1 .1  The Android project creation wizard Hello World 4  Chapter 1  Getting Started 5. Set the Build Target option to Android 4.0 (Figure 1.2). You’ll build to Android version 4.0 (API version 15) for all code in this book. Click Next. 6. Enter the package name com.example (Figure 1.3). 7. Click Finish, and the project will be created for you. 8. Run your app by right-clicking the Example project in the left-hand Package Explorer pane and selecting Run As > Android Application. Figure 1 .2  The Android project build target (left) Figure 1 .3  The Android project properties (right) Hello World  5 9. Select Yes when prompted to create a new Android Virtual Device (AVD). This will open the AVD Manager (Figure 1.4). Click the New button and configure it as shown in Figure 1.5. Click Create AVD when finished. 10. In the AVD Manager, select the AVD you just created and click Start. When prompted, click Launch. The emulator will start. 11. Once the emulator has loaded, close the AVD Manager, and the Android Device Chooser will open (Figure 1.6). Select the running emulator, and click OK. Figure 1 .4  The AVD Manager 6  Chapter 1  Getting Started Figure 1 .5  Android Virtual Device (AVD) creation dialog Figure 1 .6  The Android Device Chooser Hello World  7 Congratulations, you now have a running Android application (Figure 1.7). Running the Example App on a Phone If you want to run the Example app on your phone, follow these steps: 1 . On your phone’s home screen, press Menu > Settings > Applications. Select the “Unknown sources” checkbox to enable installation from your computer. 2 . Open the Development menu and select the “USB debugging” checkbox. 3 . Plug your phone into your computer. 4 . Now close the emulator on your computer and run your application again. It should install on your phone. If the Android Device Chooser dialog appears, select your device from the list. Figure 1 .7  Hello World app running on Android emulator 8  Chapter 1  Getting Started Basic Structure of an Android App The Eclipse IDE created an initial project structure for you when you started a new Android project. This project contains all the elements you need to build your application, and you should generally place all your work in this project. It’s possible to create a library project for code-sharing between applications, but for most apps this is unnecessary. This section covers the basic structure of the project folder and where you should place your code, layouts, and resources. Folder Structure Expand the Example project folder in the Package Explorer and take a look at the project structure. Android uses a standard Java application layout. Table 1.1 sum- marizes the project structure. Table 1 .1  Android Project Folder Structure Item Explanation src/ This folder contains your app’s Java source code. It follows the standard Java package conventions. For example, a com.example.Foo class would be located in the folder src/com/example/Foo.java. res/ This folder contains all the resources of your app and is where you declare the layout using XML. This folder contains all layout files, images, themes, and strings. gen/ This folder is auto-generated when you compile the XML layouts in res/. It usually contains only a single file, R.java. This file contains the constants you need to reference the resources in the res/ folder. Do not edit anything in this folder. assets/ This folder contains miscellaneous files needed by your application. If your app needs a binary asset to function, place it here. To use these files, you need to open them using the Java File application programming interfaces (APIs). AndroidManifest.xml The manifest contains essential information about your app that the Android system needs. This includes the activites and services your app uses, the permissions it requires, any intents it responds to, and basic info like the app name. default.properties Lists the Android API build target. Basic Structure of an Android App  9 Android Manifest The Android manifest contains all the information about your app’s structure and functionality. It includes all the activities your app uses, the services it provides, any database content it makes available via a content provider, and any intents it processes. 10  Chapter 1  Getting Started The manifest is where you declare the physical hardware features your app needs to run. For example, if your app requires a touchscreen device to operate properly, you would include the following line in the manifest: Declaring these hardware features as required by your application allows the Android Market to properly filter applications based on a user’s hardware configu- ration. Users with non-touchscreen phones would not be able to download an app that requires a touchscreen to properly function. You should strive to make your application as broadly compatible as possible. List features your app uses, but use code to dynamically determine their availability and gracefully degrade the user experience in your app. The manifest is where you declare the permissions required by your app. Unlike hardware requirements, all the permissions necessary to run your application must be declared in the manifest. There are no optional permissions. The manifest is where you declare the icons and labels used by your application. You can assign these attributes to many of the XML elements in the manifest. The most important is the top-level element. This is what will represent your application on the device home screen and app drawer. However, the icon/label combination doesn’t just apply to the element. You can use them on the permissions element, which displays when the user accepts your application to install. You can place them on the element, and the user will see them in the process monitor. These elements are inherited by any sub-components. Hence, if the icon and label are set, but the and icons and labels are not set, then those elements will use the icon and label by default. This setup allows you to use component-specific icons and labels for informing the user of your application’s functions. Note:  Users are very unforgiving of applications that request overly broad permissions. This is especially true of location permissions. Carefully consider the needs of your application, and don’t request permissions that you don’t need. Basic Structure of an Android App  11 Lastly, the manifest is where you declare your supported Android API versions. It’s important that you declare the API level properly, because attempting to refer- ence unsupported APIs will result in your application crashing. This is also a good way to prevent your app from being installed on newer API releases that you may not have tested yet. See Table 1.2 for more information on API levels. Table 1 .2  Android API Level Declaration Item Explanation android:minSDKVersion Declares the minimum API level required by your appli- cation. Devices running Android versions lower than this will not be able to install your application. android:targetSDKVersion Declares the version of your application you are build- ing against. This is what determines the features avail- able to your app. If this differs from the minSDKVersion, you may need to use Java reflection to access APIs that are unavailable on the lower version. android:maxSDKVersion Declares the maximum SDK your application supports. Use this to prevent installation on newer devices that you may not be ready to support. Resources Android apps store all resources in the res/ folder. What are resources? Basically, resources are anything that isn’t Java code. Images, layout files, app strings, localized strings, themes, and even animations go in the res/ folder. Android uses the direc- tory structure to separate resources for use in different device configurations. In the Hello World app, there are three drawable folders: drawable-ldpi, drawable-mdpi, and drawable-hdpi. These represent low-, medium-, and high-density resources. At runtime, the Android system will select the proper resource based on the device hardware. If no resource matches, it will select the most closely matching resource. This will be covered in depth in Chapter 3. 12  Chapter 1  Getting Started The res/values/ folder is where you place constant values used in your layout. You should place all colors, dimensions, styles, and strings in this folder. In the example Hello World app, there is a single strings.xml file containing all the user-visible strings used in the app: Hello World, ExampleActivity! Example You should never use string literals in your Java code or XML layouts. Always declare any user-visible strings in the strings.xml file. This makes it easier to localize your resources later. When using these strings in your app, you reference them by the name attribute of the string element. The res/layout/ folder also contains the XML files that declare your applica- tion layout. Android UI can be created using either XML or Java code. It’s recom- mended to use XML for layouts, because it provides a good separation between UI and application logic. Folder names are used to separate layouts for different device configurations. Basic Structure of an Android App  13 Android UI Basics The user interface (UI) is the connection between your app and your users. In fact, to the user, the UI is the app. The Android UI framework is powerful enough to cre- ate complex UIs with graphics and animations, but it also has enough flexibility to scale from small-screen handheld devices to tablets to TVs. This section covers the basics of Android UI development so you can start to create great UIs for your apps. Home Screen and Notification Bar To create Android apps, first you should understand the basic UI of the Android OS itself. The first screen an Android user encounters is the home screen (Figure 1.8). The home screen consists of sliding pages containing app launcher icons and widgets. You can press the launcher icons to start the corresponding applications. Widgets are like mini applications that present small chunks of data, such as weather or unread email counts. At the bottom of the screen are quick-launch icons for opening the phone dialer or email client. This also contains the launcher for the app drawer. The app drawer contains all the user’s installed applications in a grid. Figure 1 .8  The Android home screen, displaying widgets and the quick-launch bar 14  Chapter 1  Getting Started A key component of the Android UI is the notification tray (Figure 1.9). You access the tray by touching the status bar at the top of the screen and sliding your finger down. Android displays a list of all notifications in the notification tray: new mail notifications, currently playing music, system status info, and any long- running tasks such as downloads. Tapping a notification in the list typically opens the app that generated the notification. Figure 1 .9  The Android notification tray Note:  You should be aware that the user could replace the stock Android home screen with an alternative home screen. Generally, these alternatives follow the same UI conventions as the stock Android home screen. However, a few alternative home screens use radically different UI conventions, so it’s a good idea not to rely on any particular home screen feature in your app. Android UI Basics  15 XML Layout Android defines user interfaces using a combination of XML layout files and Java code. You can use Java to specify all layouts, but it’s generally preferable to use XML to take advantage of Android’s automatic resource selection. This allows you to declare layouts for different hardware configurations, and the Android system will select the most appropriate layout automatically. Here is the code in the Hello World application’s main.xml file. The first line is basic XML boilerplate, listing the version and encoding. This is always the same and must be included at the beginning of every layout file. The next line defines one of Android’s basic container types, the LinearLayout. This view arranges its child views linearly inside it. You will learn more about LinearLayouts in the next chapter. Note:  The xmlns:android attribute is necessary and must be declared in the top-level XML tag. This must always be present, or your resources will not build. 16  Chapter 1  Getting Started The LinearLayout is a simple container for displaying multiple sub-views in a linear fashion. For example, a series of items could be displayed inside a LinearLayout as a list. The android:orientation attribute declares the direction in which the sub-views are arranged. In this case, the layout uses the vertical orientation, and all sub-views will be arranged vertically. Finally, the width and height of the layout are declared to fill the entire parent view (more on this later). Inside the LinearLayout is a single TextView. As the name implies, this view is used for displaying text to the user. The text attribute of the TextView declares the text displayed in that TextView. In this case, it’s referencing a string defined in the strings.xml file. Android uses the @ symbol to reference other resources. You could have declared a string here, but it’s better to declare all user-visible strings in the strings.xml file to aid localizing the app later. Also, you can change the text dynamically using Java code. Each element in the layout is a view. A view is anything that can be drawn on the screen. Every text field, list item, web view, map, colorful spinning wheel, or button is represented by a view. The Android framework provides many views for you, such as the ListView and the TextView, but developers will need to create the more complex views that contain animations and special behavior. The Activity Class Let’s take a look at the source code for the Hello World application in the file src/com/example/ExampleActivity.java. In the manifest, you declared this activity and set it as the main activity to launch for the app. The Activity class is the main building block of Android applications. It represents a single screen of the application. Every screen your application contains will have an activity that represents it. In some cases, the content contained in an activity will be swapped without changing the activity (when using fragments, which you’ll learn about later). For effective Android development, it’s critical to understand the life cycle of the Activity class, because it has the most direct effect on the user experience. Tip:  Even when you plan to set the text of a TextView in code, it’s a good idea to declare a default string. That way, you can see what your layouts will look like with full text. Android UI Basics  17 package com.example; import android.app.Activity; import android.os.Bundle; public class ExampleActivity extends Activity { /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); } } Android activities use a callback structure for creating UI events. The framework calls the appropriate overridden method during the creation or destruction of the activity. In the example activity, only a single method is implemented: onCreate. This method is called when an activity is first created. Typically this is where the setup of the activity takes place. This is where you create your views and set up any adapters you need to load your data. It’s not strictly necessary to override any of the activity methods but onCreate. The example just sets the view for the activity. It does this by calling setContentView(R.layout.main). This references the R.java file that the Android developer tools built for you. In this case, it tells the system to load the main.xml file located in one of the layout directories. The Android runtime selects the most appropriate main.xml file (there is only one in this case) and loads it. Tip:  All Android callbacks occur on the main, or UI, thread. It’s important to remember not to block this thread. Take care to perform long-running operations on a different thread, or the UI will become unresponsive. 18  Chapter 1  Getting Started The R.java file allows you to reference the generated ID of resources stored in the res/ folder. To reference layout files, use R.layout.file_name; to reference strings, use R.string.string_name; and so on. Activities are short-lived objects. They are created and destroyed frequently. Each time the user rotates their phone, the activity that is currently displayed is destroyed and re-created. In addition, the Android system may destroy an activity if the device is running short of memory or resources. It’s important to design your activities with the understanding that they can be killed at any time. Take care to save any state the user might value. If your activity is destroyed, you don’t want to frustrate your users by making them re-enter text. You will learn more about how to save activity states in the next chapter. Hardware Buttons Android devices prior to version 3.0 have four hardware buttons: Home, Back, Menu, and Search. Android version 3.0 and above have made the hardware buttons optional. In their place, Android presents onscreen software buttons that replicate the functionality of hardware buttons (Figure 1.10). The Home button takes the user to the phone home screen. It’s not generally available to applications, unless the app is a home-screen replacement. The Back button is meant to navigate back in the Android activity stack. This allows the user to easily jump into an app and them immediately return to the previous screen. Figure 1 .10  Android tablet software buttons Tip:  To ensure that your app is a good Android citizen, always allow the user to return to a previous application if they have jumped straight into your app (for example, by pressing a notification or handling an intent created by another application). Don’t force the user to back out of many screens of your app to return to their previous task. Android UI Basics  19 The Menu button displays a context-dependent list of options (Figure 1.11). Use the options menu for displaying infrequently used options for your application. On Android tablets and phones running version 3.0 or greater, this button is not available, and options are instead presented on the action bar. You’ll learn about the differences between Android 4.0 devices and previous versions of Android in a later chapter. Figure 1 .11  The options menu on the Android home screen Tip:  It is often difficult for users to discover features hidden behind the Menu button. Carefully consider your application’s needs, and provide space in your layout for all common operations that the user will need to take. 20  Chapter 1  Getting Started Finally, the Search button exists to provide a quick interface for launching a search on Android. Not all applications use this button, and in many applications, pressing this button does nothing. In addition to these buttons, it is also possible to perform an alternative action on some hardware buttons. These actions are activated by long-pressing on the buttons. For example, long-pressing the Menu button will activate the software keyboard. Your application can take advantage of this feature to provide filtering in lists. Note:  Not all Android devices have all of these buttons. In particular, Android 4.0 devices omit the Search button, making it impossible for users to search in applications. Take care to evaluate the necessity of search to your application, and provide a button in your UI if search is an essential feature. Android UI Basics  21 Tools The Android SDK includes a suite of tools to assist in developing your apps. The suite consists of SDK tools and platform tools. The SDK tools, including ADT, are platform independent and are used regardless of which Android version you are developing against. The platform tools are specific to Android versions and are generally installed when updating the SDK to a new version. Let’s focus on the SDK tools, specifically those used to develop the UI of an Android app. Android Developer Tools The primary tool for developing Android apps is the Eclipse IDE using the ADT plugin. Eclipse is a powerful, multi-language development environment with features such as code completion, inline documentation, and automatic refactoring. You can use Eclipse to develop your application, debug it using the standard Eclipse Java debugger, and even sign a release package for publishing on the Android Market. There are many tools included in ADT, and you should familiarize yourself with all of them. Here, I’ll focus on the tools that aid in creating the user interface of Android applications. Chief among these is the graphical layout editor. The Graphical Layout Editor The graphical layout editor allows you to drag and drop views to create your UI. In early versions of ADT, the graphical layout editor was sparse and not very helpful. Luckily, the latest version is quite powerful and can be used to create complex layouts containing compound components and animations. Figure 1.12 shows the various components of the graphical layout editor. 1 The Configuration drop-down menu lets you change the way the layout is displayed. This is a quick way to view your UI for different hardware configurations, such as phones and tablets. 2 The Canvas displays the current layout as it will appear on your specified device configuration. The layout includes a set of context-specific buttons for quickly changing the parameters of selected views. You can drag views from the Palette and drop them here to build the UI. You can right-click components to get a context-specific list of available configurations. You can also use this list for refactoring the UI into reusable components. 22  Chapter 1  Getting Started 1 23 4 5 3 The Palette contains the basic building blocks of Android user interfaces. This is where you can find the basic layout containers, the form controls (including buttons and text inputs), and even advanced features like transi- tion animations. You can drag each of these components onto the Canvas to create your UI. When you drag components onto the Canvas, they will snap to the edges of the existing blocks, helping to align your layout. 4 The Outline displays an overview of your layout, with all components listed in a hierarchy. This makes it easy to see how the components are nested. It also makes finding hidden or invisible components easier. You can use this view to quickly reorder the components of your layout. 5 At the bottom of the graphical layout editor are tabs for switching to a standard XML view of your UI. While you can accomplish a lot using the graphical layout editor, it’s recommended that you tweak your final layouts by hand-coding the XML. Figure 1 .12  The graphical layout editor Tools  23 The graphical layout editor is very powerful, and you should spend some time getting to know the options available within it. Let’s experiment with the editor by adding a few buttons and text boxes to the layout. 1. In the Eclipse Package Explorer, expand the res/layout folder of the project. 2. Right-click the file named main.xml and select Open With > Android Layout Editor (Figure 1.13). This will display the graphical layout editor. You may need to set up a device configuration before you can start editing. At the top of the window are the controls for specifying the Android version, theme, locale, screen size, and orientation of the device. 3. Configure the options as seen in Figure 1.14. You may need to close main.xml and reopen it for the changes to take effect. Figure 1 .13  Package Explorer pane showing the res/ folder (top) Figure 1 .14  The device config- uration editor of the graphical layout editor (bottom) 24  Chapter 1  Getting Started 4. Now try dragging a TextView onto the layout, just below the existing TextView. Notice that you can place the view only above or below the existing view. Remember the LinearLayout container from before? It was set up with a vertical orientation, so you can arrange views only vertically within it. Now try changing the size of the TextView. 5. Make it fill the width of the window. 6. Add a Button below your newly created TextView, and expand it to fill the width of the window. You should now have something that looks like Figure 1.15. As you can see, the graphical layout editor makes it possible to quickly create complex layouts with very little effort. Figure 1 .15  Hello World layout with an extra text view and button Tools  25 Android Virtual Devices Android is designed to run on a wide range of hardware. It’s important to test your code extensively before release to ensure that your app is compatible with most Android devices. This is where the Android Virtual Devices, or AVDs, come in. An AVD is an emulated Android device. It’s not just a simulator; it actually runs the full Android framework, just as an actual device would. This is an important distinction, and it makes the emulator a far better representation of real-world devices than a simulator. Because AVDs are emulated devices, they run the standard Android graphics stack. This can be very slow for high-resolution AVDs such as tablets. Google is working on improving this, but for now it’s recommended to test your layouts in the graphical layout editor and only use the emulator for final verification. Of course, you can always use an actual Android device. You already created an AVD when you ran the Hello World application. You did this using the AVD Manager. Using the AVD Manager, you can create a range of emulated devices with different hardware characteristics, including JJ Screen size and default orientation JJ Hardware support, such as accelerometers and gamepads JJ The Android OS version JJ SD card storage, emulated using your hard disk In addition, many handset manufacturers make device-specific AVDs available to aid in testing on their hardware. You should create a range of virtual devices and test on all of them to ensure that your application has maximum device compatibility. Tip:  The emulator is useful for testing your app, but it cannot emulate all possible hardware features. For example, there is no support for emulating OpenGL graphics, Near Field Communication (NFC), or even Wi-Fi. To ensure maximum compatibility, you should always test your final application on a real hardware device. 26  Chapter 1  Getting Started Hierarchy Viewer When developing any application, it is important that the app feel very responsive. Often, an unresponsive app is due to slowdowns in drawing the UI. To assist in debugging these situations, Android includes a tool called the Hierarchy Viewer. As the name suggests, this program will display the full layout hierarchy of your app, allowing you to optimize and debug potential issues. Use the Hierarchy Viewer by running the tools/ hierarchyviewer executable in the Android SDK directory. 1. Run hierarchyviewer now and select the Hello World app. 2. Click the Load View Hierarchy button, and you will see something like Figure 1.16. Figure 1 .16  The Hierarchy Viewer displaying the modified Hello World app layout Note:  For security reasons, the Hierarchy Viewer will connect only to devices running developer versions of Android. In practice, you will be able to use Hierarchy Viewer only with the emulator or a phone that has been hacked to enable root access. Tools  27 There are four primary components in the Hierarchy Viewer: JJ The left sidebar displays all connected devices, along with the running processes on each device. This is where you select your application. JJ The Tree View displays a graphical representation of your UI layout. You can see exactly how many components make up your layout. Large layouts with many nested components will take much longer to draw than simple layouts. If you look closely, you will see colored circles on some components. These give you an at-a-glance indication of the time taken to draw the view and its children. Green is faster, red is slower. You can click a view to get more information about its draw time, along with a small preview of the view as it appears onscreen. JJ The Tree Overview provides a quick zoomed-out view of the entire hierar- chy, giving you a quick feel for the complexity of the layout. This pane also provides quick navigation around the Tree View pane. JJ The Layout View shows an outline of the actual displayed application. This helps to orient the view components to the actual displayed UI. By clicking around in this pane, you can see which components make up each portion of the display. If you look closely at the hierarchy displayed for the Hello World application, you may notice that it contains more components than are listed in the main.xml file. Here’s a quick explanation: JJ The topmost component is the PhoneWindow. This represents the display of the device. It is always present and is the base container for the entire display, excluding the notification bar. JJ There is a LinearLayout directly below the PhoneWindow. This is not the LinearLayout in our main.xml. Rather, this layout is drawn by the system to display the title bar above the content. Notice the extra FrameLayout and TextView? That is the title bar of the app. If you run the app with no title bar, then this layout would be removed. JJ The other FrameLayout is the application. This layout contains a child LinearLayout. The child LinearLayout is from the main.xml file in the example. It contains the two TextViews and the Button you created earlier in the Hello World app. 28  Chapter 1  Getting Started The hierarchy view is especially useful for debugging nested LinearLayouts. If you start creating layouts with many layers of LinearLayouts, consider switching to a RelativeLayout to reduce complexity. Taking Screenshots with DDMS The Dalvik Debug Monitor Server (DDMS) is a powerful tool for capturing the state of your application, including heap usage, running thread counts, object alloca- tions, and logs. While these features are outside the scope of this book, DDMS also has a very important function that all app developers will need: It allows you to take screen shots of your application. To run DDMS, in Eclipse choose Window > Open Perspective > Other > DDMS. Select your device in the Devices pane, then click the camera icon (Figure 1.17). This will open the Device Screen Capture window. From here you can rotate the image, refresh it to recapture the screen, and save it. Figure 1 .17  The DDMS Devices pane. To take a screenshot, click the camera icon. note:  Android version 4.0 and above has the ability to take screenshots without using DDMS. Simply hold the power and volume down buttons at the same time, and a screenshot will be saved to your device image gallery. Tools  29 Other Tools In addition to the common Android UI tools, there are some lesser-known tools that are useful for perfecting your app UI. draw9patch Images used in Android applications are often stretched to fit the available area on a device. This can distort the image, resulting in ugly graphics. Android uses an image called a 9-patch to handle scaling without distortion. For example, all buttons in Android are 9-patch graphics that stretch but maintain proper round- ing on their corners (Figure 1.18). A 9-patch image is simply a standard image file with an additional 1-pixel border. By coloring the pixels in this border black, you can indicate which parts of the image should stretch as the image is scaled up. The Android SDK provides the draw9patch command-line tool for creating these images. layoutopt Optimizing layouts by hand can be a tedious job. The layoutopt tool can do some of the work for you by analyzing your layouts and displaying inefficiencies in the view hierarchy. This command-line tool takes a list of XML layout files, or a directory of files, and outputs the results of its analysis. While this isn’t sufficient for debugging complex hierarchies, it can help in providing a first pass at fixing layout slowdowns. Figure 1 .18  An example of the Draw 9-patch tool. The button can stretch, but the corners remain the same. 30  Chapter 1  Getting Started Android Asset Studio Creating image resources for different screen densities is necessary but tedious. Luckily, there is an excellent tool that will create these resources for you. Called the Android Asset Studio, this tool will take an uploaded image and create density-specific versions. It can also be used for creating launcher icons, menu bar icons, action bar icons and tab icons. For now, you can find the Android Asset Studio at this address: http://android-ui-utils.googlecode.com/hg/asset-studio/dist/index.html The Android Asset Studio is part of the Eclipse ADT plugin and can be accessed by selecting File>New>Android Icon Set. Monkey When creating applications, it’s important to thoroughly test every aspect of the user experience. It can be difficult to truly test a UI, because the developer of the app is familiar with the interface and won’t try to do things that are unexpected or just plain weird—things like pressing multiple buttons at the same time while rotating the phone. This is where the Monkey tool comes in. The Monkey runs a specified number of iterations and randomly hits areas of the screen, changes the orientation of the device, presses volume and media keys, and generally just does crazy things. This is often a simple way of rooting out unexpected errors. Tools  31 Wrapping Up This chapter introduced the Android framework by having you create the standard Hello World application and explore the tools available for building user interfaces on Android. Along the way, you learned that JJ The AndroidManifest.xml file declares all the features used by your applica- tion. Use the manifest to prevent your app from running on unsupported hardware. JJ Images and layouts are separated into folders that allow the Android system to select the best resources for the device’s current configuration at runtime. JJ The Activity class is the primary building block of Android applications. Understanding its life cycle is key to building great apps. JJ The graphical layout editor provides a quick and easy way to create your applications. JJ You should use the Hierarchy Viewer tool to debug performance issues in your views. JJ You can use the DDMS tool to take screenshots of your application. 32  Chapter 1  Getting Started This page intentionally left blank 2 Creating Your First Application 35 Over the course of this book, you’ll develop a simple time-tracking app. This app will eventually have all the features you would expect in a time-tracking application: a Start/Stop button, a running clock indicator, lists of previous times, editing capabilities, and a home-screen widget for fast time entry. But to begin, you’ll create a basic app with a few buttons, a text view, and a list of times. In this chapter you’ll learn the differ- ent layout containers and when to use them; explore XML options for getting the interface just right; learn the proper way to load data and display it as a list; and dive deep into the Activity class, a fundamental part of Android UI development. To get started, create a new project called TimeTracker. This will be your app project throughout the book. In this chapter, you’ll work through some simple layouts and build a minimally functional application. Figure 2.1 shows what you’ll have built by the end of this chapter. This book won’t cover much of the back-end logic and will instead focus on the user interface code. However, all the code is available on the book’s website, www.peachpit.com/androiduifundamentals, for you to download. Figure 2 .1  The time-tracking app you’ll build by the end of this chapter Creating an App 36  Chapter 2  Creating Your First Application Getting Started with Android Views An Android application’s UI is composed of a hierarchy of View objects. Each view represents an area on the screen. A Button, for example, represents a rectangular area that can be pressed by the user. Android apps arrange views in layout contain- ers, also known as ViewGroups. Views have attributes that specify their look and arrangement within the container. The Android framework provides many views and containers. Figure 2.2 shows a few of the common view elements available. However, there are many more, and you should spend some time using the graphical layout editor to discover all the available views. It’s also possible to create custom views by subclassing a View class. You’ll learn more about this later in the book. Common View Attributes To control how the views of your UI are arranged and displayed onscreen, Android provides a number of View attributes—you saw some of them in Chapter 1. View attributes exist as fields in View classes and are accessed using getter and set- ter methods. They are also specified as XML attributes in XML layout files. The Figure 2 .2  Some common form widgets available to your app Getting Started with Android Views  37 attributes follow the form android:attribute_name, where attribute_name is the actual name of the attribute. All system attributes use the android: prefix. Here, we’ll go over the most important attributes and behaviors, covering a few confusing aspects along the way. Height and width Every view in Android is required to have a height and width. These are specified using the layout_height and layout_width attributes. Values for width and height are specified by using exact dimensions or by using the special symbolic values wrap_content or match_parent. Android API version 8 renamed fill_parent to match_parent, but fill_parent is still allowed for backward compatibility. You should use match_parent in your layout files because fill_parent is now deprecated. With wrap_content, the view will take only as much space as it needs to con- tain any content inside it. Using match_parent will make the view expand to fill the entire inside of its parent view. Alternatively, specifying an exact dimension will make a view take exactly that much space onscreen. So, for example, you can create a view with a width of 48px, and it will take exactly 48 pixels of space on the display. In general, you’ll find that match_parent and wrap_content are the most useful for creating your layouts. It can be tempting to use exact dimensions when creating your layouts. You should avoid this urge and instead use the more flexible wrap_content and match_parent. For example, you could have two views: one taking up a quarter of the screen and the other taking the remaining space. This will make your views flexible enough to fit any screen size. note:  Android uses special dimension units called density-independent pixels, or dp. This is one way that Android handles varying screen sizes and densities. You’ll learn more about this in Chapter 3. For now, know that you should almost always use dp units when specifying the size of your UI elements. 38  Chapter 2  Creating Your First Application Margin Padding Margins and padding When creating your layouts, you’ll want to add space around your views. This increases the usability of your app by increasing the target size of tappable areas. It can also add visual appeal to your app. Android uses two attributes for creating the space around views: layout_margin and padding. Margins create space on the outside of a view, which will separate the view from the surrounding views. Padding creates space inside a view. Figure 2.3 shows the difference between them. You can use attributes to set the dimensions of padding and margin for all sides of a view or for just a single side. Gravity By default, Android will place views on the left side of the screen. To change this, you use the gravity attribute. The gravity attribute controls the default position of a view’s children inside that view. For example, you can use the gravity attri- bute on a linear layout to position its child views on the right side of the screen. By default, layout containers have gravity set to left. Most other views have their default gravity set to center. Figure 2 .3  The difference between padding and margin Note:  When setting gravity, you must account for the size of the views. The gravity attribute only positions child views inside the parent view. If the parent view takes up half the screen, then gravity will position children only in that half of the screen. If you are trying to use gravity and not getting the results you expect, check the size of your views. Getting Started with Android Views  39 Similar to the gravity attribute is the layout_gravity attribute. While gravity affects the position of the children of a view, layout_gravity specifies the posi- tion of a view to its parent. Taking the example of a linear layout again, if you keep the gravity at its default value, all views will be positioned on the left side of the screen. You can then set the layout_gravity attribute of one of the child views to right, and that single view will be positioned on the right side of the screen. Figures 2.4, 2.5, and 2.6 show three screens: default gravity, gravity set to right, and one button’s layout_gravity set to right. More options There are many more optional view attributes. Some of them are specific to par- ticular views, like setting the source of an ImageView or the text of a TextView. Some are available on every view but have a default value, like the background used for images. Some can even be used to create animations for your views. You should explore these attributes and get familiar with the basics. You’ll learn about more attributes throughout this book, but there are too many to cover them all. Figure 2 .4  A linear layout with default gravity Figure 2 .5  A linear layout with gravity set to right Figure 2 .6  A linear layout with default settings; the first button has layout_gravity set to right. 40  Chapter 2  Creating Your First Application Arranging Views The Android view hierarchy starts with a layout container. These containers hold the child views and arrange them relative to each other. There are several container types with different characteristics, optimizing them for different situations. FrameLayout The simplest layout container is the FrameLayout. This container does not arrange child views at all. It simply presents each view, one on top of the other. The order of the views is based on their declaration in the XML file: Views declared later in the file are drawn on top. Use this layout whenever you want to create overlapping views. FrameLayout is especially useful when creating customized tappable elements. You can use the FrameLayout to pair a button with an ImageView, setting the button background to be transparent. This gives you more control over the padding and scaling of button images than just setting a background does. TableLayout The TableLayout displays data in a tabular format (Figure 2.7). It arranges sub- views into rows and columns, with each row contained in a TableRow container. A TableLayout will have as many columns as the TableRow with the most cells. Unlike the children of most views, the children of a TableLayout cannot specify a Figure 2 .7  An example of a table layout. Cell borders are not normally displayed. Arranging Views  41 layout_width. This is handled by the TableLayout and will be set for you. Cells can be marked to span multiple columns and expand or shrink to fill available space. You should use this layout only when displaying a table of data. In other cases, use a LinearLayout, a RelativeLayout, or the new GridLayout. LinearLayout You saw the LinearLayout in Chapter 1. You’ll be using this container a lot in your apps. As the name implies, this container arranges child views in a single direction, vertically or horizontally. The orientation attribute sets the direction for a linear layout’s child views. Child views specify how much space they will consume within the linear layout. They do this by setting a layout_weight. This parameter specifies the relative weight of one view versus the other views. By default, all views have a weight of 0. This means they will take up exactly as much space as they need to contain their content. Setting a weight higher than 0 will make a view expand to fill the remaining space in the layout. The relative value of the weight versus the weight of other views will determine how much space a particular view consumes. The buttons in Figure 2.8 are contained in a linear layout with orientation set to vertical. Each button takes up as much space as needed to contain its content. The top button has its weight set to 0 and is taking up only the space needed to display its content. The other two buttons have their weights set to 1 and 4, so in addition to their normal size, they expand to fill the remaining space. The bottom button has a higher weight and consumes more of the available space. It actually takes four-fifths of the remaining space, leaving one-fifth for the third button (1 + 4 = 5). Using layout weights allows you to create proportionally arranged views, greatly increasing the flexibility of your layouts. 42  Chapter 2  Creating Your First Application Figure 2 .8  A linear layout with three buttons demonstrating layout_weight Note:  A somewhat confusing aspect of using layout_weight is its relationship to the layout_height and layout_width attributes. The weight will generally override the height and width, but not always. If you plan to use the layout_weight attribute, set the corresponding height or width to 0dp. That way, the view size will be controlled by the weight and nothing else. Arranging Views  43 The linear layout is simple to use and perfect for the first version of the TimeTracker app. Remember Figure 2.1? Here is how you create that UI: 1. Open the TimeTracker project you created earlier. 2. Open the res/main.xml file that was created automatically. Replace its content with the following XML layout: This XML code uses a linear layout to arrange three children: a text view to hold the current time, another linear layout that will hold the two buttons, and a list view that displays a list of all previous times. The buttons are arranged using a second linear layout with orientation set to horizontal. Note that you set the layout_width of both buttons to 0dp and the layout_weight to 1. This makes the buttons expand to fill the width of the layout and divide that space equally. The list view will display a list of times with a custom layout for each row. You’ll learn more about using list views in the next section. RelativeLayout The other common layout container is the RelativeLayout. Relative layouts are more flexible than linear layouts, but they are also more complex. As its name implies, the relative layout arranges child views based on their position relative to the other views and to the relative layout itself. For example, to place a text view just to the left of a button, you would create the text view with the attribute toLeftOf=”@id/my_button”. This flexibility allows you to create very complex UIs within this one container. Tip:  Views that reference other views in a relative layout must be declared after the referenced view. Arranging Views  45 Figure 2.9 shows some buttons arranged in a relative layout. Buttons 1, 2, 3, 4, and 5 are positioned relative to the parent RelativeLayout container. The corner buttons have attributes aligning them with the top, bottom, left, and right of the relative layout. The center button is aligned with the center of the relative layout, and the remaining buttons are positioned relative to the center button. Table 2.1 lists the attributes available for views inside a RelativeLayout, as well as how they are used. Figure 2 .9  A relative layout with buttons arranged in corners and center Note:  The child views of a relative layout are arranged in the order they are declared. So if a view is declared to be in the center of the layout, all subsequent views aligned to that view would be arranged based on the center of the view. 46  Chapter 2  Creating Your First Application Table 2 .1  XML Attributes for RelativeLayout Attributes Description layout_alignParentTop, layout_alignParentBottom, layout_alignParentRight, layout_alignParentLeft These attributes will align the view with the parent. Use these to fix the view to the sides of the RelativeLayout container. The value can be true or false. layout_centerHorizontal, layout_centerVertical, layout_centerInParent Use these attributes to center a view horizontally or vertically or to directly center a view within the parent RelativeLayout. The value can be true or false. layout_alignTop, layout_alignBottom, layout_alignRight, layout_alignLeft These attributes are used to align the view with another view. Use these to line up views in the layout. The value must be the id of another view. layout_alignBaseline This attribute sets all the edges of a view to align with the specified view. This is useful when you have overlapping views and need them to exactly match. The value must be the id of another view. layout_above, layout_below, layout_leftOf, layout_rightOf Use these attributes to position a view relative to another view. This attribute sets rules on the view to ensure that it never crosses the boundary set by the edge of the target view. The value must be the id of another view. It can be tricky to master using the relative layout, but it will pay off when you create more-complex UIs. Remember that if you find yourself creating multiple nested linear layouts, you should consider using a relative layout to optimize the drawing of your UI. Note:  You cannot have a circular dependency in a relative layout. So, for example, you cannot set the width of the RelativeLayout to wrap_content and use alignParentTop on one of the child views. This will generate an error, and your R.java file will not be generated. Arranging Views  47 GridLayout Android 4 brought a new layout container called GridLayout. As its name implies, it arranges views into a grid of columns and rows. This layout makes it easier to create the common “dashboard”-style UI seen in apps like Google+. You would normally create such a UI using a TableLayout, but GridLayout allows you to create the same layout with a flatter hierarchy. This improves performance by reducing the number of views that Android has to draw. GridLayout has also been designed to support drag-and-drop creation of UIs using the graphical layout edi- tor. Developers will be able to create complex and efficient layouts just by using a GridLayout and the layout editor. Figure 2.10 shows an example layout created using the GridLayout container. In this layout are four buttons arranged into rows and columns. The XML to cre- ate that layout is: Figure 2 .10  GridLayout pro- duces complex layouts without nested containers. 48  Chapter 2  Creating Your First Application 2. Add an Edit button to the task_list.xml layout: 118  Chapter 4  Basic Views Boolean Buttons Buttons are convenient for indicating on/off states. Android has a number of views, including toggle buttons, checkboxes, and radio buttons, that subclass the Button class and present a toggle between a true value and a false value. In addition, Android 4.0 introduced an option called the switch. Figure 4.4 shows all these options for the 4.0 release of Android. Spinners A spinner looks like a button and displays a list of choices when pressed. Figure 4.5 shows an example of a spinner choice list. The options presented by a spinner can be specified using the XML android:entries attribute, or you can use a data adapter to load entries programmatically (you’ll learn more about loading entries into views via data adapters in Chapter 6). Figure 4 .4  Boolean buttons on Android 4.0 Figure 4 .5  A spinner on Android 4.0 Creating a Basic Form  119 Managing Settings Often, you will want to give users the ability to change the general options of your app through settings screens. It’s not necessary to create a form, because Android includes a set of classes designed to create settings screens. The basic class is the Preference, and there are several different preference forms, mimicking the standard UI form widgets. The user’s preferences will be saved to a key-value store that is local to your app. Prior to Android 3.0 (Honeycomb), you would use a PreferenceActivity class for displaying application preferences. Honeycomb and later releases use the new PreferenceFragment class to handle settings preferences. However, this class is not available in the compatibility library, so you will need to continue using the PreferenceActivity class for applications that are designed to run on Android 2.3 and earlier. ScrollView Adding entry fields to a form is simple, but what happens if you cannot fit all the views on one screen? In these cases, it’s often useful to allow scrolling in order to fit more elements in a single activity. To achieve this effect, you need to wrap your views in a ScrollView container. A ScrollView allows you to create a view that is larger than the physical screen on a device and scroll it to reveal the full contents. ScrollView is actually a subclass of FrameLayout, but it adds the ability to scroll its content. You typically place another layout container inside the ScrollView to arrange the child views. Tip:  You should never use a ListView inside a ScrollView. The behavior will be erratic and unpleasant to the user. If you find yourself wanting to use both, consider redesigning your app to use one or the other. 120  Chapter 4  Basic Views Since you want the user to enter an arbitrary amount of description text in the time tracker, you’ll want to use a ScrollView so they can see it all. Wrap the existing LinearLayout contents in a ScrollView: This code should be self-explanatory by now. The ScrollView simply wraps the LinearLayout, which contains the text and buttons you have already created. Notice the android:fillViewPort attribute? This prevents some odd behavior, which you’ll learn about next. The fillViewPort attribute A common issue you may experience with ScrollView is its interaction with child views that are smaller than the display. When the child view is larger than the display, the ScrollView behaves as expected, allowing you to scroll to see the full view. However, when the child view is smaller than the display, the ScrollView will automatically shrink itself to match the size of its content. The proper way to handle this is to use the fillViewPort attribute, which will cause the child views of a ScrollView to expand to the size of the display, if necessary; if they are already larger than the display, nothing happens. A simple example will demonstrate. Creating a Basic Form  121 A frequent task is displaying a block of text with a button at the bottom (such as in a license agreement to which a user must agree). Figure 4.6 shows the desired result: a long block of text that scrolls to reveal a button. When the text is smaller than a single screen, the naive implementation of ScrollView results in Figure 4.7— the button should still be pinned to the bottom of the screen but is instead directly below the text. The ScrollView only takes up as much space as its content. To fix this, set the fillViewPort attribute to true. Here is the code to correctly imple- ment scrolling for any size of text, resulting in Figure 4.8. Abstracting Styles and Themes  151 Like all resources, styles can be applied based on the device hardware configura- tion. By creating different style files and placing them in the appropriate resource folder, you can create different appearances for your app on different devices. Themes Styles applied to a view apply only to that view and not to the children of that view. Even if you apply the style to a ViewGroup such as a LinearLayout, the style will only apply to that view. To apply the style to the child views, you would need to set the style attribute for each of those children. It’s possible to apply a style to all the views of an activity or application with- out specifying the style attribute of all the views. You do this by applying the android:theme attribute to the or elements of your application manifest. This is known as a theme. Here is an example: The Holo theme, which is the default theme for all applications targeted at Android 3.0 and higher, will be applied to all elements of this activity. 152  Chapter 5  Reusable UI Using Fragments When Android made the jump from phones to tablets, Google had to redesign the architecture of applications because Android’s existing UI elements were insuf- ficient to create the type of information-rich interfaces required by tablets. To address this issue, Google introduced the fragments framework in Android 3.0. Fragments provide a method for decomposing your UI into its constituent parts so that each may be presented in a manner that is right for the device it’s running on (Figure 5.1). On a phone, the list view would consume the whole screen, and tapping an item would take the user to a new screen presenting content. But on a tablet, the list view is simply a part of the display, with the content being displayed simultaneously. As you can see in Figure 5.1, the list view and the content are each contained in a fragment. Fragments are the future of building interfaces on Android. They allow you to provide simple UI elements and arbitrarily combine them into new forms. Android now uses them extensively, and you should strive to do the same in your own applications. The key to using fragments is to understand how they differ from and interact with activities. Figure 5 .1  Fragments allow you to divide your UI into logical pieces and display them differently for each device. Left: Two fragments are displayed at once on a tablet device. Selecting a list item will change the content displayed. Center: The list fragment takes the entire display on a phone. Right: The detail fragment showing content is reached by selecting an item from the list fragment on the phone. Using Fragments  153 Layout Adding a fragment to your layout requires a simple XML element: Note that the tag is lowercase and has a class attribute. This attri- bute must reference a fully qualified Java class that extends the Fragment class. The class attribute is not required, however; when it is left out, you need to add the fragment at runtime using the FragmentManager. The FragmentManager is the interface for working with fragments in Java code—you use it to find, add, remove, and replace fragments. You’ll see more examples of the FragmentManager shortly. Fragment Life Cycle Fragments always run within the context of an existing activity. The life cycle of a fragment is similar to that of an activity, but with a few added callbacks that handle events such as attaching to the host activity. Table 5.1 summarizes the life cycle callbacks of fragments and how they correspond to activity callbacks. You only need to override the onCreateView method of a fragment to display its UI. Here is an example fragment that shows a simple TextView: public class SimpleTextFragment extends Fragment { @Override public View onCreateView(LayoutInflater inflater, p ViewGroup container, Bundle savedInstanceState) { TextView tv = new TextView(getActivity()); 154  Chapter 5  Reusable UI tv.setText(“Hello Fragment!”); return tv; } } Table 5 .1  Fragment Life Cycle Activity Callback Fragment Callback Description onCreate onAttach Called when the fragment is first associated with an activity. onCreate Called to initialize the fragment. Note that the host activity may not have finished its onCreate call. onCreateView Called to create the view hierarchy of the fragment. This method should return the inflated layout for the fragment. Note that it is not required that the fragment have a UI component. onActivityCreated Called when the host activity has finished its onCreate callback. Used for any fragment initialization that requires the host activity to be initialized. onStart onStart Called when the fragment is visible to the user. Generally called at the same time the host activity’s onStart method is called. onResume onResume Called when the fragment is visible to the user and actively running. Gen- erally called at the same time the host activity’s onResume method is called. onPause onPause Called when the fragment is no longer interacting with the user, either because the activity is paused or the fragment is being replaced. Gener- ally called at the same time the host activity’s onPause method is called. onStop onStop Called when the fragment is no longer visible to the user, either because the host activity is stopped or the fragment is being replaced. Generally called at the same time the host activity’s onStop method is called. onDestroy onDestroyView Called when the view returned by onCreateView is detached from the fragment. onDestroy Called when the fragment is no longer used. You should clean up any remaining states here. onDetach Called when the fragment is no longer attached to its host activity. Using Fragments  155 Android contains a few convenience fragments for displaying common views like lists and web content. These fragments take care of creating their views for you. When using them, you will not need to override the onCreateView method. It should be easy to refactor the TimeTracker app to use fragments: 1. Create a new class called TaskListFragment that extends ListFragment. The only method you need to override is the onCreateView method: @Override public View onCreateView(LayoutInflater inflater, p ViewGroup container, Bundle savedInstanceState) { return inflater.inflate(R.layout.task_list, null); } This just returns the existing task_list.xml layout as the view for the fragment. 2. Create another fragment called TimerFragment that extends Fragment. This will contain all the setup of the button views that was previously done in TimeTrackerActivity, along with the new setNameAndText method. Remove them from TimeTrackerActivity: public class TimerFragment extends Fragment { @Override public View onCreateView(LayoutInflater inflater, p ViewGroup container, Bundle savedInstanceState) { return inflater.inflate(R.layout.task_detail, null); } Tip:  Fragments must have a default no-argument constructor. The default constructor is used by the system to instantiate frag- ments when their host activities are re-created. Failing to provide a default constructor will not generate an error, but it will result in unexpected behav- ior in your application. If you need to pass arguments to your fragments at construction, it’s recommended you use the setArguments method. 156  Chapter 5  Reusable UI private void setNameAndText(View v, int nameId, p String value) { TextView name = (TextView) v.findViewById(R.id.name); TextView text = (TextView) v.findViewById(R.id.text); String s = getResources().getString(nameId); name.setText(s); text.setText(value); } @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); TimeTrackerActivity activity = (TimeTrackerActivity) p getActivity(); // Initialize the timer TextView counter = (TextView) p activity.findViewById(R.id.counter); counter.setText(DateUtils.formatElapsedTime(0)); Button startButton = (Button) p activity.findViewById(R.id.start_stop); startButton.setOnClickListener(activity); Button editButton = (Button) p activity.findViewById(R.id.edit); editButton.setOnClickListener(activity); View v = activity.findViewById(R.id.task_name); String text = getResources().getString p (R.string.task_name); setNameAndText(v, R.string.detail_name, text); v = activity.findViewById(R.id.task_date); text = DateUtils.formatDateTime(activity, date, p TimeTrackerActivity.DATE_FLAGS); Using Fragments  157 setNameAndText(v, R.string.detail_date, text); v = activity.findViewById(R.id.task_desc); text = getResources().getString(R.string.description); setNameAndText(v, R.string.detail_desc, text); } } 3. The layout used by TimeTrackerActivity is just the new TimerFragment. Save this in the main.xml file: 158  Chapter 5  Reusable UI You should now have an app that looks like Figure 5.2. You won’t yet be able to access the timer list, but in the next chapter you will add some navigation so that you can quickly switch between the task list and the timer. Fragment Transactions Because multiple fragments can be displayed onscreen at once, it’s possible to add and remove them without switching activities. For example, the content portion of your app (represented by a fragment) can be replaced with a different fragment when the user selects a different item from a list fragment. This allows you to create dynamic interfaces that change content as the user interacts with them. Figure 5 .2  The timer layout of the TimeTracker app Using Fragments  159 To make changes to the existing fragments in your UI, you must encapsulate them within a transaction. A fragment transaction, which is similar to a database transaction, batches all the operations that will affect the fragments (like add- ing or removing fragments and transitions) and performs them at the same time. Transactions are performed using the FragmentManager: FragmentManager fm = getFragmentManager() FragmentTransaction ft = fm.beginTransaction(); ExampleFragment fragment = new ExampleFragment(); ft.add(R.id.fragment_container, fragment); ft.commit(); Here, a new fragment is added to the UI, placed in the view, and given the ID fragment_container. The FragmentManager also provides the interface for retrieving the existing fragments in your layout. Fragments can be referenced by their ID or by a tag string: fm.findFragmentById(R.id.frag); fm.findFragmentByTag(“tag”); Note:  Fragments can be added to or removed from your layouts only when the activity is in the resumed state. 160  Chapter 5  Reusable UI Fragment Back Stack Like activities, fragments can have a back stack. However, you have direct control over which fragments are added to the stack and when they are added. Before committing a transaction, you can add the transaction to the back stack. Here is an example: FragmentManager fm = getFragmentManager() FragmentTransaction ft = fm.beginTransaction(); ExampleFragment fragment = new ExampleFragment(); ft.add(R.id.fragment_container, fragment); ft.addToBackStack(null); // takes a string name argument, p not used here ft.commit(); Later, when the user presses the Back button, the fragment transaction will be reversed. This actually reverses all the steps of the transaction, including any transitions. You also have the option of popping transactions off the back stack yourself by calling the FragmentManager.popBackStack() method, which simply pops the last transaction off the back stack; it takes not parameters. Using popBackStack gives you more control over how your UI behaves, rather than just relying on the forward and backward paradigm. Using Fragments  161 Wrapping Up In this chapter, you learned the basics of abstracting and componentizing your UI. By breaking a complex layout into components and altering a few key layout files, you can quickly change the look and feel of your UI. You also learned the basics of Android’s powerful fragments framework. Fragments allow you to abstract your app into functional components and then combine them to create complex layouts appropriate for phones, tablets, and televisions. Here are the highlights: JJ You can include one layout in another by using the and tags. JJ Rarely used layouts can slow down the drawing of your UI. For those situ- ations, use a ViewStub. JJ You can change the look and feel of your entire app by creating and applying a theme to your activities. JJ You should use fragments to break an app into separate, reusable components. JJ Changes to the displayed fragments must be performed in a fragment transaction. 162  Chapter 5  Reusable UI This page intentionally left blank 6 Navigation and Data Loading 165 This chapter builds on previous chapters by showing you how to create navigation within your app. You will learn that the action bar replaces the options menu and creates consistent functionality across Android apps; that tabbed inter- faces leverage the action bar, but you need to fall back to TabWidget on older versions of Android; that the ViewPager class lets you add side-to-side swiping navigation to your application; that adapters are used to bind your data to your displayed views; and that load- ers offer a simplified method of obtaining the data for your UI. Starting with version 3, Android gained a major new UI paradigm called the action bar (Figure 6.1). The action bar sits at the top of the screen and contains the app name, the app icon, navigation elements (such as tabs), and a series of buttons for quick actions. Using this native UI element, Android developers can quickly add functionality to their apps and create a platform-consistent user interface. Action Items The action bar replaces the traditional menu found on pre-3.0 versions of Android. Instead of a hidden set of options revealed by pressing the menu button, menu options are presented as buttons on the action bar. Not all menu options can fit, of course, so by default menu options are placed in an overflow menu that appears at the end of the action bar. Tapping the overflow menu drops down a list revealing the remaining options. When developing an app, the developer chooses which options should be shown as actions on the action bar. Figure 6 .1  The action bar running on Android 4.01. Shown are a single action item, the overflow menu, and the drop-down that appears after tapping the overflow menu. Tip:  The action bar is automatically added to your app if it’s using one of the Holo themes. Theme.Holo is the default for all apps with a target SDK version of 11 or higher (Android version 3.0). Introducing the Action Bar 166  Chapter 6  Navigation and Data Loading The buttons visible on the action bar are called action items. They represent options defined in the menu.xml file (you learned how to create menus in Chapter 3). There is a new option that controls whether the menu option is presented as an action item or is pushed into the overflow group: the android:showAsAction attribute. Set- ting this attribute to always will make the menu item always appear on the action bar, but this is discouraged, as there may not be room for all menu items on devices with smaller screens. In that situation, the action items will be presented as a horizontally scrolling list. For this reason, it’s recommended you set the android:showAsAction attribute to ifRoom, which will display the menu item as an action item only if there is enough room on the display. On smaller screens, the actions will be collapsed into the overflow menu. Here is an example menu file with a single item: In this example, a search menu item will be displayed as an action item. Note that the android:showAsAction attribute also has the withText option set—this declares that both the icon and the title text of the menu item will be displayed in the action bar. The action items are added to the action bar automatically when you inflate your menu: @Override public boolean onCreateOptionsMenu(Menu menu) { MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.menu, menu); return true; } Introducing the Action Bar  167 Split Action Bar Android 4.0 introduced a new option for the action bar: the split action bar. In this mode, the action bar is split between the top and bottom of the screen, with navigational elements at the top and action items at the bottom (Figure 6.2). This mode is found throughout Google’s applications, including in the Gmail app. To enable this, add an android:uiOptions attribute to the or elements in the AndroidManifest.xml file, and set it to splitActionBarWhenNarrow. Here’s an example, enabling a split action bar on an activity: On narrow-screen devices, the action bar will now show the action items in a bar along the bottom of the screen. On larger devices, like tablets, the actions will continue to be displayed at the top of the screen. Note that you can safely include the android:uiOptions attribute in your manifest for older versions of Android— unknown manifest attributes will be ignored by the system. Figure 6 .2  A split action bar with tabs 168  Chapter 6  Navigation and Data Loading The overflow menu will be added to the action bar if none of the menu items are action items. Action Views The action bar is more than just a row of buttons. It can also display interactive widgets called action views, which provide enhanced functionality beyond just a tappable button. A good example is the search widget: By default, a search icon is displayed; when the user presses the icon, a search widget appears, providing an area where the user can type a query (Figure 6.3). You will see this used throughout Android apps to provide rich functionality beyond simple option selection. Take a look at the code for the action view shown in Figure 6.3. To make this action item into an action view, add the android:actionViewClass attribute and set it to the desired view: Figure 6 .3  The search icon expands into a search action view. Note:  The action bar API is not available on Android versions before 3.0. If you want to use the action bar in older versions of Android, you will have to create your own layout and populate it with action items. Alternatively, you can use the open-source library ActionBarSherlock (http://actionbarsherlock.com). Introducing the Action Bar  169 Here, the search icon will activate the Android-provided SearchView widget, which displays a text-entry box. Note that the android:showAsAction attribute has a new setting: collapseActionView. This attribute is new to Android 4.0 and will collapse the action view into just an action item. Otherwise, the search widget would consume space on the action bar even when not in use. The ActionProvider Class Action views provide a richer widget set for the action bar, but they still use the default event handling. The ActionProvider class allows you to create an action bar component with a custom layout and custom event handling beyond the standard button press. You can optionally create a submenu, which can be triggered from both the action bar and the overflow menu. Here is an example action provider: 170  Chapter 6  Navigation and Data Loading In this example, the new ShareActionProvider is used to create a sharing interaction that is simpler than the standard sharing intent. Rather than creating a pop-up dialog window, the ShareActionProvider creates a short drop-down list of just the most-used sharing applications (Figure 6.4). This is possible using the ActionProvider’s support for custom layouts and events. ActionProvider is a new class available in Android 4.0, so you will need to ensure that your app functionality does not rely on it if compatibility with earlier versions of Android is important. The action bar is a great way to quickly add functionality to your application while maintaining a native platform look and feel. It provides a unified interface for the common interaction patterns found on Android. And as you will soon see, the action bar also provides basic navigation to your application. Figure 6 .4 The ShareActionProvider class is easier to use than the standard sharing intent. Introducing the Action Bar  171 Navigating Your App In mobile applications, speed is essential. Users expect to navigate your application quickly and won’t wait for needless animations or screen changes. Using activi- ties, you can add multiple screens to your app, but switching between them is not particularly fast. The solution to this problem is to add a navigation element like tabs to your UI. Tabs allow the user to quickly navigate between multiple screens of your app with a single tap. Action Bar Navigation The action bar is the preferred way to add a tabbed interface to your UI (Figure 6.2 shows an example of the action bar with tabs). A tabbed action bar is especially useful when combined with the split navigation mode, and you can create this slick interface with just a few lines of code. There are two primary types of action bar navigation: tabs and lists. Tab navigation mode To create a tabbed interface, you set the navigation mode for the action bar in the onCreate method of your activity: public void onCreate(Bundle bundle) { super.onCreate(bundle); final ActionBar bar = getActionBar(); bar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS); } You then just have to add the tabs and their listeners to the action bar: Tab t = bar.newTab(); t.setText(“Tab Text”); t.setTabListener(this); bar.addTab(bar.newTab() 172  Chapter 6  Navigation and Data Loading In this example, this is passed to the setTabListener method, indicating that the activity implements the ActionBar.TabListener interface. This interface is used to provide implementations for the tab-handling callbacks. In the activity, you implement the logic to swap fragments in your UI. The TabListener interface requires three callbacks to be implemented: public void onTabSelected(ActionBar.Tab tab, FragmentTransaction ft) { // Called when a tab is selected ft.replace(R.id.content, fragment, null); } public void onTabUnselected(ActionBar.Tab tab, FragmentTransaction ft) { // Called when current tab is no longer selected ft.remove(fragment); } public void onTabReselected(ActionBar.Tab tab, FragmentTransaction ft) { // Called when a tab is already selected and user presses it again } In this example, the content view is replaced with a new fragment when the users selects a tab, and that fragment is removed when the tab is no longer selected. Navigating Your App  173 List navigation mode An alternative to the tabbed interface is the drop-down list interface (Figure 6.5). This is a good choice when the number of possible navigation options is too large to make tabs practical. You enable list navigation by setting the navigation mode on the ActionBar: final ActionBar bar = getActionBar(); bar.setNavigationMode(ActionBar.NAVIGATION_MODE_LIST); bar.setListNavigationCallbacks(mSpinnerAdapter, this); You will need to supply both a SpinnerAdapter to bind data to the list and an implementation of the OnNavigationListener interface to handle callbacks trig- gered when a list item is selected. You will learn more about data binding later in this chapter. Figure 6 .5  List navigation mode displays a drop-down list instead of tabs. 174  Chapter 6  Navigation and Data Loading TabWidget The action bar is convenient for adding tabs to the top of your app. But if you need to add tabs to a sub-view of your app (for example, a sidebar in a tablet app), you can create a tab-style interface by using a TabWidget. TabWidgets are also used for adding tabbed interfaces to apps written for Android versions prior to 3.0, since the action bar is not available on those versions. To implement tabs using a TabWidget, you need both a TabWidget and a TabHost. TabHost is a container that you set as the root of your layout hierarchy. Inside it, you place a FrameLayout and a TabWidget. The TabWidget is the switchable list of labeled tabs. Typically you would arrange the FrameLayout and the TabWidget inside a LinearLayout, but you’re not required to. Unfortunately, this style of tabs is not completely compatible with the fragments API and requires some hacks to integrate with fragments. Here is an example implementation of a tab layout: Note that the IDs of the TabHost and TabWidget are system IDs—the tab classes require this to properly function. When the user changes tabs, the system will find the FrameLayout with ID android:id/tabcontent and change its content. However, those APIs are deprecated, and you should use fragments to swap the layouts in your UI. To handle this, you set the size of the tabcontent layout as 0 and instead place all your content in a second FrameLayout (this example uses the ID realtabcontent). Tip:  You should still place a FrameLayout with ID android:id/tabcontent in your layout to prevent errors. 176  Chapter 6  Navigation and Data Loading To add the tabs to your layout, you define them in the onCreate method of your activity: mTabHost = (TabHost) findViewById(android.R.id.tabhost); mTabHost.setup(); mTabHost.setOnTabChangedListener(new TabHost.OnTabChangeListener() { @Override public void onTabChanged(String tabId) { // View-switching code goes here } }); mTabHost.addTab(mTabHost.newTabSpec(“first”).setIndicator(“First”). p setContent(new DummyTabFactory(this))); mTabHost.addTab(mTabHost.newTabSpec(“second”). p setIndicator(“Second”).setContent(new DummyTabFactory(this))); mTabHost.addTab(mTabHost.newTabSpec(“third”).setIndicator(“Third”). p setContent(new DummyTabFactory(this))); You are required to call setup on the TabHost before you add any tabs to it. You register a listener for changes in the active tab. The onTabChanged method will be called every time the user presses one of the tabs. You then add the tabs to the TabHost. Finally, notice the DummyTabFactory. This is another hack that enables you to use fragments with the TabWidget. The following example has a simple imple- mentation that just returns a 0-sized view to match the API required by the TabContentFactory interface: public static class DummyTabFactory implements p TabHost.TabContentFactory { private final Context mContext; public DummyTabFactory(Context context) { mContext = context; } Navigating Your App  177 Page 1 Page 2 Screen Page 3 Page 4 Swipe @Override public View createTabContent(String tag) { View v = new View(mContext); v.setMinimumWidth(0); v.setMinimumHeight(0); return v; } } You can now implement the logic in onTabChanged to swap in a new layout (using fragments) when the user presses a tab. ViewPager When Google released Android 3.0, they also released the compatibility library— a collection of classes that brings the new APIs to older platforms. In addition, the compatibility library allows Google to ship new classes that they don’t yet want to add to the official Android release. The compatibility library contains one such class named the ViewPager. The ViewPager is similar to TabHost, but in the ViewPager—instead of pressing labeled tabs to switch views—the user drags the entire display to the left or right to switch pages (Figure 6.6). This is the same behavior used by the Android home screen to switch “pages.” This new behavior is available to any application that implements the ViewPager class. Figure 6 .6  In this illustration of ViewPager behavior, new pages are accessed by swiping left and right. 178  Chapter 6  Navigation and Data Loading ViewPager uses fragments to change the displayed content. To use the ViewPager, follow these steps: 1. Include the compatibility library JAR (Java Archive) in your application’s lib/ folder. 2. Create a layout containing the ViewPager tag. ViewPager is a ViewGroup, so you can use it as the root element of the layout: 3. Create a new class that extends the FragmentPagerAdapter class available in the compatibility library. You will have to use the compatibility library version of all classes. public class SamplePagerAdapter extends FragmentPagerAdapter { public SamplePagerAdapter(android.support.v4.app. p FragmentManager fm) { super(fm); } } 4. Override the getCount and getItem methods. The getCount method should return the total number of fragments in the pager; the getItem method returns the fragment at the specified position. These methods will be called as the user scrolls through the pages. Adjacent fragments are acquired before they are visible so that the UI can display them while the user is paging. @Override public int getCount() { return 3; } Navigating Your App  179 @Override public Fragment getItem(int position) { return SimpleTextFragment.newInstance(position); } 5. For this example, create a simple fragment that just displays a line of text: public class SimpleTextFragment extends Fragment { private int mPosition; public static SimpleTextFragment newInstance(int position) { SimpleTextFragment frag = new SimpleTextFragment(); frag.mPosition = position; return frag; } @Override public View onCreateView(LayoutInflater inflater, ViewGroup p container, Bundle savedInstanceState) { TextView tv = new TextView(getActivity()); tv.setText(“Page “ + mPosition); return tv; } } Tip:  Google has begun to use the paging-style interface throughout the built-in Android applications. You should consider adding support for this interface paradigm in your own application to achieve consistency with the platform. 180  Chapter 6  Navigation and Data Loading Loading Data into Views In Chapter 2, you learned about list views and how you bind data to the list. The ListView class is actually an implementation of the more general AdapterView. Adapter views are used to display data that is not known until runtime; these take the form of lists, spinners, grids, or anything else where a series of similar elements is displayed. To bind the data to the display, you use the Adapter class. The adapter takes data from a data source, inflates and configures the item views, and then loads the data into the adapter view. The data source is typically an array or a database cursor. You will use adapters and adapter views throughout your Android applications. Basic Data Binding The easiest way to bind data to a view is to use one of the premade adapter classes. SimpleAdapter is available for mapping static data defined in an array. SimpleCursorAdapter allows you to easily bind data from a database query. Both of these adapters require a few parameters that map the underlying data to the item view layout. Here is a simple example: SimpleCursorAdapter adapter = new SimpleCursorAdapter(this, android.R.layout.simple_list_item1, cursor, new String[] {TITLE}, new int[] {android.R.id.text1}); setListAdapter(adapter); Here, a SimpleCursorAdapter is created that binds data to the built-in simple_list_item1 layout. This layout displays a single row of text and is used to display a single row in a ListView. The two arrays provided map the columns of the database cursor to the IDs of views in the layout. Note that you’re not constrained to using one of the Android system layouts; you can supply your own layouts, as long as the column and ID arrays match the cursor data and layout. Once the adapter has been created, it’s bound to the AdapterVIew (a ListView, in this example) that will display the data to the user. Loading Data into Views  181 The Adapter Class Simple adapters work well when you have just a few views, but more complex data requires a custom adapter. To create a custom adapter, you override the Adapter class (or one of its subclasses) and implement the getView method. This method is called for each element of data and should return the fully inflated view for the data. Android is intelligent about loading the data, and it only loads as much as is needed to display onscreen. In addition, the view containers are recycled as the data is swapped. That way, the view will only consume the memory required to display what the user sees onscreen. You saw an example adapter in Chapter 2 when you implemented a list view. In that example, you created an adapter that extended ArrayAdapter to display in a list. The TimeTracker app will use a database for storing tasks, so you’ll need to implement a new adapter that extends CursorAdapter. This is also a good time to learn about an important optimization that you can use in your adapters. The ViewHolder pattern Creating smoothly scrolling lists requires that you optimize the time spent in the getView method. If you do too many allocations, it can cause stutters when the garbage collector runs and collects memory. Additionally, expensive operations like view inflation will slow down the drawing of your list. The ViewHolder pattern provides a way to optimize your adapters. Note:  Some subclasses of Adapter already implement the getView method for you. For example, CursorAdapter implements getView and requires that you instead implement newView and bindView. Be sure to read the documentation on your superclass adapter before creating your own. 182  Chapter 6  Navigation and Data Loading Here is the new adapter for the TimeTracker app, using the ViewHolder pattern: public class TimeListAdapter extends CursorAdapter { private static class ViewHolder { int nameIndex; int timeIndex; TextView name; TextView time; } public TimeListAdapter(Context context, Cursor c, int flags) { super(context, c, flags); } @Override public void bindView(View view, Context context, Cursor cursor) { ViewHolder holder = (ViewHolder) view.getTag(); holder.name.setText(cursor.getString(holder.nameIndex)); holder.time.setText(cursor.getString(holder.timeIndex)); } @Override public View newView(Context context, Cursor cursor, ViewGroup p parent) { View view = LayoutInflater.from(context).inflate p (R.layout.time_row, null); ViewHolder holder = new ViewHolder(); holder.name = (TextView) view.findViewById(R.id.task_name); holder.time = (TextView) view.findViewById(R.id.task_time); Loading Data into Views  183 holder.nameIndex = cursor.getColumnIndexOrThrow p (TaskProvider.Task.NAME); holder.timeIndex = cursor.getColumnIndexOrThrow p (TaskProvider.Task.DATE); view.setTag(holder); return view; } } This adapter extends CursorAdapter, so it needs to override the newView and bindView methods. The important thing to note is that the inflate and findViewById calls occur only when newView is called. After that, you simply retrieve the existing sub-views from the row view itself. This works because the rows of the list are recycled as you scroll. Once the system has enough rows inflated, it will stop calling newView to inflate a new row, and instead start reusing the existing views. Helpfully, the View class contains a setTag method that allows you to store an arbitrary object within the view. This is a simple pattern you can use in your adapters to greatly improve performance with little effort. Now that you’ve seen how to efficiently bind data to your views, you just have to get the data. But long-running operations, like database queries, can’t be per- formed on the main thread. So how do you get the data to bind in the first place? That’s where loaders come in. Loaders Before version 3.0, Android required a complex set of method calls to query the database asynchronously and retrieve a cursor. Android 3.0 introduced loaders to make loading, watching, and re-querying data a much simpler task. Loaders auto- mate the grunt work of querying your database and returning usable data to your app. They also monitor their data source for changes and will call into your app when something changes. You should absolutely be using loaders in your applica- tion, because they will greatly simplify your data binding code, and thanks to the compatibility library, you can use loaders with Android versions earlier than 3.0. 184  Chapter 6  Navigation and Data Loading The basic use of a loader requires three steps: 1 . Get a reference to a LoaderManager by calling getLoaderManager(). If run- ning on a version earlier than Android 3.0, your activity will have to extend FragmentActivity from the compatibility library. 2 . Initialize the loader by calling initLoader. The initLoader method takes three arguments: a unique integer ID for the loader, an optional bundle of arguments, and a LoaderManager.LoaderCallbacks implementation. In the TimeTracker app, the activity itself implements the callbacks: getLoaderManager().initLoader(0, null, this); The loader ID is used only within the context of your app. It is needed to distinguish between loaders when using the same LoaderManager callbacks for more than one loader. 3 . Implement the LoaderCallbacks, providing data for the loader and actions to take when the data changes. public Loader onCreateLoader(int id, Bundle args) { Uri uri = CONTENT_URI; return new CursorLoader(getActivity(), uri, null, null, p null, null); } public void onLoadFinished(Loader loader, Cursor data) { mAdapter.swapCursor(data); } public void onLoadReset(Loader loader) { mAdapter.swapCursor(null); } The CursorLoader in this example will query a database using a static URI and query parameters (null in this case results in the default data being returned). When onLoadFinished is called, it swaps the new cursor into the adapter that will then trigger the view to be updated. In the case that the data is reset, the adapter is configured with null data, clearing the display. Loading Data into Views  185 Wrapping Up This chapter introduced Android’s new action bar view, a simplified bar that provides quick actions for Android applications. You learned about the options for adding navigation to your app, including the ViewPager, which is used extensively through- out the Android 4 release. The TimeTracker app has really come together now that you’re loading data from a database into the UI. In this chapter, you learned that JJ The action bar replaces Android’s menu button and provides a unified look across applications. JJ You can use an action view to provide a search interface that is integrated into the action bar. JJ The action bar provides a simple tabbed browsing interface you can easily add to your app. JJ The ViewPager class, available in the compatibility library, creates a paging- style interface. JJ You can use loaders to asynchronously query and load data into your UI. 186  Chapter 6  Navigation and Data Loading This page intentionally left blank 7 Android Widgets 189 The Android home screen is more than just a collec- tion of application launchers. It’s a user-configurable dashboard that contains a mix of quick-launch shortcuts and easily accessible information. Android apps can create mini applications called widgets, which contain a subset of the full functionality of an app. This chapter covers the basics of widgets. Along the way, you’ll learn that widgets can be embedded in any application, though they are typically embedded in the home screen; that RemoteViews allow apps to create a full layout hier- archy and send it to other processes that can inflate and display it; and that widgets can display dynamic collections of data from sources such as databases. An Android widget is a small window into the functionality of a full application. They can be used to display status information, such as the current weather or the number of unread messages, and they can provide quick access to commonly used controls like the Play/Pause button on a music player. Widgets are embedded in other applications, though they are typically only embedded in the Launcher application. The Launcher app is the starting screen, or home screen, for an Android device. Figure 7.1 shows an example home screen with a few widgets. Figure 7 .1  Widgets that are included with Android Tip:  Google provides an app widget template pack to assist you in creating visually stunning widgets. It includes pre-built 9-patch graphics, XML layouts, and Adobe Photoshop templates. You can download the template pack at http://developer.android.com/guide/ practices/ui_guidelines/widget_design.html. Creating a Basic Widget 190  Chapter 7  Android Widgets Declaring the Widget The TimeTracker app needs a quick way for users to start and stop the timer. A widget is perfect for providing this control. Like activities and services, widgets must be declared in your application manifest. Add the following to the TimeTracker manifest: This code declares an AppWidgetProvider class (more on that in a bit) and gives it a name. In this case, it’s TimerWidgetProvider. Notice that the XML element is a . This declares it as a BroadcastReceiver, similar to the one you used for communicating between the TimerService and the TimeTrackerActivity. The AppWidgetProvider class is really just a BroadcastReceiver. You’ll learn more about the AppWidgetProvider class in a little bit, but just know that you have to specify the and that it should filter on the android.appwidget. action.APPWIDGET_UPDATE broadcast. You also need to declare the element that specifies the AppWidgetProviderInfo XML file. The AppWidgetProviderInfo XML Every widget that your app provides must have a corresponding AppWidgetProviderInfo XML file. This file contains all the metadata that the system needs to create and maintain your widget: size, update interval, preview image, layout, and so on. This file should be placed in the res/xml directory in your project. 1. Open the TimeTracker app, and create a folder named xml in the res/ directory. 2. In this folder, create a file named timer_appwidget_info.xml. Creating a Basic Widget  191 3. Enter the following code in the file: This should be self-explanatory. The element defines a series of metadata for the widget. Every widget you create should have a different provider info file. Table 7.1 summarizes the available options. You will also need to create the layout used by the widget. For the TimeTracker, the widget will show a simple box with the time and a Start/Stop button (Figure 7.2). Figure 7 .2  The timer widget you will build for the TimeTracker app 192  Chapter 7  Android Widgets Table 7 .1  AppWidgetProviderInfo Options Option Description minHeight, minWidth The default width and height that your widget will be given when created. Note that the widget may be given more space than requested. This should be in units of dp. Use the formula (70 x n) – 30, where n is the number of cells your widget needs. (See the sidebar “How to Calculate Widget Size” for more info.) minResizeHeight, minResizeWidth The absolute minimum height and width that your widget can be resized to. If greater than minWidth and minHeight, this is ignored. label The label the user will see when selecting a widget. icon The icon the user will see when selecting a widget. updatePeriodMillis The interval, in milliseconds, at which the widget should be updated. If this is not specified, the AppWidgetManager will update your widget once every 30 minutes by default. previewImage The image that is displayed to the user when selecting a widget. This is used only on Android 3.0 and above. initialLayout The XML layout file used by your widget. Note that the final widget layout may change if the user resizes it. resizeMode Set this to allow users to resize your widget. Valid values are NONE, VERTICAL, HORIZONTAL, or BOTH. The default is NONE. configure Optional attribute that declares the app widget configuration activity. autoAdvanceViewId Used with a collection widget, this specifies the view ID that should be auto-advanced by the widget’s host. Creating a Basic Widget  193 4. Create a shape drawable named widget_background.xml for the widget background, and place it in the res/drawable folder. This will simply be a box with rounded corners and a little transparency: 5. Create a new layout file named timer_widget.xml, and place it in the res/layout folder. Use the built-in play image for the Start/Stop button: You have now completed all the layout work for the widget. Tip:  The Android emulator contains an application called Widget Preview, which can be used to capture an image preview for your widget. Just install your app on the emulator, run Widget Preview, configure your widget, and save the image. The image will be available in the emula- tor’s sdcard/Download/ folder. Creating a Basic Widget  195 How to Calculate Widget Size The Android home screen is divided into cells arranged in a grid. Generally, phones have home screens with a 4 by 4 grid, and tablets have home screens with an 8 by 7 grid, but device manufacturers may create home screens with grids of any size. Here are some tips to ensure that your widgets function properly: JJ Widgets will expand to the grid size that contains them. So if the cell size is 10dp by 10dp and your widget requests a size of 15dp by 15dp, it will consume 4 cells on the grid (2 high and 2 wide). JJ The minimum size of a widget is 1 cell. JJ To calculate the size of your widget, use the formula (70 x n) – 30, where n is the desired number of cells. So, if you want your widget to be 4 cells wide, set the minWidth attribute to (70 x 4) – 30 = (280) – 30 = 250dp. JJ Widgets should not extend to the edges of their containers; they should have padding such that there is space around the visible part of the widget. Android 4.0 adds padding to the widget layout for you, but you should add some padding for previous versions of Android. Unlike normal layouts, you cannot use resource qualifiers for widget layouts. To create padding for a widget layout only on pre-4.0 devices, use dimens or differ- ent drawable resources. JJ For maximum compatibility, never make a widget that requires more than 4 by 4 cells. 196  Chapter 7  Android Widgets The AppWidgetProvider Class The AppWidgetProvider class is the primary interface to your widgets. It is respon- sible for defining the layout of the widget and providing all updates to it. It pro- vides callbacks that are triggered at different points in the life cycle of a widget. By implementing these callbacks, you can control the behavior of your widgets. Table 7.2 shows the callback methods and when they are called. Table 7 .2  AppWidgetProvider Methods Method Description onUpdate Called when the AppWidgetManager updates the widget. Also called the first time a widget is added, but only if you do not have a configuration activity set. This is the most important method to implement. onDeleted Called every time a widget is deleted from the host. onEnabled Called the first time a widget is created, but not for subsequent widget creations. Use this to perform a one-time initialization, such as instantiating database cursors that all app widgets can share. onDisabled Called when the last instance of a widget is deleted. Use this to clean up any operations performed in onEnabled. onReceive The standard BroadcastReceiver onReceive callback. This is called every time a broadcast that is relevant to an app widget is received. You normally don’t need to implement this. 1. For the TimeTracker app, create a new class named TimerWidgetProvider that extends AppWidgetProvider: public class TimerWidgetProvider extends AppWidgetProvider { @Override public void onUpdate(Context context, AppWidgetManager p appWidgetManager, int[] appWidgetIds) { } } Creating a Basic Widget  197 The most important method to override is onUpdate. This is called every time a new widget is added to the home screen. It’s also called when the system updates your widget, based on the updatePeriodMillis attribute you specified. You’ll learn about how to implement this method shortly. AppWidgetProvider is actually a subclass of BroadcastReceiver. Specifically, it receives the APPWIDGET_UPDATE broadcasts and generates the widget life cycle callbacks. This is just a convenience for you. You could create your own BroadcastReceiver and handle those updates yourself. In fact, you can override the onReceive method of AppWidgetProvider to handle broadcasts other than just the app widget broadcasts. The TimeTracker app can take advantage of this to update the widget whenever a timing event is broadcast. 2. Open TimeTrackerActivity and add some new static strings: public class TimeTrackerActivity extends FragmentActivity p implements OnClickListener, ServiceConnection, p ViewPager.OnPageChangeListener, TaskListener { public static final String ACTION_TIME_UPDATE = p “com.example.ActionTimeUpdate”; public static final String ACTION_TIMER_FINISHED = p “com.example.ActionTimerFinished”; public static final String ACTION_TIMER_STOPPED = p “com.example.ActionTimerStopped”; ... 3. Add a new TIMER_STOPPED broadcast to the TimerService that is broadcast when the timer is stopped: public void stopTimer() { mHandler.removeMessages(0); stopSelf(); mNM.cancel(TIMER_NOTIFICATION); updateTask(); // Broadcast timer stopped 198  Chapter 7  Android Widgets Intent intent = new Intent(TimeTrackerActivity. p ACTION_TIMER_STOPPED); intent.putExtra(“time”, mTime); sendBroadcast(intent); } 4. Also in TimerService, update the onStartCommand method to stop the timer if it is already running: @Override public int onStartCommand(Intent intent, int flags, p int startId) { if (isTimerRunning()) { stopTimer(); return START_STICKY; } ... 5. Add the three timer-related broadcasts to the of the TimerWidgetProvider: Creating a Basic Widget  199 6. Implement the onReceive method. You will create the missing updateWidgetTime method in the next section. Don’t forget to call the superclass onReceive at the end, or the widget updates will not be received: @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (TimeTrackerActivity.ACTION_TIME_UPDATE. p equals(action)) { Bundle extras = intent.getExtras(); long time = extras.getLong(“time”); updateWidgetTime(context, time, true); return; } else if (TimeTrackerActivity.ACTION_TIMER_FINISHED. p equals(action) || TimeTrackerActivity.ACTION_TIMER_STOPPED. p equals(action)) { Bundle extras = intent.getExtras(); long time = extras.getLong(“time”); updateWidgetTime(context, time, false); return; } super.onReceive(context, intent); } 200  Chapter 7  Android Widgets Remote Views All Android applications run in a sandbox that prevents them from interfering with other apps or with the system. However, widgets run as part of another application (the Launcher, in this case). How does Android handle this case? By using a new class called RemoteViews. The RemoteViews class is not actually a view. It’s a Parcelable object that con- tains all the information necessary to inflate the view hierarchy of the widget. It can then be sent to other applications via an intent. Those applications can inflate this new view hierarchy in their own processes. This is all done with no interaction among the apps other than the basic data types allowed by the Parcelable inter- face. This maintains the separation between application processes while allowing one application to create a view hierarchy in another application. You can think of this as similar to how HTML is transferred across the Internet and then displayed (inflated) on your computer. The RemoteViews object doesn’t actually contain a view hierarchy, but rather contains the necessary information to create one. Since the views don’t exist, the RemoteViews object doesn’t allow direct manipulation of the view attributes. Instead, you set view attributes using methods of the RemoteViews object itself, supplying the ID of the view to be updated. Add the rest of the TimerWidgetProvider implementation. 1. Implement the onUpdate method. Set a PendingIntent to be sent when the Start/Stop button is pressed: @Override public void onUpdate(Context context, AppWidgetManager p appWidgetManager, int[] appWidgetIds) { Intent intent = new Intent(context, p TimerService.class); Note:  Parcelable is similar to the standard Java Serializable interface. However, Parcelable requires that you implement the serialization of classes yourself. This makes it faster and more efficient than using the standard Serializable interface. Creating a Basic Widget  201 PendingIntent pi = PendingIntent.getService p (context, 0, intent, 0); RemoteViews views = new RemoteViews p (context.getPackageName(), R.layout.timer_widget); views.setOnClickPendingIntent(R.id.start_stop, pi); appWidgetManager.updateAppWidget(appWidgetIds, views); } This method simply creates a new PendingIntent that will send an Intent to the TimerService. This triggers the starting and stopping of the timer. Note that the setOnClickPendingIntent takes the view ID as the first parameter. Remember that a remote process inflates the views in a RemoteViews object, so you cannot set the fields directly. Finally, you update the widgets by call- ing updateAppWidget on the AppWidgetManager. 2. Implement the updateWidgetTime method. This method simply updates the displayed time and chooses the appropriate image for the button, based on whether the timer is running: private void updateWidgetTime(Context context, long time, p boolean isRunning) { AppWidgetManager manager = AppWidgetManager. p getInstance(context); int[] ids = manager.getAppWidgetIds(new p ComponentName(context, TimerWidgetProvider.class)); RemoteViews views = new RemoteViews p (context.getPackageName(), R.layout.timer_widget); views.setTextViewText(R.id.counter, p DateUtils.formatElapsedTime(time/1000)); views.setImageViewResource(R.id.start_stop, isRunning ? android.R.drawable.ic_media_pause p : android.R.drawable.ic_media_play); manager.updateAppWidget(ids, views); } 202  Chapter 7  Android Widgets The images used here are the platform-default images for a Play button and a Pause button. Now you should have a working widget for the TimeTracker app. You can use this to start and stop the timer. App Widget Configuration Activity You can optionally create an activity to configure your widgets. When the user adds a widget to the home screen, the activity will launch. The contents of the activity are up to you; there are no special classes to extend or interfaces to implement. In the activity, it is your responsibility to update the widget based on the user’s input. Here are the steps you need to take to create a widget configuration activity: 1. Add the configuration activity to the widget’s provider info XML file: 2. Create an activity with an for the APPWIDGET_CONFIGURE broadcast: Note:  Because RemoteViews must be parceled before they are used, they do not support the full set of views on the Android plat- form. Check the Android documentation on widgets for the full list of supported classes. Creating a Basic Widget  203 3. Implement the activity that will configure the widget. In the activity onCreate method, the widget ID is available as an extra in the intent used to start the activity: Intent intent = getIntent(); Bundle extras = intent.getExtras(); if (extras != null) { widgetId = extras.getInt(AppWidgetManager. p EXTRA_APPWIDGET_ID, -1); } 4. Set the widget configuration as you normally would—by calling AppWidgetManager.updateAppWidget(). You must return a result code from the activity to inform the host of the success or failure of the configuration: Intent resultValue = new Intent(); resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, p mAppWidgetId); setResult(RESULT_OK, resultValue); finish(); 5. Set the result to RESULT_CANCELED in onCreate, before anything else hap- pens, to cancel the creation of the widget if the user backs out of the activity. @Override public void onCreate(Bundle bundle) { super.onCreate(bundle); Intent resultValue = new Intent(); resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, p mAppWidgetId); setResult(RESULT_CANCELED, resultValue); ... 204  Chapter 7  Android Widgets Remember that when you use a widget configuration activity, the onUpdate method of the AppWidgetProvider class is not called when the app is added. It is the responsibility of your activity to perform initial setup of the new widget instance. Note:  You must return the EXTRA_APPWIDGET_INFO extra when your widget configuration activity finishes. If you fail to do so, your widget may not initialize properly. For this reason, always set the extra in your activity onCreate method. Creating a Basic Widget  205 Creating a Collection Widget Android 3.0 introduced a new type of widget that can display a collection of data provided by a remote data source such as a ContentProvider. The widget itself displays this data in a list, grid, stack, or picture frame. Creating one of these widgets is exactly like creating a standard widget but requires a new RemoteViewsService and RemoteViewsFactory to supply the collection data. Creating the Layout The layout for a collection widget should contain a collection view, as well as a TextView that displays when the collection is empty. The collection view can be one of the following: JJ ListView. A vertically scrolling list of items. JJ GridView. A vertically scrolling grid of items. JJ StackView. A stack of cards the user can flip through. JJ AdapterViewFlipper. A picture frame-style widget. Only a single view is visible at a time, and views are auto-advanced. Figure 7.3 shows the four types of collection widgets. Figure 7 .3  The view types available to display in a collection widget. From left to right: a ListView, a GridView, a StackView, and an AdapterViewFlipper. 206  Chapter 7  Android Widgets Here is an example layout that uses a ListView: The ListView and the TextView are contained inside a FrameLayout so that the TextView can be displayed when there is no data. Each item displayed by the widget will need a layout as well. Since this example uses a ListView, you can just use the standard android.R.layout.simple_list_item_1 layout. Tip:  The empty view must be a sibling of the collection view in your widget layout. Creating a Collection Widget  207 Creating the Service To provide data to the widget, you create a service that the widget’s hosting application can query. This service should extend the RemoteViewsService class and implement the onGetViewFactory method, which returns an instance of a RemoteViewsService .RemoteViewsFactory to act as the interface between the service and the widget. In fact, the service only exists to create the factory that the host application uses to display the widget. The RemoteViewsFactory provides the data for the collection widget. The inter- face mirrors that of the Adapter class and uses the same semantics. Here is a sample implementation of both the RemoteViewsService and the RemoteViewsFactory: public class ExampleWidgetService extends RemoteViewsService { public class ExampleRemoteViewsFactory implements p RemoteViewsService.RemoteViewsFactory { Context mContext; public ExampleRemoteViewsFactory(Context context) { mContext = context; } @Override public int getCount() { return 10; } @Override public RemoteViews getViewAt(int position) { RemoteViews views = new RemoteViews(mContext. p getPackageName(), android.R.layout. p simple_list_item_1); views.setTextViewText(R.id.text1, “Item: “ + position); return views; } 208  Chapter 7  Android Widgets // Other methods omitted for brevity } @Override public RemoteViewsFactory onGetViewFactory(Intent intent) { return new ExampleRemoteViewsFactory p (this.getApplicationContext()); } } The factory is used by the host activity to retrieve the data to display. The getViewAt method is where the RemoteViews object is returned. Here, the standard list item layout is used when populating the ListView; note that this example returns ten results. Finally, the widget must be set up to use the service in the AppWidgetProvider: public class ExampleWidgetProvider extends AppWidgetProvider { @Override public void onUpdate(Context context, AppWidgetManager p appWidgetManager, int[] appWidgetIds) { Intent intent = new Intent(context, p ExampleWidgetService.class); RemoteViews views = new RemoteViews p (context.getPackageName(), R.layout.widget_layout); views.setRemoteAdapter(R.id.list_view, intent); views.setEmptyView(R.id.list_view, R.id.empty_view); appWidgetManager.updateAppWidget(appWidgetIds, views); super.onUpdate(context, appWidgetManager, appWidgetIds); } } Creating a Collection Widget  209 When the onUpdate method of the AppWidgetProvider is called, it should return the view for the entire widget. When using a collection widget, you call the setRemoteAdapter method to set the ID of the collection view and pass it an Intent for the service that will populate the collection. You can optionally set a view ID to display when the collection is empty by using the setEmptyView method. You should now be able to run the example and create a widget similar to the one shown in Figure 7.4. Figure 7 .4 The ListView widget Note:  Unlike most callbacks on Android, the onDataSetChanged and getViewAt methods of RemoteViewsFactory do not run on the UI thread. You are free to perform synchronous, long-running operations in those methods. Also, if the getViewAt method takes a long time, the loading view returned by getLoadingView will be shown until it finishes. 210  Chapter 7  Android Widgets Wrapping Up Android widgets provide a convenient method by which apps can present data to the user without the need to load the full application. This is a powerful addition to your application’s feature set. Using widgets, users can quickly find the informa- tion they need without needless device interaction. This chapter covered the basics of creating widgets on Android. Along the way, you learned that JJ An AppWidgetProvider is really just a BroadcastReceiver that handles the widget update broadcasts for you. JJ The RemoteViews class allows widgets to be created and updated by other processes in Android. JJ The AppWidgetProviderInfo XML file is used to configure the widget and its behavior. JJ Collection widgets display data from a service using an adapter-like class named RemoteViewsFactory. Wrapping Up  211 This page intentionally left blank Part 3 Advanced UI Development 8 Handling Gestures 215 The basic Android UI toolkit provides the most com- mon interaction gestures you will need in your appli- cation: taps, long presses, and swipes. But sometimes the built-in gestures aren’t sufficient for your application or are not available on the view you’re using. For those cases, Android pro- vides the ability to create custom gestures. This chapter introduces the basics of detecting and responding to gestures. Along the way, you’ll learn that all views have an onTouchEvent method you can use to intercept touch events; that the MotionEvent class is the low-level interface to the touchscreen inputs; that you should use GestureDetector and its subclasses to enable the most common gestures; and that you can enable pinch-to-zoom functionality by using the ScaleGestureDetector. The most basic form of gesture recognition is the touch event. Touch events are the lowest level of user interaction with the touchscreen. Events include putting a finger on the screen, sliding a finger along the screen, and lifting a finger off the screen. Each of these represents a discrete touch event. There are two ways you can listen for these events: by registering an OnTouchListener on a view and implementing the onTouchEvent method, or by implementing your own view and overriding its onTouchEvent method. These methods are called with a MotionEvent object containing information about the event. Here is a simple example class that overrides View and implements the onTouchEvent method: 1. Create a class named TouchExample that extends View: public class TouchExample extends View { public TouchExample(Context context) { super(context); } } 2. Add some fields for position coordinates and some pre-computed color and font size values. Create a new Paint object to store the text color and size: public class TouchExample extends View { private Paint mPaint; private float mFontSize; private float dx; private float dy; public TouchExample(Context context) { super(context); mFontSize = 16 * getResources().getDisplayMetrics(). p density; mPaint = new Paint(); Listening to Touch Events 216  Chapter 8  Handling Gestures mPaint.setColor(Color.WHITE); mPaint.setTextSize(mFontSize); } } 3. Override the onDraw method, and use the dx and dy values to set the posi- tion of the text: @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); String text = “Hello World!”; canvas.drawText(text, dx, dy, mPaint); } Here, the text is drawn onto the canvas at the specified position, using the Paint object. 4. Now override the onTouchEvent method, set the x and y coordinates based on the input MotionEvent object, and finish by invalidating the view: @Override public boolean onTouchEvent(MotionEvent event) { dx = event.getX(); dy = event.getY(); invalidate(); return true; } Tip:  When overriding the onTouchEvent method, you should return true to consume the event and prevent the base class from handling it. Otherwise, you will receive only the ACTION_DOWN events and no others. Listening to Touch Events  217 5. Finally, create an activity that uses your new view as its content: public class GestureActivity extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); TouchExample view = new TouchExample(this); setContentView(view); } } When you run this app, you should now see the text “Hello World!” follow your finger around the screen (Figure 8.1). This simple example shows how you can listen to, and take action based on, the user’s touch input. But the example only handles a single finger. How would you handle a multi-touch screen? Figure 8 .1  The text will follow your finger as you drag it around the screen. 218  Chapter 8  Handling Gestures Multi-Touch Events The MotionEvent object actually contains information on multiple events. Each finger the user puts on the screen is tracked and referred to as a pointer. By default, the getX() and getY() methods return the default pointer. But all the other point- ers are available, accessible via the getX(int) and getY(int) methods. The input parameter is the index of the pointer. The primary pointer, the first one to touch the screen, is index 0. The total number of pointers is available by calling the getPointerCount() method. The index of a pointer can change, so each pointer is also assigned an ID. For example, if the user places a single finger on the screen, that finger is assigned pointer index 0 and ID 0. Now when they place a second finger on the screen, that new pointer is assigned index 1 and ID 1. If the user were to lift up their first finger, the second pointer would become index 0 but would retain ID 1. It’s the only pointer present; hence, it is the first pointer in the array of pointers. However, the pointer ID is consistent across touches. So if the user were to place their first finger on the screen again, that finger would again be assigned ID 0 and would once again become index 0. Note:  For efficiency, Android batches touch events into a single call to onTouchEvent. In addition to containing multiple pointer events, the MotionEvent object also contains a recent history for each event. You can access these times by using the getHistoricalX and getHistoricalY calls. Listening to Touch Events  219 In addition to containing the pointers, the MotionEvent object also contains an action parameter that describes what event has occurred. Table 8.1 summa- rizes the list of events relevant to touchscreen interfaces. This is not an exhaus- tive list. There are many more events for handling different interface types, such as keyboards, mice, TVs, and game controllers. You should refer to the Android documentation for a full list. Table 8 .1  MotionEvent Actions Action Description ACTION_DOWN The user has placed a finger on the screen in a single place. This is the first touch event and is known as the primary pointer. This pointer will have index 0. ACTION_POINTER_DOWN The user has placed a non-primary finger on the screen. The pointer index is greater than 0. ACTION_POINTER_UP A single finger has been removed from the screen, but not the finger corresponding to the primary pointer. ACTION_MOVE The user is sliding a finger across the screen. ACTION_UP The user has stopped touching the screen and has lifted all fingers away from it. ACTION_CANCEL Sent by the touchscreen framework when the current set of touch events should be canceled. This occurs if the hardware has generated a spurious touch or if a parent view has stolen the touch event. The earlier example always uses pointer index 0 to place and move the text. If you place a finger on the screen, the text will follow it. Try altering the example to show each touch pointer index and ID: 1. Add both a new class to hold the pointer data and an array to store the point- ers. For this example, you only need to track five points. Initialize the array with Pointer objects. You can remove the dx and dy fields: final static int MAX_POINTERS = 5; private Pointer[] mPointers = new Pointer[MAX_POINTERS]; class Pointer { 220  Chapter 8  Handling Gestures float x = 0; float y = 0; int index = -1; int id = -1; } public TouchExample(Context context) { for (int i = 0; i 2. Now create the drawable animation. This will be a one-shot animation, meaning it will run once and then stop. Set the animation duration to 250 milliseconds: The android:visible attribute specifies that the animated drawable will be visible before the animation starts. Creating Drawable Animations 232  Chapter 9  Animation 3. Create a simple layout that contains just an ImageView with its source set to the animation: 4. Create the activity, and set its content view to the layout. Set a touch listener on the image view to start the animation when the user taps it. Because the animation is a one-shot, you need to call stop() before start() for subsequent taps: public class AnimationExampleActivity extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); ImageView iv = (ImageView) p findViewById(R.id.image_view); iv.setOnTouchListener(new OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { ImageView iv = (ImageView) v; AnimationDrawable ad = (AnimationDrawable) p iv.getDrawable(); ad.stop(); ad.start(); Creating Drawable Animations  233 return true; } }); } } 5. Run the app, and tap the screen to see the circle animating as the system swaps between each drawable (Figure 9.1). This simple method is very fast to set up and works well when you want a sprite-type animation. Figure 9 .1  A drawable anima- tion transitioning between three drawables 234  Chapter 9  Animation Creating View Animations The primary method of creating animations prior to Android 3.0 was the view animation system. View animation provides a series of built-in animation opera- tions that generate in-between, or tween, views. You supply the starting and ending values, and the animation framework will transform the displayed view. This system is simple to implement, but it has a few drawbacks. First, it’s limited to operating on View objects. If you want to animate something that is not a view, you’ll need to do so yourself. Second, it operates only on a default set of properties; it does not affect other properties. Finally, view animation alters only how the view is drawn, not where it is positioned. Importantly, the rectangular hit area of views is not moved during a view animation—a common bug found in animation code. You need to alter the view after the animation is finished to update its position. These limitations led to the deprecation of the view animation framework in Android 3.0 and later. However, there are still a large number of pre-3.0 devices in use, so when possible you should use the view animation framework for maximum app compatibility. Defining Animations Like most UI code in Android, animations can be defined either in code or in XML. It’s preferred to use XML, however, because it is easier to create complex animations and is easily reusable. View animations defined in XML are placed in the res/anim/ folder. The basic structure of an animation is similar to that of a view. These are the available options for view animation: JJ translate, which moves a view JJ scale, which changes the size of a view JJ rotate, which rotates a view JJ alpha, which changes the transparency of a view Creating View Animations  235 Here is a simple example animation that moves (translates) a view: This should look familiar from other Android XML formats. The attributes here set the duration and the ending position of the animation. The position is specified as a percentage of the view size—this animation will move the view down and to the right. Duration is always expressed in milliseconds. Position can be specified as a percentage of the view size, as a percentage of the parent view size, or in pixel values (with no units). This same animation can be created in Java code as follows: TranslateAnimation anim = new TranslateAnimation( TranslateAnimation.RELATIVE_TO_SELF, 0.0f, TranslateAnimation.RELATIVE_TO_SELF, 0.25f, TranslateAnimation.RELATIVE_TO_SELF, 0.0f, TranslateAnimation.RELATIVE_TO_SELF, 0.25f); anim.setDuration(500); Animations can be grouped into sets, with several animations occurring simul- taneously. Here is the same example, but this time grouped in a set with another animation that changes the transparency of the view: 236  Chapter 9  Animation This animation will cause the view to be moved down and to the right, while simultaneously fading out. The fromAlpha and toAlpha attributes define the begin- ning and ending alpha transparency, where 1.0 is fully opaque and 0.0 is fully transparent. In the example, the animations occur simultaneously, but that is not required. You can have them occur in sequence, or partially overlapping, by defining the android:startOffset attribute. Set this to a value in milliseconds to have the ani- mation wait until that time offset before starting. Here is the same example again, but this time with the alpha animation beginning after the translate has finished: You can use this attribute to make several animations occur in sequence. In addition, you can nest animation sets to create animations that are even more complex. Check the Android documentation for the full list of animation options. Creating View Animations  237 Using Interpolators An important aspect of animation is how it is applied over time. By default, ani- mations occur in a linear fashion, meaning that they are applied evenly over the duration of the animation. However, this often feels wrong to users. To address this, you use what’s called an interpolator to change how the animation is applied. Android supports several different interpolators: accelerate, decelerate, over- shoot, bounce, and many more. Applying these interpolators is as simple as adding an attribute to your animation tags: Now when you run the animation, it will start slowly and accelerate until it reaches the end of the duration. Using Animations To use an animation in your application, you have to apply it to a view and run it. You do this by calling the startAnimation method on the view, passing it the animation you want to run: TextView tv = (TextView) findViewById(R.id.text); Animation animation = AnimationUtils.loadAnimation(this, R.anim.slide); tv.startAnimation(animation); This animation will now cause the TextView to animate down and to the right. However, once the animation is finished, the view will return to its previous position. Remember that view animation alters only how the view is drawn, not the actual view object itself. When the animation is finished, the view is drawn once again and appears to return to its previous position, because it never really moved at all. To address this, you can use an AnimationListener to change the view once its animation has finished. Here is an example that listens for the end of the anima- tion and makes the view invisible when finished: 238  Chapter 9  Animation final TextView tv = (TextView) findViewById(R.id.text1); Animation animation = AnimationUtils.loadAnimation(this, R.anim.slide); animation.setAnimationListener(new AnimationListener() { @Override public void onAnimationStart(Animation animation) {} @Override public void onAnimationRepeat(Animation animation) {} @Override public void onAnimationEnd(Animation animation) { tv.setVisibility(View.INVISIBLE); } }); tv.startAnimation(animation); Using this example, you should be able to create an animation similar to Figure 9.2. Figure 9 .2  In this view animation, the circle animates from the center to the bottom right while fading out. Tip:  As an alternative to using an AnimationListener, you can set the fillAfter attribute of your animation to true. This will cause the result of the animation to persist after it finishes. However, keep in mind that only the display of the view will change. If you expect the user to tap your view, you’ll need to use an animation listener to actually move it. Creating View Animations  239 Adding a Clock-Flipping Animation to the TimeTracker Since the TimeTracker app is being built to support Android 2.2, you’ll use view animation to create a clock-flipping animation for the time counter. To do this, you’ll need to first create a new layout for the counter, then define a few animations, and finally add the logic to the app to properly change each digit. The counter will use two separate TextViews for each digit. The animation can then slide one out and the other in while fading them to create a nice flipping effect. 1. Create a layout called digit.xml that will contain the two TextViews, and set one to be invisible by default: 240  Chapter 9  Animation 2. Create a new layout for the counter itself, and name it counter.xml. It will include multiple instances of the digit layout, separated by colons. Every- thing is arranged in a horizontal LinearLayout: 3. Add the logic to set the time and trigger the animations. Start by calculating the hours, minutes, and seconds: long hours = 0; long minutes = 0; long seconds = 0; if (time > 3600*1000) { hours = time/(3600*1000); time -= hours*3600*1000; } if (time > 60*1000) { minutes = time/(60*1000); time -= minutes*60*1000; seconds = time/1000; if (time > 1000) { seconds = time/1000; time -= seconds*1000; } 242  Chapter 9  Animation 4. Animate each digit: animateDigit(R.id.minute2, minutes%10); animateDigit(R.id.minute1, minutes/10); animateDigit(R.id.hour2, hours%10); animateDigit(R.id.hour1, hours/10); animateDigit(R.id.second2, seconds%10); animateDigit(R.id.second1, seconds/10); 5. Create the animateDigit function; this function takes a digit layout ID and a value, and it sets the digit while animating the transition. Next, prevent the animation from running if a previous animation has not finished. Then create an animation listener that sets the proper digit value once the ani- mation is complete: private void animateDigit(final int id, final long value) { final View v = findViewById(id); final TextView text1 = (TextView) p v.findViewById(R.id.text1); final TextView text2 = (TextView) p v.findViewById(R.id.text2 ); boolean running = false; if (text1.getAnimation() != null) running = !text1.getAnimation().hasEnded(); if (Long.parseLong(text1.getText().toString()) == p value || running) return; Animation animation = AnimationUtils.loadAnimation p (this, R.anim.slide_out); animation.setAnimationListener(new AnimationListener() { @Override public void onAnimationStart(Animation animation) { } Creating View Animations  243 @Override public void onAnimationRepeat(Animation animation) { } @Override public void onAnimationEnd(Animation animation) { text1.setText(“” + value); } }); text1.startAnimation(animation); animation = AnimationUtils.loadAnimation p (this, R.anim.slide_in); text2.startAnimation(animation); text2.setText(“” + value); } 6. Create the sliding animations for the two text views. These should be familiar by now. Here is the slide in animation: And the slide out animation: 7. Run TimeTracker, and you will now see a nice animated clock (Figure 9.3). As you can see, working with view animations is quite simple. However, the limitations of view animations required a reworking of the Android animation system. For 3.0 and later devices, it’s recommended you use the property anima- tion framework. Figure 9 .3  The clock-flipping animation for the TimeTracker app Creating View Animations  245 Creating Property Animations Android 3.0 introduced an animation framework called property animation. This framework allows you to change any object, not just views, and it actually changes an object’s values, not just the way it is drawn. It may seem strange to animate something that is not a view; after all, views are what the user sees. But you can think of property animation as a framework for changing any value over time. Generally, these values will cause some animation on the screen, but this is not strictly necessary. Property animation is very powerful and can make some ani- mations much easier. ValueAnimator The base class for all property animation is ValueAnimator. This class takes a starting value, an ending value, and a duration, and it calculates a new value at each time step of an animation; the time steps are determined by the speed of the animation thread. The ValueAnimator requires an Interpolator and a TypeEvaluator to compute the property values. Here is a brief explanation of each class: JJ ValueAnimator. This is the base class for all animators. At each frame of animation, it calculates the percentage of the animation that is completed, based on the animation’s starting time and duration. It then calls the Interpolator and TypeEvaluator to calculate the new property values. JJ Interpolator. Like view animation, property animation allows you to set different interpolators that define how the animated property changes over time. After the ValueAnimator determines the percentage progress of the animation, it passes that value to the interpolator to determine the amount of change that should be applied to the property value. The default interpolator is an accelerate-decelerate interpolator. JJ TypeEvaluator. Because the property animation system can operate on any type of value, it requires a TypeEvaluator class to convert between a float progress value and the appropriate value for the type of the property. The framework supplies evaluators for floats, integers, and color values. For other types, you will have to implement your own subclass of TypeEvaluator. 246  Chapter 9  Animation A simple example will demonstrate how ValueAnimator works: ValueAnimator animation = ValueAnimator.ofFloat(0f, 1f); animation.setDuration(250); animation.start(); Here, an animation is created and set to run for a duration of 250 milliseconds, starting at a floating point value of 0 and stopping at the value 1. The interpolator is the default accelerate-decelerate interpolator. Notice the ofFloat method? That method specifies that the values are floats and that a float TypeEvaluator should be used. There are also ofInt and ofObject methods for creating animations with integers and generic Object properties. In the case of objects, you will need to supply a custom TypeEvaluator for the ValueAnimator to produce correct output. This code does not yet do anything useful, because the values computed by the ValueAnimator are not used. You can register an AnimatorListener and a ViewAnimator.AnimatorUpdateListener to listen for animation events and make updates accordingly. However, Android provides a convenience implementation of ValueAnimator called ObjectAnimator that performs updates for you. The ObjectAnimator class ObjectAnimator, a subclass of ValueAnimator, sets the value of an object for you. Its API is similar to that of ValueAnimator, but it has default implementations for the callbacks that will update an object’s property. Here is a simple example that updates the alpha transparency of a view: View view = findViewById(R.id.my_view); ObjectAnimator animation = ObjectAnimator.ofFloat(view, “alpha”, p 0f, 1f); animation.setDuration(250); animation.start(); Creating Property Animations  247 Note that the API requires two new additions: the object to update and the property that should be updated. In this case, the ObjectAnimator will update the alpha transparency of the view from completely transparent to completely opaque. There are a few conditions the object must meet in order for this to work: JJ The object must have a camel-case style setter method for the property, of the form set(). If your object does not have a setter, consider creating a wrapper class that implements such a setter. JJ The ofFloat, ofInt, and ofObject methods have an alternate form that requires only the ending value. If you wish to use this short form, then your object must have a getter method that the ObjectAnimator can use to retrieve the starting value. This method must be a camel-case style getter of the form get(). JJ The getter and setter must operate on the same value type that you supply for the animator. So, if the getter and setter use integers, you must use the ofInt method when creating the ObjectAnimator. If you follow those rules, you can animate any object. For some properties of views, you may need to call invalidate on the view to force it to redraw. By default, most of the view properties will do this for you. See the Android documentation for the full API requirements. Note:  When setting the property value, be sure to use the camel-case version of the property. While the ObjectAnimator will set the initial letter to the correct case, any other letters will need to be properly cased. For example, to call setTranslationX, you would supply the string “translationX”. In addition, the ObjectAnimator uses reflec- tion internally to set the properties of objects. This can be resource intensive, so be mindful of animation performance. 248  Chapter 9  Animation The Property class In the example, the string alpha was used to specify that the alpha property of the view should be modified. This allows you to set any property of an object. However, it can sometimes lead to programming errors if the string does not match an actual property of the object. To make this easier, Android 4.0 introduced the Property class to ensure that the correct properties of an object are updated. Here is the same example, but this time using the ALPHA property of View: View view = findViewById(R.id.my_view); ObjectAnimator animation = ObjectAnimator.ofFloat(view, View.ALPHA, p 0f, 1f); animation.setDuration(250); animation.start(); As you can see, this allows you to statically check the property matches before deploying your code. You can create your own Property implementations to achieve type correctness when animating your objects. Animator Sets Property animations can be grouped into sets, just like view animations. To cre- ate a set, you instantiate an AnimatorSet object and add animations to it. The AnimatorSet object has several methods for defining how animations are grouped. Here is a simple example that demonstrates the basics: View v = findViewById(R.id.test); AnimatorSet set = new AnimatorSet(); ObjectAnimator alpha = ObjectAnimator.ofFloat(v, View.ALPHA, 0f); ObjectAnimator slide = ObjectAnimator.ofFloat(v, View.TRANSLATION_Y, p 100f); ObjectAnimator scale = ObjectAnimator.ofFloat(v, View.SCALE_X, 2f); set.play(alpha).with(slide).after(scale); set.setDuration(1000); set.start(); Creating Property Animations  249 The play method schedules an animation to run and returns an AnimatorSet .Builder object. The builder provides a simple declarative API that allows you to schedule animations using natural language. In this example, the view will first scale to become twice as large, then simultaneously slide down and fade away. The entire animation will take 3 seconds. You can also supply another AnimatorSet object to create more-complex animations with multiple different sets running sequentially or in parallel. It’s possible to create very complex animation sequences using AnimatorSet. However, as you will see later, using ViewPropertyAnimator is an even easier way to animate view classes. Property Animations in XML You are not limited to creating property animations in code. Like view animations, property animations can be defined using XML, allowing for easy reuse of common animations. When you write property animations in XML, you should place the files in the res/animator/ directory. This is not strictly necessary, but it will help you separate view animations from property animations. It is also required for the Eclipse ADT plugin to properly locate your property animations. Here is an example animation set, containing the same three animations from before: Tip:  The setDuration method of AnimatorSet applies the supplied value to all animators contained within that set. It does not set the total duration of the entire sequence of animations. 250  Chapter 9  Animation This is very similar to the XML for view animations, but the attributes are a little different. First, you must supply the property name that you wish the anima- tion to update; if the property doesn’t exist on the object, then the animation does nothing. Next, you must supply a value type; in this case, they are all floats. Finally, just as with a view animation, you supply the duration and ending value. To load and use this animation, add the following code: TextView tv = (TextView) findViewById(R.layout.text); AnimatorSet set = (AnimatorSet) AnimatorInflater.loadAnimator(this, p R.animator.animation); set.setTarget(tv); set.start(); You can also define the ordering of the animations in a set. Set the optional android:ordering property to sequential to play the animations one after the other, rather than simultaneously. Tip:  If there is any mistake in your animation XML, such as an incorrect property name or an attribute that does not exist, the animation simply does nothing. If you’re having trouble getting the animation to run, double-check all the attributes. Creating Property Animations  251 Hardware Acceleration Android 3.0 added better support for drawing 2D graphics using hardware acceleration. Previous versions of Android used more software operations for graphics rendering. To add hardware acceleration support to your application, add the android:hardwareAccelerated=”true” attribute to the tag in your application manifest. All applications targeting API level 14 or above have this setting turned on by default. In addition to enabling application-wide acceleration, you can also enable (or disable) accelerated rendering at the activity or view level. Adding hard- ware acceleration to a view can increase the performance of your animations. For example, when using an ObjectAnimator, you can turn on acceleration to get an extra boost of performance. You will need to disable it when finished, however, to free graphics memory: view.setLayerType(View.LAYER_TYPE_HARDWARE, null); ObjectAnimator anim = ObjectAnimator.ofFloat(view, “alpha”, 1f); anim.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { view.setLayerType(View.LAYER_TYPE_NONE, null); } }); anim.start(); While hardware acceleration is generally not necessary, consider enabling it if you have complex views or experience animation performance issues. 252  Chapter 9  Animation ViewPropertyAnimator ObjectAnimator is very powerful, but its ability to apply to any object requires that it have a rather verbose API. Since the common case is animating a single view, Android provides a convenience class to do just that: ViewPropertyAnimator. This class has a very concise API that enables the chaining of multiple animation API calls on a single line. In addition, because it is constrained to just views, it provides some modest performance improvements over the ObjectAnimator class. To use a ViewPropertyAnimator, you call the animate() method on a view, which returns a ViewPropertyAnimator instance. This class provides methods for updating the common view properties, such as position, transparency, and size. In addition, each of the ViewPropertyAnimator methods returns itself, so you can chain multiple API calls together. Here is a simple example that moves a view and changes the alpha level: TextView tv = (TextView) findViewById(R.id.text); tv.animate().x(200).y(200).alpha(0f); This code will move the text view to position 200 x 200 on the display, while changing the alpha from its current value to 0. Note that it never calls start. By default, the ViewPropertyAnimator is started immediately. Setting a start offset will delay the beginning of the animation. The expressive nature of the ViewPropertyAnimator API makes it very power- ful. Consider using it to create animations when you are dealing only with views and common view properties. Creating Property Animations  253 LayoutTransition A common animation you may wish to use is animating the adding and removing of views in a layout. This animation is so common that Google created a simple class to automate these animations for you. Called the LayoutTransition, it can be added to any ViewGroup to animate changes to that ViewGroup layout. You set the LayoutTransition by calling setLayoutTransition on the ViewGroup object: ViewGroup vg = (ViewGroup) findViewById(R.id.relative_layout); vg.setLayoutTransition(new LayoutTransition()); Alternatively, you can use layout transitions with a simple one-line XML change: With the android:animateLayoutChanges attribute, changes to the ViewGroup will be animated using the default LayoutTransition animation. You can optionally use a custom animation by calling the setAnimation method of LayoutTransition and passing it an Animator and flags specifying that the animator applies to views being added, removed, or changed. LayoutTransition is a simple way to add animations to your app with almost no effort. 254  Chapter 9  Animation Wrapping Up This chapter covered the basics of animation on Android. There are currently three animation frameworks: drawable animation, view animation, and property animation. Drawable animations are simple to implement but require a different image for each frame of animation. View animations can be used only on views and have been deprecated in favor of property animations. Property animations are very powerful and allow you to apply time-based functions to any object. In this chapter, you learned that JJ Drawable animations create sprite-style animated graphics by rapidly switching images. JJ You should define your animations using XML for easy reusability. JJ Android supplies several interpolators that alter how your animation is applied over time. JJ You can use the ObjectAnimator class to animate properties of any object. JJ The ViewPropertyAnimator class provides a very compact API for animat- ing View objects. JJ You can animate changes to your layout by adding the android:animate LayoutChanges attribute to your layout view groups. Wrapping Up  255 10 Creating Custom Views 257 The Android view framework provides a rich visual toolkit with which to create your application UI. How- ever, you may find that the functionality you need is not available in the stock views. For those cases, Android allows you to create a custom view that will better match your needs. In this chapter, you will learn that Android draws views in two passes, a measure phase and a layout phase; that you should override the onMeasure and onDraw methods when creating a custom view; that using custom view attributes requires that you declare a new XML namespace for those attributes; and that compound views combine multiple views into a custom component. Before learning how to create custom views, you should understand how Android draws the display. As you learned earlier in the book, the Android UI is arranged in a hierarchy. This hierarchy consists of the system elements, such as the notifi- cation bar and the navigation bar, and the current activity view hierarchy. When an activity is brought to the foreground, the system requests the root node of that activity and then draws the view hierarchy on the display. You set the root node of the activity when you call setContentView. The activity layout consumes everything between the notification and action bars at the top and the navigation bar at the bottom (if present). The region of the display that is being drawn is flagged as invalid. Anything that intersects with the invalid region needs to be redrawn. The system does this when it draws an activity, but you can force this to happen by calling invalidate() on a view. Once the view has been drawn, it is marked as valid. Drawing takes place in two passes. In the first pass, the root of the hierarchy is asked to measure itself. The root then measures each of its child views. Each child view, in turn, measures its child views. In this way, the size of each view in the view hierarchy is measured. At each level, a parent view may give its child views a specific size or may request that the child set its own size. Once the measuring phase is complete, the system performs the layout of the view hierarchy. It walks down the layout tree in a predetermined order, drawing each view onto a bitmap. Parent views are drawn first, and then the child views are drawn on top of them. Once layout is complete, the drawing system draws the bitmap to the screen to display it to the user. Understanding How Android Draws Views 258  Chapter 10  Creating Custom Views Creating a Custom View Creating your custom UI components starts with extending a view class. You can extend the base View class for maximum configurability, or you can start with one of the existing view classes and add the functionality you need. Which choice you should make depends on your application needs. The basic implementation is the same either way: extend the view and override the appropriate methods, adding your customized code along the way. This only applies to static views or low-performance 2D graphics. If you want to create 3D graphics or complex animations, you should extend SurfaceView or use either RenderScript or OpenGL. See Chapter 11 for more details on advanced graphics. To learn how to create custom views, you’re going to create a simple custom view that displays two lines in the shape of a cross (Figure 10.1). To start, create a new class called CrossView that extends the View class: Figure 10 .1  A simple custom view displaying two lines that form a cross Creating a Custom View  259 public class CrossView extends View { public CrossView(Context context, AttributeSet attrs) { super(context, attrs); } } Note that the constructor takes both a Context and an AttributeSet object. The Context provides the interface to the application resources and system ser- vices that are needed to properly inflate the view and attach it to your activity. The AttributeSet is required to pass XML parameters to your view. You’ll learn more about this when you learn how to create custom XML attributes. You should generally call through to the superclass in your overridden methods to perform the initial setup of your view. With this done, there are two basic methods you will likely want to override: onMeasure and onDraw. onMeasure The onMeasure method is called by the system to determine the size of the view and its children. It’s passed two integers that are actually MeasureSpecs. A MeasureSpec is just a combination of a mode flag and an integer size value. It’s implemented as an integer to reduce unneeded object allocations. The mode tells the view how it should calculate its size. These are the possible modes: JJ UNSPECIFIED. The parent view has placed no constraints on this view; it can be any size it wants. JJ AT_MOST. The view can be any size less than or equal to the MeasureSpec size. JJ EXACTLY. The view will be exactly the MeasureSpec size regardless of what it requests. When you create a custom view and override the onMeasure method, it is your job to properly handle each of these cases. In addition, the measuring contract dictates that you call the setMeasuredDimensions method with the determined integer size values. If you fail to do this, an IllegalStateException will be thrown. 260  Chapter 10  Creating Custom Views 1. Override the onMeasure method of ExampleCustomView: @Override protected void onMeasure(int widthMeasureSpec, p int heightMeasureSpec) { setMeasuredDimension(calculateMeasure(widthMeasureSpec), p calculateMeasure(heightMeasureSpec)); } Remember, you are required to call setMeasuredDimension with the cal- culated width and height values. Here, the code is using the same function for width and height. 2. Implement the code for calculating the measurements. Start by adding a default size for the view: private static final int DEFAULT_SIZE = 100; private int calculateMeasure(int measureSpec) { int result = (int) (DEFAULT_SIZE * p getResources().getDisplayMetrics().density); } Because the pixel density of devices varies, you must calculate the actual pixel value using the display density. Tip:  Your onMeasure method may be called multiple times as the view system and your parent view calculate their layout. For example, a parent view may call each of its children’s onMeasure methods with UNSPECIFIED dimensions to gather their desired sizes, then again after calculating the total space available. Your view may be asked to recalculate its height and width if all the child views will not fit in the parent. Creating a Custom View  261 3. Retrieve the mode and size from the MeasureSpec: private int calculateMeasure(int measureSpec) { int result = (int) (DEFAULT_SIZE * p getResources().getDisplayMetrics().density); int specMode = MeasureSpec.getMode(measureSpec); int specSize = MeasureSpec.getSize(measureSpec); } 4. Select the size based on the mode: private int calculateMeasure(int measureSpec) { int result = (int) (DEFAULT_SIZE * p getResources().getDisplayMetrics().density); int specMode = MeasureSpec.getMode(measureSpec); int specSize = MeasureSpec.getSize(measureSpec); if (specMode == MeasureSpec.EXACTLY) { result = specSize; } else if (specMode == MeasureSpec.AT_MOST) { result = Math.min(result, specSize); } return result; } If the mode is set to EXACTLY, then the input size will be used. If the mode is set to AT_MOST, then the smaller of the DEFAULT_SIZE and the input size will be used. Otherwise, the DEFAULT_SIZE for the view will be used. 262  Chapter 10  Creating Custom Views Resources When you compile your Android application, the SDK doesn’t just blindly copy your application resources into the APK. Instead, it compiles them into an efficient binary format to reduce their size and improve lookup perfor- mance. To access the resources at runtime, you use a Resources object. You retrieve a Resources object by calling getResources() from your application context. The Resources object provides methods that take the compiled inte- ger IDs for your resources and return the resource with the proper type. onDraw The onDraw method is called when the view should draw its content. It is passed a Canvas object, which holds the underlying bitmap for the view. The Canvas pro- vides methods to do the basic drawing operations you use to build your view, and it performs those drawing operations on its internal bitmap. To perform the actual drawing, you can call one of the Canvas draw methods or use a drawing primitive. Android provides several drawing primitives to construct your UI: rectangles, ovals, paths, text, and bitmaps. You will also need a Paint object to hold the styling that will be applied to the drawing. It handles things like color and text size. 1. Create a Paint object to hold the styling for the cross: private Paint mPaint; public CrossView(Context context, AttributeSet attrs) { super(context); mPaint = new Paint(); mPaint.setAntiAlias(true); mPaint.setColor(0xFFFFFFFF); } Tip:  Views also have a draw method that is called by their parents to request the view be drawn. This method handles basic drawing steps like setting up the canvas and drawing the background. You should avoid overriding this method and instead override the onDraw method. Creating a Custom View  263 2. Override the onDraw method. All calls to draw on the canvas should be bounded by corresponding save() and restore() calls: @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); canvas.save(); // Code goes here canvas.restore(); } 3. To make your drawing code simpler, scale the canvas based on the size of the view. Doing this allows you to draw using simple floats between 0 and 1 without carrying around dimensions: @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); canvas.save(); int scale = getWidth(); canvas.scale(scale, scale); canvas.restore() } 4. To draw the two lines of the cross, you’ll use the drawLines method of Canvas: float[] mPoints = { 0.5f, 0f, 0.5f, 1f, 0f, 0.5f, 1f, 0.5f}; @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); canvas.save(); int scale = getWidth(); 264  Chapter 10  Creating Custom Views canvas.scale(scale, scale); canvas.drawLines(mPoints, mPaint); canvas.restore(); } The drawLines method takes an array containing the lines to draw (two x-coordinates and two y-coordinates per line endpoint) and a Paint object that it uses to draw the lines on the canvas. By scaling the canvas, you are able to specify all your dimensions using fractional float values. 5. Create an activity to display your view: public class CustomViewsActivity extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); } } 6. Open the main.xml file and add the CrossView: Creating a Custom View  265 Inner Classes When using custom views in your layouts, you generally use the class name as the element tag name. However, this won’t work if your custom view is an inner class of another Java class, because the required $ character is not valid in Android’s XML layout. For example, if you were to make CrossView an inner class of CustomViewsActivity, then the layout would not compile. In that case, you would need to use the class attribute to set the fully qualified view name: Note that this uses the lowercase (view) rather than the capitalized (View) element. This signifies that it’s a generic view and that the class definition can be found in the class attribute. When using custom views in XML, you must use the full package name to inform the system which view class should be inflated. 7. Run the application, and you should see a cross shape similar to the one in Figure 10.1. 266  Chapter 10  Creating Custom Views Adding Custom Attributes to Your Custom Views Now that you have your custom view, you’ll want to make it configurable to use throughout your UI. Adding methods to the class for setting attributes is standard Java practice, but how do you add attributes in the XML layout? To accomplish that, you’ll first need to declare the attributes, then add a new namespace to your XML layouts, and finally handle the AttributeSet object that gets passed to your custom views constructor. Declaring the Attributes The first step in creating custom attributes is to declare them. Custom attributes are declared using a new XML resource element: . These elements should be defined in a file named attrs.xml and placed in the res/values/ directory. Create this file now to declare the CrossView attributes: The first thing to note is that the element has a name attribute. You use this to reference the styles in your code. Each custom attribute is declared using an element. The element itself has two attributes: name and format. The attribute name is used to reference custom attributes in XML. The attribute format is the data type. In this example, one of the default system attributes is used. In that case, you don’t need to declare the format, as it is already defined by Android. Every attribute with a format can be declared only once. This example works because it uses the existing android:color format. If you tried to use a different format, the project would not build. If you want to reuse the same attribute for multiple custom styles, declare it under the tag and include a format; then declare it inside each element without a format. Here is an example: Adding Custom Attributes to Your Custom Views  267 You can create custom attributes with predefined values that are similar to the built-in attributes like wrap_content and match_parent. To do that, you declare the values using or elements: Tip:  There is no real documentation on the possible attribute format types. The best documentation is the Android source code for the android.R.styleable.attr.xml file and the android.content .res.TypedArray class. Current formats include reference, string, color, dimension, Boolean, integer, float, fraction, enum, and flag. 268  Chapter 10  Creating Custom Views Enums and flags are required to be integers. The difference between them is that the flag attributes can be combined using a bitwise OR operation. Use flags when you want the option to combine multiple attribute values: Using Attributes in XML To use the new attributes in your XML code, you first must declare the namespace for the view. Recall that in all your layouts, the enclosing ViewGroup always has an XML namespace attribute: This namespace declares that all attributes that begin with the keyword android: can be found in the android package. To use custom attributes, you declare a new namespace with a new package. This prevents your custom attributes from colliding with system attributes that may be defined in later versions of Android. Add a new namespace for the CrossView attributes: Adding Custom Attributes to Your Custom Views  269 This declares that all attributes that begin with example: will reference a view in the com.example package. (You can choose any prefix you want; it’s not neces- sary to use example.) Now you can create a new layout file with more than one cross, each with dif- ferent attributes: This creates three crosses that are arranged vertically in a row. The second cross is rotated 30 degrees and is blue. The third cross is rotated 45 degrees and is yellow. 270  Chapter 10  Creating Custom Views Using Attributes in Code Now that you have the attributes defined, you need to create a constructor to use them. Remember the AttributeSet object you were passed in the constructor of your custom view: public CrossView(Context context, AttributeSet attrs) { super(context, attrs); ... The AttributeSet object is passed to your view by the system when it is instan- tiated. This object contains all the attributes declared by the XML layout, but it stores them in a more efficient binary format. Use it to retrieve the attribute values and set them on your view. 1. Update the CrossView constructor to query the AttributeSet object for the new rotation and color attributes: public CrossView(Context context, AttributeSet attrs) { super(context); mPaint = new Paint(); mPaint.setAntiAlias(true); mPaint.setColor(0xFFFFFFFF); TypedArray arr = getContext().obtainStyledAttributes p (attrs, R.styleable.cross); int color = arr.getColor(R.styleable.cross_android_color, p Color.WHITE); float rotation = arr.getFloat(R.styleable.cross_rotation, p 0f); // Remember to call this when finished arr.recycle(); setColor(color); setRotation(rotation); } Adding Custom Attributes to Your Custom Views  271 Here, the obtainStyleAttributes method is used to create a TypedArray, which is a convenience class for accessing the values stored in an AttributeSet. This class performs caching internally, so always call recycle when you are finished using it. Also, note that you access your custom attributes using a combination of the name and the name. 2. Add a rotation field and update the onDraw method to rotate the canvas: float mRotation; @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); canvas.save(); int scale = getWidth(); canvas.scale(scale, scale); canvas.rotate(mRotation); canvas.drawLines(mPoints, mPaint); canvas.restore(); } 3. Add two new setters on the view. These are called by the constructor: public void setColor(int color) { mPaint.setColor(color); } public void setRotation(float degrees) { mRotation = degrees; } 272  Chapter 10  Creating Custom Views Now when you run the app, you should see three crosses with different colors and rotations (Figure 10.2). Figure 10 .2  Custom attributes are used to rotate and change the color of CrossView. Adding Custom Attributes to Your Custom Views  273 Creating Compound Components Creating a custom view by extending the View class gives you the most control over your custom view. However, you will often need something simpler, such as adding functionality to an existing view. It is much easier to create a custom component by extending one of the built-in Android views and expanding its functionality. By leveraging the built-in view code, you can focus on adding enhanced functionality. A simple way to do this is by creating a new view that combines several existing views. This is called a compound component. Creating a Compound Component Compound components have two primary advantages over custom views. First, they leverage the existing view group classes to create the layout for you. And second, you won’t need to override the onMeasure and onDraw methods. 1. Create a new layout file named toggle_text.xml, and place it in the res/ layout/ folder: 274  Chapter 10  Creating Custom Views This is simply a horizontal LinearLayout with a ToggleButton and an EditText. 2. Create a new class that extends LinearLayout. Use the LayoutInflater system service to inflate the layout you just created: public class ToggleText extends LinearLayout { public ToggleText(Context context, AttributeSet attrs) { super(context, attrs); LayoutInflater inflater = (LayoutInflater) context. p getSystemService(Context.LAYOUT_INFLATER_SERVICE); View view = inflater.inflate(R.layout.toggle_text, p this); } } Note that you are passing in this as the parent ViewGroup for the inflated layout. 3. Add the custom functionality for your compound view. Add a listener for changes in the selected state of the toggle button, and set the enabled state of the EditText accordingly: public class ToggleText extends LinearLayout p implements OnCheckedChangeListener { EditText mTextView; public ToggleText(Context context, AttributeSet attrs) { super(context, attrs); LayoutInflater inflater = (LayoutInflater) context. p getSystemService(Context.LAYOUT_INFLATER_SERVICE); View view = inflater.inflate(R.layout.toggle_text, p this); Creating Compound Components  275 mTextView = (EditText) view.findViewById p (R.id.edit_text); ToggleButton toggle = (ToggleButton) p view.findViewById(R.id.toggle_button); toggle.setChecked(true); toggle.setOnCheckedChangeListener(this); } @Override public void onCheckedChanged(CompoundButton buttonView, p boolean isChecked) { mTextView.setEnabled(isChecked); } } You can now use the new compound component in layouts, and the toggle button will change the enabled state of the EditText (Figure 10.3). Figure 10 .3  A compound view that combines a ToggleButton with an EditText. The EditText is disabled when the ToggleButton is unchecked. 276  Chapter 10  Creating Custom Views Optimizing the Layout If you use the Hierarchy Viewer tool to look at the layout hierarchy for the ToggleButton you just created, you will see something like Figure 10.4. Notice that the ToggleButton class has a single child element: the LinearLayout. But ToggleButton itself is a LinearLayout, so this is an unnecessary level in your hier- archy. To remove it, open the toggle_text.xml file and change the LinearLayout element to a merge element: Now when you view the ToggleButton in the Hierarchy Viewer, the unneces- sary LinearLayout will be gone (Figure 10.5). Figure 10 .5  With the element, the hierarchy of the ToggleButton view is flatter and more efficient. 278  Chapter 10  Creating Custom Views Wrapping Up Creating custom views gives you greater control over the look and functionality of your application. Android allows you to extend the built-in view classes, leverag- ing the existing drawing code while adding your own functionality. And by adding custom attributes, you can use your new views in XML layout files for easy UI development. In this chapter, you learned that JJ You create a completely custom view by extending View and overloading the onMeasure and onDraw methods. JJ You use custom attributes of a view by declaring the XML namespace for those attributes. JJ Compound components let you easily build custom views out of existing components but that you must remember to merge the layout with your ViewGroup. Wrapping Up  279 11 Creating Advanced Graphics 281 The Android view framework is convenient for creat- ing complex layouts. However, this convenience comes at the cost of performance. When performance is critical, Android provides several more-robust graphics capabilities with increasing levels of difficulty. In this chapter, you will learn that the SurfaceView and TextureView classes use the standard Canvas object combined with a separate rendering thread to achieve bet- ter performance than standard views; that the new RenderScript framework can be used to create architecture-independent graph- ics rendering; and that OpenGL is available for serious graphics work and games. The easiest way to increase drawing performance is by moving your performance- critical drawing operations onto a separate thread. However, as you learned earlier, all drawing operations must take place on the UI thread or an exception will be thrown. For this reason, Android provides the SurfaceView class. This class allows you to achieve better performance by executing your drawing code outside the normal UI thread. By drawing in a separate thread, you can rapidly update graphics without waiting for the rest of the view hierarchy to finish drawing. Implementing SurfaceView The SurfaceView exists outside the normal view hierarchy. It actually exists behind the normal window and is made visible by punching a hole through the view layout in your app. The SurfaceView can then be updated independently of the rest of your views without waiting for the UI thread. To use a SurfaceView, you’ll need to create a new view that extends the SurfaceView class. In addition, you should implement the SurfaceView.Callback interface and provide implementations of the required callbacks: 1. Create a new ExampleSurfaceView class that extends SurfaceView and implements SurfaceView.Callback: public class ExampleSurfaceView extends SurfaceView implements p SurfaceHolder.Callback { } 2. You need to initialize the superclass, so create a constructor that takes a Context object. You should also set up the callback here: public ExampleSurfaceView(Context context) { super(context); SurfaceHolder holder = getHolder(); holder.addCallback(this); } 3. Now implement the callbacks. The first, surfaceCreated, is called when the surface view is ready to be used. You should start your drawing code here: Using Canvas 282  Chapter 11  Creating Advanced Graphics @Override public void surfaceCreated(SurfaceHolder holder) { // Called when the surface view is first created. p Start your drawing here. } 4. The surfaceChanged method is called when the view dimensions change, typically when the device is rotated: @Override public void surfaceChanged(SurfaceHolder holder, int format, p int width, int height) { // Called when the surface view dimensions change. p Typically called when the device is rotated. } 5. The surfaceDestroyed method is called when the view is being destroyed. You should clean up any threads and drawing code here: @Override public void surfaceDestroyed(SurfaceHolder holder) { // Called when the surface is destroyed. Clean up any p threads here. } Drawing to a SurfaceView Unlike a normal view, all drawing to a SurfaceView takes place on a separate thread. To draw on a SurfaceView, you must call the lockCanvas method of SurfaceHolder, which returns a Canvas object. The lockCanvas method prevents the SurfaceView from updating the underlying surface until you call the corresponding unlockCanvasAndPost method. This eliminates the need for synchronization around writing to the surface (though you still need to synchronize fields between your threads). You should wrap all your drawing to a SurfaceView in lockCanvas and unlockCanvasAndPost blocks. Using Canvas  283 The example doesn’t do anything yet, so you’ll need to change things to draw something. The whole reason to make a SurfaceView is to enable continuous drawing using a separate thread, so create a new thread and use it to animate a triangle. At the same time, update the background color of the SurfaceView based on the user’s touch. 1. Create a new class named DrawingThread that extends Thread and calls the onDraw method of the view. This thread will run every 20 milliseconds (for 50 fps) and will update an angle field for the rotation of the triangle. Remember to synchronize the drawing: private class DrawingThread extends Thread { boolean keepRunning = true; @Override public void run() { Canvas c; while (keepRunning) { c = null; try { c = mSurfaceHolder.lockCanvas(); synchronized (mSurfaceHolder) { mAngle += 1; onDraw(c); } } finally { if (c != null) mSurfaceHolder.unlockCanvasAndPost(c); } // Run the draw loop at 50 fps try { Note:  There is no guarantee that the surface will be unchanged the next time you call lockCanvas. You should not rely on using the canvas returned by lockCanvas() to hold drawing state. 284  Chapter 11  Creating Advanced Graphics Thread.sleep(20); } catch (InterruptedException e) {} } } } 2. Add some fields to your view for the RGB colors, the triangle, and the new thread: DrawingThread mThread; int mRed = 0; int mGreen = 0; int mBlue = 127; float[] mVertices = new float[6]; int[] mColors = { 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF}; Paint mPaint = new Paint(); float mAngle = 0; float mCenterX = 0; float mCenterY = 0; public ExampleSurfaceView(Context context) { super(context); mSurfaceHolder = getHolder(); mSurfaceHolder.addCallback(this); mThread = new DrawingThread(); mPaint.setColor(0xFFFFFFFF); Paint.setStyle(Paint.Style.FILL); } The triangle is defined using lines, called vertices. The triangle will be filled with color based on the mColors array. Using Canvas  285 3. Update the surfaceCreated and surfaceDestroyed methods to start and stop the thread: @Override public void surfaceCreated(SurfaceHolder holder) { mThread.keepRunning = true; mThread.start(); } @Override public void surfaceDestroyed(SurfaceHolder holder) { mThread.keepRunning = false; boolean retry = true; while (retry) { try { mThread.join(); retry = false; } catch (InterruptedException e) {} } } 4. Update the surfaceChanged method to create the vertices of the triangle, using the supplied width and height values: @Override public void surfaceChanged(SurfaceHolder holder, int format, p int width, int height) { mVertices[0] = width/2; mVertices[1] = height/2; mVertices[2] = width/2 + width/5; mVertices[3] = height/2 + width/5; mVertices[4] = width/2; mVertices[5] = height/2 + width/5; 286  Chapter 11  Creating Advanced Graphics mCenterX = width/2 + width/10; mCenterY = height/2 + width/10; } The vertices define the edges of the triangle. The center values define the pivot point around which the triangle will rotate. 5. Override the onDraw method of your view. Update the color and draw the triangle: @Override protected void onDraw(Canvas canvas) { canvas.drawRGB(mRed, mGreen, mBlue); canvas.rotate(mAngle, mCenterX, mCenterY); canvas.drawVertices(Canvas.VertexMode.TRIANGLES, 6, p mVertices, 0, null, 0, mColors, 0, null, 0, 0, mPaint); } 6. Implement the onTouchEvent method and update the color as you slide your finger across the screen. Note the synchronization on the changing of the color values: @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: case MotionEvent.ACTION_MOVE: synchronized(mSurfaceHolder) { mRed = (int) (255*event.getX()/getWidth()); mGreen = (int) (255*event.getY()/getHeight()); } return true; } return super.onTouchEvent(event); } Using Canvas  287 Run the example and you should see a slowly rotating triangle (Figure 11.1). Sliding your finger around the screen will change the background color. The TextureView Class The SurfaceView class lets you improve performance by moving the drawing onto a separate thread. But this comes with some significant drawbacks. In particular, because SurfaceView exists outside the normal view system, it can’t be transformed the way a normal view can. You can’t move, scale, or rotate a surface view. In addition, SurfaceView doesn’t support transparency effects using setAlpha. To address these shortcomings, Android 4.0 introduced TextureView. A texture view is essentially the same as a surface view, but it behaves as a normal view and supports normal view operations. You can use a texture view to display a content stream such as video camera preview, while also transforming it using the View API. TextureView requires hardware acceleration and, because it is more flexible than SurfaceView, incurs a perfor- mance hit. You would not want to use it for running a full-screen game, for example. However, if you are devel- oping on Android 4.0 and need to transform a high-performance canvas view, consider using TextureView. Figure 11 .1  A rotating triangle drawn by a SurfaceView. Sliding your finger across the screen should change the color. 288  Chapter 11  Creating Advanced Graphics Using RenderScript RenderScript is a language, API, and runtime used to write high-performance code on Android. Introduced in Android 3.0, RenderScript includes both graphics APIs and computing APIs similar to CUDA or OpenCL. It is architecture-independent, so there is no need to customize your code for different CPU or GPU processors. RenderScript optimizes your running code by selecting the appropriate processor and number of cores at runtime. As a fallback, RenderScript will run all operations on the CPU if the appropriate GPU is unavailable. This section covers the basics of using RenderScript with a simple example. The RenderScript File RenderScript uses a syntax based on the C99 standard of the C programming language. This makes it immediately familiar to anyone who has developed in C. Here is a simple RenderScript file example that will rotate a triangle onscreen and set the background color: #pragma version(1) #pragma rs java_package_name(com.example); #include “rs_graphics.rsh” // Background color is a 4-part float float4 bgColor; // Triangle mesh rs_mesh gTriangle; // Rotation float float gRotation; void init() { // Initialize background color to black bgColor = (float4) { 0.0f, 0.0f, 0.0f, 1.0f }; gRotation = 0.0f; } Note:  RenderScript relies on OpenGL ES 2.0 APIs that are not available in the emulator. To run the RenderScript code example, you will need an Android device. Using RenderScript  289 int root() { // Set background color rsgClearColor(bgColor.x, bgColor.y, bgColor.z, bgColor.w); // Load matrix for translate and rotate rs_matrix4x4 matrix; rsMatrixLoadIdentity(&matrix); rsMatrixTranslate(&matrix, 300.0f, 300.0f, 0.0f); rsMatrixRotate(&matrix, gRotation, 0.0f, 0.0f, 1.0f); rsgProgramVertexLoadModelMatrix(&matrix); // Draw the triangle mesh rsgDrawMesh(gTriangle); // Animate rotation gRotation += 1.0f; // Run every 20 milliseconds return 20; } This code should be saved in a file called example.rs in your Android project’s src/ directory. The first two lines declare the RenderScript version and the Java package that contains the Java code that will use the RenderScript. A graphics library, rs_graphics.rsh, is included. The init() and root() methods are special to RenderScript. init is called when the script is first loaded. The root function is like the main function in a standard C application; it will be called each time the script runs. The number returned from root() requests the interval in milliseconds at which the script should be called. Here, it is requested that the code be called every 20 milliseconds. It does not guarantee that the code will be called that often, however, only that the system will attempt to call the script that often. Tip:  The full native RenderScript API is available in the Android SDK. Navigate to /platforms/android-11/renderscript, and find the header files in the include/ and clang-include/ directories. 290  Chapter 11  Creating Advanced Graphics The rest of the root method contains the graphics drawing code, which should be self-explanatory. The color is set, and then a matrix is used to translate the surface and rotate it. The triangle is represented by an rs_mesh that will be set from the Java class. Finally, the rotation of the triangle is updated every time root runs. The Java API Once the RenderScript file is saved in your src/ directory, the Android build tools will generate a reflected Java file you can use in your application. This file is given the name ScriptC_your_renderscript_file_name and extends the ScriptC class. To use the RenderScript you just created, you’ll need to build a view that extends the RSSurfaceView class: 1. Create a new class named ExampleView that extends RSSurfaceView: public class ExampleView extends RSSurfaceView { public ExampleView(Context context) { super(context); } } 2. Initialize the RenderScript Java objects. You first must create a RenderScriptGL object, passing it a SurfaceConfig. The RenderScriptGL object ties the output of the RenderScript to the display via the root() method: public class ExampleView extends RSSurfaceView { private RenderScriptGL mRS; private ScriptC_example mScript; public ExampleView(Context context) { super(context); final RenderScriptGL.SurfaceConfig sc = p new SurfaceConfig(); mRS = createRenderScriptGL(sc); mScript = new ScriptC_example(mRS, getResources(), p R.raw.example); buildTriangle(); Using RenderScript  291 mRS.bindRootScript(mScript); } } Once the RenderScriptGL object is created, you can instantiate the ScriptC object that contains the interface to your RenderScript file. Finally, you must call the bindRootScript method to set your RenderScript as the handler for calls to render the surface. You will implement the buildTriangle() method shortly. 3. Create a simple activity that uses your new view as its content: public class RenderScriptExampleActivity extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(new ExampleView(this)); } } 4. The reflected Java API lets you set variables in your RenderScript using auto-generated setters. A setter for the bgColor variable has already been created and can be used to change the background color. Add some inter- activity by updating the graphics when you touch the screen. Override the onTouchEvent method, and call the set_bgColor method: @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: case MotionEvent.ACTION_MOVE: float x = event.getX()/getWidth(); float y = event.getY()/getHeight(); Float4 color = new Float4(x, y, 0.5f, 1.0f); 292  Chapter 11  Creating Advanced Graphics mScript.set_bgColor(color); return true; } return super.onTouchEvent(event); } 5. Finally, create the buildTriangle() method, which will define the triangle that your RenderScript will draw: public void buildTriangle() { Mesh.TriangleMeshBuilder triangles = p new Mesh.TriangleMeshBuilder(mRS, 2, p Mesh.TriangleMeshBuilder.COLOR); triangles.addVertex(0.f, -75.0f); triangles.addVertex(-150.0f, 75.0f); triangles.addVertex(150.0f, 75.0f); triangles.addTriangle(0, 1, 2); Mesh mesh = triangles.create(true); mScript.set_gTriangle(mesh); } The TriangleMeshBuilder is used to create a triangle and draw it on the screen. You add each vertex and then call the addTriangle method to build the triangle out of the specified vertex calls. In this case, the last three ver- tices are used. Finally, you create the mesh and pass it to the RenderScript. When you run this, you will see a slowly rotating triangle, just as in the SurfaceView example. The background color will change as you slide your finger across the screen. Each call to the set_bgColor method updates the bgColor vari- able. The root() method is called every 20 milliseconds and updates the back- ground color and the triangle. It also updates the rotation angle by 1 degree. Try it out by loading the app on a device and sliding your finger across the screen. This simple example only scratches the surface of the RenderScript API. In addi- tion to advanced graphics, RenderScript supports high-performance computing using the compute APIs. These APIs are primarily found in the rs_cl.rsh header file. Using RenderScript  293 Using OpenGL Android provides full support for hardware-rendered graphics using the OpenGL ES 1.0/1.1 and OpenGL ES 2.0 standards. OpenGL APIs can be called through the Java framework and through the Native Development Kit (NDK). The Java frame- work provides an easy-to-use API but suffers from a small performance hit. For full accelerated graphics support, or to port existing graphics code, the NDK offers the best solution. However, this is outside the scope of this book. OpenGL Basics A full explanation of OpenGL graphics would consume an entire book. However, a small example should give you a taste of the Java framework APIs that are available. Creating OpenGL graphics requires two classes: GLSurfaceView and GLSurfaceView .Renderer. The GLSurfaceView class is similar to SurfaceView and provides the glue between OpenGL-rendered graphics and Android’s standard view framework. This class provides a separate rendering thread that generates graphics independently of the UI thread. In addition, GLSurfaceView provides some debugging tools that assist in tracking down errors in rendering code. Typically, you will extend GLSurfaceView and override the onTouchEvent method to provide interactivity with your custom graphics. Here is an example that simply paints the screen black. This example will be compatible with the OpenGL ES 1.0 standard: 1. Create a class named ExampleRenderer that extends GLSurfaceView.Renderer: public class ExampleRenderer implements GLSurfaceView.Renderer { } Now implement the required callbacks. 2. Implement onSurfaceCreated. It’s called when the GLSurfaceView is first created. Use it to do initial setup. For this simple example, it does nothing: public void onSurfaceCreated(GL10 gl, EGLConfig config) { } Note:  The Android emulator does not support OpenGL ES 2.0 graphics. You will need to test on a physical device to develop an app that uses the 2.0 standard. 294  Chapter 11  Creating Advanced Graphics 3. Implement onDrawFrame. It’s called every time the GLSurfaceView is updated. This is where the bulk of your code will go: public void onDrawFrame(GL10 gl) { gl.glClear(GL10.GL_COLOR_BUFFER_BIT | p GL10.GL_DEPTH_BUFFER_BIT); } This is an OpenGL call that clears the specified buffers, which are referenced via their bit values. Here the color and depth buffers are cleared, making the screen black. 4. Implement onSurfaceChanged. It’s called when the basic geometry of the GLSurfaceView changes. This is most often called when you rotate the device: public void onSurfaceChanged(GL10 gl, int width, int height) { gl.glViewport(0, 0, width, height); } Here the view port is set to entirely fill the view dimensions. 5. Create a simple activity to display the OpenGL surface: public class ExampleOpenGLActivity extends Activity { GLSurfaceView mGLView; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mGLView = new GLSurfaceView(this); setContentView(mGLView); } @Override protected void onPause() { super.onPause(); mGLView.onPause(); } Using OpenGL  295 @Override protected void onResume() { super.onResume(); mGLView.onResume(); } } You should remember to call the appropriate life cycle methods on GLSurfaceView as your activity runs; OpenGL rendering can be very intensive, and this ensures that your app does not consume resources when it doesn’t need them. Run this example, and you’ll see that it just paints the screen black on each frame. This isn’t very interesting, so in the next section you’ll add some graphics. Drawing Graphics You can easily re-create the example from the RenderScript section to rotate a triangle and set the background color as you slide your finger across the screen. 1. Update the ExampleRenderer, and add a new setColor method with member variables for color: private float mRed; private float mGreen; private float mBlue; public void setColor(float red, float green, float blue) { mRed = red; mGreen = green; mBlue = blue; } 2. Add member variables to draw a triangle, and initialize its vertices in the onSurfaceCreated method: private float mAngle; private long mLastFrameTime = 0; 296  Chapter 11  Creating Advanced Graphics float[] mVertices = { -1.0f, -1.0f, 0, 1.0f, -1.0f, 0, 0.0f, 1.0f, 0}; FloatBuffer mVertexBuffer; public void onSurfaceCreated(GL10 gl, EGLConfig config) { // Set up the triangle vertices in FloatBuffers as needed p by OpenGl ByteBuffer vertexByteBuffer = ByteBuffer.allocateDirect p (mVertices.length * 4); vertexByteBuffer.order(ByteOrder.nativeOrder()); mVertexBuffer = vertexByteBuffer.asFloatBuffer(); mVertexBuffer.put(mVertices); mVertexBuffer.position(0); } 3. Update the onSurfaceChanged method to set the projection matrix. OpenGL assumes a square display, so you need to change the aspect ratio of the graphics to match that of the screen: public void onSurfaceChanged(GL10 gl, int width, int height) { gl.glViewport(0, 0, width, height); // Select the projection matrix gl.glMatrixMode(GL10.GL_PROJECTION); // Reset the matrix to default state gl.glLoadIdentity(); // Calculate the aspect ratio of the window float ratio = (float) width/height; GLU.gluPerspective(gl, 45.0f, ratio, 0.1f, 100.0f); // Set the GL_MODELVIEW transformation mode Using OpenGL  297 gl.glMatrixMode(GL10.GL_MODELVIEW); gl.glLoadIdentity(); } 4. Update the onDrawFrame method of ExampleRenderer to set the color of the view based on the color fields and to draw the triangle. This should look familiar if you have done any OpenGL graphics: public void onDrawFrame(GL10 gl) { gl.glClearColor(mRed, mGreen, mBlue, 1.0f); gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10. GL_DEPTH_BUFFER_BIT); updateAngle(); gl.glLoadIdentity(); gl.glTranslatef(0.0f, 0.0f, -7.0f); gl.glRotatef(mAngle, 0.0f, 0.0f, 1.0f); gl.glEnableClientState(GL10.GL_VERTEX_ARRAY); gl.glColor4f(255f, 255f, 255f, 0.0f); gl.glVertexPointer(3, GL10.GL_FLOAT, 0, mVertexBuffer); gl.glDrawArrays(GL10.GL_TRIANGLES, 0, 3); gl.glDisableClientState(GL10.GL_VERTEX_ARRAY); } 5. You need to implement the updateAngle() method: private void updateAngle() { long now = System.currentTimeMillis(); if (mLastFrameTime != 0) { mAngle += 10*(now - mLastFrameTime)/1000.0; } mLastFrameTime = now; } 298  Chapter 11  Creating Advanced Graphics 6. Create a new view named ExampleGLSurfaceView that extends GLSurfaceView: public class ExampleGLSurfaceView extends GLSurfaceView { public ExampleGLSurfaceView(Context context) { super(context); } } 7. Create an instance of the Renderer class, and set it as the renderer: public class ExampleGLSurfaceView extends GLSurfaceView { public ExampleRenderer mRenderer; public ExampleGLSurfaceView(Context context) { super(context); mRenderer = new ExampleRenderer(); setRenderer(mRenderer); } } 8. Override the onTouchEvent method to implement the touch logic. This is almost identical to the version in the RenderScript example, but this time it will call the setColor method you just created: @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: case MotionEvent.ACTION_MOVE: final float x = event.getX()/getWidth(); final float y = event.getY()/getHeight(); queueEvent(new Runnable() { @Override public void run() { Using OpenGL  299 mRenderer.setColor(x, y, 0.5f); } }); return true; } return super.onTouchEvent(event); } 9. Use this new view in the ExampleOpenGLActivity: public class ExampleOpenGLActivity extends Activity { GLSurfaceView mGLView; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mGLView = new ExampleGLSurfaceView(this); setContentView(mGLView); } ... This uses the queueEvent method of GLSurfaceView to schedule the background color change. GLSurfaceView performs all rendering on a separate thread from the standard Android UI, so you should be mindful of thread communication issues. The queueEvent method is a convenience that lets you post new tasks to run in the GLSurfaceView thread. You should now have an example that works just like the RenderScript and SurfaceView examples. A single triangle slowly rotates on the screen. As you slide your finger across the screen, the background color changes in response. This is only scratching the surface of OpenGL development. In addition to using the Java APIs, Android supports running a fully OpenGL-compatible application through the NDK. This offers the maximum portability for high-performance graphics code. 300  Chapter 11  Creating Advanced Graphics Wrapping Up Android provides several advanced graphics options with increasing levels of performance. There are important tradeoffs to each API. The Canvas class offers the simplest API, but with limited performance. It’s a good fit for basic graphics that don’t require complex animations or user interaction. The newly introduced RenderScript API offers a good balance of performance with ease of programming, and it has the added advantage of easy portability across different device archi- tectures. Finally, Android supports high-performance graphics using the OpenGL APIs. These are the most advanced graphics available on Android and should be used when you need to create high-performance applications like games. JJ Use the SurfaceView class to achieve better performance by moving your drawing off the UI thread. JJ The TextureView class provides the same API as SurfaceView but supports standard view transformations. JJ RenderScript is an architecture-independent, high-performance computing API that uses a simple C-based syntax. JJ Android’s OpenGL implementation includes both a Java API and a Native Development API. Wrapping Up  301 12 Localization and Accessibility 303 Creating a successful Android application requires that you reach the largest audience possible. To that end, you should work from the beginning to make your app language agnostic and accessible to people with disabilities. Localization will help you launch your app in more countries, expanding your potential market to the whole world. Accessibil- ity will also expand your target market, while helping those with special requirements utilize the next generation of computing. In this chapter, you will learn that using the strings.xml file allows you to create multiple copies of your app strings in different lan- guages; that you can define plural strings that present different text based on the input number; that Android UI elements that have no text should include a content description for screen-reading software; and that accessibility events are sent by views, enabling the accessibility service to notify the user. In Chapter 3, you learned about application resources and how Android uses folder structure to separate resources for different device configurations at runtime. This is very helpful in building apps that support multiple screen sizes. This same mechanism allows you to localize your app with alternate strings and resources for each locale. Overview of Android Localization Throughout this book, you have been instructed to use string resources when displaying text in your UI. This may seem like a burden, but it greatly simplifies localization. By specifying all strings in the strings.xml file, you can create differ- ent versions of that file for each language you need to support. Android will then select the appropriate string resources at runtime, based on the user’s selected language and region. Android will mix and match strings from different strings.xml files. It will always select the most appropriate string based on the user’s locale and your pro- vided resources. Android separates resources using the folders in the res/ directory. For exam- ple, the res/layout directory is the default directory for layout files. But you can create a landscape-specific layout by placing a layout with the same name in the res/layout-land/ folder. Similarly, when you create strings, you place them in the res/values/strings.xml file. These are the default strings that will be used throughout your application. When you want to create a Spanish-language version of your app, you first translate all the strings in the strings.xml file. Then you place the translated file in the res/values-es/ folder. When your application references a string resource, the system will automatically use the Spanish-language version when the user’s system default is Spanish. For French, you would create another strings.xml file and place it in the res/values-fr/ folder. Note:  Android uses the ISO 639-1 language codes followed by an optional ISO 3166-1-alpha-2 region code for language and region qualifiers. Consult the Android documentation on providing resources (http://developer.android.com/guide/topics/resources/ providing-resources.html) for the full list of qualifiers. Making Your App Available in Multiple Languages 304  Chapter 12  Localization and Accessibility Try creating a simple example: 1. Open Eclipse and create a new Android project. 2. Open the res/values/strings.xml file. There should be a string named “hello”: Hello World! AccessibilityExample 3. Create a Spanish-language version of that string. Create a new folder named res/values-es/. Then create a new strings.xml file inside it, and add the following content: ¡Hola Mundo! The app_name resource remains the same, so you don’t need to create a translated version. Android will select resources from both files. 4. Run the app as you normally would, and you should see the standard Hello World app. Now change the emulator language to Spanish by choosing Set- tings > Language and Input > Language > Spanish. Rerun the app, and the displayed string is now in Spanish. You are not limited to using language for localization; you can also create alternate resources for specific mobile country codes (MCC) and mobile network codes (MNC). Using these qualifiers allows you to create resources for specific geographic regions. Tip:  The MCC and MNC qualifiers take precedence over the language qualifiers. Make sure you understand resource loading precedence, and set up your resource folders appropriately. Making Your App Available in Multiple Languages  305 Note that you do not need to alter your Java code for the correct string to be selected. You simply reference strings as normal, and Android will load the correct resource. In the previous simple example, the code to access the string is the same regardless of the language: String translatedText = getResources().getString(R.string.hello); In addition to translated strings, you can provide language-specific layouts for your application. Languages with longer words or a different text direction (such as right to left) may require you to rethink your layouts. In general, you should try to create layouts that are flexible enough to accommodate any language. When this is not possible, use alternative resources to change your UI to accommodate the alternative text. In most cases, you should let the Android framework do the work of selecting the appropriate resources for you. But for those occasions when you need to access the locale yourself, you can do so as follows: String locale = context.getResources().getConfiguration().locale; Formatting and Plurals Beyond basic language selection, you should also leverage Android’s ability to format strings for you. Android supports using string substitution (which is similar to string formatting in Java) to build strings. A string: %1$s and a decimal: %2$d Tip:  Always create default resources for your application. If Android is unable to locate a suitable resource for the user’s locale, an exception will be thrown indicating that the resource could not be found. Default resources are those that have no device- configuration qualifiers. 306  Chapter 12  Localization and Accessibility In this example, the first argument (numbered 1) is a string and the second argument (numbered 2) is a decimal. To format this string, you supply arguments to the getString method of Resources: getResources().getString(R.string.hello, “Hello”, 2); In addition to basic string formatting, Android allows you to define alterna- tive strings for different pluralizations. Android lets you define these using string resources. Here is an example: One planet. %d planets. As you can see, when the quantity “one” is used, the string uses the singular “planet.” When any other number is used, it becomes “planets.” The full list of plural types is listed in Table 12.1. Table 12 .1  Plural Quantities Quantity Value Description zero Special handling of 0 in languages such as Arabic one Special handling of 1 in languages such as Russian and English two Special handling of 2 in languages such as Welsh few Special handling for small numbers in languages such as Polish many Special handling of large numbers in languages such as Maltese other Used when the language requires no special treatment of a number Making Your App Available in Multiple Languages  307 To retrieve the proper string in code, you need to use the getQuantityString method: Resources res = getResources(); String songsFound = res.getQuantityString(R.plurals.planets, p numPlanets, numPlanets); The getQuantityString method takes three arguments: the string resource ID, a number to select the appropriate plural string, and the same number again to insert into the %d field of the string. If no substitution is necessary, the third parameter may be omitted. Tips for Localizing JJ Always include default resources in your app. If you fail to include them, your app will crash when running in an unsupported locale. JJ Always define user-visible strings in the strings.xml file. Use the default strings.xml when you start developing your application. You can add translated versions later. JJ Use quantity strings to handle any plural strings in your app. JJ Design your layouts to be as flexible as possible to accommodate languages that may have different space requirements. JJ Test your application in the alternative languages you support, and check for any formatting errors. Consider creating alternative resources for those cases. Note:  The plurals resources will only be selected in cases where the language requires it. In other cases, the default will be used. For example, in English, you would say “zero items,” “one item,” or “two items.” The zero and two cases use the same plural. Android will ignore the zero quantity even if you create one. 308  Chapter 12  Localization and Accessibility Making Your App Accessible Android is part of the next generation of personal computing—a generation that is defined by natural user interfaces such as touchscreens. While this has been a boon to most, many users have disabilities that prevent them from using touchscreens. Android features accessibility support that developers should use to ensure that all users can enjoy their work. This section covers the basics of ensuring that your application is accessible. Navigation and Focus The first step to making your Android app accessible is allowing use without a touchscreen. This generally takes the form of a directional pad (d-pad) for select- ing UI elements before activating them. Luckily, Android was designed from the beginning to support phones with directional input. All views in Android support a focused state, allowing navigation without a touchscreen. When a view is focused, it is highlighted in the UI and a primary button press will trigger an onClick event. Android’s built-in views already provide focus behavior, and any custom views you create that inherit from View will retain that ability. However, if you create a custom UI for your view (custom button drawables, for example), you should pro- vide focused states along with pressed states. In addition, you should test navigat- ing your app UI using only the d-pad. Pay special attention to the order in which views are focused. Does it make sense that this view is in focus after the previous one? If not, you can change the focus ordering in your layout files by using the android:nextFocus* attributes. These attributes let you define which view will be the next to receive focus. This is useful for more than just accessibility: Using focus lets you define which fields are selected when the user tabs through your UI using a keyboard. Making Your App Accessible  309 Here is an example layout that contains three buttons (Figure 12.1):
还剩336页未读

继续阅读

下载pdf到电脑,查找使用更方便

pdf的实际排版效果,会与网站的显示效果略有不同!!

需要 10 金币 [ 分享pdf获得金币 ] 0 人已下载

下载pdf

pdf贡献者

qwre233

贡献于2015-10-18

下载需要 10 金币 [金币充值 ]
亲,您也可以通过 分享原创pdf 来获得金币奖励!
下载pdf