Tải bản đầy đủ (.pdf) (425 trang)

Expert android [GOOD]

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (5.94 MB, 425 trang )

www.it-ebooks.info
For your convenience Apress has placed some of the front
matter material after the index. Please use the Bookmarks
and Contents at a Glance links to access them.
www.it-ebooks.info

v
Contents at a Glance
About the Authors �������������������������������������������������������������������������������������������������������������� xix
About the Technical Reviewer ������������������������������������������������������������������������������������������� xxi
Acknowledgments ����������������������������������������������������������������������������������������������������������� xxiii
Introduction ���������������������������������������������������������������������������������������������������������������������� xxv
Chapter 1: Exploring Custom Views ■ ����������������������������������������������������������������������������������1
Chapter 2: Exploring Compound Controls ■ ������������������������������������������������������������������������33
Chapter 3: Principles and Practice of Custom Layouts ■ ���������������������������������������������������61
Chapter 4: JSON for On-Device Persistence ■ ��������������������������������������������������������������������81
Chapter 5: Programming for Multiple Devices ■ ����������������������������������������������������������������99
Chapter 6: Advanced Form Processing ■ �������������������������������������������������������������������������115
Chapter 7: Using the Telephony APIs ■ �����������������������������������������������������������������������������135
Chapter 8: Advanced Debugging and Analysis ■ ��������������������������������������������������������������153
Chapter 9: Programming 3D Graphics with OpenGL ■ ������������������������������������������������������175
Chapter 10: Introduction to Android Search ■ ������������������������������������������������������������������257
Chapter 11: Simple Search Suggestion Provider ■ �����������������������������������������������������������279
Chapter 12: Custom Search Suggestion Provider ■ ����������������������������������������������������������297
www.it-ebooks.info

vi Contents at a Glance
Chapter 13: Introduction to Cloud Storage with Parse ■ ��������������������������������������������������329
Chapter 14: Enhancing Parse with Parcelables ■ �������������������������������������������������������������353
Chapter 15: Exploring Push Notifications with Parse ■ ����������������������������������������������������381
Index ���������������������������������������������������������������������������������������������������������������������������������397


www.it-ebooks.info

xxv
Introduction
As a programmer, designer, or architect, you may be lulled into thinking that the Android API is
merely what you use to write mobile programs for the Android mobile platform—which, of course,
is true. However, we believe that the Android architecture has an undercurrent that makes it a key
pillar in the cloud-based Google computing era that is beckoning all of us! When you learn deeply
about the Android API, you are gaining a pass to the future of Google, and perhaps to the future of
all of us.
This book, Expert Android, is our fifth book on Android in the last four years. In the first four books,
published under the Pro Android name, we covered increasingly new material on the core Android API.
Expert Android is the outcome of our deepest desire and commitment to bring you the essentials for
writing compelling and impactful Android applications at a faster pace.
In Expert Android, you will find more difficult topics that are not covered anywhere else. You will
discover ways that help you extend Android and companion topics that will enhance your Android
mobile applications. You also will find information that is applicable for any release of Android.
Is This Book for You?
As authors, the first question we want to answer is whether this book is for you. Yes, this book is
for you if you are transitioning from learning about Android and writing stock applications to writing
applications that are impactful. Yes, it’s for you if you also want to release those applications to the
market quickly.
A key focus of Expert Android is to write components that extend Android, especially UI
components. This is important, for two reasons. First, you can write reusable components that are
specific to your suite of applications or problem space. Second, there are increasingly reusable
open-source components that you can borrow along with their source codes. Often, or even only
occasionally, you will need to tweak these components to meet your needs. You will then need to
understand how the source codes of these custom components work. This book will guide you
through the details of these customized components. The first three chapters on customizing views,
and the fourth chapter on OpenGL, serve this Android UI customization goal.

www.it-ebooks.info

xxvi Introduction
There is an advantage in the mobile space if you can release applications quickly into the
marketplace, a topic that we address in Expert Android. The chapter on JSON shows you a really
cool and quick way to use persistence, which is so essential for all mobile applications.
Additionally, many mobile applications are form based. The chapter on advanced form processing
makes writing form-based applications really easy. And the three chapters on Parse will further
expedite your writing of collaborative mobile applications in record time.
Yes, this book is for you if you want to push the mobile programming practice to the next level,
using the best tools and approaches available.
What You Need to Know Before You Begin
Expert Android assumes that you are familiar with Java and Android. The basis for most of Android
programming is Java. However, if you know any high-level object-oriented programming language,
you should be able to pick up Android programming fairly quickly. Having experience with Eclipse
or IntelliJ would be quite helpful. This book further assumes that you know the basics of Android
and that you have written a few simple applications. There are a number of books to get you to
this stage, including our Pro Android series from APress. In short, we assume you will have worked
with Java, Eclipse or IntelliJ, and Android for a year or two. With that said, here’s a brief,
quick overview of what is in Expert Android, chapter by chapter.
What’s in This Book
We start Expert Android by documenting in depth how you can customize Android UI by customizing
the views, controls, and layouts. You will see over 100 pages of this material spread over the first
three chapters.
In Chapter 4, we provide a practical way to persist the application state with JSON. This allows you
to write small to medium mobile applications really quickly, as it makes persistence super-simple.
Just quickly browse through this chapter if you are skeptical.
In Chapter 5, we address an essential question of how to write a mobile application that works well
on multiple mobile form factors.
Continuing the theme of practical guidance for mobile applications, in Chapter 6 we present an

