Creating User Interfaces

This article by Belen Cruz Zapata, the author of the book Android Studio 2 Essentials - Second Edition, focuses on the creation of the user interfaces using layouts. The layouts can be created using a graphical view or a text-based view. Since there are over 18,000 Android device types, you will learn about fragmentation on different screen types and will discuss how to prepare our application for this issue. We will end this article with basic notions of handling events on our application.

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

These are the topics we'll be covering in this article:

  • Supporting different screens
  • Changing the UI theme
  • Handling events

Supporting multiple screens

When creating Android applications, we have to take into account the existence of multiple screen sizes and screen resolutions. It is important to check how our layouts are displayed in different screen configurations. To accomplish this, Android Studio provides a functionality to change the virtual device that renders the layout preview when we are in the Design mode.

We can find this functionality in the toolbar and click on it to open the list of available device definitions, as shown in the following screenshot:

Try some of them. The difference between a tablet device and a device like those from the Nexus line is very notable. We should adapt the views to all the screen configurations our application supports to ensure that they are displayed optimally. Note that there are device definitions for Android Wear (square, round, and round chin designs) and for Android TV.

The device definitions indicate the screen size, resolution, and screen density. Android screen densities include ldpi, mdpi, tvdpi, hdpi, xhdpi, and even xxhdpi. Let's see what their values are:

  • ldpi : This is low-density dots per inch, and its value is about 120 dpi
  • mdpi: This is medium-density dots per inch, and its values is about 160 dpi
  • tvdpi: This is medium-density dots per inch, and its value is about 213 dpi
  • hdpi: This is high-density dots per inch, and its value is about 240 dpi
  • xhdpi: This is extra-high-density dots per inch, and its value is about 320 dpi
  • xxhdpi: This is extra-extra-high-density dots per inch, and its value is about 480 dpi
  • xxxhdpi: This is extra-extra-extra-high-density dots per inch, and its value is about 640 dpi

The last dashboards published by Google show that most devices have high-density screens (42.3 percent), followed by xhdpi (24.8 percent) and xxhdpi (15.0 percent). Therefore, we can cover 82.1 percent of all the devices by testing our application using these three screen densities. If you want to cover a bigger percentage of devices, test your application using mdpi screens (12.9 percent) as well so the coverage will be 95.0 percent of all devices. The official Android dashboards are available at http://developer.android.com/about/dashboards.

Another issue to keep in mind is the device orientation. Do we want to support the landscape mode in our application? If the answer is yes, then we have to test our layouts in landscape orientation. On the toolbar, click on the layout state option to change the mode either from portrait to landscape or from landscape to portrait.

If our application supports landscape mode and the layout does not get displayed as expected in this orientation, we might want to create a variation of the layout. Click on the first icon of the toolbar, that is, the Configuration to render this layout with inside the IDE option, and select the Create Landscape Variation option as shown in the next screenshot:

A new layout will be opened in the editor. This layout has been created in the resources folder, under the layout-land directory, and it uses the same name as the portrait layout - /src/main/res/layout-land/activity_main.xml. The Android system will decide which version of the layout needs to be used depending on the current device orientation. Now, we can edit the new layout variation such that it perfectly conforms to landscape mode.

Similarly, we can create a variation of the layout for extra-large screens. Select the Create layout-xlarge Variation option. The new layout will be created in the layout-xlarge folder using the same name as the original layout at /src/main/res/layout-xlarge/activity_main.xml. Android divides into the actual screen sizes small, normal, large, and extra large:

  • Small: Screens classified in this category are at least 426 dp x 320 dp.
  • Normal: Screens classified in this category are at least 470 dp x 320 dp.
  • Large: Screens classified in this category are at least 640 dp x 480 dp.
  • Extra large: Screens classified in this category are at least 960 dp x 720 dp.

A density-independent pixel (dp), equivalent to one physical pixel on a 160 dpi screen. The last dashboards published by Google show that most devices have a normal screen size (85.1 percent), followed by large screen size (8.2 percent). The official Android dashboards are available at http://developer.android.com/about/dashboards.

To display multiple device configurations at the same time, click on the Configuration to render this layout with inside the IDE option in the toolbar and select the Preview All Screen Sizes option, or click on the Preview Representative Sample option to open only the most important screen sizes, as shown in the following screenshot. We can also delete any of the samples by right-clicking on them and selecting the Delete option from the menu. Another useful action of this menu is the Save screenshot option. It allows us to take a screenshot of the layout preview.

If we create different layout variations, we can preview all of them by selecting the Preview Layout Versions option. If we want to preview what the layout looks like for different Android versions, we can use the Preview Android Versions option.

Now that we have seen how to add different components and optimize our layout for different screens, let's start working with themes.

Changing the UI theme

Layouts and widgets are created using the default UI theme of our project. We can change the appearance of the elements of the UI by creating styles. Styles can be grouped to create a theme, and a theme can be applied to an activity or to the whole application. Some themes are provided by default, such as the Material Design or Holo style. Styles and themes are created as resources under the /src/res/values folder.

To continue our example, we are going to change the default colors of the theme that we are using in our app. Using the graphical editor, you can see that the selected theme for our layout is indicated as AppTheme in the toolbar. This theme was created for our project and can be found in the styles file at /src/res/values/styles.xml.

Open the styles file. Android Studio suggests us to use the Theme Editor. You can click on the message link or you can navigate to Tools | Android | Theme Editor to open it. You can see the Theme Editor in the next screenshot:

The left panel shows what different UI components look like. For example, you can view the appearance of the app bar, different types of buttons, text views, or the appearance of the status bar. The right panel of the Theme Editor contains the settings of the theme. You can change the values from the right panel and see how the components change on the left panel of Theme Editor.

In the configuration right panel, you can change the Theme to modify, you can change the Theme parent of the selected theme, and you can change the theme colors. You will note that AppTheme is by default an extension of another theme, Theme.AppCompat.Light.DarkActionBar.

Let's try to change the main color of our app. Follow the next steps:

  1. Look for the colorPrimary property on the right panel of the Theme Editor.
  2. Click on the color square of the colorPrimary property. The color selector of the following screenshot will be opened:
  3. Select a different color and click on the OK button. Note that the theme has changed and now the app bar has the new color in Theme Editor.
  4. Open your main layout file. The preview of the layout has also changed its color. This theme primary color will be applied to all our layouts due to the fact that we configured it in the theme and not just in the layout.

The specification of the colors is saved in the colors file at /src/res/values/colors.xml. This is the current content of the colors file:

<resources>
    <color name="colorPrimary">#009688</color>
    <color name="colorPrimaryDark">#303F9F</color>
    <color name="colorAccent">#FF4081</color>
</resources>

You can also change the colors from this file. Modify the colorPrimaryDark, save the file, and note that in the Theme Editor, the status bar color has changed to the new color. Switch to your main layout file and observe that the preview of your layout has also changed to show the new color in the status bar.

To change the layout theme completely, click on the theme option from the toolbar in the graphical editor. The theme selector dialog is now opened, displaying a list of the available themes, as shown in the following screenshot:

The themes created in our own project are listed in the Project Themes section. The Manifest Themes section shows the theme configured in the application manifest file (/src/main/AndroidManifest.xml). The All section lists all the available themes.

Handling events

The user interface would be useless if the rest of the application could not interact with it. Events in Android are generated when the user interacts with our application. All the UI widgets are children of the View class, and they share some events handled by the following listeners:

  • OnClickListener: This captures the event when the user clicks on the view element. To configure this listener in a view, use the setOnClickListener method. The OnClickListener interface declares the following method to receive the click event:
    public abstract void onClick(View v)
  • OnCreateContextMenu: This captures the event when the user performs a long click on the view element and we want to open a context menu. To configure this listener in a view, use the setOnCreateContextMenu method. The OnCreateContextMenu interface declares the following method to receive the long-click event:
    public abstract void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo)
  • OnDragListener: This captures the event when the user drags and drops the event element. To configure this listener in a view, use the setOnDragListener method. The OnDragListener interface declares the following method to receive the drag event:
    public abstract boolean onDrag(View v, DragEvent event)
  • OnFocusChangedListener: This captures the event when the user navigates from an element to another in the same view. To configure this listener in a view, use the setOnFocusChangedListener method. The OnFocusChangedListener interface declares the following method to receive the change of focus event:
    public abstract void onFocusChange(View v, boolean hasFocus)
  • OnHoverListener: This captures the event when the user is moving over an element. To configure this listener in a view, use the setOnHoverListener method. The OnHoverListener interface declares the following method to receive the hover event:
    public abstract boolean onHover(View v, MotionEvent event)
  • OnKeyListener: This captures the event when the user presses any key while the view element has the focus. To configure this listener in a view, use the setOnKeyListener method. The OnKeyListener interface declares the following method to receive the key event:
    public abstract boolean onKey(View v, int keyCode, KeyEvent
    event)
  • OnLayoutChangeListener: This captures the event when the layout of a view changes its bounds due to layout processing. To configure this listener in a view, use the setOnLayoutChangeListener method. The OnLayoutChangeListener interface declares the following method to receive the layout change event:
    public abstract void onLayoutChange(View v,
    int left, int top, int right, int bottom,
    int oldLeft, int oldTop, int oldRight, int oldBottom)
  • OnLongClickListener: This captures the event when the user touches the view element and holds it. To configure this listener in a view, use the setOnLongClickListener method. The OnLongClickListener interface declares the following method to receive the long click event:
    public abstract boolean onLongClick(View v)
  • OnScrollChangeListener: This captures the event when the scroll position of a view changes. To configure this listener in a view, use the setOnScrollChangeListener method. The OnScrollChangeListener interface declares the following method to receive the scroll change event:
    public abstract void onScrollChange(View v,
    int scrollX, int scrollY,
    int oldScrollX, int oldScrollY)
  • OnTouchListener: This captures the event when the user touches the view element. To configure this listener in a view, use the setOnTouchListener method. The OnTouchListener interface declares the following method to receive the touch event:
    public abstract boolean onTouch(View v, MotionEvent event)

