This practical codelab is part of Unit 4: User Interaction in the DC3040/CS3040 module.
For the complete list of codelabs in the course, see Mobile Development Codelabs.
The user interface (UI) that appears on a screen of an Android-powered device consists of a hierarchy of objects called views. Every element of the screen is a View.
The View
class represents the basic building block for all UI components. View is the base class for classes that provide interactive UI components, such as Button elements. A Button
is a UI element the user can tap or click to perform an action.
You can turn any View
, such as an ImageView, into a UI element that can be tapped or clicked. You must store the image for the ImageView in the drawables folder of your project.
In this practical, you learn how to use images as elements that the user can tap or click.
You should be able to:
drawable
folder.ImageView
elements in the layout editor.onClick()
method to display a Toast
message.onClick()
handlers for the images to display different Toast
messages.Fragment
.In this practical, you create and build a new app starting with the Basic Activity template that imitates a dessert-ordering app. The user can tap an image to perform an action—in this case display a Toast
message—as shown in the figure below. The user can also tap a shopping-cart button to proceed to the next Activity
.
You can make a view clickable, as a button, by adding the android:onClick
attribute in the XML layout. For example, you can make an image act like a button by adding android:onClick
to the ImageView.
In this task you create a prototype of an app for ordering desserts from a café. After starting a new project based on the Basic Activity template, you modify the "Hello World" TextView
with appropriate text, and add images that the user can tap.
activity_main.xml
for the app bar and floating action button (which you don't change in this task), and content_main.xml
which contains a ConstraintLayout
containing a NavHostFragment
(see the JetPack Navigation Codelab). The layout called fragment_first.xml contains the layour for FirstFragment and fragment_second.xml contains the layout for SecondFragment.fragment_first.xml
and click the Design tab (if it is not already selected) to show the layout editor.TextView
showing "Hello first fragment"
in the layout and open the Attributes pane.Attribute | Value |
|
|
|
|
| Select |
|
|
This replaces the original android:id
attribute for the TextView
with the new id
value textintro
, changes the text, makes the text bold, and sets a larger text size of 24sp
. All references to the previous id
will have been updated so at this point but the app would NOT run because there is an onClick
listener added to the Button
in FirstFragment
. We will fix this later.
textintroTextView
to the top of the "Next" Button
and als delete that Button
., so that the TextView
snaps to the top of the layout, and choose 8 (8dp
) for the top margin as shown below."Droid Desserts"
string in the TextView
and enter intro_text
as the string resource name.Three images (donut_circle.png
, froyo_circle.png
, and icecream_circle.png
) are provided for this example, which you can download from Blackboard. As an alternative, you can substitute your own images as PNG files, but they must be sized at about 113 x 113 pixels to use in this example.
This step also introduces a new technique in the layout editor: using the Fix button in warning messages to extract string resources.
fragment_first.xml
file, and click the Design tab (if it is not already selected).Attribute | Value |
|
|
|
|
ImageView
to the layout, choose the icecream_circle
image for it, and constrain it to the bottom of the first ImageView
and to the left side of the layout with a margin of 24 (24dp
) for both constraints.Attribute | Value |
|
|
|
|
ImageView
to the layout, choose the froyo_circle
image for it, and constrain it to the bottom of the second ImageView
and to the left side of the layout with a margin of 24 (24dp
) for both constraints.Attribute | Value |
|
|
|
|
String | Enter the following name: |
|
|
|
|
|
|
The layout of fragment_first.xml
should now look like the figure below.
In this step you add a text description (TextView
) for each dessert. Because you have already extracted string resources for the contentDescription fields for the ImageView
elements, you can use the same string resources for each description TextView
.
TextView
element to the layout.ImageView
and its top to the top of the donut ImageView
, both with a margin of 24 (24dp
).24dp
). Enter donut_description for the ID field in the Attributes pane. The new TextView
should appear next to the donut image as shown in the figure below.text
field by prefacing it with the @ symbol: @d. Click the string resource name (@string/donuts) which appears as a suggestion:TextView
that is constrained to the right side and top of the ice_creamImageView
, and its right side to the right side of the layout. Enter the following in the Attributes pane:Attribute field | Enter the following: |
|
|
|
|
|
|
|
|
TextView
that is constrained to the right side and top of the froyoImageView
, and its right side to the right side of the layout. Enter the following in the Attributes pane:Attribute field | Enter the following: |
|
|
|
|
|
|
|
|
The XML layout for the content.xml file is shown below.
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".FirstFragment">
<TextView
android:id="@+id/textintro"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="@string/intro_text"
android:textSize="24sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/donut"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginTop="24dp"
android:contentDescription="@string/donuts"
android:src="@drawable/donut_circle"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textintro" />
<ImageView
android:id="@+id/ice_cream"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginTop="24dp"
android:contentDescription="@string/ice_cream_sandwiches"
android:src="@drawable/icecream_circle"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/donut" />
<ImageView
android:id="@+id/froyo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginTop="24dp"
android:contentDescription="@string/froyo"
android:src="@drawable/froyo_circle"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/ice_cream" />
<TextView
android:id="@+id/textView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginTop="24dp"
android:layout_marginEnd="24dp"
android:text="@string/donuts"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/donut"
app:layout_constraintTop_toTopOf="@+id/donut" />
<TextView
android:id="@+id/textView2"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginTop="24dp"
android:layout_marginEnd="24dp"
android:text="@string/ice_cream_sandwiches"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/ice_cream"
app:layout_constraintTop_toTopOf="@+id/ice_cream" />
<TextView
android:id="@+id/textView3"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginTop="24dp"
android:layout_marginEnd="24dp"
android:text="@string/froyo"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/froyo"
app:layout_constraintTop_toTopOf="@+id/froyo" />
</androidx.constraintlayout.widget.ConstraintLayout>
To make a View
clickable so that users can tap (or click) it, add the android:onClick attribute in the XML layout and specify the click handler. For example, you can make an ImageView act like a simple Button
by adding android:onClick
to the ImageView
. In this task you make the images in your layout clickable.
In this task you add each method for the android:onClick
attribute to call when each image is clicked. In this task, these methods simply display a Toast
message showing which image was tapped. (In another chapter you modify these methods to display another Fragment.)
strings.xml
file. Expand res > values in the Project > Android pane, and open strings.xml. Add the following string resources for the strings to be shown in the Toast
message:<string name="donut_order_message">You ordered a donut.</string>
<string name="ice_cream_order_message">You ordered an ice cream sandwich.</string>
<string name="froyo_order_message">You ordered a FroYo.</string>
displayToast()
method to the end of MainActivity (before the closing bracket):public void displayToast(String message) {
Toast.makeText(getApplicationContext(), message,
Toast.LENGTH_SHORT).show();
}
Although you could have added this method in any position within MainActivity, it is best practice to put your own methods below the methods already provided in MainActivity by the template.Each clickable image needs a click handler-a method for the android:onClick
attribute to call. The click handler, if called from the android:onClick
attribute, must be public, return void, and define a View
as its only parameter. Follow these steps to add the click handlers:
showDonutOrder()
method to MainActivity. For this task, use the previously created displayToast()
method to display a Toast
message:/**
* Shows a message that the donut image was clicked.
*/
public void showDonutOrder(View view) {
displayToast(getString(R.string.donut_order_message));
}
The first three lines are a comment in the Javadoc format, which makes the code easier to understand and also helps generate documentation for your code. It is a best practice to add such a comment to every new method you create. For more information about how to write comments, see How to Write Doc Comments for the Javadoc Tool./**
* Shows a message that the ice cream sandwich image was clicked.
*/
public void showIceCreamOrder(View view) {
displayToast(getString(R.string.ice_cream_order_message));
}
/**
* Shows a message that the froyo image was clicked.
*/
public void showFroyoOrder(View view) {
displayToast(getString(R.string.froyo_order_message));
}
FirstFragment.java
. In the onViewCreated
method, there is a reference to R.id.button_first
. We deleted that button earlier when we changed the layout in fragment_first.xml
. Look for the following code (lines 23 to 27) and delete it.view.findViewById(R.id.button_first).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
NavHostFragment.findNavController(FirstFragment.this)
.navigate(R.id.action_FirstFragment_to_SecondFragment);
}
});
In this step you add android:onClick
to each of the ImageView
elements in the fragment_first.xml
layout. The android:onClick
attribute calls the click handler for each element.
android:onClick
attribute to donutImageView
. As you enter it, suggestions appear showing the click handlers. Select the showDonutOrder
click handler as shown below:<ImageView
android:id="@+id/donut"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginTop="24dp"
android:contentDescription="@string/donuts"
android:src="@drawable/donut_circle"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textintro"
android:onClick="showDonutOrder"/>
The last line (android:onClick="showDonutOrder"
) assigns the click handler (showDonutOrder
) to the ImageView
.
fragment_first.xml
to conform to standards and make it easier to read. Android Studio automatically moves the android:onClick
attribute up a few lines to combine them with the other attributes that have android:
as the preface.android:onClick
attribute to the ice_cream
and froyoImageView
elements. Select the showIceCreamOrder
and showFroyoOrder
click handlers. You can optionally choose Code > Reformat Code to reformat the XML code. The code should now look as follows:<ImageView
android:id="@+id/froyo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginTop="24dp"
android:contentDescription="@string/froyo"
android:onClick="showFroyoOrder"
android:src="@drawable/froyo_circle"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/ice_cream" />
<TextView
android:id="@+id/textView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginTop="24dp"
android:layout_marginEnd="24dp"
android:text="@string/donuts"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/donut"
app:layout_constraintTop_toTopOf="@+id/donut" />
Note that the attribute android:layout_marginStart in each ImageView is set to 24dp, which means we have that margin size repeated throughout the file. This attribute determines the "start" margin for the ImageView, which is on the left side for most languages but on the right side for languages that read right-to-left (RTL).
<dimen name="margin_wide">24dp</dimen>
24dp
values for the various android:layout_
attributes and replace them with the reference to the new dimension that you created above. When you type the @
, the editor should pop up some suggestions and you should select @dimen/margin_wide
as shown below:Clicking the donut, ice cream sandwich, or froyo image displays a Toast
message about the order, as shown in the figure below.
The solution code for this task can be found on Blackboard as solution
1 for DroidCafe.
If you implement your button onClick handlers by adding an onClick attribute to the Button tag in the XML, your Activity class must implement the handler method. This can get unmanageable if you have many screens and all of the click handler methods are in the Activity rather than the Fragment whose layout contains the button. It is much more cohesive to have each Fragment class implement its own onClick handlers.
We will now modify the first solution and move the onClick
handlers for the clickable images into the FirstFragment
class whose layout contains the clickable images.
displayToast
to the FirstFragment
class:private void displayToast(String message) {
Toast.makeText(getActivity().getApplicationContext(), message,
Toast.LENGTH_SHORT).show();
}
The difference to the similar method you created earlier in the MainActivity
class is that the Fragment
subclass does not have access to the getApplicationContext
method, so we must call getActivity
first.onViewCreated
method in FirstFragment
and add the following code to the end of the method:ImageView donutImage = view.findViewById(R.id.donut);
donutImage.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
displayToast("Fragment Donut Button Handler");
}
});
In this code, we use the findViewById
method to find the Java object representing the donut button in the layout. Then we add a new OnClickListener
to the button programmatically which requires us to implement the onClick
method. For now, we just provide a Toast
method to indictate that the Button
was clicked.ImageView
code with the id
value @+id/donut.
android:onClick
attribute and its value from the donutImageView
.android:onClic
k attributes should be deleted from fragment_first.xml
.Toast
strings from MainActivity
to your new onClick
methods in FirstFragment
and delete the methods showFroyoOrder
, showDonutOrder
and showIceCreamOrder
.ImageView donutImage = view.findViewById(R.id.donut);
donutImage.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
displayToast("You ordered a Donut");
}
});
ImageView iceCreamImage = view.findViewById(R.id.ice_cream);
iceCreamImage.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
displayToast("You ordered an Ice Cream Sandwich");
}
});
ImageView froyoImage = view.findViewById(R.id.froyo);
froyoImage.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
displayToast("You ordered a Frozen Yogurt");
}
});
When you click the floating action button with the email icon that appears at the bottom of the screen, the code in MainActivity
displays a brief message in a drawer that opens from the bottom of the screen on a smartphone, or from the lower left corner on larger devices, and then closes after a few seconds. This is called a snackbar. It is used to provide feedback about an operation. For more information, see Snackbar.
Look at how other apps implement the floating action button. For example, the Gmail app provides a floating action button to create a new email message, and the Contacts app provides one to create a new contact. For more information about floating action buttons, see FloatingActionButton.
For this task you change the icon for the FloatingActionButton
to a shopping cart and change the action for the FloatingActionButton
to launch a new Activity. , Shopping cart icon for the floating action button, and change the action for the
FloatingActionButton
to launch a new Fragment
.
As you learned in another lesson, you can choose an icon from the set of icons in Android Studio. Follow these steps:
As you learned in a previous lesson, an Fragment represents a screen layout in your app in which can be loaded into view. You already have two fragments, FirstFragment.java and Secondragment.java. Now you add another Fragment called OrderFragment.java.
new destination icon
and in the dialog window click Create new destination.public class OrderFragment extends Fragment {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_order, container, false);
}
}
We will now delete SecondFragment.java
and its layout fragment_second.xml
from the project as we will not be using it.
FirstFragment
and SecondFragment
and then right click and deleteSecondFragment
from the graph too.fragment_second.xml
and choose Refactor > Safe Delete.... Click OK in the Safe Delete dialog and then click Delete Anyway in the Usages Detected dialog. The usages are in SecondFragment.java
which we are also about to delete. If you aren't sure where the file you are delting is referenced you can click View Usages to check.SecondFragment.java
and choose Refactor > Safe Delete.... Click OK in the Safe Delete dialog. There should not be any further references to this file.In this step you change the action for the FloatingActionButton
to display the new Fragment.
FirstFragment
to control the use of the FloatingActionButton
. Therefore we will copy the code the finds the FloatingActionbutton
and sets its OnClickListener
from MainActivity and paste that same code into the end of the onViewCreated
method in FirstFragment
.getActivity
before findViewById
:FloatingActionButton fab = getActivity().findViewById(R.id.fab);
FloatingActionButton
in MainActivity
.FloatingActionButton
should still pop up the snack bar message, only now it is controlled from inside the FirstFragment
.Now we will implement the action for the FloatingActionButton
as controlled by the Firstfragment
to navigate to the OrderFragment
.
TextView
and set its id
to order_text and its text
to This is the Order Fragment.FirstFragment
to OrderFragment
. Click the Code tab and you will see that the following action has been added:<action
android:id="@+id/action_FirstFragment_to_orderFragment"
app:destination="@id/orderFragment" />
onClick
for the FloatingActionButton
which will implement the navigation action from FirstFragment
to OrderFragment
:NavHostFragment.findNavController(FirstFragment.this)
.navigate(R.id.action_FirstFragment_to_orderFragment, null);
FloatingActionButton
takes you to the OrderFragment
and the back button takes you back to the Firstfragment
.We will now change the Icon for the FloatingActionButton
(FAB) to be the shopping cart icon that we added to the drawable folder earlier. The initial icon for the FAB is set in activity_main.xml
. If you open that file you will see the following code to define the FAB:
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="@dimen/fab_margin"
app:srcCompat="@android:drawable/ic_dialog_email" />
The last line contains a link to the drawable resource for the email icon. As our FirstFragment
now has control of the FAB, we will make that fragment change the icon.
fab
:fab.setImageResource(R.drawable.ic_shopping_cart);
OrderFragment
.The solution code for this task is included in the code and layout for Android Studio project DroidCafe.
Challenge: The DroidCafe app's FirstFragment
links to a second Fragment called OrderFragment
. You learned in another lesson (Codelab 2.4: Jetpack navigation - step 8 Safe Args) how to send data from a Fragment to another Fragment. Change the app to send the order message for the selected dessert in FirstFragment
to a new TextView
at the top of the OrderFragment
layout.
MainActivity
private AppBarConfiguration appBarConfiguration;
NavHostFragment host = (NavHostFragment) getSupportFragmentManager().findFragmentById(R.id.nav_host_fragment);
NavController navController = host.getNavController();
appBarConfiguration = new AppBarConfiguration.Builder(navController.getGraph()).build();
NavigationUI.setupActionBarWithNavController(this, navController, appBarConfiguration);
@Override
public boolean onSupportNavigateUp() {
return NavigationUI.navigateUp(Navigation.findNavController(this, R.id.nav_host_fragment), appBarConfiguration)
|| super.onSupportNavigateUp();
}
dependencies {
classpath 'com.android.tools.build:gradle:4.0.2'
def nav_version = "2.3.0"
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version"
}
apply plugin: 'com.android.application'
apply plugin: "androidx.navigation.safeargs"
Java (Generated)
folder with two sub-folders, one containing the FirstFragmentDirections
mOrderMessage
) in FirstFragment
for the order message that appears in the Toast
.mOrderMessage
to be an empty String
in the onCreateView
of FirstFragment
.showDonutOrder()
, showIceCreamOrder(),
and showFroyoOrder()
click handlers to assign the message string mOrderMessage
before displaying the Toast. For example, the following assigns the donut_order_message
string to mOrderMessage and displays the Toast:mOrderMessage = getString(R.string.donut_order_message);
displayToast(mOrderMessage);
OrderFragment
and then click the + symbol next to Arguments in the Attributes panelorder_text
, the Type to be String
, and the Default Value to be No selection made
.FirstFragment
to OrderFragment
and change its id to place_order.java (Generated)
contains a new class called OrderFragmentArgs
.onClick()
method of the FAB in FirstFragment
to set the parameter value before navigating to OrderFragment
:private String receivedOrderMessage;
receivedOrderMessage
:View v = inflater.inflate(R.layout.fragment_order, container, false);
TextView textView = v.findViewById(R.id.order_text);
textView.setText(receivedOrderMessage);
return v;
The challenge solution code can be found on Blackboard.
ImageView
to use it by dragging an ImageView
to the layout and choosing the image for it.android:onClick
attribute to make an ImageView
clickable like a button. Specify the name of the click handler and in the MainActivity, create a click handler in the Activity to perform the action.Fragment
's onViewCreated
method to use view.findViewById
to return the ImageView
object and then use the setOnclickListener
to add a listener for the click events.Fragment
destination: In res > navigation > nav_graph.xml, click the Add Destination iconToast
message from a Fragment
:Toast.makeText(getActivity().getApplicationContext(), message,
Toast.LENGTH_SHORT).show();
The related concept documentation is in 4.1: Buttons and clickable images.