不使用库的加速度计视差

Parallax with accelerometers without using libraries

我想要一个根据加速度计移动并创建视差效果的自定义视图。

现在我有自定义视图监听加速度计值,但是,我如何使用这些值正确移动视图?

代码:

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);
    }
}

在 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();
}

提前致谢

这是我以前用过的ParallaxImageViewclass:

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Matrix;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.util.AttributeSet;
import android.widget.ImageView;

import yourpackagename.R;

/**
* I did not write this, I just cant remember where I got it from and thought it could be useful for others
*/
 public class ParallaxImageView extends ImageView implements SensorEventListener {

private static final String TAG = ParallaxImageView.class.getName();

/**
 * If the x and y axis' intensities are scaled to the image's aspect ratio (true) or
 * equal to the smaller of the axis' intensities (false). If true, the image will be able to
 * translate up to it's view bounds, independent of aspect ratio. If not true,
 * the image will limit it's translation equally so that motion in either axis results
 * in proportional translation.
 */
private boolean mScaledIntensities = false;

/**
 * The intensity of the parallax effect, giving the perspective of depth.
 */
private float mParallaxIntensity = 1.15f;

/**
 * The maximum percentage of offset translation that the image can move for each
 * sensor input. Set to a negative number to disable.
 */
private float mMaximumJump = .1f;

// Instance variables used during matrix manipulation.
private SensorInterpreter mSensorInterpreter;
private SensorManager mSensorManager;
private Matrix mTranslationMatrix;
private float mXTranslation;
private float mYTranslation;
private float mXOffset;
private float mYOffset;

public ParallaxImageView(Context context) {
    this(context, null);
}

public ParallaxImageView(Context context, AttributeSet attrs) {
    this(context, attrs, 0);
}

public ParallaxImageView(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);

    // Instantiate future objects
    mTranslationMatrix = new Matrix();
    mSensorInterpreter = new SensorInterpreter();

    // Sets scale type
    setScaleType(ScaleType.MATRIX);

    // Set available attributes
    if (attrs != null) {
        final TypedArray customAttrs = context.obtainStyledAttributes(attrs, R.styleable.ParallaxImageView);

        if (customAttrs != null) {
            if (customAttrs.hasValue(R.styleable.ParallaxImageView_intensity))
                setParallaxIntensity(customAttrs.getFloat(R.styleable.ParallaxImageView_intensity, mParallaxIntensity));

            if (customAttrs.hasValue(R.styleable.ParallaxImageView_scaledIntensity))
                setScaledIntensities(customAttrs.getBoolean(R.styleable.ParallaxImageView_scaledIntensity, mScaledIntensities));

            if (customAttrs.hasValue(R.styleable.ParallaxImageView_tiltSensitivity))
                setTiltSensitivity(customAttrs.getFloat(R.styleable.ParallaxImageView_tiltSensitivity,
                        mSensorInterpreter.getTiltSensitivity()));

            if (customAttrs.hasValue(R.styleable.ParallaxImageView_forwardTiltOffset))
                setForwardTiltOffset(customAttrs.getFloat(R.styleable.ParallaxImageView_forwardTiltOffset,
                        mSensorInterpreter.getForwardTiltOffset()));

            customAttrs.recycle();
        }
    }

    // Configure matrix as early as possible by posting to MessageQueue
    post(new Runnable() {
        @Override
        public void run() {
            configureMatrix();
        }
    });
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    configureMatrix();
}

/**
 * Sets the intensity of the parallax effect. The stronger the effect, the more distance
 * the image will have to move around.
 *
 * @param parallaxIntensity the new intensity
 */
public void setParallaxIntensity(float parallaxIntensity) {
    if (parallaxIntensity < 1)
        throw new IllegalArgumentException("Parallax effect must have a intensity of 1.0 or greater");

    mParallaxIntensity = parallaxIntensity;
    configureMatrix();
}

/**
 * Sets the parallax tilt sensitivity for the image view. The stronger the sensitivity,
 * the more a given tilt will adjust the image and the smaller needed tilt to reach the
 * image bounds.
 *
 * @param sensitivity the new tilt sensitivity
 */
public void setTiltSensitivity(float sensitivity) {
    mSensorInterpreter.setTiltSensitivity(sensitivity);
}

/**
 * Sets the forward tilt offset dimension, allowing for the image to be
 * centered while the phone is "naturally" tilted forwards.
 *
 * @param forwardTiltOffset the new tilt forward adjustment
 */
public void setForwardTiltOffset(float forwardTiltOffset) {
    if (Math.abs(forwardTiltOffset) > 1)
        throw new IllegalArgumentException("Parallax forward tilt offset must be less than or equal to 1.0");

    mSensorInterpreter.setForwardTiltOffset(forwardTiltOffset);
}

