Tip:
Highlight text to annotate it
X
ROMAIN GUY: Good afternoon, everyone.
And welcome to the session on writing
custom views for Android.
And the title is a bit misleading, because we won't
be showing code, or we won't be writing code.
My name is Romain Guy.
And this is Adam Powell.
And we both work on the Android
Framework Team at Google.
Adam works a lot on the UI Toolkit.
And you have seen his work in the action bar, the view
pager, the support library.
He's the mind behind all of that.
I focus mostly on graphics, performance, and animations.
But I've done a little bit of UI Toolkit in the past.
So I think I know what I'm talking about.
ADAM POWELL: So why would you actually want to write your
own custom views?
Isn't the framework good enough for that?
Are we missing something?
Well, usually the framework has what you need.
But between the framework and the support library, you kind
of have what you need to get everything
in the design guide.
And reusing those framework components ensures the user is
familiar with the patterns there.
And hopefully the components are stable.
But sometimes you just really need to stand out.
So just like Larry was saying this morning, your app is
doing something unique.
Otherwise why are you writing it?
You might need graphs, other data visualizations, other
things that just are really unique to
what your app is doing.
Maybe you need some sort of custom
interaction with your content.
I mean, not everything fits within a list
view or a scroll view.
And touch is a great way to feel
connected to your content.
And writing a view gives you full control over that.
So who here uses
RelativeLayout, for an example?
A lot of hands.
That's great.
Probably because this guy told you to.
ROMAIN GUY: Now a while ago, I wrote a blog that was
misinterpreted.
And now everybody thinks they should be using RelativeLayout
all the time.
Please don't.
ADAM POWELL: It's a great view, right?
ROMAIN GUY: It works.
We can get to that later, if we have time.
ADAM POWELL: Sure.
So RelativeLayout will measure child views more than once in
order to solve some of the constraints that you give it.
So being generic has a cost.
A little domain knowledge can go a long way for
optimization.
Really, there should always be one part of your app that can
catch someone's eye from across a room, something
that's instantly identifiable.
If one of your users is using your app in a coffee shop,
someone clear across the room should be able to
tell what it is.
So you want to make your app fun and satisfying.
And the design guidelines are great.
But apps that do unique things will always beg for some sort
of innovative ways to do them.
So members of the Android community have already given
several talks lately that have been really great, and given a
lot of information on writing custom views.
And not only that, there are a lot of great
code examples available.
And Android Open Source project itself is a great
resource for you to look at.
But realistically, we want to keep this talk a little bit
more abstract, as you were saying earlier.
So in writing our own custom views, and helping others
write them as well, we found a lot of common patterns
emerged from this.
And we found ourselves giving the same
recommendations over and over.
And we're going to spend most of our time here today talking
about those experiences and what worked well, what didn't,
and what can really help you from getting caught in a
[? rapple ?]
of the internal design of your components.
So to do that, we still need some context about how the
system works.
So we'll go ahead and start with--
ROMAIN GUY: But just before on the previous slide, don't
believe the "Tips and Trick" bullet.
We removed that section.
So there won't be tips and there won't be
tricks for you today.
ADAM POWELL: Well, we might have some tips and tricks
along the way.
ROMAIN GUY: Yeah, along the way.
So we'll start with Live of a View.
So how many of you have ever seen the
Activity Lifecycle Diagram?
All right.
How many of you understand the Activity Lifecycle Diagram?
You're a bunch of liars.
I've been on the team for six years.
I still don't get it.
I exaggerate.
But it's complicated.
But the Lifecycle Diagram of Activities makes guarantees
about the methods that will be invoked on your
activities and when.
And we have similar things for views.
It's a lot simpler, as you will see.
But we have guarantees that we can promise.
So the life of a view is made of three things.
We have the attachment and detachment.
And this is actually a very important part of
the life of a view.
And I've never or rarely seen articles online talk about it,
or examples focusing on that.
Then with traversals that you may know as drawing and layout
and measurement.
And finally, we have the state saving and restoring.
So let's get started with the attachment and detachment.
Actually, another quick poll.
How many of you have used that method onAttachedToWindow?
All right, that's pretty good.
So whenever you attach a view to a parent-- so you call a
ViewGroup, that add view-- if that parent already belongs to
window, in UI Toolkit [? Balance ?] we said that the
view gets attached to the window.
So you get this call back, onAttachedToWindow.
And this is where the view knows it can be active.
So this is where you should start allocating any resources
you might need.
This is where you can register listeners on other pieces of
the application on the system.
And for instance, in list view, when you attach a list
view to a window, this is when we attach a listener to your
adaptor after so that list view knows when the data
changes in the adaptor.
And again, you should take a look at the
source code of Android.
So if you look at the source code of view.java, or
AbsListView.java, you will see the kind of things that we do
in onAttachedToWindow.
But even more important is the second callback called
onDetachedFromWindow.
So we call that callback whenever a view is removed
from its parent and if that parent is attached to window.
This can happen for a number of reasons.
Maybe the view is getting recycled in the list view when
you're scrolling.
Maybe you called remove view on the parent of that view.
Or maybe we are tearing down the activity.
When we tear down the activity when there's a finished event
on the activity, we're going to remove every view.
And we're going to detach them from the window.
This callback is very important, because this is
where you can stop doing any kind of work
that was already scheduled.
So if you had scheduled an animation, like a scroll or a
fling, if you had a background thread running, you can stop
those things right here.
Also, if you had allocated resources like a drawing cache
of some sort, or any other resource that your custom view
might need, this is a very good place to
clean up those resources.
A good example is for the hardware renderer.
So in Android 3.0, we added Hardware Acceleration.
And we created a lot of native objects that attached views
during the lifecycle of the view.
And when the view gets detached from the window, this
is where we can destroy those native objects, because we
know that the view will not be shown on screen anymore.
So we make sure to promise when you get
onDetachFromWindow, you can destroy all these resources.
Of course, this is a promise that we make.
But sometimes when you implement your own custom
views, and you try to be, let's say, smart about your
lifecycle of the view, you might run into real issues
where the view will be detached but it's not.
Or the view will think it is attached, it is not
attached and it is.
We've run into a number of bugs like that in our own
applications.
ADAM POWELL: A couple.
ROMAIN GUY: So be very careful when you use AdView,
RemoveView.
And View Group has a number of methods where you can
temporarily detach a view from the window.
So if you're using those methods, be very careful.
And that's about it for the lifecycle of a view.
So what is missing?
What is missing is all the events that you get on an
activity, so things like onPause, onResume, onStop.
And we have a lot of requests from people who are asking to
get those lifecycle events on View itself.
The reason why we don't have those lifecycle events on view
is about abstraction and layering.
The way the Android framework is built, we try to layer our
packages on top of each other.
So what you see on the side right here is actually the
layering that we have internally.
So the android.app package that contains activity and
context sits on top of pretty much everything else.
At the bottom, we have android.widget, where you have
your text view, your list view, your image
view, things like that.
And that sits on top of android.view.
So you could actually build a version, I would say the fork
of Android, where you get rid of android.widget.
And you just keep android.view.
And the system would just work.
So instead of adding those activity lifecycle events to
views, what you should do is create your own sets of
listeners or callbacks in your custom views.
So here, there's a very simple example of custom listeners
that you could specify on your view.
And it will be the responsibility of the activity
to invoke those callbacks on the view.
Another way of doing it is just to add methods straight
to your class.
If you have used GL SurfaceView that's used to do
OpenGL rendering, this is what happens.
This view has its own on pause, on
stop, on resume methods.
And it is your responsibility when you add that view to an
activity to call those methods at the right time.
So we don't need to add support
for that in the framework.
You can do it yourself.
We know it's a bit more work.
But you can easily write utility libraries or classes
in your code to help you do that.
I briefly mentioned traversals before.
And traversals are scheduled in three different ways.
They can be scheduled by animations.
And we're talking about the new animation system that was
introduced in Honeycomb.
The old animation system is actually part of drawing.
Traversals can also be triggered by requestLayout and
Invalidate.
And this is what the traversal looks like.
And if you want to be really scared, go look at
ViewRootImpl.java in the source code of Android.
And look there's a method called perform traversals.
And try to understand what it does.
Once again, I've been writing code in that method for years.
And I still don't quite get it.
Don't laugh.
It's true.
So we first start with animation.
So with a new animation system, there will be
callbacks related to animations that will be
triggered at the beginning of a traversal.
Then we're going to do the measurement pass, then the
layout pass, and finally the drawing pass.
The order that you see those events on screen is
guaranteed.
If you see a layout, there will always be a draw after.
You can't see a draw and then the layout.
What can happen, though, is you could have a measure in
the layout and not a draw.
Or you could have a draw but not a measure in the layout.
The only tricky part here is that the measure and the
layout always happen together.
You cannot have one without the other, unless you're doing
something really bad in your application.
ADAM POWELL: All right.
So let's dive a little bit deeper on
some of these topics.
So measurement and layout is where we'll
go ahead and begin.
All views know how to measure themselves and
lay out their content.
And leaf node views that don't actually have any children
should measure their internally displayed content
and go ahead and lay that content out
during these two steps.
So ViewGroups on the other hand organize, measure, and
lay out a series of child views.
So RequestLayout is the first step in this whole process.
At some point, some piece of state
changed within your view.
RequestLayout is the signal to the view system that it needs
to recalculate the measurement and layout of the views.
So it means something very specific in
terms of what changed.
I means that something changed that can alter the size, and
by extension the layout within all the ancestors moving up
the hierarchy.
So RequestLayout is recursive.
A change in the size of a leaf node way down at the bottom of
the tree can influence the size going all the way up the
tree as well.
ROMAIN GUY: And this is actually one of the reasons
why we often mention that you shouldn't have too many views
in your application, in your UI.
And more importantly, we said that you shouldn't have too
deep of a tree.
And this is one of the reasons, because every time we
call RequestLayout, we have to walk all the way back up.
So when you call RequestLayout once, it
doesn't really matter.
But if you [? update ?] a lot of views and every one of them
tries to go back up the tree, it can take up a significant
amount of time.
We have optimizations in the UI Toolkit to avoid doing
extra work.
But every bit can help.
ADAM POWELL: That's right.
So because of this process, anything can change.
It can dramatically change the way that the view's parent,
grandparent, or great-grandparent measures and
lays out the other views around it.
So on measure, it's where your view
implements the measure step.
It's called by the actual measure method within view to
determine the view's size.
In a ViewGroup, again, this is a recursive process.
So ViewGroups will go ahead and call measure on each one
of their child views.
And then they can use the results to help
decide their own size.
So the parameters onMeasure are these packed
measure spec values.
They're packed into a 32-bit end.
So the make MeasureSpec method goes ahead and packs a size
and a mode code into these values.
And then the getSize and getMode methods listed here
unpack them for you.
And we'll go ahead and talk a little bit more about measure
spec modes in a bit.
When a ViewGroup is measured, it has a responsibility to
make sure that it's child views have been properly
measured as well.
A layout request may have originated from a leaf node
deep down the hierarchy.
And depending on how a ViewGroup decides to arrange
its children, one small change in size down the leaf node can
drastically alter the layout above it.
If this sounds expensive, it's because it really can be.
Multi-pass measurement in a ViewGroup is sometimes
necessary when you're trying to resolve different
constraints for a layout.
But because of that, it can really add up fast.
And this is usually a good time to dust off your
algorithm analysis techniques.
So finally, onMeasure doesn't actually return a value.
Instead you call setMeasuredDimension to set
width and height explicitly.
This is enforced.
If you forget to do this, then the framework is going to
throw an exception and crash your app.
The sizes that you set here are reported by the
getMeasuredWidth and getMeasuredHeight methods, not
the normal getWidth and getHeight methods you may be
familiar with.
This is because the process is two step.
GetWidth and getHeight don't actually have their values
initialized until layout is complete.
So you can use the measured size of these child views to
decide your own size or perform any other bookkeeping
after the child's measure returns.
So back to modes.
There are three different modes.
The first one is EXACTLY.
And it means exactly what it says.
This mode can be used if there's a fixed size specified
or if the parent view has otherwise calculated a fixed
size for it.
So if you're familiar with the match parents that you might
set in the LayoutParams or weight that you might use in
linear layout, these are good examples of when we might use
the EXACTLY mode.
It's common to go ahead and do a second pass to finalize a
measurement in a multi-pass scenario.
And weight is one of these.
Since we distribute whatever leftover weight is still
remaining after we've measured all the child views, we use
EXACTLY to go ahead and lock in the extra size to each one
of the child views.
So AT_MOST specifies an upper bound with the implication
that the view should usually be conservative with that.
The measured size should always be less than or equal
to the size reported in the MeasureSpec.
When you give a view width of wrap content, then the stock
framework layouts will use AT_MOST to measure that child.
The view has the option of consuming up to that specified
size if it really wants to.
And finally, you have the UNSPECIFIED mode.
This is where we just really don't know anything enough to
tell you how to measure in this particular dimension.
The view has potentially infinite space
available to it.
So this is really common for things like scroll containers,
so scroll view, list view, et cetera.
The limits are based only on the content that you're trying
to place there.
So most views still treat this the same way that
they do with AT_MOST.
They just don't impose the upper bound constraint.
The implication is that the view should still try and be
as conservative as possible and wrap
tightly around the content.
So making sure that you actually get the right measure
specs that match what your child views want, it can be a
little bit tricky.
There are few utilities in the framework that can help out
with this, though.
One of these utilities is the getChildMeasureSpec method.
It understands the standard framework patterns for how
layout params and measure specs should
interact with one another.
It's kind of a handshake between the two that happens
during measurement and the parent.
So for example, if a parent has exactly 300 pixels
available to it and a child wants to wrap content within
it, then the spec that we'll actually send to that child is
300 pixels in the AT_MOST mode.
So once measurement is complete, layout is just a
matter of positioning the child views that
we've already measured.
So usually this is where we apply offsets, padding,
margins, so on and so forth.
And while ViewGroups can often implement multi-pass
measurement to get things just right, layout
will only happen once.
If there's some expensive computation that you need to
perform during the whole measure layout sequence, then
you should really try and defer it to layout if you can
to be more efficient.
That way you don't get caught up in these multiple measure
passes that can happen.
And even if your own custom view doesn't do multiple
measure passes, some view that's further above you in
the hierarchy may be performing that.
So you still want to be as optimal as possible.
So this two phase measurement in layout means that you'll
often want a way to get at some of the data that you've
already computed in onMeasure so that you don't have to
compute it twice.
GetMeasuredWidth and Height are always there for you to
use for this purpose.
But sometimes you need to have more info than that.
You'll want to wait for child views to communicate extra
configuration data to your layout code in general, such
as gravity, for one example.
ROMAIN GUY: We're going to talk about LayoutParams.
But just before we get started, so we mentioned the
three different measure spec modes.
It's real scary and intimidating when you're
trying to write a custom view and you think about how can I
implement all those modes?
You don't have to.
If you write a custom view and you know exactly how it's
going to be used in your UI, you know it's always going to
be match parent, match parent, just implement the measure
spec exactly.
That's the big difference between what you can do in a
custom view.
You can cut corners.
You can cheat.
You can make it a lot more efficient.
And what we can do in our views in the framework is we
have to make them work in all possible situations.
So our code is a lot more complicated and harder to
[? optimize ?]
than what you can do.
And this is why we keep telling people, you should try
to create custom views whenever you can, because you
can make them a lot easier to write and much more efficient
than the ones that we have.
For instance, if you are trying use all the different
widgets that we have in the UI Toolkit to create a really
complex UI and it doesn't quite work, you can write a
custom view instead.
It's going to be easier.
You might spend less time writing that Java code than
trying to come up with the magic XML incantation that's
going to do what you want.
And it's also going to be more efficient.
So to create the measure specs for your children, if you're
writing a custom view that extends ViewGroups, or if
you're writing a custom layout, you will be very
interested in LayoutParams.
So LayoutParams, you know what they are from
XML most of the time.
They are all those attributes that sell to layout
underscore, and a map to a class that is typically called
LayoutParams.
And it's static in your class of the layout it belongs to.
So the base class is called ViewGroup.LayoutParams.
But we also have LinearLayout.LayoutParams.
We have
FrameLayout.LayoutParams, and so on.
You don't have to do it this way.
This is just the recommended way.
The base LayoutParams class will give you
a width and a height.
There's another one you can use.
It's called margin layout params.
It will give you the extra margin parameters if you need
bespoke margins in your customer group.
So LayoutParams are really dumb objects.
They are just here to store data.
They just store the arguments that you're getting from the
view when it's added to the ViewGroup.
So either from code--
when you say add view, you press the view, then the set
of LayoutParams--
or from XML, you can [? import ?] the
LayoutParams from XML.
And you know about the width and height.
But in LinearLayout, we mentioned the weight.
This is one of the params that we store in
LinearLayout.LayoutParams.
It's always a mouthful [INAUDIBLE].
ADAM POWELL: It is.
ROMAIN GUY: We need to rename them.
Beyond LayoutParams.
So if you create custom LayoutParams because you want
more than the width and the height, there are four other
methods that you should implement
in your custom ViewGroup.
So we have one called check LayoutParams.
This is invoked when you add a view to a ViewGroup.
If that group already has LayoutParams attached to it,
we're going to call check LayoutParams to see if those
LayoutParams are compatible with the ViewGroup.
So usually what views do here, it's just
an instance of check.
So if you look at LinearLayout, it just does the
parameters instance of LinearLayout.LayoutParams.
But you could do a lot more.
If you have a lot of custom parameters, you could vary the
constraints, for instance, and throw an exception if they
don't work.
Generic LayoutParams, the texture LayoutParams
attribute, can be used to do conversion.
So if you add a view that was previously in the frame layout
and it has frame layout LayoutParams, and then that
view is added to your ViewGroup, and if you know how
to convert those frame layouts, LayoutParams in your
LayoutParams, you can do so in that method.
Generic LayoutParams within an attribute set, this is what's
used when we inflate the LayoutParams from XML.
So the default behavior here that we use-- actually, that's
what we use all the time--
we just call the constructor or LayoutParams [INAUDIBLE]
the attribute set.
And all the work is done there.
And finally, default LayoutParams.
If you don't specify LayoutParams when you add a
view from code, we're going to try to guess what's the best
configuration for ViewGroup.
So typically in the frame layout, we're going to do
match parents, match parents.
A linear layout will do something like wrap content,
wrap content, something like that.
But don't forget that LayoutParams can also be used
to store private data.
So if you do a lot of expensive computations during
the measure pass or even doing the layout pass, you can store
all that data per child in the LayoutParams.
So if you go look at the source code of RelativeLayout,
for instance, that's exactly what it does.
It does its own private fields to keep track of
what's going on.
I want to talk briefly about drawing.
We won't spend too much time on it, because we have a lot
of other sessions about rendering.
But the way to trigger a joint traversal is to call
Invalidate.
I'm sure a lot of you are familiar with that method.
And just like with the RequestLayout, it's a
recursive algorithm.
So it's going to walk up the tree.
So I'm going to call Invalidate on the parents
[INAUDIBLE]
something called InvalidateChild.
And those methods are actually pretty expensive.
RequestLayout is pretty cheap because all it does, it sends
a flag saying I want to layout.
Invalidate has to keep track of animations, of bounds, of
transforms, things like that.
So again, if you go look at the source code that occurs,
you'll see that it's not a trivial method to call.
Again, we try to optimize those calls.
So if you have two siblings of a ViewGroup that call
Invalidate, only one of them will go all the way up.
Calling Invalidate without any parameters will mark the
entire view as dirty.
There's a variant of Invalidate that takes four
parameters.
It's a sub-rectangle of the view that you want to redraw.
So whenever you can, if you have enough information about
what's going on inside the view, try to use a second
variant of the method.
It will be a lot cheaper for the UI Toolkit and for your
application to do the redraw when we come back down.
So once we call Invalidate, we're going to call the draw
method on your view, and possibly on other views.
So the draw method in the base view class is final.
You cannot override that method, because it does a lot
of bookkeeping that we don't want you to mess with.
And trust me, you really don't want to have
to deal with that.
Draw will invoke the methods that you can override.
So the first one is called onDrawing.
I'm sure a lot of you are familiar with that method.
So how many of you have ever implemented the onDraw method?
That's really good.
ADAM POWELL: They have lots of experience.
ROMAIN GUY: So the only goal of the onDraw method is to
draw the content of the view.
You don't have to draw a background.
The base view class, the draw method will do that for you.
But any other content you want to draw, like text view.
That's where you do it.
There's a little trick.
We have a method that--
I hate that name.
It's so confusing--
setWillNotDraw(true).
Again, after six years, still get it wrong every time
I try to use it.
And it's even worse, because internally it's implemented as
a bit inside the [INAUDIBLE].
So with all the mess that you have to use,
it gets really confusing.
ViewGroup by default will set that flad.
So when that flag is set, you promise that you do not want
to do anything.
Your children will draw.
But you're not been going to draw anything.
You don't have a background.
You don't want to draw anything
on top of your children.
So in that case, we're going to skip the onDraw call.
And we're going to call directly in the other method
called dispatchDraw.
And dispatchDraw is another pretty complex method.
But you can override it.
It's responsible for drawing the children.
And this is where we apply all the transforms, like the
alpha, rotation, scaling, and animations with the old
animation system.
And for every child, we call a method called
ViewGroup.drawChild.
So if you want to override that method entirely, make
sure that you call the drawChild method for every one
of your children.
Otherwise you're not going to get animations working or
transforms.
There's a big difference between software and hardware
rendering in the way we call those drawing methods.
So as I mentioned before, we introduced hardware rendering
in Android 3.0.
And in software, if you call Invalidate, we're going to
call the draw method of every view that's a parent of the
view that called Invalidate.
We are also going to call the draw method of any view that
intersects with the dirty region.
So if you have views stacked on top of each other, and even
if they're siblings, we are going to call a draw method on
every one of those views.
So it can be pretty expensive.
In hardware, however, we are a completely different system.
And I won't go into details here.
There was a talk two years ago explaining
how it works exactly.
But only the view that calls Invalidate will see its draw
method invoked.
This is much more efficient.
You can imagine that if you have 10 parents, and suddenly
we have to call only one method instead of 11, it's
going to be a lot faster.
So you can refer to that talk, "Accelerated Android
Rendering," where we explained in detail how the new
rendering system works.
ADAM POWELL: All right.
So a lot of your views are going to have instant state.
This is some extra information that you're going to want to
be able to save for later.
Android app components can come and go
at any time, remember.
So process containers get killed.
Activities get recreated if you change the orientation or
something else changes.
But views can actually save their own state for later in a
way that can be persisted by the system.
So two methods control this save and restore process,
named onSaveInstanceState and onRestoreInstanceState.
And the easiest way to write one of these instance state
objects that gets persisted is to extend the base saved state
in a static inner class of your view.
And this establishes conventions for how sub class
and super class state get organized within this
persistent bundle.
So the standard naming convention is just in a class
called saved state within your view.
Views need to have an ID for their instance state to be
saved and restored.
This is really important.
This also needs to be unique within your view hierarchy.
This is how we actually match up what data to pump back into
the onRestore method when your views get recreated.
So if you duplicate the ID within multiple views within
your hierarchy, we're going to restore that same state to
multiple views, which probably isn't what you want.
So the mechanisms behind actually saving the instance
state are pretty well documented.
But what you save is a more interesting question.
Don't bother saving anything that's a part
of your data model.
Instead, save things that are a reflection of the views
state as it relates to the user's interaction with it.
So you really want to keep these concerns
as separate as possible.
Normal data loading within your app is going to take care
of reflecting the state of your data model.
There's really no reason to save it a second time and have
multiple sources of truth.
Instead, the view instance state should take care of
leaving the details where the user left them.
So things like uncommitted user input can be a little bit
of a grey area in this, like text entered in the field if
you have a really long form, for example.
In many cases, we'll go ahead and save this for you.
So Edit Text will automatically persist whatever
text the user's entered.
ROMAIN GUY: So basically you're telling people to not
be like a webpage--
like when you reload the webpage and all your stuff
disappears?
ADAM POWELL: Yeah, basically.
So next we have touch events.
Touch events are fun.
They're really a key component of any
interactive view these days.
So the basic event handling, again, is well documented.
But what happens when it gets more complex?
What happens when you actually have views at multiple levels
of the hierarchy that need to negotiate?
Well, first off, it's important to know what a
consistent event stream means.
Android has this idea of a consistent event stream when
it comes to motion event objects.
This is a guarantee from other parts of the view system to
your views.
You really shouldn't break this guarantee you when you
write your views and you end up sending
events to other views.
You want to make sure that these guarantees hold.
So ACTION_DOWN starts a new gesture.
It's really the beginning of a touch interaction.
It's when the first touch point contacts the stream.
And then throughout the course of a gesture, you might get
additional ACTION_POINTER_DOWN and ACTION_POINTER_UP events
as more touch points beyond the first enter
and leave the screen.
Now each one of these pointers has an ID attached to it.
And these can be used to track the same touch point when you
have multiple touch points active.
ACTION_POINTER_DOWN is the start of a specific pointer
becoming valid.
And UP is that pointer becoming invalid.
So while a gesture is in progress, you'll get the
steady stream of action move events as those
pointers move around.
These events contain info about every touch point that's
currently in contact with the screen.
And you'll have been told about each and every one of
these through either the initial ACTION_DOWN or
ACTION_POINTER_DOWN events.
And if any of them leave the screen, you'll be told through
an ACTION_POINTER_UP or the final ACTION_UP event.
ACTION_UP or ACTION_CANCEL ends the current gesture in
progress entirely.
It means the user is done interacting with the screen
for the time being.
ACTION_UP means that it ended normally.
ACTION_CANCEL means that something else took the user's
control away from your view.
So what this means is that ACTION_UP is where you should
do things like fling, or actually apply a click event.
Whereas ACTION_CANCEL should never commit an action,
because you never really know what took the user's control
away from that view.
Now when you have multiple views working together to form
your UI's full touch interaction, it's important to
factor that interaction appropriately across your
different views.
Now Android gives you several tools for views to collaborate
and decide which view should actually be responsible for
handling a current touch interaction.
Now onTouchEvent is the simplest of these.
If a view returns true from onTouchEvent for an initial
ACTION_DOWN, then the rest of the event stream will be sent
to that view.
So it'll try and find the deepest view that will accept
a touch event.
It's kind of like focus for touch events, really.
Now parent views have an additional method that they
can use for this, and that's onInterceptTouchEvent.
This will be called for each event that one of the parent
views' children receives.
By returning true for this, the parent can go ahead and
steal the event stream away from the child view.
The child will get an ACTION_CANCEL event, since
that's what a view receives whenever the event stream is
taken away from it for some reason.
And the parent will receive the rest of the current events
stream through its own onTouchEvent method, not
through the onInterceptTouchEvent method.
Now onIntercept is a great way to implement things like
scrolling and dragging.
But it can really be insufficient if you have
several levels of nested views.
Now list views inside view pages are a great example.
So to handle this case, we have this method that--
what is it with our framework having all these mouthful of
constants and methods in here?
ROMAIN GUY: We have to be very specific in the names.
ADAM POWELL: Right.
So we have requestDisallowI nterceptTouchEvent.
Now by calling this method on a parent view, a view can stop
the parent's onIntercept method from being called for
the remaining events in the gesture.
And in the case of something like the list view inside the
view pager, list view invokes this method.
And then it actually will lock the pager to the current page.
The pager or has no idea that you're moving back and forth
left and right.
And so therefore you won't move between pages if you just
want to make a really sloppy scroll within that list.
So speaking of sloppy scrolling--
well, in a second.
One way or another, these three methods, when put
together, it means that you can write custom views and
ViewGroups without being too concerned about how you're
going to compose them.
And this is really important.
It means that you can keep all these concerns separate within
the different custom views that you might write.
The right thing will just happen naturally as long as
you don't nest strolling along the same axis.
I mean, how many times have we all been using a webpage, and
we'll go ahead and scroll with a mouse wheel or something
like that, and then suddenly scrolling stops because there
was this other internally scrolling region that your
cursor just happened to be hovering over?
You don't want to build that within a touch UI.
For this whole game of rock, paper, scissors to work, you
really an opportunity to determine the user's intent
before committing to one action versus the other.
And view configuration is a class in the framework that
has a lot of constants and other values that control how
the framework tunes these sorts of things.
In this case, you have getScaledTouchSlot.
Now this is going to be a distance reported back to you
in pixels adjusted for the density of the current screen
that is the distance that you have to travel in terms of
touch motion before you actually commit to scrolling
within that view.
So having this slop within that gets checked within your
onIntercept method for a scrolling container means that
the child views have an opportunity to say, no,
actually I want this event stream, and call
requestDisallowI nterseptTouchEvent up to the
parent, so on and so forth.
ROMAIN GUY: And view configuration is a very useful
class if you're doing anything useful with your custom view,
like touch handling, or even animations or drawing.
You might want to take a look at view configuration and the
kind of data it exposes.
Because everything in that class is meant to help you be
consistent with the existing UI Toolkit.
So if you need a duration, there may be a duration in
there that matches your needs.
So make sure to take a look at it.
And the class is actually pretty well documented.
You can once again look at the source code of the Android
platform and see how we use it.
So we have four minutes left for questions.
So if you have a question, please walk up to the mics in
the aisles.
And before you leave the room, there are bar
codes by the door.
You can scan them if you want to rate the session.
And we'll be at the office hours on the floor to answer
all your questions throughout the day.
ADAM POWELL: That's right.
Thank you for coming.
[APPLAUSE]
AUDIENCE: Hello.
I have a question.
I want to implement view that's like a heartbeat, like
it's pulsing.
But what I want it to do is I will have that method like it
posts every second or so.
And I wanted animation that posts back.
So how should I do that?
Should I have in my view some kind of timer that draws the
circle smaller and smaller every 20 milliseconds?
Should this be part of the view?
ROMAIN GUY: So you should definitely use
the animation framework.
And tomorrow I'm giving a talk with Chet Haase on animations.
And this is the kind of animation that we're going to
talk about.
AUDIENCE: And the other quick one is some phones send you
back an app event after 15 seconds, something like that,
that's in the original [? matrix. ?]
I want to have a little button which is press and hold.
But after 15 seconds, it give me app event.
How can I do that?
ADAM POWELL: That was the same on the Motorola
Zoom, wasn't it?
ROMAIN GUY: Yeah.
Sounds like a bug in the [? touch dryer. ?]
AUDIENCE: Any workaround for this, that you know about?
[INTERPOSING VOICES]
ADAM POWELL: Unfortunately that's one of those things
where there are some devices out there with touch firmware
that try and do this.
It's basically because the device is trying to account
for a noise or other sources of error that might be in the
environment so that you don't have this permanent touch
point down.
Unfortunately, it ends up being way too aggressive.
Because if you're in a case where you're holding down the
gas pedal in a racing game, or something like that, that
becomes really problematic.
And unfortunately, there's just not a whole lot you can
do about it.
AUDIENCE: Thank you.
AUDIENCE: Hi.
So when you started, you mentioned that there was a
blog post about RelativeLayouts that was
misinterpreted.
Yeah, it was misinterpreted.
Everybody I know follows that.
So could you elaborate on that a little bit?
ROMAIN GUY: So I think the original post was about not
using a linear layout with an image in the text view and
using only a single text view.
Or maybe you could use only--
no.
It was about using a RelativeLayout to avoid
nesting linear layouts.
AUDIENCE: That and weights, I think, as well, right?
ROMAIN GUY: Yeah, something like that.
ADAM POWELL: It was specifically in the context of
list items, too, right?
ROMAIN GUY: That's correct.
So it was the context of list items to avoid nesting a
vertical linear layout in a horizontal linear layout.
So it's still true, if you're using weights.
But using your RelativeLayout to do everything is not the
right answer.
So if a firm layout does the job because you need to stack
views, or if you just want to put the view side by side, go
view linear layout or firm layout.
A RelativeLayout will always do two measure passes.
So when you nest, then, you get an
exponential measurement algorithm.
AUDIENCE: Thank you.
ROMAIN GUY: Last question.
AUDIENCE: Hi, I have a custom view that was
a background shadow.
And I noticed that it works OK until 4.2.
And in 4.2, I get some visual artifacts that are strange.
Is there anything that changed in the rendering?
ROMAIN GUY: Not that I can think of.
It might be a bug on our side.
Or you might have been relying on the but
that existed before.
I would need to see code and a running example.
Send me an APK that [? repositions ?]
the issue.
And we can take a look at it and see if it's
a bug on our side.
ADAM POWELL: Or if you'd like we'll be in the Sandbox.
ROMAIN GUY: For office hours, yes.
AUDIENCE: If possible, another question.
Is there an easy way to add margins to a list item entry?
ROMAIN GUY: To what?
AUDIENCE: To a list view entry to add margins?
Because the margins are cut by the list view.
ROMAIN GUY: Oh, you should use the padding instead.
AUDIENCE: Even if we have a background?
ROMAIN GUY: Well, use padding in the parent view.
And then you put the background inside.
AUDIENCE: OK, thanks.
ROMAIN GUY: Thank you.
We are out of time.
Come to the office hours in the Sandbox.
ADAM POWELL: Yep, happy to answer your questions at
office hours.
Thanks.