advanced form-processing framework to write form-based mobile applications using really simple
principles.
A mobile device is a phone too, which we tend to forget. Chapter 7 covers the telephony API of
Android.
With the memory and power consumption of mobile devices always at a premium, you want your
applications to run as efficiently as possible. In Chapter 8, we cover the debugging approaches and
tools available for ensuring this is the case.
OpenGL has a come a long way on Android, now with substantial support for the new generation of
programmable GPUs. Android has been supporting ES 2.0 for sometime. In Chapter 9, we have over
100 pages covering OpenGL. With this chapter on OpenGL, we start at the begining and explain
all the concepts without needing to refer to external books, although we do provide an extensive
www.it-ebooks.info

xxviiIntroduction
bibliography on OpenGL. We cover ES 2.0, and we provide guidance to combine OpenGL and
regular views to pave the way for 3D components.
Federated search protocol of Android is powerful, as you can use it in quite a few imaginative ways.
The search experience is also shifting and pivoting with each release of Android so as to reach its
full potential. Chapters 10, 11, and 12 fully explore the fundamentals of the search protocol and also
offer some alternative ways to optimally use this Android facility.
And if our intuitions are correct, mobile applications will increasingly be collaborative, so they will
need to store data in the cloud and also collaborate among users. Chapters 13, 14, and 15 present
Parse-related material. In short, we have taken a successful cloud platform called Parse, and have
engaged it for user management, cloud storage, and push notifications. With Parse now being part
of Facebook, this coverage of Parse is a valuable addition to our book, for two reasons: its synergy
with Facebook, and how easy it is to take collaborative applications to the marketplace. Mobile in
the cloud is the future. We are proud to have taken a good first step toward exploring this potential in
Expert Android.
How to Prepare for Expert Android
Although we have used the latest Android release (4.2) to write and test Expert Android, the contents

of this book are fairly independent of any Android release. Most, if not all, sample programs and
code should work even in future releases. Expecially, the concepts and approaches presented here
should be valid across all Android releases.
To heighten the readability of these chapters, among other improvements we have reduced the
typical pages and pages of source code. Instead, the source code for each chapter is available
both on Apress.com and at our supporting site, androidbook.com. You will be able to download each
chapter’s source code and load it into Eclipse directly. If you are using IntelliJ or another editor, you
can unzip each chapter and build the code by importing the projects manually into your favorite IDE.
Furthermore, we have broken some of the bigger topics into more manageable shorter chapters.
For example, we have the discussion of custom views spread out in three chapters. Coverage of
Parse.com is spread across three chapters as well. We’ve done the same to explain Android Search.
Although most chapters are self-contained in terms of their examples, you may occasionally need to
refer to the earlier chapters on that topic.
If you are programming using any of the topics that we have covered in any of our books, including
Expert Android, remember that our websites androidbook.com and satyakomatineni.com have
dedicated knowledge folders for each topic. These knowledge folders document various items in
each topic. For example, you will see in this book the Android API links you will need as you develop
code in that context. In short, we use these sites often to grab code snippets and also quickly get to
the Android API links.
We have written Expert Android in such a way that we expect you will read through it like a novel,
chapter by chapter, and grasp an idea before implementing it. You can then come back to the book
for clarification or additional reference when you start implementing these ideas.
www.it-ebooks.info

xxviii Introduction
How to Reach Us
We can be reached readily via our respective e-mail addresses: Satya Komatineni at
, and Dave MacLean at Also, keep this URL in
your bookmarks: Here you will find links to source code,
links to downloadable projects, key feedback from readers, full contact information, future

notifications, errata, news on our future projects, a reading guide, additional resources—even some
future alpha chapters and perhaps more.
www.it-ebooks.info
1

Chapter 1
Exploring Custom Views
Your understanding of Android SDK is not vigorous until you master the architecture of Android’s
Views. So it is appropriate that we begin Expert Android by exploring the power of Android’s custom
views. Our goal in this and the next two chapters is to unwrap the architecture of Android’s Views by
customizing them. In Android you can customize views in three ways:
Custom views (by extending the  View class)
Compound views/controls (by composing other existing controls through 
extending one of the existing Layout classes) (Note that in this and the next few
chapters we are using custom views and custom components synonymously)
Custom layouts (by extending the  ViewGroup class)
We have learned a lot in researching each of these topics. We are eager to share with you this
information on custom components, presented in this and the next two chapters. We believe custom
components hold the key to unlocking the full potential of the Android SDK.
We start this chapter by covering the custom views. This chapter also forms the basis for the next
two chapters: Compound views/controls and Custom layouts.
To demonstrate custom views, in this chapter we:
Create a custom view called  CircleView and explain the theory and mechanics
of customizing a View.
Present the entire source code of  CircleView in order to guide you to write your
own custom views.
Show how to embed the  CircleView in any of Android layouts.
Show how the  CircleView responds to touch events by changing the size of the circle.
(Note that we are using “click” and “touch” synonymously in much of the book!)
Show how the  CircleView remembers state (such as the size of the circle) as

