Welcome to the Treehouse Community

Want to collaborate on code errors? Have bugs you need feedback on? Looking for an extra set of eyes on your latest project? Get support with fellow developers, designers, and programmers of all backgrounds and skill levels here with the Treehouse Community! While you're at it, check out some resources Treehouse students have shared here.

Looking to learn something new?

Treehouse offers a seven day free trial for new students. Get access to thousands of hours of content and join thousands of Treehouse students and alumni in the community today.

Start your free trial

Android

Shake detector Fun facts

I want to implement a shake detector in my Fun Facts app, so that the user can either click the button or shake the phone to go to the next fact. I'm new to android and having difficulty understanding how to implement transitions on this simple app. I understand how the button works because it's built in. I got this shakedetector,java file from a previous retired project in Treehouse, and I'm unable to watch the video from that retired section for some reason.

This is the shake detector.

import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;

public class ShakeDetector implements SensorEventListener {

    // Minimum acceleration needed to count as a shake movement
    private static final int MIN_SHAKE_ACCELERATION = 5;

    // Minimum number of movements to register a shake
    private static final int MIN_MOVEMENTS = 2;

    // Maximum time (in milliseconds) for the whole shake to occur
    private static final int MAX_SHAKE_DURATION = 500;

    // Arrays to store gravity and linear acceleration values
    private float[] mGravity = { 0.0f, 0.0f, 0.0f };
    private float[] mLinearAcceleration = { 0.0f, 0.0f, 0.0f };

    // Indexes for x, y, and z values
    private static final int X = 0;
    private static final int Y = 1;
    private static final int Z = 2;

    // OnShakeListener that will be notified when the shake is detected
    private OnShakeListener mShakeListener;

    // Start time for the shake detection
    long startTime = 0;

    // Counter for shake movements
    int moveCount = 0;

    // Constructor that sets the shake listener
    public ShakeDetector(OnShakeListener shakeListener) {
        mShakeListener = shakeListener;
    }

    @Override
    public void onSensorChanged(SensorEvent event) {
        // This method will be called when the accelerometer detects a change.

        // Call a helper method that wraps code from the Android developer site
        setCurrentAcceleration(event);

        // Get the max linear acceleration in any direction
        float maxLinearAcceleration = getMaxCurrentLinearAcceleration();

        // Check if the acceleration is greater than our minimum threshold
        if (maxLinearAcceleration > MIN_SHAKE_ACCELERATION) {
            long now = System.currentTimeMillis();

            // Set the startTime if it was reset to zero
            if (startTime == 0) {
                startTime = now;
            }

            long elapsedTime = now - startTime;

            // Check if we're still in the shake window we defined
            if (elapsedTime > MAX_SHAKE_DURATION) {
                // Too much time has passed. Start over!
                resetShakeDetection();
            }
            else {
                // Keep track of all the movements
                moveCount++;

                // Check if enough movements have been made to qualify as a shake
                if (moveCount > MIN_MOVEMENTS) {
                    // It's a shake! Notify the listener.
                    mShakeListener.onShake();

                    // Reset for the next one!
                    resetShakeDetection();
                }
            }
        }
    }

    @Override
    public void onAccuracyChanged(Sensor sensor, int accuracy) {
        // Intentionally blank
    }

    private void setCurrentAcceleration(SensorEvent event) {
        /*
         *  BEGIN SECTION from Android developer site. This code accounts for 
         *  gravity using a high-pass filter
         */

        // alpha is calculated as t / (t + dT)
        // with t, the low-pass filter's time-constant
        // and dT, the event delivery rate

        final float alpha = 0.8f;

        // Gravity components of x, y, and z acceleration
        mGravity[X] = alpha * mGravity[X] + (1 - alpha) * event.values[X];
        mGravity[Y] = alpha * mGravity[Y] + (1 - alpha) * event.values[Y];
        mGravity[Z] = alpha * mGravity[Z] + (1 - alpha) * event.values[Z];

        // Linear acceleration along the x, y, and z axes (gravity effects removed)
        mLinearAcceleration[X] = event.values[X] - mGravity[X];
        mLinearAcceleration[Y] = event.values[Y] - mGravity[Y];
        mLinearAcceleration[Z] = event.values[Z] - mGravity[Z];

        /*
         *  END SECTION from Android developer site
         */
    }

    private float getMaxCurrentLinearAcceleration() {
        // Start by setting the value to the x value
        float maxLinearAcceleration = mLinearAcceleration[X];

        // Check if the y value is greater
        if (mLinearAcceleration[Y] > maxLinearAcceleration) {
            maxLinearAcceleration = mLinearAcceleration[Y];
        }

        // Check if the z value is greater
        if (mLinearAcceleration[Z] > maxLinearAcceleration) {
            maxLinearAcceleration = mLinearAcceleration[Z];
        }

        // Return the greatest value
        return maxLinearAcceleration;
    }

