Skip to content

Instantly share code, notes, and snippets.

@alexjlockwood
Last active April 14, 2018 23:56
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save alexjlockwood/2633ace57f70f9265f909dff3171008f to your computer and use it in GitHub Desktop.
Save alexjlockwood/2633ace57f70f9265f909dff3171008f to your computer and use it in GitHub Desktop.
An alternative polygon animation for the Kyrie sample app. See also j.mp/PolygonsFragment and the Twitter discussion here: https://twitter.com/alexjlockwood/status/985049112495222785
package com.example.kyrie;
import android.graphics.Color;
import android.graphics.PointF;
import android.os.Bundle;
import android.support.annotation.ColorInt;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import com.github.alexjlockwood.kyrie.Animation;
import com.github.alexjlockwood.kyrie.CircleNode;
import com.github.alexjlockwood.kyrie.GroupNode;
import com.github.alexjlockwood.kyrie.Keyframe;
import com.github.alexjlockwood.kyrie.KyrieDrawable;
import com.github.alexjlockwood.kyrie.PathData;
import com.github.alexjlockwood.kyrie.PathNode;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class PolygonsFragment extends Fragment {
private static final float VIEWPORT_WIDTH = 1080;
private static final float VIEWPORT_HEIGHT = 1080;
private static final int DURATION = 7500;
private final Polygon[] polygons = {
new Polygon(15, 0xffe84c65, 362f, 2),
new Polygon(14, 0xffe84c65, 338f, 3),
new Polygon(13, 0xffd554d9, 314f, 4),
new Polygon(12, 0xffaf6eee, 292f, 5),
new Polygon(11, 0xff4a4ae6, 268f, 6),
new Polygon(10, 0xff4294e7, 244f, 7),
new Polygon(9, 0xff6beeee, 220f, 8),
new Polygon(8, 0xff42e794, 196f, 9),
new Polygon(7, 0xff5ae75a, 172f, 10),
new Polygon(6, 0xffade76b, 148f, 11),
new Polygon(5, 0xffefefbb, 128f, 12),
new Polygon(4, 0xffe79442, 106f, 13),
new Polygon(3, 0xffe84c65, 90f, 14)
};
private View rootView;
private ImageView imageViewLaps;
private ImageView imageViewVortex;
@Nullable
@Override
public View onCreateView(
@NonNull LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
rootView = inflater.inflate(R.layout.fragment_two_pane, container, false);
imageViewLaps = rootView.findViewById(R.id.image_view_pane1);
imageViewVortex = rootView.findViewById(R.id.image_view_pane2);
return rootView;
}
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
final KyrieDrawable lapsDrawable = createLapsDrawable();
imageViewLaps.setImageDrawable(lapsDrawable);
final KyrieDrawable rotatingDrawable = createRotatingDrawable();
imageViewVortex.setImageDrawable(rotatingDrawable);
rootView.setOnClickListener(
v -> {
lapsDrawable.start();
rotatingDrawable.start();
});
}
private KyrieDrawable createLapsDrawable() {
final KyrieDrawable.Builder builder =
KyrieDrawable.builder().viewport(VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
for (Polygon polygon : polygons) {
builder.child(
PathNode.builder()
.pathData(PathData.parse(polygon.pathData))
.strokeWidth(4f)
.strokeColor(polygon.color));
}
for (Polygon polygon : polygons) {
final PathData pathData =
PathData.parse(TextUtils.join(" ", Collections.nCopies(polygon.laps, polygon.pathData)));
final Animation<PointF, PointF> pathMotion =
Animation.ofPathMotion(PathData.toPath(pathData))
.repeatCount(Animation.INFINITE)
.duration(DURATION);
builder.child(
CircleNode.builder()
.fillColor(Color.BLACK)
.radius(8)
.centerX(pathMotion.transform(p -> p.x))
.centerY(pathMotion.transform(p -> p.y)));
}
return builder.build();
}
private KyrieDrawable createVortexDrawable() {
final KyrieDrawable.Builder builder =
KyrieDrawable.builder().viewport(VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
for (Polygon polygon : polygons) {
final float length = polygon.length;
final float totalLength = length * polygon.laps;
builder.child(
PathNode.builder()
.pathData(PathData.parse(polygon.pathData))
.strokeWidth(4f)
.strokeColor(polygon.color)
.strokeDashArray(
Animation.ofFloatArray(new float[] {0, length}, new float[] {length, 0})
.repeatCount(Animation.INFINITE)
.duration(DURATION))
.strokeDashOffset(
Animation.ofFloat(0f, 2 * totalLength)
.repeatCount(Animation.INFINITE)
.duration(DURATION)));
}
return builder.build();
}
private KyrieDrawable createRotatingDrawable() {
final KyrieDrawable.Builder builder =
KyrieDrawable.builder().viewport(VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
final int duration = 5000;
final float minRadius = polygons[polygons.length - 1].radius;
final float maxRadius = polygons[0].radius;
for (int i = polygons.length - 1; i >= 0; i--) {
final Polygon polygon = polygons[i];
final Keyframe<Float>[] keyframes =
new Keyframe[] {
Keyframe.of(0f, polygon.radius / minRadius),
Keyframe.of(
(polygon.radius - minRadius) / (2f * (maxRadius - minRadius)),
minRadius / minRadius),
Keyframe.of(
((polygon.radius - minRadius) + (maxRadius - minRadius))
/ (2f * (maxRadius - minRadius)),
maxRadius / minRadius),
Keyframe.of(1f, polygon.radius / minRadius)
};
builder.child(
GroupNode.builder()
.pivotX(VIEWPORT_WIDTH / 2f)
.pivotY(VIEWPORT_HEIGHT / 2f)
.scaleX(minRadius / polygon.radius)
.scaleY(minRadius / polygon.radius)
.child(
PathNode.builder()
.pathData(PathData.parse(polygon.pathData))
.pivotX(VIEWPORT_WIDTH / 2f)
.pivotY(VIEWPORT_HEIGHT / 2f)
.rotation(
Animation.ofFloat(0f, 360f / polygon.sides)
.duration(duration)
.repeatCount(Animation.INFINITE))
.scaleX(
Animation.ofFloat(keyframes)
.duration(duration)
.repeatCount(Animation.INFINITE))
.scaleY(
Animation.ofFloat(keyframes)
.duration(duration)
.repeatCount(Animation.INFINITE))
.scalingStroke(false)
.strokeWidth(4f)
.strokeColor(polygon.color)));
}
return builder.build();
}
private static class Polygon {
final int sides;
@ColorInt final int color;
final float radius;
final int laps;
final String pathData;
final float length;
Polygon(int sides, @ColorInt int color, float radius, int laps) {
this.sides = sides;
this.color = color;
this.radius = radius;
this.laps = laps;
final List<PointF> points = getPoints(sides, radius);
this.pathData = pointsToPathData(points);
this.length = pointsToLength(points);
}
private static List<PointF> getPoints(int sides, float radius) {
final List<PointF> points = new ArrayList<>(sides);
final float angle = (float) (2 * Math.PI / sides);
final float startAngle = (float) (3 * Math.PI / 2);
for (int i = 0; i <= sides; i++) {
final float theta = startAngle + angle * i;
points.add(getPolygonPoint(radius, theta));
}
return points;
}
private static PointF getPolygonPoint(float radius, float theta) {
return new PointF(
(VIEWPORT_WIDTH / 2) + (float) (radius * Math.cos(theta)),
(VIEWPORT_HEIGHT / 2) + (float) (radius * Math.sin(theta)));
}
private static String pointsToPathData(List<PointF> points) {
final StringBuilder sb = new StringBuilder("M");
for (PointF p : points) {
sb.append(" ").append(p.x).append(" ").append(p.y);
}
return sb.append("Z").toString();
}
private static float pointsToLength(List<PointF> points) {
float length = 0;
for (int i = 1, size = points.size(); i < size; i++) {
final PointF prev = points.get(i - 1);
final PointF curr = points.get(i);
length += Math.hypot(curr.x - prev.x, curr.y - prev.y);
}
return length;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment