我正在实现一种指南针箭头",它根据使用磁场传感器的设备的物理方向跟踪目的地.突然遇到一个小问题.
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).
现在我正在使用 interpolator.accelerate_decelerate
并且一切都很好,直到更新开始快速到达.这使得动画相互重叠,箭头变得抽搐和紧张.我想避免这种情况.我尝试实现一个队列,让每个下一个动画都等到前一个结束,或者丢弃很快到来的更新.这使动画看起来很流畅,但箭头的行为变得完全不合逻辑.
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.
所以我有 2 个问题:
1) 有什么方法可以在动画相互重叠的情况下使动画过渡更加平滑?
1) is there some way to make animated transitions more smooth in the cases when animations overlap each other?
2) 有没有办法停止当前正在处理的动画并获取对象的中间位置?
我的代码如下.UpdateRotation()
方法处理方向和方位更新,并执行外部 viewArrow
视图的动画.
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;
}
}
这是我的自定义 ImageDraw 类,我根据磁场中偶极子的圆周运动方程实现了指向箭头的物理行为.
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.
它不使用任何动画器或插值器——在每次迭代时,角度位置都会根据物理参数重新计算.这些参数可以通过 setPhysical
方法进行广泛的调整.例如,为了使旋转更平滑和更慢,增加alpha
(阻尼系数),使箭头更灵敏,增加mB
(磁场系数),使箭头旋转时振荡,增加 inertiaMoment
.
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
.
动画和重绘是通过在每次迭代中调用 invalidate()
隐式执行的.无需显式处理.
Animation and redraw is performed implicitly by invoke of invalidate()
on every iteration. There is no need to handle it explicitly.
要更新箭头应旋转的角度,只需调用 rotationUpdate
(由用户选择或使用设备方向传感器回调).
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 更流畅和“物理"?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持跟版网!