    private void resetShakeDetection() {
        startTime = 0;
        moveCount = 0;
    }

    /*
     * Definition for OnShakeListener definition. I would normally put this
     * into it's own .java file, but I included it here for quick reference
     * and to make it easier to include this file in our project.
     */
    public interface OnShakeListener {
        public void onShake();
    }
}

Here are my files:

Here are the files:

colorwheel.java:

package com.iamakash.funfacts;

import android.graphics.Color;

import java.util.Random;

/**
 * Created by akash on 2015-07-26.
 */
public class colorwheel {

    public String[] mcolors = {
            "#39add1",
            "#3079ab",
            "#c25975",
            "#e15258",
            "#f9845b",
            "#838cc7",
            "#7d669e",
            "#53bbb4",
            "#51b46d",
            "#e0ab18",
            "#637a91",
            "#f092b0",
            "#b7c0c7",
            "#4169E1",
            "#F0F8FF",
            "#FFEBCD",
            "#F5F5DC",
            "#FFE4C4 ",
         "#000000",
            "#FFEBCD",
           "#0000FF",
           "#8A2BE2",
    };
    public int getcolor(){

        //button clicked, update with new fact
        String COLOR = "";
        //randomly select fact
        Random randomGenerator = new Random();
        int randomNumber = randomGenerator.nextInt(mcolors.length);
        COLOR=mcolors[randomNumber];
        int colorAsInt= Color.parseColor(COLOR);
        return colorAsInt;
    }


}

factbook.java:

import java.util.Random;


public class factBook {

    public String[] mfacts = {
            "Ants stretch when they wake up in the morning.",
            "Ostriches can run faster than horses.",
            "Olympic gold medals are actually made mostly of silver.",
   "You are born with 300 bones; by the time you are an adult you will have 206.",
         "It takes about 8 minutes for light from the Sun to reach  Earth.",
       "Some bamboo plants can grow almost a meter in just one day.",

    };
    public String getFact(){

    //button clicked, update with new fact
    String fact = "";
    //randomly select fact
    Random randomGenerator = new Random();
    int randomNumber = randomGenerator.nextInt(mfacts.length);
    fact=mfacts[randomNumber];
    return fact;
    }


}

MainActivity.Java:

import android.app.Activity;
import android.app.DatePickerDialog;
import android.graphics.Color;
import android.support.v7.app.ActionBar;
import android.support.v7.app.ActionBarActivity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.RelativeLayout;
import android.widget.TextView;
import java.util.*;


public class FunFactsActivity extends Activity {
    private factBook mFactBook=new factBook();
    private colorwheel mcolorwheel= new colorwheel();


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_fun_facts);


    //declare view variables

    final TextView factlabel = (TextView) findViewById(R.id.facttextView);
    final Button showFactButton = (Button) findViewById(R.id.showfactbutton);
        final RelativeLayout relativeLayout=(RelativeLayout) findViewById(R.id.relativeLayout);
        View.OnClickListener Listener = new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                String fact= mFactBook.getFact();



                //update label with dynamic fact

                factlabel.setText(fact);
                int color=mcolorwheel.getcolor();
                relativeLayout.setBackgroundColor(color);



            };
        };


    showFactButton.setOnClickListener(Listener);
};



    };

Can someone explain where I would implement the shake detector. Thanks!

1 Answer

Hi Akash!

I think what you're doing is a cool addition to the Fun Facts app!

Ok, it looks like you're all set up, you just need to utilize the ShakeDetector.OnShakeListener.onShake() method in order for you to show a new fact.

before jumping into this code, read this doc on SensorManager and make sure to check out the code snippet.

public class FunFactsActivity extends Activity {
...
    private ShakeDetector mShakeDetector;
    private SensorManager mSensorManager;
    private Sensor mAccelerometer;
...

  @Override
    protected void onCreate(Bundle savedInstanceState) {
....

        mSensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
        mAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
        mShakeDetector = new ShakeDetector(new ShakeDetector.OnShakeListener() {
            @Override
            public void onShake() {
                showFact();
            }
        });

    }

...
    @Override
    protected void onResume() {
        super.onResume();
        mSensorManager.registerListener(mShakeDetector, mAccelerometer, SensorManager.SENSOR_DELAY_UI);  
// Registers (mShakeDetector which implements) SensorEventListener
    }

    @Override
    protected void onPause() {
        mSensorManager.unregisterListener(mShakeDetector);
        super.onPause();
    }

}

For best practice, add uses-feature for the accelerometer in AndroidManifest.xml

<uses-feature android:name="android.hardware.sensor.accelerometer" android:required="true"/>

Reference: http://stackoverflow.com/a/11972661/3743265 Ben's Answer :-)

feel free to leave me a comment on this Answer if something doesn't make sense.

Good luck and happy coding! :-)

Thanks for the response! So, the code you provided, is that to be added in between my code? Also, how would i be able to test this on the android emulator, since I can't physically shake it

