By the end of this chapter, the missing link between Java and our XML layouts will be fully revealed, leaving us with the power to add all kinds of widgets to our apps as we have done before. However, this time, we will be able to control them through our Java code.
In this chapter, we will get to take control of some fairly simple UI elements, such as Button and TextView, and, in the next chapter, we will take things further and manipulate a whole range of UI elements.
To enable us to understand what is happening, we need to find out a bit more about the memory in an Android device and two areas of it – the Stack and the Heap.
In this chapter, we will learn about the following topics:
Back to that news flash.
When our app is run and the setContentView
method is called from onCreate
, the layout is inflated from XML UI classes and loaded into memory as usable objects. They are stored in a part of the DVM's memory, called the Heap.
But where are all these UI objects/classes? We certainly can't see them in our code. And how on earth do we get our hands on them?
The DVM inside every Android device takes care of memory allocation to our apps. In addition, it stores different types of variables in different places.
Variables that we declare and initialize in methods are stored on an area of memory known as the Stack. We can stick to our existing warehouse analogy when talking about the Stack – almost. We already know how we can manipulate variables on the Stack with straightforward expressions. So, let's talk about the Heap and what is stored there.
Think of the Heap as a separate area of the same warehouse. The Heap has lots of floor space for odd shaped objects, racks for smaller objects, lots of long rows with smaller sized cubby holes, and so on. This is where objects are stored. The problem is that we have no direct access to the Heap. Think of it as a restricted access part of the warehouse. You can't actually go there, but you can refer to what is stored there. Let's look at what a reference variable actually is.
It is a variable that we refer to and use via a reference. A reference can be loosely, but usefully, defined as an address or location. The reference (address or location) of the object is on the Stack.
So, when we use the dot operator, we are asking Dalvik to perform a task at a specific location – a location that is stored in the reference.
Why oh why would we ever want a system like this? Just give me my objects on the Stack already. Here is why.
This is what the whole Stack and Heap thing does for us.
As we know, the DVM keeps track of all our objects for us and stores them in a devoted area of our warehouse called the Heap. Regularly, while our app is running, the DVM will scan the Stack, the regular racks of our warehouse, and match up references to objects that are on the Heap, and any objects it finds without a matching reference, it destroys – or, in Java terminology, it garbage collects.
Think of a very discerning refuse vehicle driving through the middle of our Heap, scanning objects to match up to references (on the Stack). No reference means it is garbage now.
If an object has no reference variable, we can't possibly do anything with it anyway because we have no way to access it/refer to it. This system of garbage collection helps our apps run more efficiently by freeing up unused memory.
If this task was left to us, our apps would be much more complicated to code.
So, variables declared in a method are local, on the Stack, and only visible within the method where they were declared. A member variable (in an object) is on the Heap and can be referenced from anywhere where there is a reference to it and the access specification (encapsulation) allows.
Let's take a quick look at what we learned about Stack and Heap:
Let's move on and see exactly what this information buys us in terms of taking control of our UI.
Any UI element that has its id
attribute set in the XML layout can have its reference retrieved from the Heap using the findViewById
method, which is part of the Activity/AppCompatActivity
class. As it is part of the class that we extend in all our apps, we have access to it, just like the following code shows:
myButton = (Button) findViewById(R.id.myButton);
The preceding code assumes that myButton
has been declared previously to an appropriate type, in this case, Button
. The code also assumes that within the XML layout is a Button
with an id
attribute set to myButton
.
Notice that findViewById
is also polymorphic. We know this because we use a cast, (Button)
, to be explicit about making the returned object a Button
from its View
parent type, just like we did with our object of type Elephant
with the feed
method in the last chapter.
This is quite exciting because it implies we can grab a reference to a whole bunch of stuff from our layout. We can then start using all the methods that these objects have. Here are some examples of the methods we can use for Button
objects:
myButton.setText myButton.setHeight myButton.setOnCLickListener myButton.setVisibility
If you think that after eleven chapters, we are finally going to start doing some neat stuff with Android, you would be right!
To follow along with this project, create a new Android Studio project, call it Java Meet UI
, choose the Empty Activity template, and leave all the other options at their defaults. As usual, you can find the Java code and the XML layout code in the Chapter 12/Java Meet UI
folder.
First, let's build a simple UI by observing the following steps:
activity_main.xml
and make sure that you are on the Design tab.TextView
, the one that reads "Hello world!".0
, its id
property to txtValue
, and its textSize
to 40sp
. Pay careful attention to the case of the id
. It has an uppercase V
.text
and id
properties, as shown in the following table:
The |
The |
---|---|
add |
|
take |
|
grow |
|
shrink |
|
hide |
|
reset |
|
When you're done, your layout should look like the following diagram:
The precise position and text on the buttons is not very important, but the values given to the id
properties must be the same. The reason for this is that we will be using these ids to get a reference to the buttons and the TextView
in this layout from our Java code.
Switch to the MainActivity.java tab in the editor and we will write the code.
Amend the following line:
public class MainActivity extends AppCompatActivity{
To the following:
public class MainActivity extends AppCompatActivity implements View.OnClickListener{
Notice that the entire line we just amended is underlined in red, showing an error. Now, because we have made MainActivity
into an OnClickListener
by adding it as an interface, we must implement the abstract method of OnClickListener
. The method is called onClick
. When we add the method, the error will be gone.
We can get Android Studio to add it for us by left-clicking anywhere on the line containing an error and then using the keyboard combination Alt + Enter. Left-click Implement methods, as shown in the following screenshot:
Now, left-click OK to confirm that we want Android Studio to add the onClick
method. The error is gone, and we can carry on adding code. We also have an onClick
method and we will soon see what we will do with that.
Now, we will declare an int
variable called value
and initialize it to 0
. We will also declare six Button
objects and a TextView
object. We will give them the exact same names we gave the id
property values in our UI layout. This name association is not mandatory, but it is useful to keep track of which Button
in our Java code will be holding a reference to which Button
from our UI.
Furthermore, we are declaring them all with the private
access specification because we know they will not be needed outside of this class.
Before you go ahead and type the code, note that all these variables are members of the MainActivity
class. This means that we enter all the code shown next, immediately after the class declaration that we amended in the previous step.
Making all these variables into members/fields means that they have class scope and that we can access them from anywhere within the MainActivity
class. This will be essential for this project because we will need to use them all in onCreate
and in our new onClick
method.
Enter the following code that we have just discussed after the opening curly brace {
of the MainActivity
class and before the onCreate
method:
// An int variable to hold a value private int value = 0; // A bunch of Buttons and a TextView private Button btnAdd; private Button btnTake; private TextView txtValue; private Button btnGrow; private Button btnShrink; private Button btnReset; private Button btnHide;
Next, we want to prepare all our variables ready for action. The best place for this to happen is in the onCreate
method because we know that will be called by Android just before the app is shown to the user. This code uses the findViewById
method to associate each of our Java objects with an item from our UI.
It does so by returning a reference to the object associated with the UI widget on the Heap. It "knows" which one we are after because we use the proper id
as an argument. For example ...(R.id.btnAdd)
will return the Button
with the text ADD that we created in our layout.
Enter the following code, just after the call to setContentView
in the onCreate
method:
// Get a reference to all the buttons in our UI // Match them up to all our Button objects we declared earlier btnAdd = (Button) findViewById(R.id.btnAdd); btnTake = (Button) findViewById(R.id.btnTake); txtValue = (TextView) findViewById(R.id.txtValue); btnGrow = (Button) findViewById(R.id.btnGrow); btnShrink = (Button) findViewById(R.id.btnShrink); btnReset = (Button) findViewById(R.id.btnReset); btnHide = (Button) findViewById(R.id.btnHide);
Now that we have a reference to all our Button
objects and our TextView,
we can start using their methods. In the code that follows, we use the setOnClickListener
method on each of the Button
references to make Android pass any clicks from the user onto our onClick
method.
This works because when we implemented the View.OnClickListener
interface, our MainActivity
class effectively became an OnClickListener
.
So, all we have to do is call setOnClickListener
on each button in turn. As a reminder, the this
argument is a reference to MainActivity
. So, the method call says, "hey Android, I want an OnClickListener
and I want it to be the MainActivity
class."
Android now knows on which class to call onClick
. The following code wouldn't work if we hadn't implemented the interface first. Also, we must set up these listeners before the Activity starts, which is why we do it in onCreate
.
We will add code to onClick
to actually handle what happens soon.
Add the following code after the previous code, inside the onCreate
method:
// Listen for all the button clicks btnAdd.setOnClickListener(this); btnTake.setOnClickListener(this); txtValue.setOnClickListener(this); btnGrow.setOnClickListener(this); btnShrink.setOnClickListener(this); btnReset.setOnClickListener(this); btnHide.setOnClickListener(this);
Now, scroll down to the onClick
method that Android Studio added for us after we implemented the OnClickListener
interface. Add the float size;
variable declaration and an empty switch block inside it so that it looks like the following code snippet. The new code to add is highlighted here:
public void onClick(View view) // A local variable to use later float size; switch(view.getId()){ } }
Remember that switch
will check for a case
to match the condition inside the switch
statement.
In the previous code, the switch
condition is view.getId()
. Let's step through and explain this. The view variable is a reference to an object of the View
type that was passed into the onClick
method by Android:
public void onClick(View view)
View
is the parent class for Button
, TextView,
and more. So, perhaps, as we might expect, calling v.getId()
will return the id
attribute of the UI widget that has been clicked and triggered the call to onClick
in the first place.
All we need to do then is provide a case
statement (and appropriate action) for each of the Button
references we want to respond to.
The following code we will see is the first three case
statements. They handle R.id.btnAdd
, R.id.btnTake,
and R.id.btnReset
.
The code in the R.id.btnAdd
case simply increments the value
variable, and then it does something new.
It calls the setText
method on the txtValue
object. Here is the argument –(""+ value)
. This argument uses an empty string and adds (concatenates) whatever value is stored in value
to it. This has the effect of causing our TextView txtValue
to display whatever value is stored in value
.
The TAKE button (R.id.btnTake
) does exactly the same, only it subtracts one from value
instead of adding one.
The third case statement handles the RESET button and sets value
to zero, again updating the text
attribute of txtValue
.
Then, at the end of each case
, there is a break
statement. At this point, the switch
block is exited, the onClick
method returns, and life goes back to normal, until the user's next click.
Enter the following code that we have just discussed inside the switch
block after the opening curly brace {
:
case R.id.btnAdd: value ++; txtValue.setText(""+ value); break; case R.id.btnTake: value--; txtValue.setText(""+ value); break; case R.id.btnReset: value = 0; txtValue.setText(""+ value); break;
The next two case
statements handle the SHRINK and GROW buttons from our UI. We can confirm this from the id's R.id.btnGrow
and R.id.btnShrink
methods. What is new and more interesting are the two new methods that are used.
The getTextScaleX
method returns the horizontal scale of the text within the object it is used on. We can see that the object it is used on is our TextView txtValue
. The size =
at the start of the line of code assigns that returned value to our float
variable size
.
The next line of code in each case
statement changes the horizontal scale of the text using setTextScaleX
. When the GROW button is pressed, the scale is set to size + 1,
and when the SHRINK button is pressed, the scale is set to size - 1
.
The overall effect is to allow these two buttons to grow and shrink the text in txtValue
by a scale of 1
on each click.
Enter the following two case
statements that we have just discussed, below the previous code:
case R.id.btnGrow: size = txtValue.getTextScaleX(); txtValue.setTextScaleX(size + 1); break; case R.id.btnShrink: size = txtValue.getTextScaleX(); txtValue.setTextScaleX(size - 1); break;
In our final case statement that we will code next, we have an if
-else
block. The condition takes a little bit of explaining, so here is advance sight of it:
if(txtValue.getVisibility() == View.VISIBLE)
The condition to be evaluated is txtValue.getVisibility() == View.VISIBLE
. The first part of that before the ==
operator returns the visibility
attribute of our txtValue TextView
. The return value will be one of three possible constant values as defined in the View
class. They are View.VISIBLE
, View.INVISIBLE,
and View.GONE
.
If the TextView
is visible to the user on the UI, the method returns View.VISIBLE
, the condition is evaluated as true,
and the if
block is executed.
Inside the if
block, we use the setVisibility
method on txtValue
and make it invisible to the user with the View.INVISIBLE
argument.
In addition to this, we change the text on the btnHide
to SHOW using the setText
method.
After the if
block has executed, txtValue
is invisible, and we have a button on our UI that says SHOW. When the user clicks on it in this state, the if
statement will be false and the else
block will execute. In the else
block, we reverse the situation. We set txtValue
back to View.VISIBLE
and the text
property on btnHide
back to HIDE.
If this is in any way unclear, just enter the code, run the app, and revisit this final piece of code and explanation once you have seen it in action:
case R.id.btnHide: if(txtValue.getVisibility() == View.VISIBLE) { // Currently visible so hide it txtValue.setVisibility(View.INVISIBLE); // Change text on the button btnHide.setText("SHOW"); }else{ // Currently hidden so show it txtValue.setVisibility(View.VISIBLE); // Change text on the button btnHide.setText("HIDE"); } break;
We have the UI and the code in place, so it is time to run the app and try out all the buttons. Notice that the ADD and TAKE buttons change the value of value
by one in either direction and then display the result in the TextView
. In the following diagram, I have clicked the ADD button three times:
Notice that the SHRINK and GROW buttons increase the width of the text, and RESET sets the value
variable to 0 and displays it on the TextView
. In the following diagram, I have clicked the GROW button eight times:
Finally, the HIDE button not only hides the TextView,
but changes its own text to SHOW and will indeed re-show the TextView
if tapped again.
Notice that there was no need for Log
or Toast
in this app as we are finally manipulating the UI by using our Java code.
Before we go ahead to the next chapter and build apps with loads of different widgets that will put into practice and reinforce everything we have learned in this chapter, we will have a very brief introduction to Anonymous and Inner classes.
When we implemented our Basic classes demo app in Chapter 10, Object-Oriented programming, we declared and implemented the class in a separate file to our MainActivity
class. That file had to have the same name as the class. This is the way to create a regular class.
We can also declare and implement classes within a class. The only question remaining, of course, is why would we do this?
When we implement an inner class, the inner class can access the member variables of the enclosing class and the enclosing class can access the members of the inner class.
This often makes the structure of our code more straightforward. Therefore, inner classes are sometimes the way to go.
In addition, we can also declare and implement an entire class within a method of one of our classes. When we do so, we use a slightly different syntax and do not use a name with the class. This is an anonymous class.
We will see both inner and anonymous classes in action throughout the rest of this book and we will thoroughly discuss them when we use them.
18.119.138.123