/**
 * Sets whether translation should be limited to the image's bounds or should be limited
 * to the smaller of the two axis' translation limits.
 *
 * @param scaledIntensities the scaledIntensities flag
 */
public void setScaledIntensities(boolean scaledIntensities) {
    mScaledIntensities = scaledIntensities;
}

/**
 * Sets the maximum percentage of the image that image matrix is allowed to translate
 * for each sensor reading.
 *
 * @param maximumJump the new maximum jump
 */
public void setMaximumJump(float maximumJump) {
    mMaximumJump = maximumJump;
}

/**
 * Sets the image view's translation coordinates. These values must be between -1 and 1,
 * representing the transaction percentage from the center.
 *
 * @param x the horizontal translation
 * @param y the vertical translation
 */
private void setTranslate(float x, float y) {
    if (Math.abs(x) > 1 || Math.abs(y) > 1)
        throw new IllegalArgumentException("Parallax effect cannot translate more than 100% of its off-screen size");

    float xScale, yScale;

    if (mScaledIntensities) {
        // Set both scales to their offset values
        xScale = mXOffset;
        yScale = mYOffset;
    } else {
        // Set both scales to the max offset (should be negative, so smaller absolute value)
        xScale = Math.max(mXOffset, mYOffset);
        yScale = Math.max(mXOffset, mYOffset);
    }

    // Make sure below maximum jump limit
    if (mMaximumJump > 0) {
        // Limit x jump
        if (x - mXTranslation / xScale > mMaximumJump) {
            x = mXTranslation / xScale + mMaximumJump;
        } else if (x - mXTranslation / xScale < -mMaximumJump) {
            x = mXTranslation / xScale - mMaximumJump;
        }

        // Limit y jump
        if (y - mYTranslation / yScale > mMaximumJump) {
            y = mYTranslation / yScale + mMaximumJump;
        } else if (y - mYTranslation / yScale < -mMaximumJump) {
            y = mYTranslation / yScale - mMaximumJump;
        }
    }

    mXTranslation = x * xScale;
    mYTranslation = y * yScale;

    configureMatrix();
}

/**
 * Configures the ImageView's imageMatrix to allow for movement of the
 * source image.
 */
private void configureMatrix() {
    if (getDrawable() == null || getWidth() == 0 || getHeight() == 0) return;

    int dWidth = getDrawable().getIntrinsicWidth();
    int dHeight = getDrawable().getIntrinsicHeight();
    int vWidth = getWidth();
    int vHeight = getHeight();

    float scale;
    float dx, dy;

    if (dWidth * vHeight > vWidth * dHeight) {
        scale = (float) vHeight / (float) dHeight;
        mXOffset = (vWidth - dWidth * scale * mParallaxIntensity) * 0.5f;
        mYOffset = (vHeight - dHeight * scale * mParallaxIntensity) * 0.5f;
    } else {
        scale = (float) vWidth / (float) dWidth;
        mXOffset = (vWidth - dWidth * scale * mParallaxIntensity) * 0.5f;
        mYOffset = (vHeight - dHeight * scale * mParallaxIntensity) * 0.5f;
    }

    dx = mXOffset + mXTranslation;
    dy = mYOffset + mYTranslation;

    mTranslationMatrix.set(getImageMatrix());
    mTranslationMatrix.setScale(mParallaxIntensity * scale, mParallaxIntensity * scale);
    mTranslationMatrix.postTranslate(dx, dy);
    setImageMatrix(mTranslationMatrix);
}

/**
 * Registers a sensor manager with the parallax ImageView. Should be called in onResume
 * from an Activity or Fragment.
 *
 */
@SuppressWarnings("deprecation")
public void registerSensorManager() {
    if (getContext() == null || mSensorManager != null) return;

    // Acquires a sensor manager
    mSensorManager = (SensorManager) getContext().getSystemService(Context.SENSOR_SERVICE);

    if (mSensorManager != null) {
        mSensorManager.registerListener(this,
                mSensorManager.getDefaultSensor(Sensor.TYPE_ORIENTATION),
                SensorManager.SENSOR_DELAY_FASTEST);
    }
}

/**
 * Unregisters the ParallaxImageView's SensorManager. Should be called in onPause from
 * an Activity or Fragment to avoid continuing sensor usage.
 */
public void unregisterSensorManager() {
    unregisterSensorManager(false);
}

/**
 * Unregisters the ParallaxImageView's SensorManager. Should be called in onPause from
 * an Activity or Fragment to avoid continuing sensor usage.
 * @param resetTranslation if the image translation should be reset to the origin
 */