you rotate the device.
Show how to use custom attributes in layout files to initialize the  CircleView.
www.it-ebooks.info

2 CHAPTER 1: Exploring Custom Views
Planning a Custom View
Before we explain the implementation of a custom view like the CircleView, let us show you the
expected look, feel, and behavior of the CircleView. This, we believe, will make it easier for you to
follow the subsequent explanation and code.
Let’s begin by examining the CircleView in Figure 1-1. In Figure 1-1, the CircleView is between
two text views in a linear layout. The width of the view is set to match_parent. The height of the
CircleView is set to wrap_content.
Figure 1-1. Custom CircleView with wrap_content
Figure 1-2. Custom CircleView expanded with clicks
When we design this CircleView, we make the circle stroke color and width configurable in the
layout file using custom attributes. To test responding to events, we use click events to expand the
circle and redraw. Figure 1-2 shows what the CircleView would look like after a couple of clicks.
Each click expands the circle by 20 percent.
We then implement state management to the CircleView so that when we flip the device to
landscape, the view retains its magnification. Figure 1-3 shows the rotated device with CircleView
maintaining its expansion.
www.it-ebooks.info

3CHAPTER 1: Exploring Custom Views
Let’s get started and cover all the essential things (there are a lot of them) about custom views so
that you can design and code the CircleView that is shown in Figures 1-1, 1-2, and 1-3.
Nature of Drawing in Android
To understand how to draw in Android, you have to understand the architecture of the following classes:

View

ViewParent (interface)
ViewGroup (extends View and implements ViewParent)
ViewRoot (implements ViewParent)

View is the fundamental class that all of the visible components in Android are derived from.
It defines a number of callbacks to customize its behavior, like the ability to define size, draw, and
save state.
A ViewParent defines the protocol for any object (including another view) that wants to play the
role of a parent to other views. There are two important view parents. Of those, ViewGroup is the
key one. In addition to being a ViewParent, a ViewGroup also defines the protocol for a collection of
child views. All layouts like the FrameLayout and LinearLayout in the Android SDK extend this class
ViewGroup. ViewGroup plays a central role in defining these layouts in XML files and in placing the
controls (views) at the right place. A ViewGroup also controls the background and animation of its
child views.
The other key ViewParent, the ViewRoot is implementation centric and is not a public API. In some
releases it is called ViewRoot, and in some implementations it is called ViewRootImplementation—and
it may even be changed in the future to something else. However, this class is important for
understanding how drawing is done in Android.
We advise you to keep tabs on the source code of these three classes (View, ViewGroup,
ViewParent) to refer back to, should you have questions that were not answered anywhere else.
For instance, if you want to look up the source code for View.java, Google that name and you will
Figure 1-3. Custom CircleView retaining state after rotation
www.it-ebooks.info
4 CHAPTER 1: Exploring Custom Views
see a number of places on the Web that has this source code. The source code may not match the
latest release, but for understanding what this class does, it is sufficient. I tend to download the
latest android.jar source code and keep it in eclipse, then quickly locate a file in the source using
CTRL-SHIFT-R (R stands for “resource”).
Being a root parent of all views in the activity, the ViewRoot schedules traversals of all the views in
order to first lay them out at the right place with the right size; this is called the layout phase. The

ViewRoot then traverses the view hierarchy to draw them; this phase is called the drawing phase.
We will talk about each of these phases now.
Layout Phase: Measurement and Layout
The goal of the layout phase is to know the position and size of each view in the view hierarchy
owned by a parent such as the ViewRoot. To calculate the position and size of each view, the
ViewRoot initiates a layout phase. However, in the layout phase, the view root does a traversal of only
those views that reported or requested a layout change. This conditional measurement is to save
resources and improve response time.
The trigger to initiate the layout phase may come from multiple events. One trigger may be the very
first time everything is being drawn. Or one of the views, while reacting to an event like a click or
touch, could report that its size has changed. In such an event, the view that got clicked on calls the
method requestLayout( ). This call walks up the chain and gets to the root view (ViewRoot). The root
view then schedules a layout traversal message on the main thread’s queue.
The layout phase has two passes: a measure pass and a layout pass. The measure pass is
implemented by the measure( ) function of the View class.The signature of this function is
public final void measure(int widthMeasureSpec, int heightMeasureSpec)
Make a note of this method’s signature. This signature will help you to easily locate this method
measure( ) in the source code of the large View.java source file. This method, measure( ), does some
housekeeping and calls the onMeasure( ) of the derived views. The derived views need to set their
dimensions by calling setMeasuredDimension(). These measured dimensions set on each view are
then subsequently used in the layout pass. In this context, your primary override is View.onMeasure().
Keep in mind that there is a default implementation for onMeasure(). The default implementation of
onMeasure( ) decides the size of your view based on suggestions from the layout files, including an
exact size passed in. We will cover this later in the chapter.
Although it is the onMeasure( ) that you care about when you are a creating custom view like the
CircleView, there are times when measure( ) is important as well. If the inherited custom view is a
collection of other views, as in a ViewGroup, then you need to call measure( ) on child views in your
onMeasure( ) method. The signature of measure( ) earlier clearly supports the idea that you can’t
override it by being final, but you are expected to call it by being public. We will cover the measure( )
method arguments widthMeasureSpec and heightMeasureSpec when we work with onMeasure() later

