[Design, Prototyping, UI, Graphics] gerosyab:
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.

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;

[COLOR="SeaGreen"] if(xCon != 0 && yCon != 0){
radian = (float) Math.atan2(yCon, xCon);
}[/COLOR]

[COLOR="RoyalBlue"] xCon = (float) (Math.cos(radian) * boundary);
yCon = (float) (Math.sin(radian) * boundary);[/COLOR]
}
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;

}

@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.

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