Search code examples
android-layoutandroid-animationaccelerometer

Moving ball with accelerometer inside a view on camera preview in android


I have been searching and coding for 3 days now on this problem, no result :(

I made a camera overlay which the code is here + the Accelerometer

public class CameraActivity extends Activity implements SurfaceHolder.Callback, SensorEventListener {
    Camera camera;
    SurfaceView surfaceView;
    SurfaceHolder surfaceHolder;
    boolean previewing = false;
    LayoutInflater controlInflater = null;

    static String TAG = CameraActivity.class.getSimpleName();

    // Accelerometer
    private SensorManager mSensorManager;
    private Sensor mAccelerometer;


    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.surface_view);
        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);

        surfaceView = (SurfaceView) findViewById(R.id.camerapreview);
        surfaceHolder = surfaceView.getHolder();
        surfaceHolder.addCallback(this);
        surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);

        controlInflater = LayoutInflater.from(getBaseContext());
        View viewControl = controlInflater.inflate(R.layout.camera_control,
                null);

        LayoutParams layoutParamsControl = new LayoutParams(
                LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);

        this.addContentView(viewControl, layoutParamsControl);


        // Accelerometer
        mSensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
        mAccelerometer = mSensorManager
                .getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
        mSensorManager.registerListener(this, mAccelerometer,
                SensorManager.SENSOR_DELAY_NORMAL);

    }

    @Override
    public void onAccuracyChanged(Sensor sensor, int accuracy) {

    }

    @Override
    public void onSensorChanged(SensorEvent event) {

    }


    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width,
            int height) {
        // TODO Auto-generated method stub
        if (previewing) {
            camera.stopPreview();
            previewing = false;
        }

        if (camera != null) {
            try {
                camera.setPreviewDisplay(surfaceHolder);
                camera.startPreview();
                previewing = true;
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        camera = Camera.open();
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        camera.stopPreview();
        camera.release();
        camera = null;
        previewing = false;
    }
}

Then in the camera_control.xml I have this:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/layout_holder"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:background="@android:color/transparent"
    android:gravity="bottom" >

    <ImageView
        android:id="@+id/imageView2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignBottom="@+id/camera_red_button"
        android:layout_alignLeft="@+id/camera_back_button"
        android:layout_marginBottom="27dp"
        android:src="@drawable/camera_accelerometer_red" />

</RelativeLayout>

camera_accelerometer_red

ball

and in the surface_view.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:id="@+id/camerapreview_holder"
    >

<SurfaceView
    android:id="@+id/camerapreview"  
    android:layout_width="fill_parent" 
    android:layout_height="fill_parent" 
    />
</LinearLayout>

The Image View is a image for spirit level.

The idea is user needs to level the camera before she takes a photo. I have cleared all the un-nessessery code. The above code are working code.

Question: I need to make a ball for the spirit level. it doesnt need to be the smoothest spirit level. If I can find the way to confine the ball within the spirit level and move it based on the Accelerometer results I will be as happy as Larry :)/

If someone please put me in the right direction regarding these 3 things:

  1. confining the animation within an area
  2. confinement area to be Circle, how to make it

Thanks in advance. H.