I can't figure it out, I'm getting so many errors, can you help me fix the code please!. Okay, so I have a file "ShakeDetector.java" which contains the first code snippet I posted in the question. What do I put in the FunFactsActivity.java file??

This is what I have so far, I tried combining your code with what I had before ( scroll up to see in question post).
Note: * Remember, I want user to be able to click the button AND shake the device, to go to the next fact*

package com.iamakash.funfacts;

import android.app.Activity;
import android.app.DatePickerDialog;
import android.content.Context;
import android.graphics.Color;
import android.hardware.Sensor;
import android.hardware.SensorManager;
import android.support.v7.app.ActionBar;
import android.support.v7.app.ActionBarActivity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.RelativeLayout;
import android.widget.TextView;
import java.util.*;



public class FunFactsActivity extends Activity {
    private ShakeDetector mShakeDetector;
    private SensorManager mSensorManager;
    private Sensor mAccelerometer;

    private factBook mFactBook=new factBook();
    private colorwheel mcolorwheel= new colorwheel();


    @Override
    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_fun_facts);
        final TextView factlabel = (TextView) findViewById(R.id.facttextView);
        final Button showFactButton = (Button) findViewById(R.id.showfactbutton);
        final RelativeLayout relativeLayout=(RelativeLayout) findViewById(R.id.relativeLayout);


        mSensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
        mAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
        mShakeDetector = new ShakeDetector(new ShakeDetector.OnShakeListener() {


            @Override
            public void onShake() {
                mFactBook.getFact();
            }
        });


        protected void onResume(); {
                super.onResume();
                mSensorManager.registerListener(mShakeDetector, mAccelerometer, SensorManager.SENSOR_DELAY_UI);
            }


        protected void onPause() {
            mSensorManager.unregisterListener(mShakeDetector);
            super.onPause();
        }





    //declare view variables

                @Override
                public void onClick(View view); {
                    String fact= mFactBook.getFact();

                //update label with dynamic fact

                factlabel.setText(fact);
                int color=mcolorwheel.getcolor();
                relativeLayout.setBackgroundColor(color);



            };
        };


    showFactButton.setOnClickListener(Listener);
}
            }

I'm getting red errors on the "Onresume" and "Onpause" methods, as well as Public void OnClick, as well as the last
"showFactButton.setOnClickListener(Listener);"

I really appreciate your help, please respond ASAP=)

"Thanks for the response! So, the code you provided, is that to be added in between my code? Also, how would i be able to test this on the android emulator, since I can't physically shake it"

Yes, the code that i posted is to be added in with your FunFactsActivity.java class. as for the emulator accelerometer question, I think Genymotion can do that, you have to check, my advice is to post this question on another thread, because its another question and you'll get more answers :-).

"Note: * Remember, I want user to be able to click the button AND shake the device, to go to the next fact"

Your first question was "user can either click the button or shake the phone to go to the next fact" , i'm confused, so i'll continue assuming that its an OR, if that isn't the case and you want the user to press the button AND(then) shake then you just need to create a flag that you turn on if the button is pressed and when there is a shake, show the new fact if the flag is on and then reset the flag to false.

This is the complete java class, you just need to copy and paste it. but, i highly recommend for you to go through the Java course here at Team Treehouse, because you have some basic Java mistake & bad practices.

public class FunFactsActivity extends Activity {

    private ShakeDetector mShakeDetector;
    private SensorManager mSensorManager;
    private Sensor mAccelerometer;

    private factbook mFactBook = new factbook();
    private colorwheel mcolorwheel = new colorwheel();


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_fun_facts);


        //declare view variables

        final TextView factlabel = (TextView) findViewById(R.id.facttextView);
        final Button showFactButton = (Button) findViewById(R.id.showfactbutton);
        final RelativeLayout relativeLayout = (RelativeLayout) findViewById(R.id.relativeLayout);
        View.OnClickListener Listener = new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                String fact = mFactBook.getFact();
                //update label with dynamic fact
                factlabel.setText(fact);
                int color = mcolorwheel.getcolor();
                relativeLayout.setBackgroundColor(color);
            }
        };

        showFactButton.setOnClickListener(Listener);

        mSensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
        mAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
        mShakeDetector = new ShakeDetector(new ShakeDetector.OnShakeListener() {
            @Override
            public void onShake() {
                String fact = mFactBook.getFact();
                //update label with dynamic fact
                factlabel.setText(fact);
                int color = mcolorwheel.getcolor();
                relativeLayout.setBackgroundColor(color);
            }
        });
    }

    @Override
    protected void onResume() {
        super.onResume();
        mSensorManager.registerListener(mShakeDetector, mAccelerometer, SensorManager.SENSOR_DELAY_UI);
        // Registers (mShakeDetector which implements) SensorEventListener
    }

    @Override
    protected void onPause() {
        mSensorManager.unregisterListener(mShakeDetector);
        super.onPause();
    }
}

I hope this helps. Good luck ! :-)