Android的 - 如何让RotateAnimation更加光滑"物理"? [英] Android - how to make RotateAnimation more smooth and "physical"?

查看:254
本文介绍了Android的 - 如何让RotateAnimation更加光滑"物理"?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我采取一个样的罗盘箭,这取决于使用磁场传感器设备的物理方向下面的目的地。突然,我面对的是一个小问题。

获取轴承和方位是美好的,但在执行一个逼真的动画变成了一个很艰难的任务。我试图用不同的内插器,使动画更实际(即在真正的指南针,而箭头振荡发夹旋转后,加速和运动等过程中减速)。

现在我用 interpolator.accelerate_decelerate ,一切都相当不错,直到更新启动迅速到达。这使得动画彼此重叠和箭头变得颠簸和紧张。我想避免这种情况。我试图实现一个队列,使每一个动画等到previous结束,或丢弃来非常快的更新。这使得动画看起来光滑,但箭头的行为变成完全不合逻辑。

所以,我有2个问题:

1)是有一些方法来的做动画过渡的情况下,更流畅的动画时互相重叠的?

2)是有办法的停止动画当前正在处理,并得到一个物体的中间位置?

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

 公共类DirectionArrow {

//查看了重新presents箭头
最后查看viewArrow;

//箭头的旋转速度,度/秒
最终双rotationSpeed​​;

轴承和方位//当前值
浮bearingCurrent = 0;
浮azimuthCurrent = 0;


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

/ **
 *基本构造
 *
 *参数视野重新presenting应旋转箭头
 *在度/秒旋转@参数rotationSpeed​​速度。从50推荐(慢)到500(快)
 * /
公共DirectionArrow(查看视图中,双击rotationSpeed​​){
    this.viewArrow =图。
    this.rotationSpeed​​ = rotationSpeed​​;
}

/ **
 *扩展构造
 *
 *参数viewArrow查看重新presenting应旋转箭头
 *在度/秒旋转@参数rotationSpeed​​速度。从50推荐(慢)到500(快)
 *参数轴承初始轴承
 *参数方位的初始方位
 * /
公共DirectionArrow(查看viewArrow,双rotationSpeed​​,浮动轴承,浮方位){
    this.viewArrow = viewArrow;
    this.rotationSpeed​​ = rotationSpeed​​;
    UpdateRotation(轴承,方位角);
}

/ **
 *调用此更新的方向和动画的箭头
 *
 *参数bearingNew新轴承的价值,集> 180或LT; -180,如果你不需要更新
 *参数azimuthNew新方位价值,集> 360或℃的,如果你不需要更新
 * /
公共无效UpdateRotation(浮动bearingNew,浮azimuthNew){

    //看,如果任何一个参数不应该被更新
    如果(bearingNew< -180 || bearingNew> 180){
        bearingNew = bearingCurrent;
    }
    如果(azimuthNew℃,|| azimuthNew> 360){
        azimuthNew = azimuthCurrent;
    }

    //日志
    Log.println(Log.DEBUG,圆规,设置旋转:B =+ bearingNew +A =+ azimuthNew);

    //计算旋转值
    浮rotationFrom = bearingCurrent  -  azimuthCurrent;
    浮rotationTo = bearingNew  -  azimuthNew;

    //正确的旋转角度
    如果(rotationFrom< -180){
        rotationFrom + = 360;
    }
    而(rotationTo  -  rotationFrom< -180){
        rotationTo + = 360;
    }
    而(rotationTo  -  rotationFrom> 180){
        rotationTo  -  = 360;
    }

    //再次登录
    Log.println(Log.DEBUG,圆规,开始旋转+ rotationTo);

    //创建一个动画对象
    RotateAnimation rotateAnimation =新RotateAnimation(rotationFrom,rotationTo,
            Animation.RELATIVE_TO_SELF,(浮动)0.5,Animation.RELATIVE_TO_SELF,(浮动)0.5);

    //设置一个插
    rotateAnimation.setInterpolator(viewArrow.getContext(),interpolator.accelerate_decelerate);

    //强制视图记住动画完成后,其位置
    rotateAnimation.setFillAfter(真正的);

    //设置持续时间取决于速度
    rotateAnimation.setDuration((长)(Math.abs(rotationFrom  -  rotationTo)/ rotationSpeed​​ * 1000));

    //启动动画
    viewArrow.startAnimation(rotateAnimation);

    //更新cureent旋转
    bearingCurrent = bearingNew;
    azimuthCurrent = azimuthNew;
}
}
 

解决方案

下面是我的,我implemted的箭头指向的物理特性的基础上偶极磁场圆周运动的方程定制ImageDraw类。

