1. Welcome
Introduction
In this chapter you learn how to apply common styles to your views, use drawable resources, and apply themes to your app. These practices reduce your code and make your code easier to read and maintain.
What you should already know
You should be able to:
- Create and run apps in Android Studio.
- Create and edit UI elements using the layout editor.
- Edit XML layout code, and access elements from your Java code.
- Extract hardcoded strings into string resources.
- Extract hardcoded dimensions into dimension resources.
- Add menu items and icons to the options menu in the app bar.
- Create a click handler for a
Button
click. - Display a
Toast
message. - Pass data from one
Activity
to another using anIntent
.
What you'll learn
- How to define a style resource.
- How to apply a style to a
View
. - How to apply a theme to an
Activity
or app in XML and programmatically. - How to use
Drawable
resources.
What you'll do
- Create a new app and add
Button
andTextView
elements to the layout. - Create
Drawable
resources in XML and use them as backgrounds for yourButton
elements. - Apply styles to UI elements.
- Add a menu item that changes the theme of the app to a low contrast "night mode."
2. App overview
The Scorekeeper app consists of two sets of Button
elements and two TextView
elements, which are used to keep track of the score for any point-based game with two players.
3. Task 1: Create the Scorekeeper app
In this section, you create your Android Studio project, modify the layout, and add the android:onClick
attribute to its Button
elements.
1.1 Create the project
- Start Android Studio and create a new Android Studio project with the name Scorekeeper.
- Accept the default minimum SDK and choose the Empty Activity template.
- Accept the default
Activity
name (MainActivity
), and make sure the Generate Layout file and Backwards Compatibility (AppCompat) options are checked. - Click Finish.
1.2 Create the layout for MainActivity
The first step is to change the layout from ConstraintLayout
to LinearLayout
:
- Open activity_main.xml and click the Text tab to see the XML code. At the top, or root, of the
View
hierarchy is theConstraintLayout
ViewGroup
:
android.support.constraint.ConstraintLayout
- Change this
ViewGroup
toLinearLayout
. The second line of code now looks something like this:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- Delete the following line of XML code, which is related to
ConstraintLayout
:
xmlns:app="http://schemas.android.com/apk/res-auto"
The block of XML code at the top should now look like this:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.example.android.scorekeeper.MainActivity">
- Add the following attributes (without removing the existing attributes):
Attribute | Value |
|
|
|
|
1.3 Create the score containers
The layout for this app includes score containers defined by two RelativeLayout
view group elements—one for each team. The following diagram shows the layout in its simplest form.
- Inside the
LinearLayout
, add twoRelativeLayout
elements with the following attributes:
RelativeLayout attribute | Value |
|
|
|
|
|
|
You may be surprised to see that the layout_height
attribute is set to 0dp. This is because we are using the layout_weight
attribute to determine how much space these RelativeLayout
elements take up in the parent LinearLayout
.
- Add two
ImageButton
elements to eachRelativeLayout
: one for decreasing the score, and one for increasing the score. Use these attributes:
ImageButton attribute | Value |
|
|
|
|
|
|
|
|
|
|
|
|
Use increaseTeam1
as the android:id
for the second ImageButton
that increases the score. Use decreaseTeam2
and increaseTeam2
for the third and fourth ImageButton
elements. Use alignParentRight
and alignParentEnd
instead of alignParentLeft
and alignParentStart
for the second and fourth ImageButton
elements.
- Add a
TextView
to eachRelativeLayout
between theImageButton
elements for displaying the score. Use these attributes:
TextView attribute | Value |
|
|
|
|
|
|
|
|
|
|
|
|
Use score_2
as the android:id
for the second TextView
between the ImageButton
elements.
- Add another
TextView
to eachRelativeLayout
above the score to represent the Team Names. Use these attributes:
TextView attribute | Value |
|
|
|
|
|
|
|
|
|
|
Use "Team 2"
as the android:text
for the second TextView
.
1.4 Add vector assets
You will use material icons in the Vector Asset Studio for the scoring ImageButton
elements.
- Select File > New > Vector Asset to open the Vector Asset Studio.
- Click the icon to open a list of material icon files. Select the Content category.
- Choose the add icon and click OK.
- Rename the resource file ic_plus and check the Override checkbox next to size options.
- Change the size of the icon to 40dp x 40dp.
- Click Next and then Finish.
- Repeat this process to add the remove icon and name the file ic_minus.
You can now add the icons and content descriptions for the ImageButton
elements. The content description provides a useful and descriptive label that explains the meaning and purpose of the ImageButton
to users who may require Android accessibility features such as the Google TalkBack screen reader.
- Add the following attributes to the
ImageButton
elements on the left side of the layout:
android:src="@drawable/ic_minus"
android:contentDescription="Minus Button"
- Add the following attributes to the
ImageButton
elements on the right side of the layout:
android:src="@drawable/ic_plus"
android:contentDescription="Plus Button"
- Extract all of your string resources. This process removes all of your strings from the Java code and puts them in the
strings.xml
file. This allows for your app to be easily localized into different languages.
Solution code for the layout
The following shows the layout for the Scorekeeper app, and the XML code for the layout.
XML code:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp"
tools:context="com.example.android.scorekeeper.MainActivity">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:text="@string/team_1" />
<ImageButton
android:id="@+id/decreaseTeam1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_centerVertical="true"
android:src="@drawable/ic_minus"
android:contentDescription=
"@string/minus_button_description" />
<TextView
android:id="@+id/score_1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:text="@string/initial_count" />
<ImageButton
android:id="@+id/increaseTeam1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:src="@drawable/ic_plus"
android:contentDescription=
"@string/plus_button_description" />
</RelativeLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:text="@string/team_2" />
<ImageButton
android:id="@+id/decreaseTeam2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_centerVertical="true"
android:src="@drawable/ic_minus"
android:contentDescription=
"@string/minus_button_description" />
<TextView
android:id="@+id/score_2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:text="@string/initial_count" />
<ImageButton
android:id="@+id/increaseTeam2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:src="@drawable/ic_plus"
android:contentDescription=
"@string/plus_button_description" />
</RelativeLayout>
</LinearLayout>
1.5 Initialize the TextView elements and score count variables
To keep track of the scores, your app needs two things:
- Integer variables to keep track of the scores.
- A reference to each score
TextView
element inMainActivity
so that you can update the scores.
Follow these steps:
- Create two integer member variables representing the score of each team.
// Member variables for holding the score.
private int mScore1;
private int mScore2;
- Create two
TextView
member variables to hold references to theTextView
elements.
// Member variables for holding the score.
private TextView mScoreText1;
private TextView mScoreText2;
- In the
onCreate()
method ofMainActivity
, find your scoreTextView
elements by id and assign them to the member variables.
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Find the TextViews by ID.
mScoreText1 = (TextView)findViewById(R.id.score_1);
mScoreText2 = (TextView)findViewById(R.id.score_2);
}
1.6 Implement the click handlers for the ImageButton elements
You will add the android:onClick
attribute to each ImageButton
, and create two click handler methods in MainActivity
. The left ImageButton
elements should decrement the score TextView
, while the right ones should increment it.
- Open activity_main.xml if it is not already open, and add the following
android:onClick
attribute to the firstImageButton
on the left side of the layout:
android:onClick="decreaseScore"
- The
decreaseScore
method name is underlined in red. Click the red bulb icon in the left margin, or click the method name and press Option-Return, and choose Create ‘decreaseScore(view)' in ‘MainActivity'. Android Studio creates thedecreaseScore()
method stub inMainActivity
. - Add the above
android:onClick
attribute to the secondImageButton
on the left side of the layout. This time the method name is valid, because the stub has already been created. - Add the following
android:onClick
attribute to eachImageButton
on the right side of the layout:
android:onClick="increaseScore"
- The
increaseScore
method name is underlined in red. Click the red bulb icon in the left margin, or click the method name and press Option-Return, and choose Create ‘increaseScore(view)' in ‘MainActivity'. Android Studio creates theincreaseScore()
method stub inMainActivity
. - Add code to the stub methods to decrease and increase the score as shown below. Both methods use
view.getID()
to get the ID of the team'sImageButton
that was clicked, so that your code can update the proper team.
Solution code for increaseScore() and decreaseScore()
/**
* Method that handles the onClick of both the decrement buttons
* @param view The button view that was clicked
*/
public void decreaseScore(View view) {
// Get the ID of the button that was clicked.
int viewID = view.getId();
switch (viewID) {
// If it was on Team 1
case R.id.decreaseTeam1:
//Decrement the score and update the TextView
mScore1--;
mScoreText1.setText(String.valueOf(mScore1));
break;
// If it was Team 2
case R.id.decreaseTeam2:
// Decrement the score and update the TextView
mScore2--;
mScoreText2.setText(String.valueOf(mScore2));
}
}
/**
* Method that handles the onClick of both the increment buttons
* @param view The button view that was clicked
*/
public void increaseScore(View view) {
// Get the ID of the button that was clicked
int viewID = view.getId();
switch (viewID) {
// If it was on Team 1
case R.id.increaseTeam1:
// Increment the score and update the TextView
mScore1++;
mScoreText1.setText(String.valueOf(mScore1));
break;
// If it was Team 2
case R.id.increaseTeam2:
// Increment the score and update the TextView
mScore2++;
mScoreText2.setText(String.valueOf(mScore2));
}
}
4. Task 2: Create a drawable resource
You now have a functioning Scorekeeper app! However, the layout is dull and does not communicate the function of the ImageButton
elements. In order to make it clearer, the standard grey background of the buttons can be changed.
In Android, graphics are often handled by a resource called a Drawable
. In the following step you learn how to create a certain type of Drawable
called a ShapeDrawable
, and apply it to your ImageButton
elements as a background.
For more information on Drawables
, see Drawable resources.
2.1 Create a ShapeDrawable
A ShapeDrawable
is a primitive geometric shape defined in an XML file by a number of attributes including color, shape, padding and more. It defines a vector graphic, which can scale up and down without losing any definition.
- In the Project > Android pane, right-click (or Control-click) on the drawable folder in the res directory.
- Choose New > Drawable resource file.
- Name the file button_background and click OK. (Don't change the Source set or Directory name, and don't add qualifiers). Android Studio creates the file
button_background.xml
in thedrawable
folder. - Open button_background.xml, click the Text tab to edit the XML code, and remove all of the code except:
<?xml version="1.0" encoding="utf-8"?>
- Add the following code which creates an oval shape with an outline:
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<stroke
android:width="2dp"
android:color="@color/colorPrimary"/>
</shape>
2.2 Apply the ShapeDrawable as a background
- Open activity_main.xml.
- Add the
Drawable
as the background for all fourImageButton
elements:
android:background="@drawable/button_background"
- Click the Preview tab in the layout editor to see that the background automatically scales to fit the size of the
ImageButton
. - To render each
ImageButton
properly on all devices, you change theandroid:layout_height
andandroid:layout_width
attributes for eachImageButton
to70dp
, which is a good size on most devices. Change the first attribute for the firstImageButton
as follows:
android:layout_width="70dp"
- Click once on
"70dp"
, press Alt-Enter in Windows or Option-Enter in macOS, and choose Extract dimension resource from the popup menu. - Enter button_size for the Resource name.
- Click OK. This creates a dimension resource in the
dimens.xml
file (in thevalues
folder), and the dimension in your code is replaced with a reference to the resource:
@dimen/button_size
- Change the
android:layout_height
andandroid:layout_width
attributes for eachImageButton
element using the new dimension resource:
android:layout_width="@dimen/button_size"
android:layout_height="@dimen/button_size"
- Click the Preview tab in the layout editor to see the layout. The
ShapeDrawable
is now used for theImageButton
elements.
5. Task 3: Style your View elements
As you continue to add View
elements and attributes to your layout, your code will start to become large and repetitive, especially when you apply the same attributes to many similar elements. A style can specify common properties such as padding, font color, font size, and background color. Attributes that are layout-oriented such as height, width and relative location should remain in the layout resource file.
In the following step, you learn how to create styles and apply them to multiple View
elements and layouts, allowing common attributes to be updated simultaneously from one location.
3.1 Create Button styles
In Android, styles can inherit properties from other styles. You can declare a parent for your style using an optional parent
parameter and has the following properties:
- Any style attributes defined by the parent style are automatically included in the child style.
- A style attribute defined in both the parent and child style uses the child style's definition (the child overrides the parent).
- A child style can include additional attributes that the parent does not define.
For example, all four ImageButton
elements in the Scorekeeper app share a common background Drawable
but with different icons for plus (increase the score) and minus (decrease the score). Furthermore, the two plus ImageButton
elements share the same icon, as do the two minus ImageButton
elements. You can therefore create three styles:
- A style for all of the
ImageButton
elements, which includes the default properties of anImageButton
and also theDrawable
background. - A style for the minus
ImageButton
elements. This style inherits the attributes of theImageButton
style and includes the minus icon. - A style for the plus
ImageButton
elements. This style inherits from theImageButton
style and includes the plus icon.
These styles are represented in the figure below.
Do the following:
- Expand res > values in the Project > Android pane, and open the styles.xml file.
This is where all of your style code will be located. The "AppTheme"
style is always automatically added, and you can see that it extends from Theme.AppCompat.Light.DarkActionBar
.
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
Note the parent
attribute, which is how you specify your parent style using XML.
The name
attribute, in this case AppTheme
, defines the name of the style. The parent attribute, in this case Theme.AppCompat.Light.DarkActionBar
, declares the parent style attributes that AppTheme
inherits. In this case it is the Android default theme, with a light background and a dark action bar.
A theme is a style that's applied to an entire Activity
or app instead of a single View
. Using a theme creates a consistent style throughout an entire Activity
or app—for example, a consistent look and feel for the app bar in every part of your app.
- In between the
<resources>
tags, add a new style with the following attributes to create a common style for all buttons:
<style name="ScoreButtons" parent="Widget.AppCompat.Button">
<item name="android:background">@drawable/button_background</item>
</style>
The above snippet sets the parent style to Widget.AppCompat.Button
to retain the default attributes of a Button
. It also adds an attribute that changes the background of the Drawable
to the one you created in the previous task.
- Create the style for the plus buttons by extending the
ScoreButtons
style:
<style name="PlusButtons" parent="ScoreButtons">
<item name="android:src">@drawable/ic_plus</item>
<item name=
"android:contentDescription">@string/plus_button_description</item>
</style>
The contentDescription
attribute is for visually impaired users. It acts as a label that certain accessibility devices use to read out loud to provide some context about the meaning of the UI element.
- Create the style for the minus buttons:
<style name="MinusButtons" parent="ScoreButtons">
<item name="android:src">@drawable/ic_minus</item>
<item name=
"android:contentDescription">@string/minus_button_description</item>
</style>
- You can now use these styles to replace specific style attributes of the
ImageButton
elements. Open the activity_main.xml layout file forMainActivity
, and replace the following attributes for both of the minusImageButton
elements:
Delete these attributes | Add this attribute | |
|
| |
| ||
|
- Replace the following attributes for both of the plus
ImageButton
elements:
Delete these attributes | Add this attribute | |
|
| |
| ||
|
3.2 Create TextView styles
The team name and score display TextView
elements can also be styled since they have common colors and fonts. Do the following:
- Add the following attribute to all
TextView
elements:
android:textAppearance="@style/TextAppearance.AppCompat.Headline"
- Right-click (or Control-click) anywhere in the first score
TextView
attributes and choose Refactor > Extract > Style... - Name the style ScoreText and check the textAppearance box (the attribute you just added) as well as the Launch ‘Use Styles Where Possible' refactoring after the style is extracted checkbox. This will scan the layout file for views with the same attributes and apply the style for you. Do not extract the attributes that are related to the layout—click the other checkboxes to turn them off.
- Choose OK.
- Make sure the scope is set to the activity_main.xml layout file and click OK.
- A pane at the bottom of Android Studio will open if the same style is found in other views. Select Do Refactor to apply the new style to the views with the same attributes.
- Run your app. There should be no change except that all of your styling code is now in your resources file and your layout file is shorter.
Layout and style solution code
The following are code snippets for the layout and styles.
styles.xml
:
<resources>
<!-- Base application theme. -->
<style name="AppTheme"
parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>
<style name="ScoreButtons" parent="AppTheme">
<item
name="android:background">@drawable/button_background</item>
</style>
<style name="PlusButtons" parent="ScoreButtons">
<item name="android:src">@drawable/ic_plus</item>
<item name=
"android:contentDescription">@string/plus_button_description</item>
</style>
<style name="MinusButtons" parent="ScoreButtons">
<item name="android:src">@drawable/ic_minus</item>
<item name=
"android:contentDescription">@string/minus_button_description</item>
</style>
<style name="ScoreText">
<item name= "android:textAppearance">@style/TextAppearance.AppCompat.Headline</item>
</style>
</resources>
activity_main.xml
:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp"
tools:context="com.example.android.scorekeeper.MainActivity">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:text="@string/team_1"
style="@style/ScoreText" />
<ImageButton
android:id="@+id/decreaseTeam1"
android:layout_width="@dimen/button_size"
android:layout_height="@dimen/button_size"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_centerVertical="true"
style="@style/MinusButtons"
android:onClick="decreaseScore"/>
<TextView
android:id="@+id/score_1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:text="@string/initial_count"
style="@style/ScoreText" />
<ImageButton
android:id="@+id/increaseTeam1"
android:layout_width="@dimen/button_size"
android:layout_height="@dimen/button_size"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
style="@style/PlusButtons"
android:onClick="increaseScore"/>
</RelativeLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:text="@string/team_2"
style="@style/ScoreText" />
<ImageButton
android:id="@+id/decreaseTeam2"
android:layout_width="@dimen/button_size"
android:layout_height="@dimen/button_size"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_centerVertical="true"
style="@style/MinusButtons"
android:onClick="decreaseScore"/>
<TextView
android:id="@+id/score_2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:text="@string/initial_count"
style="@style/ScoreText" />
<ImageButton
android:id="@+id/increaseTeam2"
android:layout_width="@dimen/button_size"
android:layout_height="@dimen/button_size"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
style="@style/PlusButtons"
android:onClick="increaseScore"/>
</RelativeLayout>
</LinearLayout>
3.3 Updating the styles
The power of using styles becomes apparent when you want to make changes to several elements of the same style. You can make the text bigger, bolder and brighter, and change the icons to the color of the button backgrounds.
- Open the styles.xml file, and add or modify the following attributes for the styles:
Style | Item |
|
|
|
|
The colorPrimary
value is automatically generated by Android Studio when you create the project. You can find it in the Project > Android pane in the colors.xml
file inside the values
folder. The TextAppearance.AppCompat.Display3
attribute is a predefined text style supplied by Android.
- Create a new style called TeamText with the
textAppearance
attribute set as follows:
<style name="TeamText">
<item name=
"android:textAppearance">@style/TextAppearance.AppCompat.Display1
</item>
</style>
- In activity_main.xml, change the
style
attribute of theTextView
team name elements to the newly createdTeamText
style.
style="@style/TeamText"
- Run your app. With only these adjustments to the
style.xml
file, all of the views updated to reflect the changes.
6. Task 4: Themes and final touches
You've seen that View
elements with similar characteristics can be styled together in the styles.xml
file. But what if you want to define styles for an entire activity, or the entire app? It's possible to accomplish this by using themes. To set a theme for each activity, you need to modify the AndroidManifest.xml
file.
In this task, you add the "night mode" theme to your app, which will allow the users to use a low contrast version of your app that is easier on the eyes at night time, as well as make a few polishing touches to the UI.
4.1 Explore themes
- Open the AndroidManifest.xml file, find the
<application>
tag, and change theandroid:theme
attribute to:
android:theme="@style/Theme.AppCompat.Light.NoActionBar"
This is a predefined theme that removes the action bar from your activity.
- Run your app. The toolbar disappears!
- Change the theme of the app back to
AppTheme
, which is a child of theTheme.Appcompat.Light.DarkActionBar
theme as can be seen instyles.xml
.
To apply a theme to an activity instead of the entire application, place the theme attribute in the <Activity>
tag instead of the <application>
tag. For more information on themes and styles, see the Style and Theme Guide.
4.2 Add theme button to the menu
One use for setting a theme for your app is to provide an alternate visual experience for browsing at night. In such conditions, it is often better to have a low contrast, dark layout. The Android framework provides a theme that is designed exactly for this: The DayNight
theme.
This theme has built-in options that allow you to control the colors in your app programmatically: either by setting it to change automatically by time, or by user command.
In this step you add an options menu item that will toggle the app between the regular theme and a "night-mode" theme.
- Open strings.xml and create two string resources for this options menu item:
<string name="night_mode">Night Mode</string>
<string name="day_mode">Day Mode</string>
- Right-click (or Control-click) on the res directory in the Project > Android pane, and choose New > Android resource file.
- Name the file main_menu, change the Resource Type to Menu, and click OK. The layout editor appears for the
main_menu.xml
file. - Click the Text tab of the layout editor to show the XML code.
- Add a menu item with the following attributes:
<item
android:id="@+id/night_mode"
android:title="@string/night_mode"/>
- Open MainActivity, press Ctrl-O to open the Override Method menu, and select the onCreateOptionsMenu(menu:Menu):boolean method located under the android.app.Activity category.
- Click OK. Android Studio creates the
onCreateOptionsMenu()
method stub withreturn super.onCreateOptionsMenu(menu)
as its only statement. - In
onCreateOptionsMenu()
right before thereturn.super
statement, add code to inflate the menu:
getMenuInflater().inflate(R.menu.main_menu, menu);
4.3 Change the theme from the menu
The DayNight
theme uses the AppCompatDelegate
class to set the night mode options in your Activity
. To learn more about this theme, visit this blog post.
- In your styles.xml file, modify the parent of
AppTheme
to "Theme.AppCompat.DayNight.DarkActionBar". - Override the
onOptionsItemSelected()
method inMainActivity
, and add code to check which menu item was clicked:
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Check if the correct item was clicked
if (item.getItemId() == R.id.night_mode) {
// TODO: Get the night mode state of the app.
}
return true;
}
- Replace the
TODO:
comment in the above snippet with code that checks if the night mode is enabled. If enabled, the code changes night mode to the disabled state; otherwise, the code enables night mode:
if (item.getItemId() == R.id.night_mode) {
// Get the night mode state of the app.
int nightMode = AppCompatDelegate.getDefaultNightMode();
//Set the theme mode for the restarted activity.
if (nightMode == AppCompatDelegate.MODE_NIGHT_YES) {
AppCompatDelegate.setDefaultNightMode
(AppCompatDelegate.MODE_NIGHT_NO);
} else {
AppCompatDelegate.setDefaultNightMode
(AppCompatDelegate.MODE_NIGHT_YES);
}
// Recreate the activity for the theme change to take effect.
recreate();
}
In response to a click on the menu item, the code verifies the current night mode setting by calling AppCompatDelegate.getDefaultNightMode()
.
The theme can only change while the activity is being created, so the code calls recreate()
for the theme change to take effect.
- Run your app. The Night Mode menu item should now toggle the theme of your
Activity
. - You may notice that the label for your menu item always reads Night Mode, which may be confusing to your user if the app is already in the dark theme.
- Replace the
return super.onCreateOptionsMenu(menu)
statement in theonCreateOptionsMenu()
method with the following code:
// Change the label of the menu based on the state of the app.
int nightMode = AppCompatDelegate.getDefaultNightMode();
if (nightMode == AppCompatDelegate.MODE_NIGHT_YES) {
menu.findItem(R.id.night_mode).setTitle(R.string.day_mode);
} else {
menu.findItem(R.id.night_mode).setTitle(R.string.night_mode);
}
return true;
- Run your app. The menu item label Night Mode, after the user taps it, now changes to Day Mode (along with the theme).
4.4 SaveInstanceState
You learned in previous lessons that you must be prepared for your Activity
to be destroyed and recreated at unexpected times, for example when your screen is rotated. In this app, the TextView
elements containing the scores are reset to the initial value of 0 when the device is rotated. To fix this, do the following:
- Open
MainActivity
and add tags under the member variables, which will be used as the keys inonSaveInstanceState()
:
static final String STATE_SCORE_1 = "Team 1 Score";
static final String STATE_SCORE_2 = "Team 2 Score";
- At the end of
MainActivity
, override theonSaveInstanceState()
method to preserve the values of the two scoreTextView
elements:
@Override
protected void onSaveInstanceState(Bundle outState) {
// Save the scores.
outState.putInt(STATE_SCORE_1, mScore1);
outState.putInt(STATE_SCORE_2, mScore2);
super.onSaveInstanceState(outState);
}
- At the end of the
onCreate()
method, add code to check if there is asavedInstanceState
. If there is, restore the scores to theTextView
elements:
if (savedInstanceState != null) {
mScore1 = savedInstanceState.getInt(STATE_SCORE_1);
mScore2 = savedInstanceState.getInt(STATE_SCORE_2);
// Set the score text views.
mScoreText1.setText(String.valueOf(mScore1));
mScoreText2.setText(String.valueOf(mScore2));
}
- Run the app, and tap the plus
ImageButton
to increase the score. Switch the device or emulator to horizontal orientation to see that the score is retained.
That's it! Congratulations, you now have a styled Scorekeeper app that continues to work if the user changes the device to horizontal or vertical orientation.
7. Solution code
Android Studio project: Scorekeeper
8. Coding challenge
Challenge: Right now, your buttons do not behave intuitively because they do not change their appearance when they are pressed. Android has another type of Drawable
called StateListDrawable
which allows for a different graphic to be used depending on the state of the object.
For this challenge problem, create a Drawable
resource that changes the background of the ImageButton
to the same color as the border when the state of the ImageButton
is "pressed". You should also set the color of the text inside the ImageButton
elements to a selector that makes it white when the button is "pressed".
9. Summary
Drawable
elements enhance the look of an app's UI.- A
ShapeDrawable
is a primitive geometric shape defined in an XML file. The attributes that define aShapeDrawable
include color, shape, padding, and more. - The Android platform supplies a large collection of styles and themes.
- Using styles can reduce the amount of code needed for your UI components.
- A
style
can specify common properties such as height, padding, font color, font size, and background color. - A
style
should not include layout-related information. - A
style
can be applied to aView
,Activity
, or the entire app. Astyle
applied to anActivity
or the entire app must be defined in theAndroidManifest.xml
file. - To inherit a style, a new style identifies a
parent
attribute in the XML. - When you apply a
style
to a collection ofView
elements in an activity or in your entire app, that is known as a theme. - To apply a theme, you use the
android:theme
attribute.
10. Related concepts
The related concept documentation is in 5.1: Drawables, styles, and themes.
11. Learn more
Android Studio documentation:
- Android Studio User Guide
- Add Multi-Density Vector Graphics
- Create App Icons with Image Asset Studio
Android developer documentation:
- Best Practices for User Interface
- Linear Layout
- Drawable Resources
- Styles and Themes
- Buttons
- Layouts
- Support Library
- Screen Compatibility Overview
- Support Different Screen Sizes
- Animate Drawable Graphics
- Loading Large Bitmaps Efficiently
- R.style class of styles and themes
- support.v7.appcompat.R.style class of styles and themes
Material Design:
Android Developers Blog: Android Design Support Library
Other:
12. Homework
This section lists possible homework assignments for students who are working through this codelab as part of a course led by an instructor. It's up to the instructor to do the following:
- Assign homework if required.
- Communicate to students how to submit homework assignments.
- Grade the homework assignments.
Instructors can use these suggestions as little or as much as they want, and should feel free to assign any other homework they feel is appropriate.
If you're working through this codelab on your own, feel free to use these homework assignments to test your knowledge.
Build and run an app
Create an app that displays an ImageView
and plus and minus buttons, as shown below. The ImageView
contains a level list drawable that is a battery level indicator. Tapping the plus or minus button changes the level of the indicator.
Use the battery icons from the Vector Asset Studio to represent 7 different values for the battery level.
The app should have the following properties:
- The plus button increments the level, causing the battery indicator to appear more full.
- The minus button decrements the level, causing the indicator to empty one level.
Answer these questions
Question 1
Which type of Drawable
do you use to create a Button
with a background that stretches properly to accommodate the text or image inside the Button
so that it looks correct for different screen sizes and orientations? Choose one:
LevelListDrawable
TransitionDrawable
StateListDrawable
NinePatchDrawable
Question 2
Which type of Drawable
do you use to create a Button
that shows one background when it is pressed and a different background when it is hovered over? Choose one:
LevelListDrawable
TransitionDrawable
StateListDrawable
NinePatchDrawable
Question 3
Suppose you want to create an app that has a white background, dark text, and a dark action bar. Which base style does your application style inherit from? Choose one:
Theme.AppCompat.Light
Theme.AppCompat.Dark.NoActionBar
Theme.AppCompat.Light.DarkActionBar
Theme.AppCompat.NoActionBar
Theme.NoActionBar
Submit your app for grading
Guidance for graders
Check that the app has the following features:
- The buttons increment a count variable. The count variable sets the level on the
ImageView
, using thesetImageLevel()
method. - The levels in the
LevelListDrawable
go from 0 to 6. - Before incrementing or decrementing the image level, the
onClick()
methods check to see whether the count variable is within the range of theLevelListDrawable
(0 to 6). This way, the user can't set a level that doesn't exist.
13. Next codelab
To find the next practical codelab in the Android Developer Fundamentals (V2) course, see Codelabs for Android Developer Fundamentals (V2).
For an overview of the course, including links to the concept chapters, apps, and slides, see Android Developer Fundamentals (Version 2).