Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Animation through points array

I'm sure there is an easy way to do this but I'm stuck. Let's say I have a list of points :

Point[] list = {pointA, pointB, pointC, ...}

I'd like to animate an ImageView through each point So I tried this :

id = 0;
AnimatorListenerAdapter animEnd = new AnimatorListenerAdapter() {
    @Override
    public void onAnimationEnd(Animator animation) {
        super.onAnimationEnd(animation);
        id++;
        if(id != list.length) {
            iv.animate()
                .translationX(list[id].getX())
                .translationY(list[id].getY())
                .setDuration(200)
                .setListener(this);
        }
    }
};

iv.animate()
    .translationX(list[id].getX()).translationY(list[id].getY())
    .setDuration(200).setListener(animEnd);

It works but there 's a slight delay between each animation.

Any idea? Thanks !

like image 653
La masse Avatar asked Mar 22 '16 15:03

La masse


1 Answers

You probably get the delays between your animation steps because you always start a fresh animation on each transition from step to step. To overcome this situation you have multiple options.

Keyframes

Here you can find a technique called Keyframe Animation, which is a very common animation technique and is probably exactly what you want.

A Keyframe object consists of a time/value pair that lets you define a specific state at a specific time of an animation. Each keyframe can also have its own interpolator to control the behavior of the animation in the interval between the previous keyframe's time and the time of this keyframe.

Keyframe kf0 = Keyframe.ofFloat(0f, 0f);
Keyframe kf1 = Keyframe.ofFloat(.5f, 360f);
Keyframe kf2 = Keyframe.ofFloat(1f, 0f);
PropertyValuesHolder pvhRotation = PropertyValuesHolder.ofKeyframe("rotation", kf0, kf1, kf2);
ObjectAnimator rotationAnim = ObjectAnimator.ofPropertyValuesHolder(target, pvhRotation)
rotationAnim.setDuration(5000ms);

In your case you could map the points in your list to a list of Keyframe instances and ...

Point[] list = {pointA, pointB, pointC, ...}
List<Keyframe> kfs = new ArrayList<Keyframe>();
foreach (Point p : points) {
  Keyframe kf = new Keyframe.ofFloat(p.x); // or what ever
  kfs.add(kf);
}

... later pass these keyframes to some factory method to create some PropertyValuesHolder, like ofFloat that has the following signature:

public static PropertyValuesHolder ofFloat (
    Property<?, Float> property, 
    float... values
)

The second parameter is a variable argument list which also accepts arrays as inputs. Thus it should be possible to pass your kfs to the method as a second argument, somehow.

In your case i would craft the following methods, since you are developing for dalvik VM you cannot use java8 lambda expressions:

// maps points to X/Y float values
List<Float> toArrayX(Point[] points) { ... }
List<Float> toArrayY(Point[] points) { ... }
// maps float values to Keyframes
List<Keyframe> toKeyframes(List<Float> floats) { ... }
void createAnimation(Point[] points) {
  List<Keyframe> xs = toKeyframes(toArrayX(points));
  PropertyValuesHolder phvX = PropertyValuesHolder
    .ofKeyframe("translationX", xs);

  List<Keyframe> ys = toKeyframes(toArrayY(points));
  PropertyValuesHolder phvY = PropertyValuesHolder
    .ofKeyframe("translationY", ys);

  linkPropertyValuesHolder(phvX);
  linkPropertyValuesHolder(phvY);
}
void linkPropertyValuesHolder(PropertyValuesHolder phv) {
  // setup target
  ObjectAnimator anim = ObjectAnimator
    .ofPropertyValuesHolder(target, phv)
  anim.setDuration(5000ms);
}

Interpolators

Alternatively you can specify the transitions given by the points through an Interpolator instance.

An interpolator defines the rate of change of an animation. This allows the basic animation effects (alpha, scale, translate, rotate) to be accelerated, decelerated, repeated, etc.

An interpolator maps a fractional float between 0.0 and 1.0 to another fractional float between 0.0 and 1.0. Like the following three; LinearInterpolator, AccelerateDecelerateInterpolator and BounceInterpolator:

Images from here

Path Interpolator

With PathInterpolators it is possible to draw arbritary interpolators using Path instances. The drawn path will be used to drive the animation. In your case you could create two paths, one for the x and another one for the y translation.

However, take care when constructing interpolators from paths, because

... the Path must conform to a function y = f(x).

The Path must not have gaps in the x direction and must not loop back on itself such that there can be two points sharing the same x coordinate. It is alright to have a disjoint line in the vertical direction:

So take a look at the following code snippet that is taken from here, creates a valid path and could be used as input for the PathInterpolator constructor.

Path path = new Path();
path.lineTo(0.25f, 0.25f);
path.moveTo(0.25f, 0.5f);
path.lineTo(1f, 1f);

Custom Interpolator

When the given interpolators are not flexible enough, you can also just implement the Interpolator interface and create a new instance from it afterwards. The interface is very narrow and it only provides a single method called getInterpolation with the following signature.

public abstract float getInterpolation (float input)    

Code vs. XML

Another last option is to move all the animation configuration into XML instead of baking these details directly into the binary distribution of the code. However, each of the given options will require a different setup to be manageable through XML, if possible.

Hope this helps, but it is only pseudo code. I didn't test the code... so no warranties for correctness.

like image 192
isaias-b Avatar answered Oct 22 '22 14:10

isaias-b