in this chapter.
After the measure pass, each view knows its dimensions. The control then passes to the layout pass.
This layout pass is implemented in the layout( ) method, whose signature in the base View class is:
public void layout(int left, int top, int right, int bottom)
www.it-ebooks.info

5CHAPTER 1: Exploring Custom Views
Again, we are giving the full signature of the layout( ) method because this signature will help
you to locate this method in the base View class source file. Much like the measure( ) method, the
layout( ) method carries out an internal protocol for the base View and result in calling the overridden
methods in Listing 1-1, in that order.
Listing 1-1. Overridden Methods Called by a View’s Layout( ) Method
protected void onSizeChanged(int w, int h, int oldw, int oldh);
protected void onLayout(boolean changed, int left, int top, int right, int bottom)

The layout pass, implemented in layout( ), will take the dimensions measured by the measure
pass into account and give out the starting position for each view and the dimension each view
needs to use. The base layout( ) method actually sets these dimensions on the view on which it is
called. It then calls the onSizeChanged( ) if there is actually a change in size or position. The default
implementation of onSizeChanged( ) exists in the View class but it is a no-op.
After calling the onSizeChanged( ) method, the layout( ) method calls the onLayout( ) to allow
for something like a view group to call layout( ) on its children. The default implementation for
onLayout( ) exists but it is a no-op. To apply this to our CircleView, we don’t need to do anything in
the onLayout( ) because our position and dimensions are already fixed, and we have no children to
advise their layouts by calling their layout( ) method.
Once both of the passes of the layout phase are completed, the traversal initiated by the view root
will move to the drawing phase.
Drawing Phase: Mechanics of onDraw
The draw traversal is implemented in the View's draw( ) method. The protocol implemented by this
method is:


Draw the background
Draw view's content by delegating to onDraw()
Draw children by delegating to dispatchDraw()
Draw decorations such as scroll bars

Because the draw traversal happens after the layout traversal, you already know the position and the
size of your views. If your view like the CircleView doesn’t have children, you don’t care much about
dispatchDraw( ). The default implementation for this method in the base View class exists but is empty.
You could ask: If my custom view has children, why am I not choosing to draw them in onDraw?
Perhaps because, in a framework, the base class View's fixed protocol of draw( ) may choose
to do something between your onDraw( ) and your children’s onDraw( ). So, it is suggested to the
programmer, by dispatchDraw( ) of the View, that the View’s drawing is complete and the derived
implementation could choose whatever is needed.
In a sense, the programmer could even treat dispatchDraw( ) as a post onDraw( ). We suggest you
examine the source code for the draw( ) method of the View class. You can use the following method
signature to search for it in the source code of the View class.

public void draw( )

www.it-ebooks.info

6 CHAPTER 1: Exploring Custom Views
Although draw( ) is a public method that you could override, you shouldn’t. It implements a protocol
and a contract defined by the base View. Your choices are to override its suggested methods:

public void onDraw( )
public void dispatchDraw( )

This idea of a dispatch… pattern is used often in Android to do things for children after you have

done them for yourself.
As the trigger for the layout phase is requestLayout(), the trigger for the draw phase is invalidate( ).
When you invalidate a view, it goes up the chain and results in scheduling of the traversal from the
view root. It is possible that if a view did not request an invalidate or if its position and size haven’t
changed then the onDraw( ) may not be called for that View.
However if size or position has changed for a view, the base view will call invalidate on that view. So
it is probably not necessary to invalidate things in your onSizeChanged( ). When you are in doubt, call
invalidate( ) after a requestlayout( ) as the performance impact is minimal because all these calls
get aggregated for a traversal at the end of current main thread cycle.
Let us recap and look at the methods available to customize a view:

onMeasure
onSizeChanged

onLayout
onDraw
dispatchDraw

All of these are callbacks. Especially interesting are onMeasure(), onLayout(), and onDraw(). All three
of them have their equivalent “protocol” or “template” methods: measure(), layout(), draw().
This pattern is often called a template/hook. For example, draw( ) is the template method that fixes
the behavior in a certain manner while relying on the hook onDraw() to specialize itself. Another way
to look at this is that a template method is like an “HTML template with substitution fillers” and a
hook or hooks are the data that goes there and get substituted to complete the whole web page.
You could even further call this pattern template/hook/children. The idea is:

measure()
onMeasure()
for(child in children) child.measure()


or

template()
hook()
for(child in children) child.hook()

So, draw( ) breaks this mold a little, but measure() and layout() follow the mold. This is a good rule
of thumb to avoid getting lost in the sea of callback names when customizing components.
www.it-ebooks.info

7CHAPTER 1: Exploring Custom Views
When you are customizing views that don’t have any children, the methods that are usually
overridden are:

onMeasure( )
onDraw( )

The method that might also be overrridden once in a while is onSizeChanged( ).
This concludes the coverage on how drawing is done in Android. To quickly summarize in order to
lead you to the next section, note that we have established the following:
There is a layout phase where a measure pass happens and we need to override 
onMeasure( ).
There is a drawing phase where we need to implement  onDraw().
Let us now show you the mechanics of implementing onMeasure( ).
Implementing Measure Pass
In the measure pass, a custom view needs to return the size that it wants (or dictated to) when it
gets painted in the subsequent draw pass. The view needs to set its dimensions in the overriden
onMeasure( ). Setting the size of a view is not straightforward. Your view size depends on how your
view is going to fit with the rest of the views. It is not as simple as saying your view is 400 pixels by
200 pixels. Android passes something called a mode bit to onMeasure( ) to give context to calculating