In addition to these standard events and listeners, some UI widgets have some more specific events and listeners. Checkboxes can register a listener to capture when its state changes (OnCheckedChangeListener), and spinners can register a listener to capture when an item is clicked (OnItemClickListener).

The most common event to capture is when the user clicks on the view elements. There is an easy way to handle it—using the view properties. Select the Accept button in our layout and look for the onClick property. This property indicates the name of the method that will be executed when the user presses the button. This method has to be created in the activity associated with the current layout, our main activity (MainActivity.java) in this case. Type onAcceptClick as the value of this property.

Open the main activity to create the method definition. When a view is clicked, the event callback method when has to be public with a void return type. It receives the view that has been clicked on as parameter. This method will be executed every time the user clicks on the button:

public void onAcceptClick(View v) {
  // Action when the button is pressed
}

From the main activity, we can interact with all the components of the interface, so when the user presses the Accept button, our code can read the text from the name field and change the greeting to include the name in it.

To get the reference to a view object, use the findViewById method inherited from the Activity class. This method receives the ID of the component and returns the View object corresponding to that ID. The returned view object has to be cast to its specific class in order to use its methods, such as the getText method of the EditText class, to get the name typed by the user:

public void onAcceptClick(View v) {
  TextView tvGreeting =
    (TextView) findViewById(R.id.textView_greeting);
  EditText etName = (EditText) findViewById(R.id.editText_name);

  if(0 < etName.getText().length()) {
    tvGreeting.setText("Hello " + et_name.getText());
  }
}

In the first two lines of the method, the references to the elements of the layout are retrieved: the text view that contains the greeting and the text field where the user can type a name. The components are found by their IDs, the same ID that we indicated in the properties of the element in the layout file. All the IDs of resources are included in the R class. The R class is autogenerated in the build phase and therefore we must not edit it. If this class is not autogenerated, then probably some file of our resources contain an error.

The next line is a conditional statement used to check whether the user typed a name. If they typed a name, the text will be replaced by a new greeting that contains that name.

If the event we want to handle is not the user's click, then we have to create and add the listener by code to the onCreate method of the activity. There are two ways to do this:

  • Implementing the listener interface in the activity and then adding the unimplemented methods. The methods required by the interface are the methods used to receive the events.
  • Creating a private anonymous implementation of the listener in the activity file. The methods that receive the events are implemented in this object.

Finally, the listener implementation has to be assigned to the view element using the setter methods, such as setOnClickListener, setOnCreateContextMenu, setOnDragListener, setOnFocusChange, setOnKeyListener, and so forth. The listener assignment is usually included in the onCreate method of the activity. If the listener is implemented in the same activity, then the parameter indicated to the setter method is the own activity using the this keyword, as shown in the following code:

Button bAccept = (Button) findViewById(R.id.button_accept);
bAccept.setOnClickListener(this);

The activity should then implement the listener and the onClick method required by the listener interface:

public class MainActivity extends Activity
implements View.OnClickListener {
  @Override
  public void onClick(View view) {
    // Action when the button is pressed
  }

If we implement it using a private anonymous class, the code would be the following:

bAccept.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        // Action when the button is pressed
    }
});

Summary

In this article, you saw different styles, screen sizes, and screen resolutions. You also learned about the different available UI themes. Finally, you learned about events and learned how to handle them using listeners.

Resources for Article:


Further resources on this subject:


You've been reading an excerpt of:

Android Studio 2 Essentials - Second Edition

Explore Title