Chapter 3. Creating User Interfaces

Creating an intuitive, good-looking interface is one of the most important aspects of a successful Android application. It doesn’t matter how wonderful your code is if the user cannot easily find and use the features you’ve worked so hard to create. Although Android’s diversity may give your application access to a greater pool of devices, you must also contend with making sure your application correctly supports multiple screen sizes.

Fortunately, Android gives developers many tools to make the process as straightforward as possible. In this chapter, you’ll learn about all but one of the major building blocks for displaying information. The one major omission is the ListView class, which will be covered in a chapter of its own because it has a few unique concepts that don’t apply to a standard view.

The View Class

No, this isn’t a television show where several women debate the merits of common culture. The View class is the superclass for all the Android display objects. Each and every user interface (UI) class—from the simple ImageView to the mighty RelativeLayout—subclasses from this same object. In this section, you’ll learn the basics of creating, adding, and modifying your existing layouts using Java code and XML. You’ll also learn to create your own custom view. A view, at its very core, is simply a rectangle into which you can display something. Subclasses take many different forms, but they all serve the same purpose: to show something to the user.

Creating a View

Creating a new view is something you’ve already done. In Chapter 1, you added a view to an XML layout file as part of exploring how to launch a new activity. At the time, I told you we’d get into the specifics of how these views were created and modified, and, well, now’s the time! Let’s take a look at the default views generated for you automatically when you create a new Android project.

Views in XML

Here’s what the default XML layout looks like in a new project:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/
   android"
   android:orientation="vertical"
   android:layout_width="match_parent"
   android:layout_height="match_parent" >
<TextView
   android:layout_width="match_parent"
   android:layout_height="wrap_content"
   android:text="@string/hello" />
</LinearLayout>

Question: Which of the two elements (LinearLayout and TextView) in this default XML layout is a view?

Answer: Both.

All visual objects onscreen subclass from the View class. If you go high enough in the inheritance chain, you’ll find an "extends View" somewhere.

In the default XML layout, the Android tools have added a simple TextView contained inside a LinearLayout. When displayed onscreen, the contents of the hello string will appear by default in the upper-left corner of the screen. I’ll discuss how to position visual elements onscreen later in this chapter.

To display the XML layout onscreen, you need to call the setContentView() method and pass the name of the layout to the activity.

From the code editor in your IDE, you should be able to find code in your activity that looks like this:

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

In this code, you’re telling the activity that you want it to inflate and render the main layout for this activity. By inflating, I mean the conversion from the XML in the project to a Java object that can be told to draw on the screen. You might be thinking, “Is it possible to build and display a layout and view within Java alone? Can I skip layout in XML entirely and go straight to the source?” Yes, you can, but in practice you shouldn’t. Even so, understanding how to build a layout without the XML will help you potentially modify any aspect of your layout at runtime.

Views in Java

Anything you can lay out or build in XML, you can lay out and build within the Java code itself; it’s just more complex. It’s important, however, to understand what Android is doing as it inflates and builds your layouts.

Here’s what the Java code looks like to build the exact same user interface that Android generates for you in XML when you create a new project:

public void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
   setContentView(buildLayouts());
}
public View buildLayouts() {
   LinearLayout topView = new LinearLayout(this);
   LayoutParams topLayoutParams = new FrameLayout.LayoutParams(
      LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
   topView.setLayoutParams(topLayoutParams);

   TextView textView = new TextView(this);
   LinearLayout.LayoutParams textLayoutParams = new LinearLayout.LayoutParams(
      LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);

   textView.setLayoutParams(textLayoutParams);
   textView.setText(R.string.hello);
   topView.addView(textView);
   return topView;
}

Let’s look at what is happening in the Java code. Instead of calling setContentView on an ID from the R.java file, I’m passing it an instance that the LinearLayout object returned from the buildLayouts method. In the buildLayouts method, I’m first creating a new LinearLayout (passing in this, which is referencing the activity) and a new LayoutParams object. To match what’s in the XML, the new LayoutParam is initialized with both the width and the height set to MATCH_PARENT.

Once I have the layout parameters initialized for the top LinearLayout, I can pass them to the object with a setLayoutParams call. I’ve now got the LinearLayout configured, so it’s time to move on to building the TextView.

This is a simple text view, so its layout parameters are very similar to those of its parent’s layout. The only noticeable difference is that I’m setting the height, when creating the layout parameters, to scale to fit the natural size of the TextView. (Much more on dynamic heights and widths soon.)

Once I’ve told the TextView how it will be positioned in its parent layout via the layout parameters, I tell it which string I’d like to display. This string is defined in /res/values/strings.xml. The name attribute in XML determines what needs to appear after R.string for the system to locate your resource. You’ll learn much more about resource management in the next section.

Last, I need to add the new TextView into the LinearLayout and then return the LinearLayout so it can be set as the main content view for the activity. Once that’s finished, I have a layout constructed at runtime with Java code, which identically matches the layout provided by the system in XML form.

The Java code looks fairly straightforward, but XML is a much better idea for working with the layout. Using XML, you can use Android’s resource management system and give non-software engineers the ability to modify the application UI.

Altering the UI at Runtime

It’s one thing to use XML or Java to define the pile of views that compose your user interface. But particularly in the XML case, you’ll want to be able to retrieve and alter views with data acquired over the network, from the user, or from any other information source. Android provides a simple method for gaining access to the views that currently compose your screens by calling findViewById, which is an Activity class method.

Identifying your views

Before you can find one of your views at runtime, you’ll need to give it an ID. Once you’ve called setContentView on an activity, you can call findViewById to retrieve your views and then alter them. This process should look at least a little bit familiar, because you saw it in the previous chapter. Here’s what it looks like in XML:

<TextView
   android:id="@+id/text_holder"
   android:layout_width="match_parent"
   android:layout_height="wrap_content"
   android:text="@string/hello" />

In this case, I’ve added an android:id line to name the TextView. The @+ notation tells Android that rather than referring to a view, you’d like to create an ID for the view. The first reference to any ID must start with @+id. Subsequent references to that ID will start simply with @id.

Android keeps the name-spaces of its own reserved IDs. For example, if you’re creating a layout to be used by ListActivity (an activity specifically designed to show lists of information) or a ListFragment (a fragment specifically showing a list), you’ll want to set the ID of the ListView in your layout file to "android:id="@id/android:list". These well-known IDs allow Android’s system code to correctly find and interact with the list view that you specify. I’ll provide you with more on this subject in Chapter 5, which covers list creation and management.

If you’re creating a view at runtime, simply call setId and pass in an integer, and you’ll be able to retrieve it later.

Finding your resources with Android

When Android compiles your project, it assigns a number value for your new static numeric ID. It places this new ID in the R.java file within your project. This file is your gateway to everything you’ve defined inside the res folder. For every ID, layout, drawable, string, and style (and a host of other things), Android places a subsequent statically defined int into the R file that identifies those things. Anytime you add a line to your layout XML defining a new ID (for example, android:id=”@+id/my_new_id"), you’ll find that the next time you compile your project, you’ll have an entry in the R.id class. In the previous example, this entry would be R.id.my_new_id.

Retrieving a view

Getting the existing instance of an Android view is as simple as calling findViewById and passing in the ID value found in the R.java file. Given the earlier XML example, here’s how you would grab an instance of the text view and modify its contents.

public void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
   setContentView(R.layout.main);
   TextView tv = (TextView)findViewById(R.id.text_holder);
   tv.setText(R.string.hello);
   }

If you have been reading this book in order, this should look eerily familiar. I’m retrieving an instance of the text view as described by the layout XML. Remember that calling findViewById only works after you’ve called setContentView within the onCreate method.

Keeping your views around

Calling findViewById will return an object that persists for the duration of your activity. This means you can add private data members to your Activity class to reduce the number of times you need to reach into the view hierarchy and find them. If you modify a view only once or twice during the lifetime of your activity, this trick won’t save you much time, but it can save significant time if you’re making frequent updates to multiple views on a very complex screen.

It’s a very bad idea, however, to make changes to your views once your activity’s onDestroy method has been invoked by the system. Making changes to a view that was once part of an expired activity will have dire consequences (force close dialogs, grumpy users, bad market reviews).

XML vs. Java layouts

On the whole, laying out your view in Java is nearly as simple as writing it out in XML. So why would you put any layouts in XML? The short answer includes the following:

Image Resource resolution. Android has a very powerful resource resolution system in place to choose the appropriate resource based on the hardware and settings of the user’s device. Resource resolution is what allows tablet users in Germany and phone users in America to use the same application. By having separate XML layouts for tablet and phone, and separate string files for English and German, Android can deliver the appropriate XML resources, and your Java code won’t know the difference.

Image Keeping view and business logic separate. By keeping your view logic separate from your business logic, you help maintain a clear division of what should go where. Building views in code programmatically is doable, but where do you draw the line on how much work those views should be doing? XML layouts help keep those divisions clear.

Image Get those designers to work! Ask your designer to look for a Java source file and align the title TextView in the center instead of to the left. Good luck! Ask that same designer to accomplish the same task via XML, and you’ll have much greater success. With the Android UI Designer, most designers can beat the XML layouts into displayable shape. Although you might be the only one working on your current project, this will not be true for all your projects.

The longer answer to the question of XML versus Java layouts will become clear as you read the “Resource Management” section.

Handling a Few Common Tasks

Some tasks are more common than others. Let’s take a look at how you can handle some of the ones that you’ll do a few dozen times before you finish your first application.

Changing the visibility of a view

Most of the time, if you want to define your UI in XML you’ll need to add views that will be visible only some of the time.


Image Tip

If you find yourself doing this a lot, be sure to check out the ViewStub class.


Depending on how dynamic your application is, views come and go fairly regularly. Showing and hiding views is as simple as using the ever-trusty findViewById and then calling setVisibility on the returned object:

Button button = (Button)findViewById(R.id.sample_button);
//Cycle through the View Visibility settings
//Gone (no impact on layouts, essentially not there)
button.setVisibility(View.GONE);
//Invisible (holds its space in a layout but is not drawn)
button.setVisibility(View.INVISIBLE);
//Visible (duh)
button.setVisibility(View.VISIBLE);

This code has three visibility settings. GONE and VISIBLE are the most obvious. You’ll find yourself using INVISIBLE less often, but it’s great for keeping all the other views inside your layouts from moving around when you want to hide something.

Setting an OnClickListener

Setting up a listener to tell you that one of your views has been clicked is one of the most common tasks you’ll do when working on an Android application. Your click listener will be called if the user taps a finger down on your view and then lifts up with the finger still within the bounds of the view. This is an important distinction: Your click listener will not be invoked when a finger actually clicks down on the view but rather when the user lifts the finger up. This gives the user the chance to put a finger down in the wrong place and then correct the position before lifting it.

You can track view clicks in several ways. You can declare that your activity itself implements the view’s OnClickListener interface, add a public void onClick (View v) method, and pass a reference to your activity to the view you wish to track. Here’s what that looks like in code for theoretical buttons with IDs button_one and button_two declared in an imaginary main.xml layout file:

public class UiDemoActivity extends Activity implements OnClickListener {

   @Override
   public void onCreate(Bundle savedInstanceState) {
     super.onCreate(savedInstanceState);
     setContentView(R.layout.main);
     Button buttonOne = (Button)findViewById(R.id.button_one);
     if(buttonOne != null)
        buttonOne.setOnClickListener(this);
     Button buttonTwo = (Button)findViewById(R.id.button_two);
     if(buttonTwo!= null)
        buttonTwo.setOnClickListener(this);
   }

   @Override
   public void onClick(View selectedView) {
      if(selectedView.getId() == R.id.button_one){
         //Take Button One actions
      }
      if(selectedView.getId() == R.id.button_one){
         //Take Button Two actions
      }
    }
}

There are two methods at work here. In the onCreate method that is called when the activity is being initialized, you’ll see me pulling button_one and button_two out of the layout with findViewById. If the system correctly returned an instance, I register my activity (passing in a reference to the activity with this) as the click listener for each view.

Registering a click listener with a view does two things. First, it tells the system to call the appropriate onClick method. Second, it tells the system that this view accepts both focus (highlightable by the navigation buttons) and touch events. You can switch these states on or off by yourself with code, but setting a click listener ensures that click events can actually be heard by the view.

There’s a second way to set up the same dynamic. This method sets up a new OnClick Listener object for each view. This can help keep code separate if your screen has a lot of clickable items on it. Here’s what this pattern looks like, and it achieves the same results as the previous code:

public class UiDemoActivity extends Activity{

   @Override
   public void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.main);
      Button buttonOne = (Button)findViewById(R.id.button_one);
      if(buttonOne != null)
         buttonOne.setOnClickListener(mClickListenerOne);
      Button buttonTwo = (Button)findViewById(R.id.button_two);
      if(buttonTwo!= null)
         buttonTwo.setOnClickListener(mClickListenerTwo);
    }

   private View.OnClickListener mClickListenerOne =
      new View.OnClickListener() {
      @Override
      public void onClick(View v) {
         //Do button one stuff here
      }
    };
    private View.OnClickListener mClickListenerTwo =
      new View.OnClickListener() {
      @Override
      public void onClick(View v) {
         //Do button two stuff here
      }
    };
}