the size of the view.
This mode bit can be one of three: AT_MOST, UNSPECIFIED, and EXACT. For example, if the mode bit
is EXACT, your view should use the size passed in and no calculation is necessary. You will have a full
understanding of these mode bits by the end of this section.
The key responsibility of onMeasure( ) is to recognize how it is called (mode) and then calculate the
view size, if that is an option (based on mode), and then set that size using setMeasuredDimension().
Another wrinkle in onMeasure( ) is that it may be called multiple times depending on how the parent
layout is coordinating space for all its children. There is a brief protocol of negotiation that needs to
be implemented in this method. You will know this protocol as well by the end of this section.
Sometimes you may be able to use the default implementation of onMeasure( ) from the base View
class. But first we will explain what we did in this method and then go into why we didn’t use the
default implementation for the CircleView. Listing 1-2 shows how we implemented onMeasure( ).
Listing 1-2. How to Override a Views onMeasure( ) Method
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
logSpec(MeasureSpec.getMode(widthMeasureSpec));
Log.d(tag, "size:" + MeasureSpec.getSize(widthMeasureSpec));

setMeasuredDimension(getImprovedDefaultWidth(widthMeasureSpec),
getImprovedDefaultHeight(heightMeasureSpec));
}

4
www.it-ebooks.info

8 CHAPTER 1: Exploring Custom Views
Let’s elaborate on our implementation of the onMeasure( ) method in Listing 1-2, point by point. Note
that this implementation in Listing 1-2 relies on two other methods we have specialized, namely:
getImprovedDefaultWidth( ) and getImprovedDefaultHeight( ). We will cover them shortly.
Let’s start with the arguments in Listing 1-2: the width and height measure specifications. We know

that our CircleView could be part of a layout. This means a developer can specify a dimension like
height in three different ways in a layout file. Listing 1-3 provides an example of a layout file.
Listing 1-3. Providing Layout Sizes in a Layout File that Could Impact onMeasure
<com.androidbook.custom.CircleView
android:id="@+id/circle_view_id"
android:layout_width="match_parent"
android:layout_height="wrap_content"
circleViewPkg:strokeWidth="5"
circleViewPkg:strokeColor="@android:color/holo_red_dark"
/>

The argument android:layout_height can be one of:

wrap_content
match_parent
or exact width in pixels like: 30dp

In each case, the onMeasure( ) gets called differently. The widthMeasureSpec is actually two arguments
rolled into one integer. The class that encapsulates this behavior is View.MeasureSpec.
Listing 1-4 shows how you get to their individual parts.
Listing 1-4. Deciphering Through MeasureSpec
int inputMeasureSpec;
int specMode = MeasureSpec.getMode(inputMeasureSpec);
int specSize= MeasureSpec.getSize(inputMeasureSpec);

Listing 1-5 shows how you can print the various modes that a measure spec can come in.
Listing 1-5. Understanding MeasureSpec Modes
private void logSpec(int specMode)
{
if (specMode == MeasureSpec.UNSPECIFIED) {

Log.d(tag,"mode: unspecified");
return;
}
if (specMode == MeasureSpec.AT_MOST) {
Log.d(tag,"mode: at most");
return;
}
if (specMode == MeasureSpec.EXACTLY) {
Log.d(tag,"mode: exact");
return;
}
}

www.it-ebooks.info

9CHAPTER 1: Exploring Custom Views
If the layout specification says match_parent, then onMeasure( ) will be called with a specification of
EXACT. The size will be equal to the size of the parent. Then, onMeasure( ) will need to take that exact
size and set it on the same view by calling setMeasuredDimension (as shown in Listing 1-2)
If the layout specification says exact pixels, then the onMeasure( ) will be called with a specification
of EXACT. The size will be equal to the size of the specified pixels. Then onMeasure( ) will set this size
using setMeasuredDimension.
Now comes the harder mode. If you set the dimension to wrap_content, then the mode will be
AT_MOST. The size that gets passed could be much larger, taking up the rest of the space. So it might
say, “I have 411 pixels. Tell me your size that doesn’t exceed 411 pixels.” The question then to the
programmer is: What should I return?
In your circle, you can take all the size that is given to you and draw a circle big enough. But if you
do that, the rest of the views will not have any space. (We’re not sure why Android does this, but
that’s what happens.) So, you should give a “reasonable” size. In our case, we chose to return
minimum size, like a well-meaning conservative who dispatches cash.

To see how we handled each of these measuring modes, let’s return to the
getImprovedDefaultHeight( ) and getImprovedDefaultWidth( ) that were cited previously in
Listing 1-5. Listing 1-6 has the implementation of these methods showing how they handle
onMeasure( ) modes.
Listing 1-6. Implementing onMeasure( ) Properly
private int getImprovedDefaultHeight(int measureSpec) {
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);

switch (specMode) {
case MeasureSpec.UNSPECIFIED:
return hGetMaximumHeight();
case MeasureSpec.EXACTLY:
return specSize;
case MeasureSpec.AT_MOST:
return hGetMinimumHeight();
}
//you shouldn't come here
Log.e(tag,"unknown specmode");
return specSize;
}

