Search code examples
androidparallax

Parallax with accelerometers without using libraries


I want a custom view that moves depending on the accelerometers and creating the parallax effect.

Now I have the custom view listening the accelerometers values but, how I can use these values to move the view properly?

code:

public class ParallaxView extends AppCompatImageView
        implements SensorEventListener {

    private static final int SENSOR_DELAY = SensorManager.SENSOR_DELAY_FASTEST;

    //...

    public ParallaxView(Context context) {
        super(context);
    }

    public ParallaxView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public ParallaxView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    public void init() {
        WindowManager windowManager = (WindowManager) getContext().getSystemService(WINDOW_SERVICE);
        mDisplay = windowManager.getDefaultDisplay();
        mSensorManager = (SensorManager) getContext().getSystemService(SENSOR_SERVICE);
        mAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
    }

    public void setNewPosition(
            @Nullable Float sensorX,
            @Nullable Float sensorY) {
        // ???
    }

    //...

    @Override
    public void onSensorChanged(SensorEvent event) {
        if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER){
            setNewPosition(event.values[0], event.values[1]);
        }
    }

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

    }

    public void registerSensorListener() {
        mSensorManager.registerListener(this, mAccelerometer, SENSOR_DELAY);
    }

    public void unregisterSensorListener() {
        mSensorManager.unregisterListener(this);
    }
}

Use this view in the Activity:

@Override
protected void onCreate(Bundle savedInstanceState) {
    //...
    mParallaxView.init();
}

@Override
protected void onResume() {
    mParallaxView.registerSensorListener();
    super.onResume();
}

@Override
protected void onPause() {
    mParallaxView.unregisterSensorListener();
    super.onPause();
}

Thanks in advance


Solution

  • Finally I created my custom view for get what I want.

    Here the repository: https://github.com/GVMarc/ParallaxView

    Here the code:

    public class ParallaxView extends AppCompatImageView implements SensorEventListener {
    
        private static final int DEFAULT_SENSOR_DELAY = SensorManager.SENSOR_DELAY_FASTEST;
        public static final int DEFAULT_MOVEMENT_MULTIPLIER = 3;
        public static final int DEFAULT_MIN_MOVED_PIXELS = 1;
        private static final float DEFAULT_MIN_SENSIBILITY = 0;
    
        private float mMovementMultiplier = DEFAULT_MOVEMENT_MULTIPLIER;
        private int mSensorDelay = DEFAULT_SENSOR_DELAY;
        private int mMinMovedPixelsToUpdate = DEFAULT_MIN_MOVED_PIXELS;
        private float mMinSensibility = DEFAULT_MIN_SENSIBILITY;
    
        private float mSensorX;
        private float mSensorY;
        private Float mFirstSensorX;
        private Float mFirstSensorY;
        private Float mPreviousSensorX;
        private Float mPreviousSensorY;
    
        private float mTranslationX = 0;
        private float mTranslationY = 0;
    
        private SensorManager mSensorManager;
        private Sensor mAccelerometer;
    
        public enum SensorDelay {
            FASTEST,
            GAME,
            UI,
            NORMAL
        }
    
        public ParallaxView(Context context) {
            super(context);
        }
    
        public ParallaxView(Context context, AttributeSet attrs) {
            super(context, attrs);
        }
    
        public ParallaxView(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
        }
    
        public void init() {
            mSensorManager = (SensorManager) getContext().getSystemService(SENSOR_SERVICE);
            mAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
        }
    
        private void setNewPosition() {
            int destinyX = (int) ((mFirstSensorX - mSensorX) * mMovementMultiplier);
            int destinyY = (int) ((mFirstSensorY - mSensorY) * mMovementMultiplier);
    
            calculateTranslationX(destinyX);
            calculateTranslationY(destinyY);
        }
    
        private void calculateTranslationX(int destinyX) {
            if (mTranslationX + mMinMovedPixelsToUpdate < destinyX)
                mTranslationX++;
            else if (mTranslationX - mMinMovedPixelsToUpdate > destinyX)
                mTranslationX--;
        }
    
        private void calculateTranslationY(int destinyY) {
            if (mTranslationY + mMinMovedPixelsToUpdate < destinyY)
                mTranslationY++;
            else if (mTranslationY - mMinMovedPixelsToUpdate > destinyY)
                mTranslationY--;
        }
    
        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            setTranslationX(mTranslationX);
            setTranslationY(mTranslationY);
            invalidate();
        }
    
        @Override
        public void onSensorChanged(SensorEvent event) {
            if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
                mSensorX = event.values[0];
                mSensorY = -event.values[1];
    
                manageSensorValues();
            }
        }
    
        private void manageSensorValues() {
            if (mFirstSensorX == null)
                setFirstSensorValues();
    
            if (mPreviousSensorX == null || isSensorValuesMovedEnough()) {
                setNewPosition();
                setPreviousSensorValues();
            }
        }
    
        private void setFirstSensorValues() {
            mFirstSensorX = mSensorX;
            mFirstSensorY = mSensorY;
        }
    
        private void setPreviousSensorValues() {
            mPreviousSensorX = mSensorX;
            mPreviousSensorY = mSensorY;
        }
    
        private boolean isSensorValuesMovedEnough() {
            return mSensorX > mPreviousSensorX + mMinSensibility ||
                    mSensorX < mPreviousSensorX - mMinSensibility ||
                    mSensorY > mPreviousSensorY + mMinSensibility ||
                    mSensorY < mPreviousSensorX - mMinSensibility;
        }
    
        public void registerSensorListener() {
            mSensorManager.registerListener(this, mAccelerometer, mSensorDelay);
        }
    
        public void registerSensorListener(SensorDelay sensorDelay) {
            switch (sensorDelay) {
                case FASTEST:
                    mSensorDelay = SensorManager.SENSOR_DELAY_FASTEST;
                    break;
                case GAME:
                    mSensorDelay = SensorManager.SENSOR_DELAY_GAME;
                    break;
                case UI:
                    mSensorDelay = SensorManager.SENSOR_DELAY_UI;
                    break;
                case NORMAL:
                    mSensorDelay = SensorManager.SENSOR_DELAY_NORMAL;
                    break;
            }
            registerSensorListener();
        }
    
        public void unregisterSensorListener() {
            mSensorManager.unregisterListener(this);
        }
    
        public void setMovementMultiplier(float multiplier) {
            mMovementMultiplier = multiplier;
        }
    
        public void setMinimumMovedPixelsToUpdate(int minMovedPixelsToUpdate) {
            mMinMovedPixelsToUpdate = minMovedPixelsToUpdate;
        }
    
        public void setMinimumSensibility(int minSensibility) {
            mMinSensibility = minSensibility;
        }
    
        @Override
        public void onAccuracyChanged(Sensor sensor, int i) {
        }
    }