This time, instead of declaring my activity as an implementer of the OnClickListener, I’m creating two separate anonymous inner class objects to handle the click event from each individual button. I’ll put the code required for button_one in the first object and the code for button_two in the second. I do this frequently in my own applications when I have several buttons on the screen. It keeps me from having a heaping pile of if statements (or one switch statement) that figure out which view was clicked and then take the appropriate action.

Depending on your needs, you can mix and match the two techniques. There isn’t a huge advantage either way, but it’s good to know each so you can keep your code in good order.

In this example, I’ve added a click listener to two buttons. A click listener can be attached to any view that you want users to interact with. This can be anything from entire view groups to simple text views.

It’s worth mentioning again that by setting a click listener, you’re telling the system that the item can be selected (touched with a finger) and potentially even clicked (highlighted with the trackpad and then clicked with the center key or trackball). As a result, whatever default selection action is configured for the view will run automatically on a select event (either from the directional keypad or the touchscreen). Buttons, for example, change colors when a user selects them. Text views, depending on the device’s default UI style, may also change the active color of the text. In the end, you can (and probably should) specify custom display behavior by declaring a selector drawable. I’ll show you how to do such things later in the book.

Creating Custom Views

The concept of custom views can be broken out into two sections: extending an existing view and creating an entirely new one. I’ve rarely, in my career as an Android developer, created a completely custom view, so we’ll skip over it here. The Android SDK documentation has directions for the industrious among you who want to roll your very own from scratch. However, even if you plan to extend an Android view, you must create a new class that extends the existing view. Here’s how you’d go about it.

Declaring the new class

The first step in declaring a custom view is to create the class. Android allows you to subclass any of its existing UI objects by simply extending an existing class. The declaration looks like this:

public class CustomTextView extends TextView{
   public CustomTextView(Context context) {
      super(context);
   }
}

That’s all it takes to create a custom text view. However, since there’s about as much custom in this custom text view as there is beef in fast-food tacos, I’ll add something simple to set it apart.

Extending a view

Although Android’s layouts and views are powerful and versatile, there are times when they just won’t do exactly what you want. Fortunately, their functionality is easy to extend. To demonstrate, I’ve written a custom text view that changes the color of every letter in the text to be displayed onscreen. While this isn’t the most practical use case, it will show how simple it is to implement your own behavior.

Customizing an extended view

You’d be amazed at how much code it takes to correctly render text to the screen. Android’s TextView.java class is over 9000 lines of code. But thanks to the ability to extend a class, you can use all the complex layout code and customize only the part that appeals to you. In this example, I catch the text as it changes and add a new ForegroundColorSpan for each letter in the new string. First, I declare an array of colors.

