Android - 如何让RotateAnimation更加流畅和“物理”?

时间:2014-03-29 00:52:25

标签: android animation rotation android-sensors

我正在实施一种指南针箭头"取决于使用磁场传感器的设备的物理方向,其在目的地之后。突然间,我遇到了一个小问题。

获得方位和方位角是可以的,但是执行逼真的动画变成了一项非常艰巨的任务。我尝试使用不同的内插器来使动画更多"物理" (即,在真正的罗盘中,箭头在发夹旋转后振荡,在运动过程中加速和减速等)。

现在我使用interpolator.accelerate_decelerate,一切都很好,直到更新快速到达。这使得动画彼此重叠,箭头变得抽搐和紧张。我想避免这种情况。我试图实现一个队列,使每个下一个动画等到上一个结束,或者删除非常快的更新。这使动画看起来很流畅,但箭头的行为变成了绝对不合逻辑的行为。

所以我有两个问题:

1)在动画相互重叠的情况下,是否有某种方法可以使动画过渡更加平滑

2)有没有办法停止当前处理的动画并获得对象的中间位置?

我的代码如下。 UpdateRotation()方法处理方向和方位更新,并执行外部viewArrow视图的动画。

public class DirectionArrow {

// View that represents the arrow
final View viewArrow;

// speed of rotation of the arrow, degrees/sec
final double rotationSpeed;

// current values of bearing and azimuth
float bearingCurrent = 0;
float azimuthCurrent = 0;


/*******************************************************************************/

/**
 * Basic constructor
 * 
 * @param   view            View representing an arrow that should be rotated
 * @param   rotationSpeed   Speed of rotation in deg/sec. Recommended from 50 (slow) to 500 (fast)
 */
public DirectionArrow(View view, double rotationSpeed) {
    this.viewArrow = view;
    this.rotationSpeed = rotationSpeed;
}

/**
 * Extended constructor
 * 
 * @param   viewArrow       View representing an arrow that should be rotated
 * @param   rotationSpeed   Speed of rotation in deg/sec. Recommended from 50 (slow) to 500 (fast)
 * @param   bearing         Initial bearing 
 * @param   azimuth         Initial azimuth
 */
public DirectionArrow(View viewArrow, double rotationSpeed, float bearing, float azimuth){
    this.viewArrow = viewArrow;
    this.rotationSpeed = rotationSpeed;
    UpdateRotation(bearing, azimuth);
}

/**
 * Invoke this to update orientation and animate the arrow
 * 
 * @param   bearingNew  New bearing value, set >180 or <-180 if you don't need to update it 
 * @param   azimuthNew  New azimuth value, set >360 or <0 if you don't need to update it
 */
public void UpdateRotation(float bearingNew, float azimuthNew){

    // look if any parameter shouldn't be updated
    if (bearingNew < -180 || bearingNew > 180){
        bearingNew = bearingCurrent;
    }
    if (azimuthNew < 0 || azimuthNew > 360){
        azimuthNew = azimuthCurrent;
    }

    // log
    Log.println(Log.DEBUG, "compass", "Setting rotation: B=" + bearingNew + " A=" + azimuthNew);

    // calculate rotation value
    float rotationFrom = bearingCurrent - azimuthCurrent;
    float rotationTo = bearingNew - azimuthNew;

    // correct rotation angles
    if (rotationFrom < -180) {
        rotationFrom += 360;
    }
    while (rotationTo - rotationFrom < -180) {
        rotationTo += 360;
    }
    while (rotationTo - rotationFrom > 180) {
        rotationTo -= 360;
    }

    // log again
    Log.println(Log.DEBUG, "compass", "Start Rotation to " + rotationTo);

    // create an animation object
    RotateAnimation rotateAnimation = new RotateAnimation(rotationFrom, rotationTo, 
            Animation.RELATIVE_TO_SELF, (float) 0.5, Animation.RELATIVE_TO_SELF, (float) 0.5);

    // set up an interpolator
    rotateAnimation.setInterpolator(viewArrow.getContext(), interpolator.accelerate_decelerate);

    // force view to remember its position after animation
    rotateAnimation.setFillAfter(true);

    // set duration depending on speed
    rotateAnimation.setDuration((long) (Math.abs(rotationFrom - rotationTo) / rotationSpeed * 1000));

    // start animation
    viewArrow.startAnimation(rotateAnimation);

    // update cureent rotation
    bearingCurrent = bearingNew;
    azimuthCurrent = azimuthNew;
}
}

4 个答案:

答案 0 :(得分:9)

这是我自定义的ImageDraw类,我根据磁场中偶极子的圆周运动方程实现了指向箭头的物理行为。

它不使用任何动画师和插值器 - 在每次迭代时,根据物理参数重新计算角度位置。可以通过setPhysical方法广泛调整这些参数。例如,为了使旋转更加平滑和缓慢,增加alpha(阻尼系数),使箭头更具响应性,增加mB(磁场系数),使箭头在旋转时振荡,增加{ {1}}。

通过在每次迭代时调用inertiaMoment来隐式执行动画和重绘。没有必要明确处理它。

要更新箭头应旋转的角度,只需拨打invalidate()(由用户选择或使用设备方向传感器回调)。

rotationUpdate

答案 1 :(得分:1)

您是否过滤了传感器数据?磁力计是一种痛苦的低通滤波还不够。您可以使用weighted-smoothing或者舍入数据可能会有所帮助: Math.round(xyz * 10)/ 10; ? 您还可以降低传感器更新的频率。这可能有所帮助。

mSensorManager.registerListener(this, mMagnetometer, 10000);

答案 2 :(得分:1)

特别是gilonm,很好地实现了固定大小的队列并获得了它的平均值:

float queue[ARRAY_LENGTH] = {0};
int queueFront = queue.length - 1 // position of front element
float meanValue = 0; // calculated mean value

float pushNewAndGetMean(float newValue){
   // recalculate mean value
   meanValue = meanValue + (newValue - queue[queueFront]) / queue.length;
   // overwrite value in front pointer position
   queue[queueFront] = newValue;
   // shift front pointer 1 step right or to '0' if end of array reached
   queueStart = (queueFront + 1) % array.length;

   return  meanValue
};   

这里,不依赖于数组长度,只需要对变量(而不是N)进行2次重新分配,并且在均值计算中仅使用3个元素(而不是N)。这使得算法O(1)复杂度而不是O(N)。

答案 3 :(得分:0)

你可以做的是从传感器获取数据的地方 - 你可以使用和数组来平均说出最后5个读数 - 这应该可以平滑下来。

类似的东西:

声明数组private float azimArray[] = {0,0,0,0,0};

现在获取传感器数据,请使用:

azimArray[0] = azimArray[1]; azimArray[1] = azimArray[2]; azimArray[2] = azimArray[3]; azimArray[3] = azimArray[4]; azimArray[4] = event.values[0]; //get actual sensor data into last array cell currentAzimuth = Math.round(azimArray[0]+azimArray[1]+azimArray[2]+azimArray[3]+azimArray[4]/5);

现在,currentAzimuth保留了最后5个读数的四舍五入平均值,这可能会让你感到沮丧。

希望这有帮助!