package com.nirhart.shortrain.train; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ValueAnimator; import android.app.Activity; import android.content.pm.ShortcutManager; import android.content.res.Resources; import android.graphics.Rect; import android.os.Bundle; import android.os.Handler; import android.support.annotation.NonNull; import android.util.DisplayMetrics; import android.util.TypedValue; import android.view.View; import android.view.WindowManager; import android.view.animation.DecelerateInterpolator; import android.view.animation.LinearInterpolator; import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.Toast; import com.nirhart.shortrain.R; import com.nirhart.shortrain.path.PathParser; import com.nirhart.shortrain.path.PathPoint; import com.nirhart.shortrain.path.TrainPath; import com.nirhart.shortrain.rail.RailInfo; import com.nirhart.shortrain.utils.ShortcutsUtils; import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; public class TrainActionActivity extends Activity { final public static String TRAIN_ID_KEY = "id"; final public static String TRAIN_ID_VALUE = "new_train"; final private static int TRAIN_FRACTION = 14; // The lowest this number, the faster the train will go private static final float START_ROTATION_FACTOR = 0.25f; // The lowest this number, the closer the train will start rotate next to a rotation private static final long TIME_BETWEEN_CAR_AND_ENGINE = 1000; private static final int NUMBER_OF_CARS = 2; final private List<TrainView> carsView = new ArrayList<>(); private TrainView engineView; private FrameLayout rootView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); getWindow().setFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS, WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS); overridePendingTransition(0, 0); final List<RailInfo> rails = ShortcutsUtils.getRails(getSystemService(ShortcutManager.class)); final Rect trainRect = getIntent().getSourceBounds(); addTrainToScreen(trainRect); DisplayMetrics displayMetrics = new DisplayMetrics(); getWindowManager().getDefaultDisplay().getMetrics(displayMetrics); int width = displayMetrics.widthPixels; int height = displayMetrics.heightPixels; PathParser pathParser = new PathParser(width, height); TrainPath path = null; try { path = pathParser.parse(trainRect.left, trainRect.top, trainRect, rails); } catch (ArrayIndexOutOfBoundsException e) { // If someone clicks the "set start point" instead of dragging it to screen, the tileRect will be larger // the cols will not be calculated correctly and an ArrayIndexOutOfBoundsException will be thrown in here Toast.makeText(this, R.string.starting_point_explanation, Toast.LENGTH_LONG).show(); finish(); return; } List<Animator> engineAnimators = createTrainAnimation(engineView, path); final List<AnimatorSet> carsAnimatorSets = new ArrayList<>(); for (int i = 0; i < NUMBER_OF_CARS; i++) { final TrainView carView = carsView.get(i); List<Animator> carAnimators = createTrainAnimation(carView, path); carView.setVisibility(View.INVISIBLE); final AnimatorSet carSet = new AnimatorSet(); carSet.playSequentially(carAnimators); carSet.setStartDelay(TIME_BETWEEN_CAR_AND_ENGINE * (i + 1)); carSet.start(); new Handler().postDelayed(new Runnable() { @Override public void run() { carView.setVisibility(View.VISIBLE); } }, TIME_BETWEEN_CAR_AND_ENGINE * (i + 1)); carsAnimatorSets.add(carSet); } engineAnimators.get(engineAnimators.size() - 1).addListener(new AnimatorListenerAdapter() { @Override public void onAnimationStart(Animator animation) { fadeOutActivity(); } }); AnimatorSet engineSet = new AnimatorSet(); engineSet.playSequentially(engineAnimators); engineSet.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { for (AnimatorSet carSet : carsAnimatorSets) { carSet.pause(); } } }); engineSet.start(); } private List<Animator> createTrainAnimation(final TrainView trainView, TrainPath path) { List<Animator> animators = new ArrayList<>(); List<PathPoint> pathPoints = path.getPath(); for (int i = 1; i < pathPoints.size(); i++) { PathPoint lastPathPoint = pathPoints.get(i - 1); PathPoint pathPoint = pathPoints.get(i); final TrainDirection animationDirection = pathPoints.get(i).getDirection(); final boolean isHorizontal = animationDirection.isHorizontal(); final int animationLength = getAnimationLength(lastPathPoint, pathPoint, isHorizontal); long animationDuration = getAnimationDuration(animationLength); ValueAnimator valueAnimator = ValueAnimator.ofInt(0, animationLength); valueAnimator.setInterpolator(new LinearInterpolator()); valueAnimator.setDuration(animationDuration); final AtomicInteger lastUpdate = new AtomicInteger(0); final float rotationStartFraction = 1f - ((float) trainView.getTrainHeight() * START_ROTATION_FACTOR / Math.abs(animationLength)); valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { int update = (int) animation.getAnimatedValue(); if (isHorizontal) { trainView.setX(trainView.getX() + (update - lastUpdate.get())); if (animationDirection.isTurningUp()) { animateOneTrainPath(animation, trainView, 270, rotationStartFraction); } else if (animationDirection.isTurningDown()) { animateOneTrainPath(animation, trainView, 90, rotationStartFraction); } else { animateOneTrainPath(animation, trainView, -1, rotationStartFraction); } } else { trainView.setY(trainView.getY() + (update - lastUpdate.get())); if (animationDirection.isTurningLeft()) { animateOneTrainPath(animation, trainView, 180, rotationStartFraction); } else if (animationDirection.isTurningRight()) { animateOneTrainPath(animation, trainView, 0, rotationStartFraction); } else { animateOneTrainPath(animation, trainView, -1, rotationStartFraction); } } lastUpdate.set(update); } }); animators.add(valueAnimator); } return animators; } private void animateOneTrainPath(ValueAnimator animation, TrainView trainView, int desiredRotation, float rotationStartFraction) { if (animation.getAnimatedFraction() <= 1 - rotationStartFraction) { float fraction = 0.5f + (animation.getAnimatedFraction() / (2 * (1 - rotationStartFraction))); trainView.finishAnimation(fraction); } else if (desiredRotation != -1 && animation.getAnimatedFraction() >= rotationStartFraction) { float fraction = (animation.getAnimatedFraction() - rotationStartFraction) / (2 * (1 - rotationStartFraction)); trainView.setRotation(desiredRotation, fraction); } else { trainView.finishAnimation(1f); } } private long getAnimationDuration(int animationLength) { long animationDuration; float oneDp = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 1, getResources().getDisplayMetrics()); animationDuration = (long) (Math.abs(animationLength / oneDp) * TRAIN_FRACTION); return animationDuration; } private int getAnimationLength(PathPoint lastPathPoint, PathPoint pathPoint, boolean isHorizontal) { int animationLength; if (isHorizontal) { animationLength = pathPoint.getLeft() - lastPathPoint.getLeft(); } else { animationLength = pathPoint.getTop() - lastPathPoint.getTop(); } return animationLength; } private void addTrainToScreen(Rect trainRect) { rootView = new FrameLayout(this); engineView = addTrain(trainRect, TrainView.ENGINE, rootView); for (int i = 0; i < NUMBER_OF_CARS; i++) { carsView.add(addTrain(trainRect, TrainView.CAR, rootView)); } rootView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { fadeOutActivity(); } }); float iconToWidthFactor = 0.34f; ImageView startRoof = new ImageView(this); startRoof.setImageResource(R.drawable.long_start_point_roof); int trainRectSize = (int) (trainRect.width() * (1f - iconToWidthFactor)); FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(trainRectSize, trainRectSize); startRoof.setX(trainRect.left + trainRect.width() * iconToWidthFactor / 2 - 15); startRoof.setY(trainRect.top + trainRect.width() * iconToWidthFactor / 2); rootView.addView(startRoof, lp); startRoof.setAlpha(0f); startRoof.animate() .alpha(1f) .setInterpolator(new DecelerateInterpolator()) .setDuration(500) .start(); setContentView(rootView); } private void fadeOutActivity() { rootView.animate() .alpha(0f) .setDuration(1000) .setInterpolator(new DecelerateInterpolator()) .setListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { finish(); } }) .start(); } @NonNull private TrainView addTrain(Rect trainRect, int trainType, FrameLayout frameLayout) { final int size = Math.min(trainRect.width(), trainRect.height()); final TrainView trainView = new TrainView(this); Resources res = getResources(); int trainWidth = (int) (trainRect.width() * 0.70); int trainHeight = (int) res.getFraction(R.fraction.rail_height_width_fraction, trainWidth, trainWidth); trainView.setType(trainType); trainView.setTrainHeight(trainHeight); FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(trainWidth, trainHeight); frameLayout.addView(trainView, lp); trainView.setX(trainRect.left + (size - trainWidth) / 2); trainView.setY(trainRect.top + (size - trainHeight) / 2); return trainView; } @Override public void onBackPressed() { fadeOutActivity(); } }