我想要一个根据加速度计移动并创建视差效果的自定义视图。
现在我有自定义视图监听加速度计值,但如何使用这些值来正确移动视图?
代码:
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);
}
}
在活动中使用此视图:
@Override
protected void onCreate(Bundle savedInstanceState) {
//...
mParallaxView.init();
}
@Override
protected void onResume() {
mParallaxView.registerSensorListener();
super.onResume();
}
@Override
protected void onPause() {
mParallaxView.unregisterSensorListener();
super.onPause();
}
提前致谢
答案 0 :(得分:3)
这是我以前用过的PHP Notices
课程:
ParallaxImageView
<强> SensorInterpreter 强>
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) { }
}
将此添加到attrs.xml
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;
}
将此添加到布局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>
将此添加到活动类
<yourpackagenamehere.ParallaxImageView
android:id="@+id/background"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop" />
答案 1 :(得分:2)
最后,我创建了自定义视图以获得我想要的内容。
这里是存储库: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) {
}
}