Tap or drag on the screen and watch the string of pearls dangle and wiggle.
The first
Pearl
hangs from the
touchPoint
,
initially the center of the screen.
Each subsequent
Pearl
hangs from the center of its predecessor.
I made the above screenshots on my
Azpen
A727 tablet,
because I couldn’t press the Android Studio screenshot button
and drag the pearls at the same time.
A
Pearl
object does no graphics and doesn’t even have a constructor.
It’s just a holder for two fields,
center
and
velocity
.
But it does do some arithmetic
based on the second most famous formula in physics:
force equals mass times acceleration.
float
),
which is why it’s printed in italics.
The force and the acceleration are
vectors
(pairs of numbers,
x and y,
stored as a
PointF
),
which is why they’re printed in bold.
Using this formula, the method
dragTowards
drags the
Pearl
towards a point.
You can tune the static fields of class
Pearl
to increase or decrease the gravity,
mass, etc.
The
BackgroundView
creates an array of five
Pearl
s.
The
onDraw
method draws a circle and a line for each
Pearl
.
The infinite loop
(the
for (;;) {
)
repositions the
Pearl
s
60 times a second.
It is executed by a
thread
that is not the UI thread.
Class
Pearl
does its arithmetic in units of
dp’s,
because I wanted the pearls to be the same size on every device
and because I wanted the statement of the classic laws of physics to be
uncontaminated with pixels.
But class
BackgroundView
does its arithmetic in units of pixels,
because the parameters of
onTouch
,
drawLine
,
and
drawCircle
are in pixels.
BackgroundView
therefore has to convert pixels to
dp’s
when putting a value into
touchPoint
for use by
Pearl
,
and it has to convert
dp’s
back to pixels when using the values that it gets back from
Pearl
.
These simple conversions are performed by division and multiplication by
density
,
which is the number of pixels per
dp.
MainActivity.java
:
onCreate
creates a
BackgroundView
.
BackgroundView.java
Pearl.java
activity_main.xml
:
unused and ignored.
AndroidManifest.xml
build.gradle
(Module: app).
Point your browser at
html5.html
and click and drag on the screen.
Then select
View Source
or
Page Source
.
In Safari, it’s the Develop menu.
In Chrome, it’s in the View menu.
03-20 08:34:00.772 1382-1382/edu.nyu.scps.pearl I/Choreographer: Skipped 32 frames! The application may be doing too much work on its main thread.
getCenter
method of class
Pearl
a security risk?
It returns a reference to a private field of the class,
and the caller could use that reference
to change the value of the field.
Is there a way to prevent this?
Is any other method a risk to the security of this field?
backgroundView
in the
onCreate
method of
MainActivity
will have to be mentioned in more than one method of this class,
so make it a field of the class.
private BackgroundView backgroundView;In
onCreate
,
change
BackgroundView backgroundView = new BackgroundView(this);to
backgroundView = new BackgroundView(this);
MainActivity
:
private SensorManager sensorManager; //Can't initialize them here. private Sensor sensor;Initialize them in
onCreate
before creating the
BackgroundView
.
sensorManager = (SensorManager)getSystemService(Context.SENSOR_SERVICE); sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
MainActivity
will be the listener that will be plugged into the sensor.
public class MainActivity extends AppCompatActivity implements SensorEventListener {Listening to the sensor is expensive. The
MainActivity
should listen only when the
MainActivity
is in the foreground.
Add the following two
lifecycle
methods
to class
MainActivity
.
To get Android Studio to type some of the code in for you,
click on the word
MainActivity
in the file
MainActivity.java
and pull down
onResume
,
and press OK.
@Override protected void onResume() { super.onResume(); if (sensor != null) { sensorManager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_NORMAL); } } @Override protected void onPause() { super.onPause(); if (sensor != null) { sensorManager.unregisterListener(this); } }What should the listener (which is the
MainActivity
object)
do when it hears an acceleration?
Add the following two methods to the listener.
Can we simplify the
switch
by calling
remapCoordinateSystem
?
See
One
Screen Turn Deserves Another.
Alternatively, we could have kept the
Activity
object alive during a change of orientation.
@Override public void onSensorChanged(SensorEvent sensorEvent) { //Use only two out of the three values. float x = sensorEvent.values[0]; float y = sensorEvent.values[1]; WindowManager windowManager = (WindowManager)getSystemService(Context.WINDOW_SERVICE); Display defaultDisplay = windowManager.getDefaultDisplay(); int rotation = defaultDisplay.getRotation(); switch (rotation) { case Surface.ROTATION_0: backgroundView.setAcceleration(-x, y); break; case Surface.ROTATION_90: backgroundView.setAcceleration(y, x); break; case Surface.ROTATION_180: backgroundView.setAcceleration(x, -y); break; case Surface.ROTATION_270: backgroundView.setAcceleration(-y, -x); break; default: break; } } @Override public void onAccuracyChanged(Sensor sensor, int accuracy) { }
BackgroundView
the following field,
with a setter and getter.
private float acceleration[] = {0f, 9.81f}; //Gravity downwards by default. public void setAcceleration(float x, float y) { acceleration[0] = x; acceleration[1] = y; } public float getAcceleration(int i) { if (i == 0 || i == 1) { return acceleration[i]; } return 0f; }
Pearl
a new field.
The class will now need a constructor to initialize the new field.
The constructor for class
BackgroundView
will pass
this
as an argument to the constructor for class
Pearl
.
private BackgroundView backgroundView; Pearl(BackgroundView backgroundView) { this.backgroundView = backgroundView; //this.backgroundView is the field, backgroundView is the argument }The
force
in
drawTowards
will now depend on the horizontal and vertical g-forces
experienced by the Android device.
final PointF force = new PointF( (p.x - center.x) * elasticity + backgroundView.getAcceleration(0) * gravity * mass, (p.y - center.y) * elasticity + backgroundView.getAcceleration(1) * gravity * mass );On my Azpen A727 tablet, I tuned the value of the static field
Pearl.gravity
from
3.0f
to
.3f
.
Java programmers should note
that we should have created an interface containing the methods
setAcceleration
and
getAcceleration
.
Instead of containing a reference to a
BackgroundView
,
a
Pearl
should have contained a reference to an object that implements this interface.