Solution

  • Ok I have got the answer. Hope it can help someone. You need to use calculate the distance from the middle to the corner (radius) the offset the corners.

    Also I have change the Accelerometor to Orientation Sensor which then I can get the 3d rotation of the phone in the X and Y Axis.

    The following code is working code and I tested them. I am using the Android 2.3.3+ The ball movement is not very smooth as this is not the purpose of the application.

    I think in order to smooth the movement you might need to add the timer and collision detection to it too. Please check the android sample.

    I haven't refactor the code too yet. So this is not a production level code :)

    Codes:

    public class CameraActivity extends Activity implements SurfaceHolder.Callback,
            SensorEventListener {
    
        // Accelerometer
        private SensorManager mSensorManager;
        private Sensor mAccelerometer;
    
        /** Called when the activity is first created. */
        public static float x;
        public static float y;
    
        FrameLayout layout_holder;
        FrameLayout ball_holder;
    
        // private float hOriginSize;
        float halfOfWidth;
        int centerYOnImage;
        private Sensor mOrientation;
    
            // float viewInset = 14.0f; // I remove this simply to make the code cleaner. I used this to calculate the radius and the offset later on
    
        /** Called when the activity is first created. */
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.surface_view);
            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
    
            surfaceView = (SurfaceView) findViewById(R.id.camerapreview);
            surfaceHolder = surfaceView.getHolder();
            surfaceHolder.addCallback(this);
            surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
    
            controlInflater = LayoutInflater.from(getBaseContext());
            View viewControl = controlInflater.inflate(R.layout.camera_control,
                    null);
    
            LayoutParams layoutParamsControl = new LayoutParams(
                    LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
    
            this.addContentView(viewControl, layoutParamsControl);
    
            // Accelerometer
            mSensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
            mOrientation = mSensorManager.getDefaultSensor(Sensor.TYPE_ORIENTATION);
            mSensorManager.registerListener(this, mAccelerometer,
                    SensorManager.SENSOR_DELAY_NORMAL);
    
            Bitmap bitmap = BitmapFactory.decodeResource(getResources(),
                    R.drawable.ball);
            CustomDrawableView mCustomDrawableView = new CustomDrawableView(this,
                    bitmap);
    
            ball_holder = (FrameLayout) findViewById(R.id.ball_holder);
            ball_holder.addView(mCustomDrawableView);
    
            halfOfWidth = 40; // You can calculate this, I just put this so I can test it. This is the half of the width of target image - attached in the question
    
            centerYOnImage = 40; // Not important :)
    
        }
    
        @Override
        public void onAccuracyChanged(Sensor sensor, int accuracy) {
    
        }
    
        // This method will update the UI on new sensor events
        public void onSensorChanged(SensorEvent event) {
    
            // float azimuth_angle = event.values[0];
            x = event.values[1];
            y = event.values[2];
    
            // FIXME: Fine tune this for the image taking part
            float ratio = 70.0f / 25.0f;
            x = x * ratio;
            y = y * ratio;
    
            Log.d(TAG, "x and y: " + x + " " + y);
    
            float maxDistance = 35; // To calculate this halfOfWidth - viewInset;
    
                    // to calculate between 2 distances 
            float distance = (float) Math.sqrt(((x) * (x)) + ((y) * (y)));
    
            if (distance > maxDistance) {
    
                float angle = (float) Math.atan2(x, y);
    
                / Get new point on the edge of the circle
                y = (float) (Math.cos(angle) * maxDistance);
                x = (float) (Math.sin(angle) * maxDistance);
            }
    
            x = x + 40; // 40 is the half od the distance of the full width
            y = (y * -1.0f) + 40; // -1.0f is so orientation works like the actual spirit level
    
            canUserTakePhoto(distance);
    
        }
    
        // Change the background
        public void canUserTakePhoto(float treshold) {
            if (treshold > 10) {
                // Not Yet
            } else {
                // take it
            }
        }
    
    
        @Override
        protected void onResume() {
            super.onResume();
            mSensorManager.registerListener(this, mOrientation,
                    SensorManager.SENSOR_DELAY_NORMAL);
        }
    
        @Override
        protected void onPause() {
            super.onPause();
            mSensorManager.unregisterListener(this);
        }
    
        public class CustomDrawableView extends ImageView {
    
            Bitmap b;
    
            public CustomDrawableView(Context context, Bitmap bitmap) {
                super(context);
    
                this.b = bitmap;
            }
    
            @Override
            protected void onDraw(Canvas canvas) {
    
                canvas.drawBitmap(this.b, x, y, null);
    
                invalidate();
            }
        }
    
        @Override
        public void onDestroy() // main thread stopped
        {
            super.onDestroy();
            // wait for threads to exit before clearing app
            System.runFinalizersOnExit(true);
            // remove app from memory
            android.os.Process.killProcess(android.os.Process.myPid());
        }
    
        @Override
        public void surfaceChanged(SurfaceHolder holder, int format, int width,
                int height) {
            // TODO Auto-generated method stub
            if (previewing) {
                camera.stopPreview();
                previewing = false;
            }
    
            if (camera != null) {
                try {
    
                    camera.setPreviewDisplay(surfaceHolder);
                    camera.startPreview();
                    previewing = true;
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }
    
        @Override
        public void surfaceCreated(SurfaceHolder holder) {
            // TODO Auto-generated method stub
            camera = Camera.open();
        }
    
        @Override
        public void surfaceDestroyed(SurfaceHolder holder) {
            // TODO Auto-generated method stub
            camera.stopPreview();
            camera.release();
            camera = null;
            previewing = false;
        }
    
    }
    

    Cheers.