它不使用任何动画,也没有内插器 - 基于物理参数重新计算每次迭代角位置。这些参数可以通过 setPhysical 方法得到广泛的调整。例如,使旋转更平稳和缓慢,增加字母(阻尼系数),使箭头更responsitive,增加 MB (磁场系数),使箭头振荡的旋转,增加 inertiaMoment

动画和重绘其隐式的调用执行无效()在每次迭代。没有必要明确地处理它。

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

  / **
 *类CompassView延伸的Andr​​oid ImageView的执行爽,现实生活中的动画对象
 *这样的罗盘针的磁场。旋转执行是相对图像的中心。
 *
 *它采用磁偶极子的角度运动方程在磁场实现这种动画。
 *为了改变行为(阻尼振荡,响应等)设置的各种物理性质。
 *
 *使用`setPhysical()`来改变物理性能。
 *使用`rotationUpdate()`变磁场的角度为图像应转动。
 *
 * /

公共类CompassView扩展ImageView的{

static final的公众持股量TIME_DELTA_THRESHOLD = 0.25f;迭代,秒之间//最大时间差
static final的公众持股量ANGLE_DELTA_THRESHOLD = 0.1F; //最小旋转变化重新绘制,度

static final的公众持股量INERTIA_MOMENT_DEFAULT = 0.1F; //默认的物理性能
static final的公众持股量ALPHA_DEFAULT = 10;
static final的公众持股量MB_DEFAULT = 1000;

长的时间1,时间2; //将previous重复时间戳 - 用于数值积分
浮角度1,ANGLE2,angle0; //将previous迭代角度
浮动angleLastDrawn; //最后绘制anglular位置
布尔animationOn = FALSE; //是否应执行动画

浮inertiaMoment = INERTIA_MOMENT_DEFAULT; // 转动惯量
浮阿尔法= ALPHA_DEFAULT; //阻尼系数
浮MB = MB_DEFAULT; //磁场系数

/ **
 *从ImageView的构造继承
 *
 * @参数方面
 * /
公共CompassView(上下文的背景下){
    超(上下文);
}

/ **
 *从ImageView的构造继承
 *
 * @参数方面
 * @参数ATTRS
 * /
公共CompassView(上下文的背景下,ATTRS的AttributeSet){
    超(背景下,ATTRS);
}

/ **
 *从ImageView的构造继承
 *
 * @参数方面
 * @参数ATTRS
 * @参数defStyle
 * /
公共CompassView(上下文的背景下,ATTRS的AttributeSet,诠释defStyle){
    超(背景下,ATTRS,defStyle);
}

/ **
 * OnDraw的覆盖。
 *如果动画的观点是在每次重绘无效,
 *对UI重画的每一个回路进行重新计算
 * /
@覆盖
公共无效的OnDraw(帆布油画){
    如果(animationOn){
        如果(angleRecalculate(新日期()。的getTime())){
            this.setRotation(角度1);
        }
    } 其他 {
        this.setRotation(角度1);
    }
    super.onDraw(画布);
    如果(animationOn){
        this.invalidate();
    }
}

/ **
 *使用此设置的物理性质。
 *负值将默认值替换
 *
 * @参数inertiaMoment转动惯量(默认值0.1)
 *参数阿尔法阻尼系数(默认为10)
 *参数MB磁场系数(默认1000)
 * /
公共无效setPhysical(浮动inertiaMoment,浮α,浮MB){
    this.inertiaMoment = inertiaMoment> = 0? inertiaMoment:this.INERTIA_MOMENT_DEFAULT;
    this.alpha =阿尔法> = 0?阿尔法:ALPHA_DEFAULT;
    this.mB = MB> = 0? MB:MB_DEFAULT;
}


/ **
 *使用此设置新的磁场角度的限制,图像应旋转
 *
 * @参数angleNew新磁场角度,相对于垂直轴。
 *参数动画真的,如果图像768,16使用动画旋转,假立即设置新的旋转
 * /
公共无效rotationUpdate(最终浮动angleNew,最后布尔动画){
    如果(动画){
        如果(Math.abs(angle0  -  angleNew)> ANGLE_DELTA_THRESHOLD){
            angle0 = angleNew;
            this.invalidate();
        }
        animationOn = TRUE;
    } 其他 {
        角度1 = angleNew;
        ANGLE2 = angleNew;
        angle0 = angleNew;
        angleLastDrawn = angleNew;
        this.invalidate();
        animationOn = FALSE;
    }
}

/ **
 *使用偶极圆周运动的方程重新计算的角度
 *
 方法调用的*参数timeNew时间戳
 返回:如果有需要重绘旋转
 * /
保护布尔angleRecalculate(最终长timeNew){

    //使用运动方程简单的数值积分重新计算的角度
    浮动deltaT1 =(timeNew  - 时间1)/ 1000F;
    如果(deltaT1> TIME_DELTA_THRESHOLD){
        deltaT1 = TIME_DELTA_THRESHOLD;
        时间1 = timeNew + Math.round(TIME_DELTA_THRESHOLD * 1000);
    }
    浮动deltaT2 =(时间1  - 时间2)/ 1000F;
    如果(deltaT2> TIME_DELTA_THRESHOLD){
        deltaT2 = TIME_DELTA_THRESHOLD;
    }

    //循环加速系数
    浮koefI = inertiaMoment / deltaT1 / deltaT2;

    //圆周速度系数
    浮koefAlpha =阿尔法/ deltaT1;

    //角动量系数
    浮koefk = MB *(浮动)(Math.sin(Math.toRadians(angle0))* Math.cos(Math.toRadians(角度1)) - 
                             (Math.sin(Math.toRadians(角度1))* Math.cos(Math.toRadians(angle0))));

    浮动angleNew =(koefI *(角度1 * 2楼 -  ANGLE2)+ koefAlpha *角度1 + koefk)/(koefI + koefAlpha);

    //重新分配previous迭代变量
    ANGLE2 =角度1;
    角度1 = angleNew;
    时间2 =时间1;
    时间1 = timeNew;

    //如果角度变化小于阈值时,返回false  - 无需重新绘制视图
    如果(Math.abs(angleLastDrawn  - 角度1)< ANGLE_DELTA_THRESHOLD){
        返回false;
    } 其他 {
        angleLastDrawn =角度1;
        返回true;
    }
}
 

I'm implementing a kind of a "compass arrow" that follows destination depending on physical orientation of the device using magnetic field sensor. Suddenly I faced with a little problem.

Obtaining bearing and azimuth is OK, but performing a realistic animation turned into a really hard task. I tried to use different interpolators to make animation more "physical" (i. e. as in real compass, which arrow oscillate after hairpin rotation, accelerate and decelerate during movement etc).

Now I'm using interpolator.accelerate_decelerate and everything is quite good until updates start arriving quickly. That makes animations overlap each other and the arrow becomes twitchy and nervous. I want to avoid this. I tried to implement a queue to make every next animation wait until previous ends, or drop updates that come very quickly. That made animation look smooth, but arrow's behavior turned into absolutely illogical.

So I have 2 questions:

1) is there some way to make animated transitions more smooth in the cases when animations overlap each other?

