In
Japan
we drew a
circle
in a big white
View
.
Now let’s make the view touch-sensitive.
When we touch it,
the circle will jump to the point where we touched.
See
Input
Events.
A
Button
should have a
View.OnClickListener
containing an
onClick
method;
see
Button.
Any
View
can have a
View.OnTouchListener
containing an
onTouch
method.
The
onClick
method of the
View.onClickListener
took only one argument,
because we don’t care about the x, y coördinates within a
button where the button was clicked.
But the
onTouch
method of the
View.OnTouchListener
takes two arguments,
because we do care about the coördinates.
MainActivity.java
.
The
MainActivity
creates a big white
TouchView
.
TouchView.java
.
We plug a
View.OnTouchListener
into the
TouchView
to make the
TouchView
touch-sensitive.
activity_main.xml
.
Unused and ignored.
AndroidManifest.xml
build.gradle
(Module: app)
Create a subclass of class
View
named
TouchView
.
See
Japan
for creating a subclass of class
View
.
private PointF point = new PointF(); //holds 2 floatsin class
TouchView
,
would it be simpler to have the following two fields?
private float x, y; //coordinates of pixel where finger touchedTry it, but change it back when you’re done. I want to keep the data structure as high-level as possible.
onLayout
method to class
TouchView
.
This method is called automatically by we-don’t-know-who
whenever the view is resized.
This includes the time
when the view is given its initial size,
sometime between the view’s construction and the first call to
onDraw
.
The methods
getWidth
and
getHeight
return zero before the first call to
onLayout
,
so we can’t call them in the constructor.
Open the file
TouchView.java
in Android Studio.
Code →
Generate… →
Override Methods
Press the Sort Alphabetically (↓a–z) button and select
onLayout
.
@Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); point.set(getWidth() / 2f, getHeight() / 2f); }
TouchView
,
change the field
private PointF point = new PointF();to the following. An
ArrayList<PointF>
is like an expandable array of
PointF
s.
private ArrayList<PointF> points = new ArrayList<PointF>();
onLayout
method you added in the previous exercise.
onTouch
method of the
View.OnTouchListener
,
change
point.set(event.getX(), event.getY());to
points.add(new PointF(event.getX(), event.getY()));
onDraw
method of the
TouchView
,
change
canvas.drawCircle(point.x, point.y, radius, paint);to
for (int i = 0; i < points.size(); ++i) { PointF point = points.get(i); canvas.drawCircle(point.x, point.y, radius, paint); }
onTouch
method of the
View.OnTouchListener
,
change the
switch
statement from
switch (event.getAction()) { case MotionEvent.ACTION_DOWN: //Put finger's x, y into point. point.set(event.getX(), event.getY()); invalidate(); //call onDraw method of TouchView return true; default: return false; }to
switch (event.getAction()) { case MotionEvent.ACTION_DOWN: return true; //do nothing case MotionEvent.ACTION_UP: //Put finger's x, y into point. point.set(event.getX(), event.getY()); invalidate(); //call onDraw method of TouchView return true; //do nothing else default: return false; }If the
MotionEvent.ACTION_DOWN
case had returned
false
,
we would have ignored all subsequent stimulus from this finger,
including the
ACTION_UP
.
See
Event
Listeners.
switch
statement to the following.
Then drag the circle with your finger.switch (event.getAction()) { case MotionEvent.ACTION_DOWN: return true; //do nothing case MotionEvent.ACTION_UP: return true; //do nothing case MotionEvent.ACTION_MOVE: //Put finger's x, y into point. point.set(event.getX(), event.getY()); invalidate(); //call onDraw method of TouchView return true; //do nothing else default: return false; }
TouchView
.
private int circleColor = Color.RED;Remove the call to
setColor
in the constructor for class
TouchView
.
In the
onDraw
method of
TouchView
,
insert the following statement before the call to
drawCircle
.
paint.setColor(circleColor);Change the
switch
statement to the following.
switch (event.getAction()) { case MotionEvent.ACTION_DOWN: //Put finger's x, y into point. point.set(event.getX(), event.getY()); circleColor = Color.BLUE; invalidate(); //call onDraw method of TouchView return true; case MotionEvent.ACTION_UP: circleColor = Color.RED; invalidate(); //call onDraw method of TouchView return true; case MotionEvent.ACTION_MOVE: //Put finger's x, y into point. point.set(event.getX(), event.getY()); invalidate(); //call onDraw method of TouchView return true; default: return false; }
TouchView.java
file to the following.
The white area will no longer be touch sensitive,
and the point in the circle that was initially touched
will remain under the finger.
For example, if we touch the lower edge of the circle,
our finger will stick to that point.
The
View.OnTouchListener
can have a field
(the object
previousCenter
)
even though
View.OnTouchListener
is an anonymous inner class.
The radius is now a field of class
TouchView
.
package edu.nyu.sps.touch; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.PointF; import android.view.MotionEvent; import android.view.View; public class TouchView extends View { private PointF center = new PointF(); //of circle private float radius; //of circle private Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); public TouchView(Context context) { super(context); paint.setColor(Color.RED); paint.setStyle(Paint.Style.FILL); setOnTouchListener(new OnTouchListener() { PointF previousCenter = new PointF(); //where previous MotionEvent occurred @Override public boolean onTouch(View v, MotionEvent event) { float dx = event.getX() - center.x; float dy = event.getY() - center.y; if (Math.hypot(dx, dy) > radius) { return false; //touched outside the circle } switch (event.getAction()) { case MotionEvent.ACTION_DOWN: previousCenter.set(event.getX(), event.getY()); return true; case MotionEvent.ACTION_UP: return true; case MotionEvent.ACTION_MOVE: //How far have we dragged since previous MotionEvent? dx = event.getX() - previousCenter.x; dy = event.getY() - previousCenter.y; center.offset(dx, dy); previousCenter.set(event.getX(), event.getY()); invalidate(); return true; default: return false; } } }); } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); center.set(getWidth() / 2f, getHeight() / 2f); radius = .2f * Math.min(getWidth(), getHeight()); }; @Override protected void onDraw(Canvas canvas) { canvas.drawColor(Color.WHITE); //background canvas.drawCircle(center.x, center.y, radius, paint); } }
MotionEvent
passed to the
onTouch
method of the
listener
can tell you the
size
and
pressure
of the touch, at least on some hardware.
(I’ve never seen it work.)
Make the circle bigger if user presses harder.
Drawable
object like the triangle
here.
Another possibility would be to let the circle be a (subclass of class)
View
,
and put the circle object in a big white
ViewGroup
object such as a
RelativeLayout
.