This guide is about floating, moving a point in coordinate system.
It may be useful to make spirit level (bubble level) or magic 8 ball and so on :)
Just use device's accelerometer sensor to moving a center point.
I wrote android custom view which implements SensorEventListener.
Do some initialization (measuring screen size, set boundary size, assign values...) first.
Draw the x, y axis and boundary and little circle.
In end of the onDraw(), invalidate() makes little circle keep moving.

Important formula to make little circle inside circular boundary is below.
Code:
private void calc(){
//for simulating object floating on water
//against gravity
xCon += mSensorX;
yCon -= mSensorY;
/*
//for simulating object rolling on ground
//adjust to gravity
xCon -= mSensorX;
yCon += mSensorY;
*/
//for circular boundary
if(Math.pow(xCon, 2) + Math.pow(yCon, 2) >= boundarySquare){
isBoundaryOut = true;
if(xCon != 0 && yCon != 0){
radian = (float) Math.atan2(yCon, xCon);
}
xCon = (float) (Math.cos(radian) * boundary);
yCon = (float) (Math.sin(radian) * boundary);
}
else{
isBoundaryOut = false;
}
}
add the sensor's value to x, y coordinate (xCon, yCon) and check it is out of the circular boundary.
If it is change the value with the Formula
x = cos(atan2(y, x)) * CIRCULAR_BOUDNARY_RADIUS
y = sin(atan2(y, x)) * CIRCULAR_BOUDNARY_RADIUS
You can select the circle to float on water or roll on ground just change addition <-> subtraction.
- For simulating object floating on water, against gravity
xCon += mSensorX;
yCon -= mSensorY;- For simulating object rolling on ground, adjust to gravity
xCon -= mSensorX;
yCon += mSensorY;Also you can change boundary shape easily
For rectangle boundary
if(xCon > horizontalBound){
xCon = horizontalBound;
}
else if(xCon < -horizontalBound){
xCon = -horizontalBound;
}
if(yCon > verticalBound){
yCon = verticalBound;
}
else if(yCon < -verticalBound){
yCon = -verticalBound;
} Code:
@Override
public void onSensorChanged(SensorEvent event) {
if (event.sensor.getType() != Sensor.TYPE_ACCELEROMETER)
return;
mSensorX = sensor_weight * event.values[0];
mSensorY = sensor_weight * event.values[1];
}
onSensorChanged() I just add sensor values(weighted) to center point x, y and it seems to be quite enough to do rough simulation.
Whole source code of my custom view is like below.
Code:
package net.gerosyab.circularboundary;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.util.AttributeSet;
import android.view.Display;
import android.view.Surface;
import android.view.View;
import android.view.WindowManager;
public class MyView extends View implements SensorEventListener{
Context context;
//weight for calculating speed of floating image
//multiplied with accelrometer sensor value
//faster if it is more than 1
//slower if it is less than 1
float sensor_weight = 2.15f;
float width;
float height;
float cx;
float cy;
float x;
float y;
float xCon, yCon;
float boundary;
float boundarySquare;
float dotRadius = 15;
float radian;
Paint linePaint, circlePaint, dotPaint, textPaint;
float horizontalBound;
float verticalBound;
boolean isBoundaryOut = false;
private float mSensorX;
private float mSensorY;
private SensorManager mSensorManager;
private Sensor mAccelerometer;
private WindowManager mWindowManager;
private Display mDisplay;
public MyView(Context context) {
super(context);
this.context = context;
init();
}
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
this.context = context;
init();
}
public MyView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
this.context = context;
init();
}
private void init(){
linePaint = new Paint();
circlePaint = new Paint();
dotPaint = new Paint();
textPaint = new Paint();
linePaint.setColor(Color.WHITE );
linePaint.setAntiAlias(true);
linePaint.setStrokeWidth(2);
linePaint.setStyle(Paint.Style.STROKE);
circlePaint.setColor(Color.YELLOW);
circlePaint.setAntiAlias(true);
circlePaint.setStrokeWidth(2);
circlePaint.setStyle(Paint.Style.STROKE);
dotPaint.setColor(Color.RED);
dotPaint.setAntiAlias(true);
dotPaint.setStrokeWidth(5);
dotPaint.setStyle(Paint.Style.FILL);
textPaint.setColor(Color.WHITE);
textPaint.setAntiAlias(true);
textPaint.setStyle(Paint.Style.FILL_AND_STROKE);
textPaint.setTextSize(40);
x = cx;
y = cy;
xCon = 0;
yCon = 0;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//draw x, y axis
canvas.drawLine(cx, 0, cx, height, linePaint);
canvas.drawLine(0, cy, width, cy, linePaint);
//draw circular boundary
canvas.drawCircle(cx, cy, boundary, circlePaint);
canvas.drawRect(cx - horizontalBound, cy - verticalBound, cx + horizontalBound, cy + verticalBound, circlePaint);
calc();
//draw dot
canvas.drawCircle(xCon + cx, yCon + cy, dotRadius, dotPaint);
//draw text
canvas.drawText("isBoundaryOut : " + isBoundaryOut, 100, 50, textPaint);
canvas.drawText("sensorX : " + mSensorX, 100, 100, textPaint);
canvas.drawText("sensorY : " + mSensorY, 100, 150, textPaint);
canvas.drawText("xCon : " + xCon, 100, 200, textPaint);
canvas.drawText("yCon : " + yCon, 100, 250, textPaint);
canvas.drawText("cx : " + cx, 100, 300, textPaint);
canvas.drawText("cy : " + cy, 100, 350, textPaint);
canvas.drawText("horizontalBound : " + horizontalBound, 100, 400, textPaint);
canvas.drawText("verticalBound : " + verticalBound, 100, 450, textPaint);
invalidate();
}
private void calc(){
//for simulating object floating on water
//against gravity
xCon += mSensorX;
yCon -= mSensorY;
/*
//for simulating object rolling on ground
//adjust to gravity
xCon -= mSensorX;
yCon += mSensorY;
*/
/*
//for rectangle boundary
if(xCon > horizontalBound){
xCon = horizontalBound;
}
else if(xCon < -horizontalBound){
xCon = -horizontalBound;
}
if(yCon > verticalBound){
yCon = verticalBound;
}
else if(yCon < -verticalBound){
yCon = -verticalBound;
}
*/
//for circular boundary
if(Math.pow(xCon, 2) + Math.pow(yCon, 2) >= boundarySquare){
isBoundaryOut = true;
if(xCon != 0 && yCon != 0){
radian = (float) Math.atan2(yCon, xCon);
}
xCon = (float) (Math.cos(radian) * boundary);
yCon = (float) (Math.sin(radian) * boundary);
}
else{
isBoundaryOut = false;
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
width = getWidth();
height = getHeight();
cx = width / 2;
cy = height / 2;
boundary = width * 0.15f;
horizontalBound = boundary;
verticalBound = boundary;
boundarySquare = boundary * boundary;
mSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE);
mAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
// Get an instance of the WindowManager
mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
mDisplay = mWindowManager.getDefaultDisplay();
mSensorManager.registerListener(this, mAccelerometer, SensorManager.SENSOR_DELAY_UI);
}
@Override
public void onSensorChanged(SensorEvent event) {
if (event.sensor.getType() != Sensor.TYPE_ACCELEROMETER)
return;
switch (mDisplay.getRotation()) {
case Surface.ROTATION_0:
mSensorX = sensor_weight * event.values[0];
mSensorY = sensor_weight * event.values[1];
break;
case Surface.ROTATION_90:
mSensorX = sensor_weight * -event.values[1];
mSensorY = sensor_weight * event.values[0];
break;
case Surface.ROTATION_180:
mSensorX = sensor_weight * -event.values[0];
mSensorY = sensor_weight * -event.values[1];
break;
case Surface.ROTATION_270:
mSensorX = sensor_weight * event.values[1];
mSensorY = sensor_weight * -event.values[0];
break;
}
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
mSensorManager.unregisterListener(this);
}
}
Hope this is helpful!
Thanks :D