private int getImprovedDefaultWidth(int measureSpec) {
identical to getImprovedDefaultHeight
but of course uses the width as opposed to height
}

//Override these methods to provide a maximum size
//"h" stands for hook pattern
abstract protected int hGetMaximumHeight();

abstract protected int hGetMaximumWidth();

www.it-ebooks.info

10 CHAPTER 1: Exploring Custom Views
protected int hGetMinimumHeight() {
return this.getSuggestedMinimumHeight();
}
protected int hGetMinimumWidth() {
return this.getSuggestedMinimumWidth();
}

Notice how we are calling the getSuggestedMinimumHeight( ) from the base View class to get
the minimum size for this view. This means the derived view must call setMinimumHeight( ) and
setMinimumWidth( ). If a derived view like CircleView calls these set methods in its constructor, then
the size of the widget for wrap_content will use the minimum dimension. If your intention is to return
an average width, as opposed to a minimum width, change this code accordingly.
From Listing 1-6 you also see that we have used maximum size for UNSPECIFIED mode. So when
does this get called? Documentation says that this mode is passed in when the layout wants to
know what the true size is. True size could be as big as it could be; layout will likely then scroll it.
With that thought, we have returned the maximum size for our circle. You will see this when we show
you the full source code for CircleView, later in this chapter.
Also notice that, to satisfy onMeasure( ) (in Listings 1-2 and 1-6), we have used two built-in functions:

setMeasuredDimension() //from view class
getSuggestedMinimumWidth() //from view class

Let’s see now what the default implementation of onMeasure( ) does (Listing 1-7) and why we didn’t
choose it.
Listing 1-7. Default Implementation of onMeasure( ) by the View Class

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
public static int getDefaultSize(int size, int measureSpec) {
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);

switch (specMode) {
case MeasureSpec.UNSPECIFIED:
result = size;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}

www.it-ebooks.info

11CHAPTER 1: Exploring Custom Views
Notice that this implementation will result in taking the entire remaining space when the mode is
wrap_content! That is why we have overridden the class. If you don’t anticiapte wrap_content on
your widget (meaning it has no natural size), then you can use the default implementation and you
don’t allow wrap_content in the layout file.
Most of the work in understanding custom views is in the measure pass and how you implement the
onMeasure( ). Now that it is behind us, let us turn to onDraw( ).

Implementing Drawing through onDraw( )
Unlike onMeasure( ), there is no confusion about onDraw( ). For starters, the default implementation
does nothing. It is your job to draw. Here is how we implemented it in Listing 1-8:
Listing 1-8. Overriding onDraw( )

private int defRadius;
private int strokeWidth;
private int strokeColor;


//Called by the constructor
public void initCircleView()
{
//Set the minimum width and height
this.setMinimumHeight(defRadius * 2);
this.setMinimumWidth(defRadius * 2);

//Say we respond to clicks
this.setOnClickListener(this);
this.setClickable(true);

//allow for statmanagement
this.setSaveEnabled(true);
}

//we don't use the defRadius variable here
//we just use the dimensions that are passed
//defRadius is used to set the minimum dimension
@Override
public void onDraw(Canvas canvas) {

super.onDraw(canvas);
Log.d(tag,"onDraw called");

int w = this.getWidth();
int h = this.getHeight();
int t = this.getTop();
int l = this.getLeft();

www.it-ebooks.info

12 CHAPTER 1: Exploring Custom Views
int ox = w/2;
int oy = h/2;
int rad = Math.min(ox,oy)/2;
canvas.drawCircle(ox, oy, rad, getBrush());
}
private Paint getBrush()
{
Paint p = new Paint();
p.setAntiAlias(true);
p.setStrokeWidth(strokeWidth);
p.setColor(strokeColor);
p.setStyle(Paint.Style.STROKE);
return p;
}

Simple. You get the canvas. You ask the view for width, height, left, and top. Left and top are relative
to the parent view, starting at 0. The width and height also include padding. Use the getPadding…( )
series of methods to get padding coordinates, if you choose to use them.
In Listing 1-8, there are no surprises at all. Of course, as you start using canvas in inventive ways,

then you get into the wonderful world of 2D graphics. But the focus of this chapter is on the
plumbing of custom views and not on the gravity-defying 2D graphics programming.
To get the bare-bones custom view up and running, the only two methods that need to be
overwrittern are onMeasure( ) and onDraw( ). With some thought you can continue to use the same
implementation of onMeasure( ) for a whole class of custom components. Once you understand the
basics of these methods, writing a custom view that draws to the canvas is a cinch.
Responding to Events
As a next step for our custom view, we want to exercise the requestLayout( ) and invalidate( )
methods. To demonstrate these two methods in Listing 1-9, we make our circle respond to a touch.
Listing 1-9. Custom Views Responding to Events
public class CircleView
extends implements OnClickListener {
other stuff
public void initCircleView() {
other stuff
this.setOnClickListener(this);
this.setClickable(true);
other stuff
}
other stuff
public void onClick(View v) {
//increase the radius
defRadius *= 1.2;
adjustMinimumHeight();
requestLayout();
invalidate();
}
www.it-ebooks.info

13CHAPTER 1: Exploring Custom Views

private void adjustMinimumHeight() {
this.setMinimumHeight(defRadius * 2);
this.setMinimumWidth(defRadius * 2);
}
other stuff
}

To respond to a click, our custom control implements the click listener and overrides the onClick
method. It also tells the base View class that this is the click listener and clicking is enabled for this
view. In the onClick( ) method, we increase the default radius and use that radius to change the
minimum height and width.
Because the onClick event has caused the dimensions to change, our view needs to become bigger
and take more space. How do we express that need to Android? Well, we requestLayout( ). This
method goes up the chain, marking every view parent that it needs to be remeasured. When the final
parent gets this request (the view root), the parent schedules a layout traversal. A layout traversal
may or may not result in onDraw, although in this case it should. As a good programming practice,
we also call the invalidate( ) to ensure the drawing phase as well.
It is possible that a particular event will detect no change to the size but just the color of the circle;
in that case, we just need to do invalidate( ) and not call the requestLayout( ).
If you are in the layout phase, you shouldn’t call methods that could potentially result in a
requestLayout( ). Say, you added a background image in onSizeChanged( ). You shouldn’t call
requestLayout again from the same phase. It won’t take effect, as the view root resets these flags at
the end of the current cycle. But you can do that in the painting phase. Alternatively, you can post an
event to the queue that calls the requestLayout( ).
There is another method on a view called forceLayout( ). The difference between this and
requestLayout( ) is that the latter goes up the chain and results in a scheduling of layout pass.
Nothing makes this more clear than looking at the source code (taken from API 14) for these two
methods in the View class (shown in Listing 1-10):
Listing 1-10. Difference Between forceLayout( ) and requestLayout( )
public void forceLayout() {

mPrivateFlags |= FORCE_LAYOUT;
mPrivateFlags |= INVALIDATED;
}

public void requestLayout() {
if (ViewDebug.TRACE_HIERARCHY) {
ViewDebug.trace(this, ViewDebug.HierarchyTraceType.REQUEST_LAYOUT);
}

mPrivateFlags |= FORCE_LAYOUT;
mPrivateFlags |= INVALIDATED;

if (mParent != null) {
if (mLayoutParams != null) {
mLayoutParams.resolveWithDirection(getResolvedLayoutDirection());
}
www.it-ebooks.info
14 CHAPTER 1: Exploring Custom Views
if (!mParent.isLayoutRequested()) {
mParent.requestLayout();
}
}
}
Please note that the methods in Listing 1-10 are internal methods and are not part of the
public API, so they may change with newer releases. However, the underlying protocol would remain
the same.
In Listing 1-10, it is easier to first tell what forceLayout( ) is. It is like a touch command in build
environments. Usually when a file hasn’t changed, the build dependencies will ignore it. So, you
force that file to be compiled by “touch”ing, and thereby updating its time stamp. Just like touch,
the forceLayout( ) will not invoke any build commands by itself (unless your build environment is too

sophisticated to kick off right away). The effect of touch is that, when a build is requested, you don’t
ignore this file.
So, when you forceLayout( ) a view, you are marking that view (only that one) as a candidate for
measuring. If a view is not marked, then its onMeasure( ) will not be called. You can see this in the
measure( ) method of the view. The measure( ) method checks to see if this view is marked for layout.
The behavior of requestLayout( ) is (only) slightly different. A requestlayout touches the current
view as does forceLayout( ), but it also walks up the chain touching every parent of this view until it
reaches ViewRoot. ViewRoot overrides this method and schedules a layout pass. Because it is just a
schedule to run, it doesn’t start the layout pass right away. It waits for the main thread to complete
its chores and attend the message queue.
You may wonder, Still, explain to me when I use forceLayout()! I understand the requestLayout()
because I end up scheduling a pass. What am I doing with forceLayout()? Obviously if you are
calling a requestLayout on a view, there is no point in calling forceLayout on that view. What is
“force” anyway?
Recall that a “force” is like a “touch” for build! So, you are “forcing” the file to compile. Although it
may look like it is going to run the layout pass right away, it does not.
When a view gets its requestLayout called, neither its siblings nor its children are touched. They
don’t have this flag up. So, if the view that is touched is a view group (as when you delete a view or
add a view), the view group doesn’t need to calculate the sizes of its children because their sizes
haven’t changed. There’s no point in calling their onMeasure. But if for some reason the view group
decides that these children need to be measured, it will call forceLayout on each of them, followed
by measure( ), which now correctly calls onmeasure( ). It won’t call requestLayout on children because
there’s no need to trigger another pass when you are in the middle of the current pass. (It’s a “Don’t
interrupt me while I am interrupting” kind of deal.)
That introduces the question, then, of what happens the very first time. Who touched all the views to
begin with, so that they are measured? When a view is added to a view group, the view group makes
sure the view is marked for measurement and it calls a request layout for that view. More important,
when does one call each of these methods?
Developers typically have more occasions to call requestLayout. For example, when you respond
to a click on a view to increase its size, you do that and then you say requestLayout on that view.

For whatever reason, if that doesn’t do anything, you are inclined to call forceLayout, as the name
www.it-ebooks.info

