# Delightful swapping views animation

## And how to do it by the simple math

In my previous articles I promised to write about building dynamic photosets, but then I faced with interesting task and decided to write about it.

**Task: **there are 2 square views with same size. We need to swap it with animation.

You might think, that this is not interesting, since there are easy and obvious solution called Linear Motion:

val v1 = ...// take first view

val v2 = ...// take second viewval v1X = v1.x

val v2X = v2.x

val v1Y = v1.y

val v2Y = v2.yval t = ...// fraction that in range [0, 1]v1.x = v1X * (1 - t) + v2X * t

v1.y = v1Y * (1 - t) + v2Y * t

v2.x = v2X * (1 - t) + v1X * t

v2.y = v2Y * (1 - t) + v1Y * t

That’s all, but this give us an next effect. For example:

As you can see, two views are simply moving on a straight line. Thus, on some moment of time they cross with each other. For me it was not acceptable, since it only looks as too easy solution, but moreover it is not such graceful, as it can be:

As you see here, such swapping, with curve path looks more delightful and more realistic (because as you know in the real world solid objects can not intersect). So we need to invite solution that will build such curve path for us.

*Note:*** **When I stumbled on this problem, I didn’t thought about Bezier curves. It is true, that you can easy build needed curve path (especially simple one), just using quadratic or cubic Bezier curves. But when I remembered about that, I was enchanted by other problem. How can we swap views by using circle arcs as motions paths?

Gathering all and formulating task we have next statement:

How can we build an circle arc path for swapping two square views with same sizes, that they will not intersect to each other?

And this leads us to the next solution.

# Solution

## Step 1. Finding a circle radius d

First of all, because of task limitations, we know that both paths (motion path for first view and motion path for the second one) will just mirror each other. Thus, some dimension values will be same both for the first and for the second paths. One of such values is circle radius `d`

.

As we already know, our views will move on arc path. This arc is a part of some circle, that has (of course) a radius `d`

, which we want to find. Also, we know that two views will not intercept if the distance between their center will be no less that diagonal of a any of those views, since they are squares with same size.

Here you can see a sketch of a first step:

We know `size`

, `A(x, y)`

, `B(x, y)`

, `r'`

and we want to find `d`

. Note that `r’= size / sqrt(2) + delta`

, because of aforementioned statement about distance and where `delta`

is any non-negative integer. You can play with it and insert any `delta`

as you want, but for the simplicity I will use `delta = 0`

.

If we will look close to the sketch and collect all previous thoughts, we will have a System of polynomial equations:

where `d`

and `r`

are unknown values and `diff(B,C)`

is a dinstance between two points: *B *and *C*. If will keep in mind, that `r' = size / sqrt(2)`

and will solve that system, we find out next solution:

where *C* is center point, between given points *A* and* B*.

## Step 2. Finding coordinates of D point

Now, when we found radius `d`

, we need to find coordinates of point *D*. It is a center of circle, on the arc of which our path is placed. Below you can see a sketch of a second step:

We know *A*, *B*, *C* points, radius d. Also we know, that BCD is the right angle. Now we introduce *B’* and *D’* points, which is result of placing center of the plane on *C* point:

Gathering together result of the first step and formula of angle between two vectors, we have next System of polynomial equations:

By slightly intricate calculations we find the following solution:

## Step 3. Building rotation function

Now, we have center of the circle *D*, its radius `d`

and begin and end points *A*, B of an arc. Now we need to provide a point *P1* and *P2* by the given `t`

, which lies on a range [0, 1].

To do that we need to place center of a plane on the point *D*. Then we need to adapt or know points *A*, *B* and *C* and receive new *A’’*, *B’’*, *C’’*:

Then we need to compute angle of A’’ and angle of B’’ which are computed with help of arc tangent function. Also we need to remember two things:

- As arc tangent returns a value in range [-90, 90] degrees, we need to adapt it by seeing x coordinate sign. If it is less than zero, we need to add 180 to result angle. Also, we need to handle situations when x is zero.
- At some points the difference between to angles will be more that 180 degrees. In that case we need to sum second angle with 360 or truncate 360 from it, in order to reduce differentiation to value that is less than 180.

Thus we will have two functions:

// gets point angle by its coordinatesprivate fungetPointAngle(x: Float, y: Float): Float {

return if(x == 0f) {

if(y > 0f)PI.toFloat() / 2f

else-PI.toFloat() / 2f

}else{

atan(y / x) + (if((x < 0))PI.toFloat()else0f)

}

}//adjust second angle towards to the first oneprivate funnormalizeAngleDiff(aAngle: Float, bAngle: Float): Float {

return if(abs(aAngle- bAngle) >=PI) {

valtemp = bAngle + 2 *PI.toFloat()

if(abs(aAngle- temp) >=PI) {

bAngle - 2 *PI.toFloat()

}else{

temp

}

}else{

bAngle

}

}

Also we will have a function that computes intermediate angle between `aAngle`

and `bAngle`

:

**private fun **computeAngle(fraction: Float) =

**bAngle *** (1f - fraction) + **aAngle *** fraction

Thus we will have next for retrieving both points *P1* and *P2*:

**val **angle = computeAngle(t)

**var **x1 = **dVector *** *cos*(angle)

**var **y1 = **dVector *** *sin*(angle)

**var **x2 = 2 * (**cX** - **dX) **- x1

**var **y2 = 2 * (**cY** - **dY**) - y1

x1 += **dX**

y1 += **dY**

x2 += **dX**

y2 += **dY**

Where `x1`

and `y1`

are coordinates of point *P1* and `x2`

and `y2`

are coordinates of point *P2*. Second point are received by reflecting the first one with respect to point *C*.

# Show me the code!

Okay, as you saw last step I provided not with formulas, but with pieces of code. I myself can not wait to get started writing code. So lets start. Here is the code of *Transformator* that uses information of input coodinates and square size and provides a function `transform`

that generates exchange coordinates by given `fraction`

that is in range [0, 1]:

Here is an example of its usage:

**double **durationMult = *diff*(pickedTV, fakeTV) / *max*(pickedTV.getMeasuredWidth(), fakeTV.getMeasuredWidth());

**final **ExchangeTransformator transformator = **new **CurveExchangeTransformator(

**new **ExchangeCoords(**currentTileX**, **currentTileY**, prevTileX, prevTileY),

*max*(clickedTileView.getMeasuredWidth(), clickedTileView.getMeasuredHeight())

);

ValueAnimator animator = ValueAnimator.*ofFloat*(0, 1);

animator.setDuration((**long**) (*sqrt*(durationMult) * 300));

animator.setInterpolator(**new **AccelerateDecelerateInterpolator());

animator.addUpdateListener(**new **ValueAnimator.AnimatorUpdateListener() {

@Override

**public void **onAnimationUpdate(ValueAnimator valueAnimator) {

ExchangeCoords coords = transformator.transform(valueAnimator.getAnimatedFraction());

**if **(isExchangeableTile) {

fakeTV.setX(coords.getSourceX());

fakeTV.setY(coords.getSourceY());

}

pickedTV.setX(coords.getTargetX());

pickedTV.setY(coords.getTargetY());

}

});

animator.start();

Here is the result of execution:

# Afterwords

As you can see, creating an swap views function is not so hard, as I expected :)