public void unregisterSensorManager(boolean resetTranslation) {
    if (mSensorManager == null) return;

    mSensorManager.unregisterListener(this);
    mSensorManager = null;

    if (resetTranslation) {
        setTranslate(0, 0);
    }
}

@Override
public void onSensorChanged(SensorEvent event) {
    final float [] vectors = mSensorInterpreter.interpretSensorEvent(getContext(), event);

    // Return if interpretation of data failed
    if (vectors == null) return;

    // Set translation on ImageView matrix
    setTranslate(vectors[2], vectors[1]);
}

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

}

传感器解释器

import android.content.Context;
import android.hardware.SensorEvent;
import android.view.Surface;
import android.view.WindowManager;

/**
 * I did not write this, I just cant remember where I got it from and thought it could be useful for others
 */
public class SensorInterpreter {

private static final String TAG = SensorInterpreter.class.getName();
private float[] mVectors;
private float mTiltSensitivity = 2.0f;
private float mForwardTiltOffset = 0.3f;

public SensorInterpreter() {
    mVectors = new float[3];
}

public final float[] interpretSensorEvent(Context context, SensorEvent event) {
    if (event == null || event.values.length < 3 || event.values[0] == 0
            || event.values[1] == 0 || event.values[2] == 0)
        return null;

    // Acquire rotation of screen
    final int rotation = ((WindowManager) context
            .getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay()
            .getRotation();

    // Adjust for forward tilt based on screen orientation
    switch (rotation) {
        case Surface.ROTATION_90:
            mVectors[0] = event.values[0];
            mVectors[1] = event.values[2];
            mVectors[2] = -event.values[1];
            break;

        case Surface.ROTATION_180:
            mVectors[0] = event.values[0];
            mVectors[1] = event.values[1];
            mVectors[2] = event.values[2];
            break;

        case Surface.ROTATION_270:
            mVectors[0] = event.values[0];
            mVectors[1] = -event.values[2];
            mVectors[2] = event.values[1];
            break;

        default:
            mVectors[0] = event.values[0];
            mVectors[1] = -event.values[1];
            mVectors[2] = -event.values[2];
            break;
    }

    // Adjust roll for sensitivity differences based on pitch
    // double tiltScale = 1/Math.cos(mVectors[1] * Math.PI/180);
    // if (tiltScale > 12) tiltScale = 12;
    // if (tiltScale < -12) tiltScale = -12;
    // mVectors[2] *= tiltScale;

    // Make roll and pitch percentages out of 1
    mVectors[1] /= 90;
    mVectors[2] /= 90;

    // Add in forward tilt offset
    mVectors[1] -= mForwardTiltOffset;
    if (mVectors[1] < -1)
        mVectors[1] += 2;

    // Adjust for tilt sensitivity
    mVectors[1] *= mTiltSensitivity;
    mVectors[2] *= mTiltSensitivity;

    // Clamp values to image bounds
    if (mVectors[1] > 1)
        mVectors[1] = 1f;
    if (mVectors[1] < -1)
        mVectors[1] = -1f;

    if (mVectors[2] > 1)
        mVectors[2] = 1f;
    if (mVectors[2] < -1)
        mVectors[2] = -1f;

    return mVectors;
}

public float getForwardTiltOffset() {
    return mForwardTiltOffset;
}

public void setForwardTiltOffset(float forwardTiltOffset) {
    mForwardTiltOffset = forwardTiltOffset;
}

public float getTiltSensitivity() {
    return mTiltSensitivity;
}

public void setTiltSensitivity(float tiltSensitivity) {
    mTiltSensitivity = tiltSensitivity;
}

将此添加到 attrs.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="ParallaxImageView">
    <attr name="intensity" format="float" />
    <attr name="tiltSensitivity" format="float" />
    <attr name="forwardTiltOffset" format="float" />
    <attr name="scaledIntensity" format="boolean" />
</declare-styleable>

将其添加到布局中 xml

 <yourpackagenamehere.ParallaxImageView
    android:id="@+id/background"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:scaleType="centerCrop" />

将此添加到 Activity class

private ParallaxImageView background;


background = (ParallaxImageView) findViewById(R.id.background);
background.setImageResource(R.drawable.main_back);


@Override
public void onResume() {
    background.registerSensorManager();
    super.onResume();
}

@Override
public void onPause() {
    background.unregisterSensorManager();
    super.onPause();
}

最后我创建了自定义视图以获得我想要的。

这里是存储库:https://github.com/GVMarc/ParallaxView

代码如下:

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) {
    }
}