15CHAPTER 1: Exploring Custom Views
is quite misleading. Based on what we know, that is like yelling twice. If the first yell is a no-op, the
second yell will get the same response.
If a target view is changing size, and if you believe the size of your sibling view will get impacted,
then call requestLayout on yourself and call forceLayout on your sibling. Of course, you can call the
siblings requestLayout as well, but that adds a few more cycles to the CPU; if youknow what you
are doing, a simpler forceLayout would do the trick. It is possibly also common in complex layouts
that are derived from view groups, where a change to a single sibling may need a measurement of its
siblings, so you want to explicitly decide if they need to remeasure again or not.
Saving View State
By now, you know this about working in Android: When you flip a phone or your device, you go from
portrait to landscape or the other way around. This is called, at large, a configuration change to the
device. A configuration change will stop the activity and remove it, and recreate a new instance using
the new configuration. So, all memory variables held by the activity are gone and are recreated.
If you have a view that has local variables, they are gone and reinitialized as well. If you have
transient data that you have created since the view was initialized, and was not written to a
permanent store, that data is gone, too.
In order to retain the transient state, you use an activity or a fragment to save and restore instance
data. “Instance data” refers to the local variables maintained by classes such as Activity, Fragment,
or View. Activity and Fragment have a predefined protocol to manage this method. We are not going
to cover that in detail in this chapter; our focus is on how to manage the instance state of the view.
There are three ways to manage the view state:
Have the  Activity use save and restore instance methods to explicitly call the
view to save and restore its state.
Use the built-in functionality of a  View to save and restore its state.
Use the built-in functionality of a  View to save and restore its state, as in item
above, but use a BaseSavedState protocol.

We will discuss the pros and cons of each way, and recommend that the third way is the best for
industrial-strength components.
Rely on Activity Methods
Pseudo code in Listing 1-11 shows how an Activity can locate and call the View to save and restore
the transient state.
Listing 1-11. View State Management Through an Activity
YourActivity
{
@Override
protected void onSaveInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);

www.it-ebooks.info

16 CHAPTER 1: Exploring Custom Views
//locate your view component
CircleView cv = findViewById(R.id.circle_view_id);

//call a custom method and get a bundle
Bundle b = cv.saveState();

//Put the bundle to be saved
savedInstanceState.putBundle("circle_view_bundle",b);
}
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);

//locate your view component
CircleView cv = findViewById(R.id.circle_view_id);


//call a custom method
cv.restoreState(savedInstanceState.getBundle("circle_view_bundle"));
}
}

This is the approach used in some of the Android SDK API samples, like the SnakeView. Listing 1-12
shows a snippet of the source from the SnakeView example:
Listing 1-12. Example of Saving and Restoring State
public Bundle saveState() {
Bundle map = new Bundle();
map.putIntArray("mAppleList", coordArrayListToArray(mAppleList));
map.putInt("mDirection", Integer.valueOf(mDirection));
more
return map;
}
public void restoreState(Bundle icicle) {
mAppleList = coordArrayToArrayList(icicle.getIntArray("mAppleList"));
mDirection = icicle.getInt("mDirection");
more
mSnakeTrail = coordArrayToArrayList(icicle.getIntArray("mSnakeTrail"));
}

This approach is really simple, and that is its charm. However, if the activity contains a lot of views,
then we have to save and restore the state for each view in Listing 1-12. We also have to define
string constants for each view and make sure they don’t collide. That will be lot of code, not to
mention being a bit error-prone. This approach as shown in Listing 1-12 is nevertheless useful for
simple scenarios.
www.it-ebooks.info


17CHAPTER 1: Exploring Custom Views
Enabling the View for Self State Management
If you could have the view do its own state management, then you don’t need to do the bookkeeping
in higher-level components, like fragments and activities. You could tell Android that a view does its
own state management by calling:

view.setSaveEnabled();

This will call the following methods, shown in Listing 1-13, on the view (as long as the view definition
in the layout file has a unique ID defined; this is a limitation and requirement for the view to manage
its own state).
Listing 1-13. Overriding a View’s Save and Restore State Methods
@Override
protected void onRestoreInstanceState(Parcelable p)
{
//Code for these two methods are presented a little later
this.onRestoreInstanceStateSimple(p);
this.initCircleView();
}
@Override
protected Parcelable onSaveInstanceState()
{
//Code for this method is presented a little later
return this.onSaveInstanceStateSimple(p);
}

The caveat with this approach is that the view has to have a unique ID to trigger these two methods.
This is not a problem when the view like CircleView stands by itself and is independently hooked
to a layout. But if the CircleView becomes part of a compound component, and if that compound
component is specified multiple times in a layout, then the IDs will collide. We will cover this topic in

more detail in the next chapter, when we tell you how to program compound controls.
In Listing 1-13, we have only showed what methods are called by the view to save and restore state.
We haven’t shown how to actually save the state. There is a simple way to do this, and there is a
standard way to do it. We will cover the simple approach first, as presented in Listing 1-14.
Listing 1-14. A Simple Approach to Managing View State
private Parcelable onSaveInstanceStateSimple()
{
Parcelable p = super.onSaveInstanceState();
Bundle b = new Bundle();
b.putInt("defRadius",defRadius);
b.putParcelable("super",p);
return b;
}

www.it-ebooks.info

Tài liệu bạn tìm kiếm đã sẵn sàng tải về

Tải bản đầy đủ ngay
×