package com.perflyst.twire.activities.setup; import android.content.Intent; import android.os.Bundle; import android.os.Handler; import android.util.DisplayMetrics; import android.util.Log; import android.util.TypedValue; import android.view.View; import android.view.WindowManager; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.AlphaAnimation; import android.view.animation.Animation; import android.view.animation.AnimationSet; import android.view.animation.DecelerateInterpolator; import android.view.animation.OvershootInterpolator; import android.view.animation.RotateAnimation; import android.view.animation.ScaleAnimation; import android.view.animation.TranslateAnimation; import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.RelativeLayout; import android.widget.TextView; import androidx.core.app.ActivityCompat; import com.perflyst.twire.R; import com.perflyst.twire.activities.UsageTrackingAppCompatActivity; import com.perflyst.twire.service.Service; import io.codetail.animation.SupportAnimator; import io.codetail.animation.ViewAnimationUtils; public class WelcomeActivity extends UsageTrackingAppCompatActivity { final int REVEAL_ANIMATION_DURATION = 650; final int REVEAL_ANIMATION_DELAY_DURATION = 200; final int ANIMATIONS_START_DELAY = 500; final int LOGO_ANIMATION_DURATION = 1000; final int LOGO_Container_ANIMATION_DURATION = 1750; final int WELCOME_TEXT_ANIMATION_DURATION = 900; final int WELCOME_TEXT_ANIMATION_BASE_DELAY = 175; final int CONTINUE_FAB_ANIMATION_DURATION = 750; private String LOG_TAG = getClass().getSimpleName(); private boolean hasTransitioned = false; private SupportAnimator transitionAnimationWhite = null; private SupportAnimator transitionAnimationBlue = null; private TextView mWelcomeTextLineOne, mWelcomeTextLineTwo, mWelcomeTextLineThree; private ImageView mLogo, mContinueIcon; private View mLogoCenter, mContinueFAB, mContinueFABShadow, mTransitionViewWhite, mTransitionViewBlue; private FrameLayout mLogoContainer; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_welcome); RelativeLayout mWelcomeText = findViewById(R.id.welcome_text); mWelcomeTextLineOne = findViewById(R.id.welcome_text_line_one); mWelcomeTextLineTwo = findViewById(R.id.welcome_text_line_two); mWelcomeTextLineThree = findViewById(R.id.welcome_text_line_three); mLogo = findViewById(R.id.welcome_icon); mContinueIcon = findViewById(R.id.forward_arrow); mLogoContainer = findViewById(R.id.welcome_icon_layout); mLogoCenter = findViewById(R.id.welcome_icon_center); mContinueFAB = findViewById(R.id.continue_circle); mContinueFABShadow = findViewById(R.id.welcome_continue_circle_shadow); mTransitionViewWhite = findViewById(R.id.transition_view); mTransitionViewBlue = findViewById(R.id.transition_view_blue); mTransitionViewBlue.setVisibility(View.INVISIBLE); mTransitionViewWhite.setVisibility(View.INVISIBLE); mWelcomeTextLineOne.setVisibility(View.INVISIBLE); mWelcomeTextLineTwo.setVisibility(View.INVISIBLE); mWelcomeTextLineThree.setVisibility(View.INVISIBLE); mLogo.setVisibility(View.INVISIBLE); mLogoContainer.setVisibility(View.INVISIBLE); mContinueFAB.setVisibility(View.INVISIBLE); mContinueIcon.setVisibility(View.INVISIBLE); Service.bringToBack(mTransitionViewWhite); Service.bringToBack(mTransitionViewBlue); // Change the position of the WelcomeText. Doing it this way is more dynamic, instead of a fixed // DP length from the bottom int yPosition = (int) (2.5 * (getScreenHeight() / 5)); mWelcomeText.setY(yPosition); // Start the animations. Make sure the animations that in the correct order, // by adding Animation Listeners that start the next animation on animation end. new Handler().postDelayed(() -> { startLogoContainerAnimations().setAnimationListener(new Animation.AnimationListener() { @Override public void onAnimationStart(Animation animation) { mLogoContainer.setVisibility(View.VISIBLE); } @Override public void onAnimationEnd(Animation animation) { } @Override public void onAnimationRepeat(Animation animation) { } }); startLogoOuterAnimations().setAnimationListener(new Animation.AnimationListener() { @Override public void onAnimationStart(Animation animation) { } @Override public void onAnimationEnd(Animation animation) { startWelcomeTextLineAnimations(mWelcomeTextLineOne, 1); startWelcomeTextLineAnimations(mWelcomeTextLineTwo, 2); startWelcomeTextLineAnimations(mWelcomeTextLineThree, 3).setAnimationListener(new Animation.AnimationListener() { @Override public void onAnimationStart(Animation animation) { } @Override public void onAnimationEnd(Animation animation) { startContinueFABAnimations(); } @Override public void onAnimationRepeat(Animation animation) { } }); } @Override public void onAnimationRepeat(Animation animation) { } }); }, ANIMATIONS_START_DELAY); mContinueFAB.setOnClickListener(v -> { // Get the center for the FAB int cx = (int) mContinueFAB.getX() + mContinueFAB.getMeasuredHeight() / 2; int cy = (int) mContinueFAB.getY() + mContinueFAB.getMeasuredWidth() / 2; // get the final radius for the clipping circle int dx = Math.max(cx, mTransitionViewWhite.getWidth() - cx); int dy = Math.max(cy, mTransitionViewWhite.getHeight() - cy); float finalRadius = (float) Math.hypot(dx, dy); final SupportAnimator whiteTransitionAnimation = ViewAnimationUtils.createCircularReveal(mTransitionViewWhite, cx, cy, 0, finalRadius); whiteTransitionAnimation.setInterpolator(new AccelerateDecelerateInterpolator()); whiteTransitionAnimation.setDuration(REVEAL_ANIMATION_DURATION); whiteTransitionAnimation.addListener(new SupportAnimator.AnimatorListener() { @Override public void onAnimationStart() { mTransitionViewWhite.bringToFront(); mTransitionViewWhite.setVisibility(View.VISIBLE); mContinueFAB.setClickable(false); startHideContinueIconAnimations(); } @Override public void onAnimationEnd() { transitionAnimationWhite = whiteTransitionAnimation; } @Override public void onAnimationCancel() { onAnimationEnd(); } @Override public void onAnimationRepeat() { } }); final SupportAnimator blueTransitionAnimation = ViewAnimationUtils.createCircularReveal(mTransitionViewBlue, cx, cy, 0, finalRadius); blueTransitionAnimation.setInterpolator(new AccelerateDecelerateInterpolator()); blueTransitionAnimation.setDuration(REVEAL_ANIMATION_DURATION); blueTransitionAnimation.addListener(new SupportAnimator.AnimatorListener() { @Override public void onAnimationStart() { mTransitionViewBlue.setVisibility(View.VISIBLE); mTransitionViewBlue.bringToFront(); mContinueFABShadow.bringToFront(); mContinueFAB.bringToFront(); } @Override public void onAnimationEnd() { transitionAnimationBlue = blueTransitionAnimation; } @Override public void onAnimationCancel() { onAnimationEnd(); } @Override public void onAnimationRepeat() { } }); whiteTransitionAnimation.start(); blueTransitionAnimation.setStartDelay(REVEAL_ANIMATION_DELAY_DURATION); blueTransitionAnimation.start(); new Handler().postDelayed(() -> { Log.d(LOG_TAG, "Navigating To Login Activity"); navigateToLoginActivity(); }, REVEAL_ANIMATION_DELAY_DURATION + REVEAL_ANIMATION_DURATION); }); } @Override public void onResume() { super.onResume(); // The user has returned from the login screen. Lol wtf? if (transitionAnimationWhite != null && hasTransitioned) { SupportAnimator blueReversed = transitionAnimationBlue.reverse(); blueReversed.setInterpolator(new AccelerateDecelerateInterpolator()); blueReversed.addListener(new SupportAnimator.AnimatorListener() { @Override public void onAnimationStart() { mTransitionViewBlue.setVisibility(View.VISIBLE); mTransitionViewBlue.bringToFront(); } @Override public void onAnimationEnd() { Service.bringToBack(mTransitionViewBlue); mTransitionViewBlue.setVisibility(View.INVISIBLE); } @Override public void onAnimationCancel() { } @Override public void onAnimationRepeat() { } }); blueReversed.setDuration(REVEAL_ANIMATION_DURATION); blueReversed.start(); final SupportAnimator whiteReversed = transitionAnimationWhite.reverse(); whiteReversed.setInterpolator(new AccelerateDecelerateInterpolator()); whiteReversed.addListener(new SupportAnimator.AnimatorListener() { @Override public void onAnimationStart() { mTransitionViewWhite.setVisibility(View.VISIBLE); mTransitionViewWhite.bringToFront(); } @Override public void onAnimationEnd() { Service.bringToBack(mTransitionViewWhite); mTransitionViewWhite.setVisibility(View.INVISIBLE); mContinueFAB.setClickable(true); startShowContinueIconAnimations(); } @Override public void onAnimationCancel() { } @Override public void onAnimationRepeat() { } }); whiteReversed.setDuration(REVEAL_ANIMATION_DURATION); new Handler().postDelayed(whiteReversed::start, REVEAL_ANIMATION_DELAY_DURATION); hasTransitioned = false; } } private void navigateToLoginActivity() { // Go to the login activity, with no transition. hasTransitioned = true; Intent loginActivityIntent = new Intent(getBaseContext(), LoginActivity.class); loginActivityIntent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION); ActivityCompat.startActivity(this, loginActivityIntent, null); } private int getScreenHeight() { WindowManager wm = (WindowManager) getSystemService(WINDOW_SERVICE); final DisplayMetrics displayMetrics = new DisplayMetrics(); wm.getDefaultDisplay().getMetrics(displayMetrics); return displayMetrics.heightPixels; } /** * Animations for the views in this activity */ private AnimationSet startLogoContainerAnimations() { mLogoCenter.setLayerType(View.LAYER_TYPE_HARDWARE, null); AnimationSet mInitLogoAnimations = new AnimationSet(true); TranslateAnimation trans = new TranslateAnimation(0, 0, getScreenHeight(), 0); mInitLogoAnimations.setDuration(LOGO_Container_ANIMATION_DURATION); mInitLogoAnimations.setFillAfter(true); mInitLogoAnimations.setInterpolator(new OvershootInterpolator(0.7f)); mInitLogoAnimations.addAnimation(trans); mInitLogoAnimations.setAnimationListener(new Animation.AnimationListener() { @Override public void onAnimationStart(Animation animation) { } @Override public void onAnimationEnd(Animation animation) { mLogoCenter.setLayerType(View.LAYER_TYPE_NONE, null); } @Override public void onAnimationRepeat(Animation animation) { } }); mLogoContainer.startAnimation(mInitLogoAnimations); return mInitLogoAnimations; } private AnimationSet startLogoOuterAnimations() { mLogo.setLayerType(View.LAYER_TYPE_HARDWARE, null); Animation mScaleAnimation = new ScaleAnimation(0, 1, 0, 1, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); mScaleAnimation.setDuration(LOGO_ANIMATION_DURATION); mScaleAnimation.setInterpolator(new OvershootInterpolator(0.7f)); Animation mAlphaAnimation = new AlphaAnimation(0f, 1f); mAlphaAnimation.setDuration(LOGO_ANIMATION_DURATION); mAlphaAnimation.setInterpolator(new DecelerateInterpolator()); mAlphaAnimation.setAnimationListener(new Animation.AnimationListener() { @Override public void onAnimationStart(Animation animation) { mLogo.setVisibility(View.VISIBLE); } @Override public void onAnimationEnd(Animation animation) { } @Override public void onAnimationRepeat(Animation animation) { } }); RotateAnimation mRotateAnimation = new RotateAnimation( 0, 360, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f ); mRotateAnimation.setInterpolator(new DecelerateInterpolator()); mRotateAnimation.setDuration(LOGO_ANIMATION_DURATION); mRotateAnimation.setRepeatCount(0); final AnimationSet mLogoAnimations = new AnimationSet(false); mLogoAnimations.setInterpolator(new AccelerateDecelerateInterpolator()); mLogoAnimations.setFillBefore(true); mLogoAnimations.setFillAfter(true); mLogoAnimations.addAnimation(mScaleAnimation); mLogoAnimations.addAnimation(mRotateAnimation); mLogoAnimations.addAnimation(mAlphaAnimation); mLogoAnimations.setAnimationListener(new Animation.AnimationListener() { @Override public void onAnimationStart(Animation animation) { } @Override public void onAnimationEnd(Animation animation) { mLogo.setLayerType(View.LAYER_TYPE_NONE, null); } @Override public void onAnimationRepeat(Animation animation) { } }); new Handler().postDelayed(() -> mLogo.startAnimation(mLogoAnimations), LOGO_Container_ANIMATION_DURATION - LOGO_ANIMATION_DURATION); return mLogoAnimations; } private AnimationSet startWelcomeTextLineAnimations(final TextView mWelcomeTextLine, int lineNumber) { mWelcomeTextLine.setLayerType(View.LAYER_TYPE_HARDWARE, null); int travelDistance = (lineNumber < 3) ? (int) TypedValue.applyDimension( TypedValue.COMPLEX_UNIT_SP, getResources().getDimension(R.dimen.welcome_text_line_three_size), getResources().getDisplayMetrics()) : 0; float overshoot = (lineNumber == 1) ? 2f : 1f; final Animation mTranslationAnimation = new TranslateAnimation(0, 0, travelDistance, 0); mTranslationAnimation.setInterpolator(new OvershootInterpolator(overshoot)); final Animation mAlphaAnimation = new AlphaAnimation(0f, 1f); mAlphaAnimation.setInterpolator(new DecelerateInterpolator()); mAlphaAnimation.setAnimationListener(new Animation.AnimationListener() { @Override public void onAnimationStart(Animation animation) { mWelcomeTextLine.setVisibility(View.VISIBLE); } @Override public void onAnimationEnd(Animation animation) { mWelcomeTextLine.setLayerType(View.LAYER_TYPE_NONE, null); } @Override public void onAnimationRepeat(Animation animation) { } }); final AnimationSet mWelcomeTextAnimations = new AnimationSet(false); mWelcomeTextAnimations.setDuration(WELCOME_TEXT_ANIMATION_DURATION); mWelcomeTextAnimations.setInterpolator(new AccelerateDecelerateInterpolator()); mWelcomeTextAnimations.setFillBefore(true); mWelcomeTextAnimations.setFillAfter(true); mWelcomeTextAnimations.addAnimation(mAlphaAnimation); mWelcomeTextAnimations.addAnimation(mTranslationAnimation); int delay = (lineNumber < 3) ? WELCOME_TEXT_ANIMATION_BASE_DELAY * lineNumber : WELCOME_TEXT_ANIMATION_BASE_DELAY * (lineNumber * 2); new Handler().postDelayed(() -> mWelcomeTextLine.startAnimation(mWelcomeTextAnimations), delay); return mWelcomeTextAnimations; } private void startContinueFABAnimations() { mContinueFAB.setLayerType(View.LAYER_TYPE_HARDWARE, null); mContinueFABShadow.setLayerType(View.LAYER_TYPE_HARDWARE, null); int travelDistance = Service.dpToPixels(getBaseContext(), getResources().getDimension(R.dimen.welcome_continue_circle_diameter)); final Animation mTranslationAnimation = new TranslateAnimation(0, 0, travelDistance, 0); final AnimationSet mContinueFABAnimations = new AnimationSet(true); mContinueFABAnimations.setDuration(CONTINUE_FAB_ANIMATION_DURATION); mContinueFABAnimations.setInterpolator(new OvershootInterpolator(1f)); mContinueFABAnimations.addAnimation(mTranslationAnimation); mContinueFABAnimations.setAnimationListener(new Animation.AnimationListener() { @Override public void onAnimationStart(Animation animation) { // Start running the show animation for the FAB icon a third into this animation mContinueFAB.setVisibility(View.VISIBLE); new Handler().postDelayed(() -> { mContinueIcon.setVisibility(View.VISIBLE); startShowContinueIconAnimations(); }, CONTINUE_FAB_ANIMATION_DURATION / 3); } @Override public void onAnimationEnd(Animation animation) { mContinueFAB.setLayerType(View.LAYER_TYPE_NONE, null); mContinueFABShadow.setLayerType(View.LAYER_TYPE_NONE, null); } @Override public void onAnimationRepeat(Animation animation) { } }); mContinueFAB.startAnimation(mContinueFABAnimations); mContinueFABShadow.startAnimation(mContinueFABAnimations); } private void startShowContinueIconAnimations() { Animation mScaleAnimation = new ScaleAnimation(0, 1, 0, 1, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); Animation mRotateAnimation = new RotateAnimation( 0, 360, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f ); mRotateAnimation.setRepeatCount(0); AnimationSet mAnimations = new AnimationSet(true); mAnimations.setDuration(REVEAL_ANIMATION_DURATION); mAnimations.setFillAfter(true); mAnimations.setInterpolator(new OvershootInterpolator(1.5f)); mAnimations.addAnimation(mScaleAnimation); mAnimations.addAnimation(mRotateAnimation); mContinueIcon.startAnimation(mAnimations); } private void startHideContinueIconAnimations() { Animation mScaleAnimation = new ScaleAnimation(1, 0, 1, 0, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); Animation mRotateAnimation = new RotateAnimation( 0, 360, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f ); mRotateAnimation.setRepeatCount(0); AnimationSet mAnimations = new AnimationSet(true); mAnimations.setDuration(REVEAL_ANIMATION_DURATION); mAnimations.setFillAfter(true); mAnimations.setInterpolator(new OvershootInterpolator(1.5f)); mAnimations.addAnimation(mScaleAnimation); mAnimations.addAnimation(mRotateAnimation); mContinueIcon.startAnimation(mAnimations); } }