public class CustomTextView extends TextView{
   int colorArray[] = new int[]{Color.WHITE,
      Color.RED,
      Color.YELLOW,
      Color.GREEN,
      Color.BLUE,
      Color.MAGENTA,
      Color.CYAN,
      Color.DKGRAY};

Now, each time the text changes, I add a new ForegroundColorSpan for each letter.

protected void onTextChanged(CharSequence text,
     int start, int before, int after )
{
   //Keep the view from getting into an infinite loop
   if(selfChange){
     selfChange = false;
     return;
   }
   selfChange=true;
}

I make sure I don’t get stuck in an infinite loop (with the change in color triggering another onTextChanged call, which changes the color again, which changes the color... you get the idea). Next comes the code that changes the colors:

SpannableStringBuilder builder = new SpannableStringBuilder(text);
builder.clearSpans();
ForegroundColorSpan colorSpan;
int color;
for(int i=0; i < text.length(); i++){
   //pick the next color
   color = colorArray[i%colorArray.length];
   //Create the color span
   colorSpan = new ForegroundColorSpan(color);
   //Add the color span for this one char
   builder.setSpan(colorSpan,i, i,
           Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
setText(builder);

Again, not very complex, but then neither is extending an existing view class. Also, be warned that this code will clear any formatting that may have already been set on the text. (At this point, don’t stress too much about how spans and SpannableStringBuilders work. In short, they’re blocks of formatting that you can drop over strings. Check the Android SDK documentation for more info.) If you’re looking for a coding challenge, try creating an array with every possible RGB hex color value and cycling through that array.

Using your extended view

Just as with any other Android view, you can create a new instance of it at runtime in your Java code or pre-declare it in your XML layout file. Here’s how you can use it in your activity:

public void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
   CustomTextView customView = new CustomTextView(this);
   customView.setText("Hello There!");
   setContentView(customView);
}

I’m creating a new instance of my view, setting the text for it, and then setting it as the main view for my activity. You could also put it inside a layout object with other views. It’s also possible to add this custom view to an XML-described layout. But before you can start declaring your custom view in an XML file, you need to create the full suite of View constructors. Your custom view should look something like this:

public class CustomTextView extends TextView{
   public CustomTextView(Context context,
              AttributeSet attributeSet,
              int defSytle) {
      super(context, attributeSet, defSytle);
   }
   public CustomTextView(Context context,
              AttributeSet attributeSet) {
      super(context, attributeSet);
   }
   public CustomTextView(Context context){
      super(context);
   }
   //Rest of the class omitted for brevity
}

When Android parses your XML layout and creates your view, it needs to pass an attribute set to the constructor because this contains all the layout information, text, and whatever else you’ve added that starts with android. If you forget to add these, everything will compile, but it will show the Unexpected Force Close window of doom when you try to draw the screen.

Now that you have the correct constructors, it’s possible to create and lay out your custom view within XML. In the code that follows, I’ve added a single instance of the rainbow animating custom text display.

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

   <com.peachpit.ui.CustomTextView
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
     android:text="See how the colors change!" />

As you can see, adding a custom text view to your XML layouts only requires you to use the full Java package and class name. You can also see that because CustomTextView extends TextView, I can use any attribute (like android:text) that I would use with one of Android’s TextViews.

Congrats, you’ve created a custom Android view to do your bidding in only a few lines of code, you’ve displayed it to the screen, and you even have the capability to include it within a more complex layout system. Google has done a fantastic job of allowing developers to extend the functionality of the basic building blocks included in the Android SDK. If this extended custom view leaves you wanting more of a challenge, try making a simple text view that does exactly the same things as the extended view. You’ll need to explore the onMeasure and onDraw methods of your own view. Go ahead, check it out; I’ll be here when you get back.

Resource Management

Android has many tools to help you manage string literals, images, layouts, and more. Moving all this constant data into external files makes life as a programmer easier in a multitude of ways. In previous code examples, I’ve referenced the R.java file when specifying strings, drawable images, and layouts and mentioned that an explanation would be forthcoming. Now is the time to explain Android resource management in detail.

Resource Folder Overview

Every Android project, by default, contains a res folder with several subfolders inside it. Each subfolder is responsible for a different aspect of your application’s data.

The drawable folders (drawable-xhdpi, drawable-hdpi, drawable-mdpi, and so on) hold images and XML files describing drawable objects. You’ll learn much more about what you can do with drawable objects in later chapters.

The values folder holds all your textual content, from string literals to menu list value arrays to color constants.

Lastly, the layout folders contain XML files to describe how you want your screens to look.

At compile time, the Android tools take all the folders in your res directory and place a corresponding ID into an R.java file. This file is re-created and placed automatically in the project’s gen (Eclipse) or build (Android Studio) folder. Consequently, you should never directly change this R.java file, because any changes are removed the next time you compile. The IDs in R.java can be passed to everything from XML parsers to text and image views. When you call setText on a TextView and pass in R.string.hello, the view then knows to look for that ID in the string file and display what it finds there. When you set the main view of an activity by calling setContentView and passing in R.layout.main, the system knows that it needs to inflate and create the views found in res/layout/main.xml and add them to the active views on the screen for the current activity.

Here’s what the R.java file looks like for a newly created project:

/* AUTO-GENERATED FILE. DO NOT MODIFY.
 *
 * This class was automatically generated by the
 * aapt tool from the resource data it found. It
 * should not be modified by hand.
 */
package com.peachpit;
public final class R {
   public static final class attr {
   }
   public static final class drawable {
      public static final int icon=0x7f020000;
   }
   public static final class layout {
      public static final int main=0x7f030000;
   }
   public static final class string {
      public static final int app_name=0x7f040001;
      public static final int hello=0x7f040000;
   }
}

When Android compiles your XML files, it renders them to a packed binary format. The upside of this format is that it loads much faster, so your screens can snap into focus more quickly. The downside is that you cannot modify any of these files once they’ve been compiled. So you can’t manipulate your layout and string files at runtime. You can, however, modify what is rendered to the screen by loading and changing strings in the Java representation of the views.

Additionally, if you want to reference various resources from other XML files, you’ll use the @folder/object_id structure. While you may not have been aware of it, you’ve seen this kind of declaration in action already. Think back to the initial Hello World layout that the Android tools provide for you. In it, you saw a text view with the following line: android:text="@string/hello". This was Android’s resource system at work. Instead of specifying R.string.hello, you’ll use the XML’s @string/hello for XML.

Each type of folder (drawable, layout, values, and several more) has special naming conventions and tricks you can use to battle the time-consuming problems of device diversity, language localization, and differing screen resolutions and densities. Let’s look at what you can do with each type of file.

Values Folder

The prospect of putting all the constant values for your user interface (strings, colors, or int/string arrays) in a separate file might sound annoying at first (especially when you consider that all text manipulators will take a resource ID or a CharSequence). However, it can cut down many days of work when translating your application to different languages.

Having all your strings in an external XML file also means that your nontechnical colleagues (product managers, designers, or micromanaging bosses) can manipulate the display text in the screens, menus, and pop-up dialogs without bothering you. This assumes, of course, that you teach them how to compile and run the project; feel free to share the first chapter of this book with them.

The values folder can contain:

Image Strings. All string literals should go into your strings.xml file.

Image Arrays. There is a file for the XML-defined arrays, but string arrays can still go in the strings.xml file if you don’t feel like using a separate file.

Image Colors. colors.xml can contain any number of declared color constants for use in everything from text fonts to layout backgrounds. Unless you’re planning on doing a lot of custom UI drawing, this file will probably not be very large.

Image Dimensions. dimens.xml can contain any number of possible size values used elsewhere in your layouts. This file is particularly handy if you wish to make a view taller or shorter based on the display size of the device your application is being displayed on. This might seem like a simple thing to do, but it can be very powerful when combined with the layout folders.

Image Styles. styles.xml... yeah... more about this later.

You can create new values folders for each language by using the two-letter ISO639-2 suffix for a particular language as a suffix to values. You can, for example, create a values-es folder containing a Spanish version of the strings.xml file. When a user sets his or her phone to Spanish, Android will check automatically for an R.string.hello value defined in the strings.xml file within the new values-es folder. If it finds one, it will display the values-es version rather than the default. If it doesn’t find a Spanish translation, it will default to the value you defined in values/strings.xml.

In this way, Android provides you with an easy way to localize all the strings in your application. It does, however, require you to be vigilant about placing your string literals in the strings.xml file rather than just calling setText(“Text like this”); or using android:text="Text like this" in your XML.

Layout Folders

I’m going to give you three guesses as to what exactly goes into the layout folders. Typically, it’s where you place either layout XML files for use in setContentView calls, or sub-layouts that can be included via ViewStubs or inherited views (two tools that allow you to reuse views in different layouts). Android builds a helpful mechanic into the layout folders. You can have many folders, with different suffixes, that describe how you want the application to appear under various screen configurations.

The simplest example is a layout folder and a layout-land folder. If you place a firstscreen.xml file in both folders, Android will use the one that most closely resembles the current screen mode. If you keep the android:id settings consistent, you will be able to specify two completely different-looking screens within your XML markups and interact with any of them via your Java code. This technique is complicated, so let’s look at an example.

Let’s say you create portrait and landscape layouts for the first screen of your application. Both files are called firstscreen.xml, and the portrait version goes in the layout folder while the landscape version goes in the layout-land folder. You could also put the portrait version in a folder named layout-port. In both versions of firstscreen.xml, you provide all the appropriate IDs for the text views, buttons, and images.

If your screen had an OK button, you’d provide the portrait and landscape versions of these buttons the same ID: R.id.ok_button. Remember that you gain access to views by calling findViewById() and passing in the ID you specified on the android:id="@+id/id_goes_here" line. In this case, if you wanted to set a click listener, you’d fetch the OK button by calling findViewById(R.id.ok_button);, and Android would return the button from your portrait screen if you’re in portrait mode and the landscape version of the button if you’re in landscape mode. Your code knows what it must do when that button is pressed, but it doesn’t know about the dimensions or location of the button. Welcome to the happy land of the Model-View-Controller (MVC).

MVC is your number one friend when handling the diversity in device screen sizes. You can lay out your views in any possible configuration, and as long as the IDs match, you’ll need to write only a single Activity class to handle all possible screen permutations. You can have specific folders, each with its own set of layout files for different screen sizes (layout-small to layout-xlarge) and densities (layout-ldpi to layout-xxhdpi), and you can mix and match. For example, layout-large-land would specify layouts for large screens (VGA and WVGA) that are in landscape mode. For the exact order in which Android defaults through the folders, be sure to check the Android SDK documentation.

I’m only scratching the surface of the possibilities that these layout folders put at your disposal. You’ll learn more about this topic in coming chapters on dealing with display and hardware diversity.

Drawable Folders

For Android, a drawable is simply something that can be drawn to the screen. Android abstracts away the differences between colors, shapes, and images and allows you to deal with a superclass that can represent any number of them: Drawable.

You keep the definitions for these objects in the drawable folders.

You should consider using a drawable for two main types of objects:

Image Image resources (mostly PNG files)

Image XML files describing things you want drawn: shapes, gradients, or colors

The drawable set of folders works similarly to the layout folders. You can specify any mix of screen densities, layouts, or resolutions, provided you specify them in the right order.


Image Tip

Be sure to check the Android SDK documentation for more information on ordering your suffixes correctly.


Referencing a drawable is accomplished in the same way that you reference values and layouts. For XML files, use @drawable/resource_name. For example, in an ImageView (android:src=”@drawable/bridge”), omit the suffix if you’re referring to XML files or images. Also, keep in mind that Android will always try to use the drawable folder that is closest to your current configuration. If it can’t find a good match, it’ll cascade back to what it can find in the default drawable folder.

Layout Management

Layouts, from the simple to the complex, describe how to arrange a complex series of views. This section covers the basics of Android’s arsenal of layout classes starting with the ViewGroup, moving through LinearLayouts, and ending with the king of the Android screen arrangers, the RelativeLayout.

The ViewGroup

Each layout in Android extends what’s known as the ViewGroup. This is the class of views that by definition can contain other views as children.

To demonstrate how each of the major layouts function, I’ll be laying out an example picture-viewer screen. While this probably won’t be the most beautiful photo viewer, it will be simple and an excellent vehicle for showing you how the various layouts work.

Figure 3.1 shows the screen that we’ll be rendering using the AbsoluteLayout, the LinearLayout, and the RelativeLayout.

Image

Figure 3.1 A taste of what’s to come.

The example picture viewer has two sections: the button bar with title, and the image itself. The button bar contains Next and Prev buttons and a text view displaying the name of the image.

Before getting too deep into the layout, there are a few terms to watch for in the XML:

Image dip or dp. This is how Android helps you scale your screen layout to devices with different pixel densities. For example, on a high density pixel screen (HDPI), 1dp = 1.5 pixels; on an extra high density screen (XHDPI) screen, 1dp = 2 pixels; and so on. It can be annoying to constantly convert the locations onscreen to dips, but this small investment in time will pay huge dividends when you’re running on a multitude of Android screens. Example: android:padding="20dp".

Image px. Use this suffix to define an absolute pixel value for the specified dimension. In most cases, you should avoid declaring the absolute pixel value and use dp. Example: android:paddingLeft="15px".

Image match_parent and wrap_content. Before you can draw an Android view to the screen, it must have a defined width and height. You can define either of these two values as a constant value (20dp), or you can use one of the two special height and width values, match_parent or wrap_content. Each value does exactly what you’d expect. The match_parent value will make the view attempt to match the dimension of its parent. The wrap_content value will first request the measured size of the view and then attempt to set that dimension as the layout width for the view itself.

With a few simple definitions out of the way, I can start in on the various layout classes. I’ll start with one that you’ll find appealing but that you should never use in practice.

The AbsoluteLayout

The most important thing you should know about AbsoluteLayouts is that you should never use them. They are the quintessential beginner’s trap. They appear to be a good idea at first (as they quickly give you exact pixel design), but they can be frustrating, require excess time laying out new screens, and cause frequent face-desk interaction. Consult your local Android expert if you experience any of these side effects while trying to use an AbsoluteLayout. They’ll likely try to talk you out of this lunacy.

The AbsoluteLayout, as you might have guessed, allows you to specify exactly where on the screen you want a particular view to go. Each child of an AbsoluteLayout should have android:layout_x and android:layout_y values along with the required width and height settings.

You are probably thinking, “That sounds like a great way to make layouts look exactly the way I want them to. Why bother learning anything else when I can take my screen designs and convert them directly into pixel x/y locations?”

I thought the same thing... at first.

Here’s what the AbsoluteLayout layout XML looks like:

<?xml version="1.0" encoding="utf-8"?>
<AbsoluteLayout
   xmlns:android="http://schemas.android.com/apk/res/android"
   android:layout_width="match_parent"
   android:layout_height="match_parent">
   <View
      android:background="#333333"
      android:layout_height="52dp"
      android:layout_width="match_parent"/>
   <Button
      android:id="@+id/prev"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_x="5dp"
      android:layout_y="3dp"
      android:text="@string/prev_string"
android:textColor="@android:color/white" />
   <TextView
      android:id="@+id/text_view"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_x="125dp"
      android:layout_y="20dp"
      android:text="Empire State Building"
android:textColor="@android:color/white" />
   <Button
     android:id="@+id/next"
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
     android:layout_x="293dp"
     android:layout_y="3dp"
     android:text="@string/next_string"
android:textColor="@android:color/white"/>
   <ImageView
     android:layout_width="match_parent"
     android:layout_height="468dp"
     android:layout_x="0dp"
     android:layout_y="52dp"
     android:scaleType="centerCrop"
     android:src="@drawable/empire_state_snapshot" />
</AbsoluteLayout>

Nothing in this layout code should look shocking. Each button, text view, and image view has x and y coordinates. This simple code arranges the views to look very similar to the original original (Figure 3.1).

But hang on, this is only one of the 20-something ways that you can consume a single layout. Let’s look at what happens to this layout when switching to landscape (Figure 3.2).

Image

Figure 3.2 AbsoluteLayout seems like a good idea at first—until you do anything else, like rotate.

Yikes, that looks... bad! This is no way for a screen to look on any layout. Sure, you could do an alternative layout for every portrait and landscape screen out there, but that could add weeks to your schedule, and, worse, every time you need a new screen aspect you’ll have to start from scratch.

While the AbsoluteLayout can give you a pixel-perfect design, it can achieve that look only for a single screen configuration. Given that Android has many dozens of screen sizes and types spread across a huge number of physical devices, layouts like these will make your life miserable once you move beyond your initial test device.

The only way to make life harder for yourself is to use an AbsoluteLayout with its children views defined in exact pixels (px) x and y values. Not only will a screen laid out in this way break when you switch to landscape, but it’ll break when you switch from, say, the Moto X to the Galaxy S4, because they each have different screen densities (XHDPI and XXHDPI, respectively).

I’ve included the AbsoluteLayout in this chapter because if I didn’t, you might find it on your own and wonder at what a gem you’d found. This is a cautionary tale. The other layouts can be frustrating and time consuming up front, but trust me, they’ll pay off in the end.

Bottom line: Don’t use AbsoluteLayouts except for extremely simple cases. I could see it being used to lay out a small, sophisticated button that could then be dropped into one of the more dynamic layout classes—but please, for your own sanity, don’t use this layout object unless you absolutely cannot avoid it.

The LinearLayout

A LinearLayout is the exact opposite of the AbsoluteLayout. Within it, you’ll define a series of views, and the system will size and place them dynamically on the screen in the order you’ve specified. This layout is, and I cannot emphasize this enough, not very good for putting views exactly where you want them. I’m saving the layout class that is best at this for last.

Here’s how the original picture viewer looks when designed to work with a LinearLayout:

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

   <LinearLayout
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:background="#333333"
      android:orientation="horizontal">
      <Button
         android:id="@+id/prev"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_weight="1"
         android:text="@string/prev_string"
         android:textColor="@android:color/white" />
      <TextView
         android:id="@+id/url_view"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_weight="1"
         android:gravity="center_horizontal"
         android:text="Empire State Building"
         android:textColor="@android:color/white" />
      <Button
         android:id="@+id/next"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_weight="1"
         android:text="@string/next_string"
         android:textColor="@android:color/white" />
   </LinearLayout>
   <ImageView
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:scaleType="centerCrop"
      android:src="@drawable/empire_state_snapshot" />
 </LinearLayout>

Notice that there are two LinearLayouts at work in this example. The top-level layout contains the second LinearLayout object and the image to be displayed. The second LinearLayout contains the two buttons and the text of the title. Two layouts are required, in this case, because any one LinearLayout may have only one orientation.

Figure 3.3 shows what this example XML produces in portrait mode.

Image

Figure 3.3 The LinearLayout-based screen

This is where Android’s dynamic layouts really start to shine; take a look at what the exact same layout code looks like when the user shifts into landscape mode (Figure 3.4).

Image

Figure 3.4 The same layout but in landscape

Not perfect, but a vast improvement over the AbsoluteLayout’s version of the landscape screen.

Using LinearLayouts

When using the LinearLayout, the order in which you define your views is the order in which they’re placed on the screen. First, take a look at that second LinearLayout:

<LinearLayout
   android:id="@+id/button_bar"
   android:layout_width="match_parent"
   android:layout_height="wrap_content"
   android:background="#333333"
   android:orientation="horizontal">

By setting the orientation to horizontal, Android knows to place the children in order from left to right across the top of the screen. The outer layout is a vertical one, allowing placement of the button bar above the image.

By setting the width to match_parent, I’m making sure the layout itself stretches all the way across the parent layout (in this case the entire screen). The height is set to wrap_content, so it will be exactly as tall as the final measured height of its children.

The LinearLayout distributes its children in the order they’re defined. It then measures all the child views to see how much space they’d like to take up. It will then divvy up the available space in proportion to the ideal size of its children. If there is too little space to accommodate all the child views, it’ll give each child a part of their required space in proportion to their measured size. If there’s more than enough space for all the child views, it’ll pass out the extra space based on how large the child’s onMeasure call tells the layout it wants to be. You can modify this proportional distribution through the layout_weight attribute in each child.

Layout really happens more in the definition of the children than in the declaration of the layout itself. So, for a bit of context, let’s take a look at the individual members of the button bar layout.

Here is the first of the child views in the definition for the Prev button:

<Button
   android:id="@+id/prev"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   android:layout_weight="1"
   android:text="@string/prev_string"
   android:textColor="@android:color/white" />

The layout_weight value, which can be any number, decimal, or integer, tells the system how much space to give to the child view when the layout has either too much or too little space. The total weight distributed is the ratio of all the weights added together.

Take a look at the landscape version of the button bar again (Figure 3.5).

Image

Figure 3.5 Overly large buttons thanks to the LinearLayout

This may be very close to the way the screen looks in portrait mode, but not close enough. As the top bar grows, the LinearLayout hands out extra pixel space in proportion to the measured size of the child view. In this case, because all three elements (two buttons and the text title) are weighted the same (1), the layout divides the extra space evenly among them. You can re-weight the buttons such that they grow in different ratios to the text title. Since you want the buttons to grow at a slower rate than the text, you could try setting their weights to 0. Here is what the new values look like (with everything else omitted):

<LinearLayout>
   <Button
      android:layout_weight="0" />
   <TextView
      android:layout_weight="1" />
   <Button
     android:layout_weight="0" />
</LinearLayout>

Figure 3.6 shows what this change does to the button bar.

Image

Figure 3.6 Oops, too small!

Well, technically that’s correct, but it looks awful. You have two options. You can declare exactly how much extra space you’d like each of the two buttons to have through the android:padding declaration, or you can give them a little bit more weight. You can fiddle with the first option on your own, but let’s take a look at the padding option together.

Although you don’t want the buttons to get too large, you still need to give them a bit more space than exactly what fits around the text. Let’s try .25 for a weight. I’ve pulled out all non-layout_weight lines for brevity:

<LinearLayout>
   <Button
      android:layout_weight=".25" />
   <TextView
      android:layout_weight="1" />
   <Button
      android:layout_weight=".25" />
</LinearLayout>

Figure 3.7 shows how that looks in landscape mode.

Image

Figure 3.7 That’s much better!

The result is much more reasonable. But to be sure, check what the bar looks like in portrait mode. Figure 3.8 shows the result.

Image

Figure 3.8 Check back in on portrait mode.

Perhaps the Next and Prev buttons could be a little bit larger in portrait mode, but this result is more than acceptable. They’re not huge, they don’t look crowded, and they should be big enough for even large fingers to hit.

In the end, nothing beats the LinearLayouts for easily handling different dynamic screen sizes. Throw as much into one as you please, and it’ll try to accommodate everything. There are, however, two major issues to watch out for. First, because they can orient themselves in only one direction, you may end up needing a lot of them to handle a complex layout, which can slow drawing performance significantly. Second, getting a complex screen to render exactly as your designer wants it to can be an intensive process. For more complex or busy screens and objects, you’re much better off using a RelativeLayout.

The RelativeLayout

The RelativeLayout is the king of the Android screen layout system. It allows you to position all your child views in relation to either the parent (the layout itself) or any other child view within the layout. Let’s take a look at one in action. Here’s the now familiar image and button arrangement in the photo viewer, but with a RelativeLayout (Figure 3.9).

Image

Figure 3.9 Designing the same screen, but with the RelativeLayout.

There are a few slight measurement differences between this image and the one produced with the LinearLayout. This one is also missing the gray background behind the buttons, which I’ll show you how to add shortly.

Take a look at the XML layout that produced the image in Figure 3.9.

 <?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
   xmlns:android="http://schemas.android.com/apk/res/android"
   android:layout_width="match_parent"
   android:layout_height="match_parent">
   <View
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:background="#333333"
      android:layout_alignBottom="@+id/next"
      android:layout_alignParentTop="true" />
   <Button
      android:id="@+id/prev"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_alignParentLeft="true"
      android:layout_alignParentTop="true"
      android:text="@string/prev_string"
      android:textColor="@android:color/white" />
   <Button
      android:id="@+id/next"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_alignParentRight="true"
      android:layout_alignParentTop="true"
      android:text="@string/next_string"
      android:textColor="@android:color/white" />
   <TextView
      android:id="@+id/text_view"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:gravity="center"
      android:layout_toLeftOf="@id/next"
      android:layout_toRightOf="@id/prev"
      android:layout_alignBottom="@id/prev"
      android:layout_alignTop="@id/prev"
      android:text="Empire State Building"
      android:textColor="@android:color/white" />
   <ImageView
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:layout_below="@id/prev"
   android:scaleType="centerCrop"
      android:src="@drawable/empire_state_snapshot" />
</RelativeLayout>


Image Note

If you reference an "@id/..." for layout purposes, before the view is declared in XML you need to declare it with an "@+id/...". Otherwise, your compiler will complain that it doesn’t see the ID you’re referencing.


In this layout code, you see the same view components that made up the LinearLayout, but with the relative version, there’s no need for a second, nested layout. The mechanics to each member of a RelativeLayout can be more complex than its linear cousin, so I’ll break down all four pieces of this screen one at a time.

The <RelativeLayout> declaration contains only enough information to tell the layout to fill the entire screen. All information on how to lay out the screen is found in the child elements. Here’s the first one:

<Button
   android:id="@+id/prev"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   android:layout_alignParentLeft="true"
   android:layout_alignParentTop="true"
   android:text="@string/prev_string"
   android:textColor="@android:color/white" />

The first view, which declares the Prev button, initially declares its ID in the android:id line. The Prev button needs an ID so you can assign a click listener to it in the activity code. The layout height and width declarations simply tell the view to make it large enough to accommodate all the content (in this case, the “prev” text and a little padding).

The padding declaration tells the system to push the boundaries for the button out from the smallest required space for the text. In this case, android:padding="15dp" tells the system to measure the required space for the “prev” text and then 15 more device-independent pixels to the outer boundary of the view. As a general rule, it’s always good to pad your buttons between 10 and 20 dp (depending on screen and text size). This gives them a little more space to be recognized as buttons, and it also gives people with large fingers a chance of actually hitting the view.

Now come the parts that tell the system where inside the layout object to place the button. The attribute android:layout_alignParentLeft="true" tells Android to align the left edge of the view with the left edge of the parent’s bounding rectangle. In this case, it’s the left edge of the screen. The android:layout_alignParentTop="true" attribute does the same thing except with respect to the top of the layout object (in this case, the top of the application’s available space).

If you don’t specify any layout parameters, views will default to the upper-left corner of the layout object. This code example declares these views for explanation purposes.

Now that the Prev button is in place, you’re ready to move on. Here’s the relevant XML for the Next button:

<Button
   android:id="@+id/next"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   android:layout_alignParentRight="true"
   android:layout_alignParentTop="true"
   android:text="@string/next_string"
   android:textColor="@android:color/white" />

The Next button is nearly identical to the Prev button except for the ID (required to set up a click listener in the activity), the text displaying on the button (“next”), and the android:layout_alignParentRight="true" attribute (to lock it to the right side of the layout object—and thus the right side of the screen—instead of the left). Here’s the code for the title:

<TextView
   android:id="@+id/text_view"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   android:gravity="center"
   android:layout_toLeftOf="@id/next"
   android:layout_toRightOf="@id/prev"
   android:layout_alignBottom="@id/prev"
   android:layout_alignTop="@id/prev"
   android:text="Empire State Building"
   android:textColor="@android:color/white" />

In this text view, things start to get a little more interesting. Again, the ID, height, and width are things you’ve seen before, but you need to change the title text as the images change. As the image changes, you’ll need an ID so the activity can change the name of the picture displayed above it.

android:layout_toRightOf="@id/prev" tells the layout to align the left edge of the text view with the right edge of the Prev button. android:layout_toLeftOf= "@id/next" tells the right edge of the text view to align with the left-most edge of the Next button. The android:gravity="center" attribute tells the text to center itself within any extra available space. This will center it vertically (so it doesn’t stick to the top of the screen) and horizontally (so it doesn’t stick against the left-most button).

This technique of centering a view in the space between two objects is one I use frequently in my Android work, and it’s a good way to eat up extra space caused by small and large fluctuations in screen size. That is, the text in the center of the buttons will float, centered, within any available screen space you might get when using a larger screen than the one you’re designing.

Adding that gray background

So, you might be asking, if the LinearLayout example has such an easy way to add the gray background, why does the RelativeLayout need an extra view? First, stop asking your book questions; you’ll look a little odd in public. Second, I’ve put it in this way so you can see that sometimes, even though you can do it all in one way, the best solutions are a mix of both:

<!-- This is the top level layout -->
<RelativeLayout
   xmlns:android="http://schemas.android.com/apk/res/android"
   android:layout_width="match_parent"
   android:layout_height="match_parent">
   <View
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:background="#333333"
      android:layout_alignBottom="@+id/next"
      android:layout_alignParentTop="true" />

   <!--Rest of the screen goes here -->
</RelativeLayout>

I want the gray box to be drawn behind the button bar, so I placed it as the first view in the layout. Android draws the view stack in the order they’re declared. So, were I to incorrectly place the listed XML below the button and text declarations, you’d see only the gray bar covering over both the text and the buttons.

The key attribute here is layout_alignBottom, which will align the bottom of the view with the bottom of the view that has the ID you give it. This same property works with layout_alignTop, layout_alignLeft, and layout_alignRight.

With that, you’ve successfully added a gray background and brought the RelativeLayout version of this view into parity with the earlier LinearLayout demonstration. The RelativeLayout can handle more complex displays without requiring other nested layouts. It also can, if you’re smart about it, handle changes in screen size, as shown by having the image’s name float between the buttons no matter how far apart they get.

With great power comes great responsibility

The RelativeLayout is a great tool for getting things where you want them in a variety of complicated situations, but this flexibility comes at a price. As indicated before with LinearLayout, nesting too deeply in RelativeLayout can quickly turn into a performance disaster. By nature of having pieces laid out relative to each other, the system must perform multiple layout passes to determine how much space each view can take up.

It’s easy to see how—if one view’s left bound depends on another view’s right bound, which depends on the size of the parent view, which even further depends on the gravity of its parent view—measuring these sizes can get out of hand. While I’m confident that you will always get the result you’re imagining, you may also run into system failures if things get too complex.

Wrapping Up

Throughout this chapter, you’ve come to understand the fundamental building blocks that make up Android’s UI. While I haven’t yet had time to show you any of these particular classes in much depth, together we’ve laid the groundwork for more serious chapters to come. In this way, I can dive deep into TextViews (yes, we will) without worrying that you won’t know how to arrange them next to an image or make them respond to click events.

This concludes the overview of displaying information to users. You should be comfortable building and changing basic user interfaces through Java and Android’s XML layout system. If you didn’t skip any sections, you’ll also be able to extend existing built-in views to make Android do exactly your bidding. Next you’ll take a break from drawing things on screens and look at how to acquire data for your pretty user interfaces.

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

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