2) is there a way to stop animation that is currently processing and get intermediate position of an object?

My code is below. An UpdateRotation() method handles orientation and bearing updates and executes animation of external viewArrow view.

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

解决方案

Here is my custom ImageDraw class where I implemted physical behavior of the pointing arrow based on equation of circular motion of dipole in magnetic field.

It don't uses any animators nor interpolators--on every iteration angular position is recalculated based on physical parameters. These parameters can be widely adjusted via setPhysical method. For example, to make rotations more smooth and slow, increase alpha (damping coefficient), to make arrow more responsitive, increase mB (coefficient of magnetic field), to make arrow oscillate on rotations, increase inertiaMoment.

Animation and redraw is performed implicitly by invoke of invalidate() on every iteration. There is no need to handle it explicitly.

To update the angle at which the arrow should rotate, just call rotationUpdate (by user's choice or using device orientation sensor callback).

/**
 * Class CompassView extends Android ImageView to perform cool, real-life animation of objects
 * such compass needle in magnetic field. Rotation is performed relative to the center of image.
 * 
 * It uses angular motion equation of magnetic dipole in magnetic field to implement such animation.
 * To vary behaviour (damping, oscillation, responsiveness and so on) set various physical properties.
 * 
 * Use `setPhysical()` to vary physical properties.
 * Use `rotationUpdate()` to change angle of "magnetic field" at which image should rotate.
 *
 */

public class CompassView extends ImageView {

static final public float TIME_DELTA_THRESHOLD = 0.25f; // maximum time difference between iterations, s 
static final public float ANGLE_DELTA_THRESHOLD = 0.1f; // minimum rotation change to be redrawn, deg

static final public float INERTIA_MOMENT_DEFAULT = 0.1f;    // default physical properties
static final public float ALPHA_DEFAULT = 10;
static final public float MB_DEFAULT = 1000;

long time1, time2;              // timestamps of previous iterations--used in numerical integration
float angle1, angle2, angle0;   // angles of previous iterations
float angleLastDrawn;           // last drawn anglular position
boolean animationOn = false;    // if animation should be performed

float inertiaMoment = INERTIA_MOMENT_DEFAULT;   // moment of inertia
float alpha = ALPHA_DEFAULT;    // damping coefficient
float mB = MB_DEFAULT;  // magnetic field coefficient

/**
 * Constructor inherited from ImageView
 * 
 * @param context
 */
public CompassView(Context context) {
    super(context);
}

/**
 * Constructor inherited from ImageView
 * 
 * @param context
 * @param attrs
 */
public CompassView(Context context, AttributeSet attrs) {
    super(context, attrs);
}

/**
 * Constructor inherited from ImageView
 * 
 * @param context
 * @param attrs
 * @param defStyle
 */
public CompassView(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
}

/**
 * onDraw override.
 * If animation is "on", view is invalidated after each redraw, 
 * to perform recalculation on every loop of UI redraw
 */
@Override
public void onDraw(Canvas canvas){
    if (animationOn){
        if (angleRecalculate(new Date().getTime())){
            this.setRotation(angle1);
        }
    } else {
        this.setRotation(angle1);
    }
    super.onDraw(canvas);
    if (animationOn){
        this.invalidate();
    }
}

/**
 * Use this to set physical properties. 
 * Negative values will be replaced by default values
 * 
 * @param inertiaMoment Moment of inertia (default 0.1)
 * @param alpha             Damping coefficient (default 10)
 * @param mB                Magnetic field coefficient (default 1000)
 */
public void setPhysical(float inertiaMoment, float alpha, float mB){
    this.inertiaMoment = inertiaMoment >= 0 ? inertiaMoment : this.INERTIA_MOMENT_DEFAULT;
    this.alpha = alpha >= 0 ? alpha : ALPHA_DEFAULT;
    this.mB = mB >= 0 ? mB : MB_DEFAULT;
}


/**
 * Use this to set new "magnetic field" angle at which image should rotate
 * 
 * @param   angleNew    new magnetic field angle, deg., relative to vertical axis.
 * @param   animate     true, if image shoud rotate using animation, false to set new rotation instantly
 */
public void rotationUpdate(final float angleNew, final boolean animate){
    if (animate){
        if (Math.abs(angle0 - angleNew) > ANGLE_DELTA_THRESHOLD){
            angle0 = angleNew;
            this.invalidate();
        }
        animationOn = true;
    } else {
        angle1 = angleNew;
        angle2 = angleNew;
        angle0 = angleNew;
        angleLastDrawn = angleNew;
        this.invalidate();
        animationOn = false;
    }
}

/**
 * Recalculate angles using equation of dipole circular motion
 * 
 * @param   timeNew     timestamp of method invoke
 * @return              if there is a need to redraw rotation
 */
protected boolean angleRecalculate(final long timeNew){

    // recalculate angle using simple numerical integration of motion equation
    float deltaT1 = (timeNew - time1)/1000f;
    if (deltaT1 > TIME_DELTA_THRESHOLD){
        deltaT1 = TIME_DELTA_THRESHOLD;
        time1 = timeNew + Math.round(TIME_DELTA_THRESHOLD * 1000);
    }
    float deltaT2 = (time1 - time2)/1000f;
    if (deltaT2 > TIME_DELTA_THRESHOLD){
        deltaT2 = TIME_DELTA_THRESHOLD;
    }

    // circular acceleration coefficient
    float koefI = inertiaMoment / deltaT1 / deltaT2;

    // circular velocity coefficient
    float koefAlpha = alpha / deltaT1;

    // angular momentum coefficient
    float koefk = mB * (float)(Math.sin(Math.toRadians(angle0))*Math.cos(Math.toRadians(angle1)) - 
                             (Math.sin(Math.toRadians(angle1))*Math.cos(Math.toRadians(angle0))));

    float angleNew = ( koefI*(angle1 * 2f - angle2) + koefAlpha*angle1 + koefk) / (koefI + koefAlpha);

    // reassign previous iteration variables
    angle2 = angle1;
    angle1 = angleNew;
    time2 = time1;
    time1 = timeNew;

    // if angles changed less then threshold, return false - no need to redraw the view
    if (Math.abs(angleLastDrawn - angle1) < ANGLE_DELTA_THRESHOLD){
        return false;
    } else {
        angleLastDrawn = angle1;
        return true;
    }
}

这篇关于Android的 - 如何让RotateAnimation更加光滑&QUOT;物理&QUOT;?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