/* * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.example.android.startingwindow; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.animation.ValueAnimator; import android.annotation.SuppressLint; import android.app.Activity; import android.content.ComponentName; import android.content.Intent; import android.content.pm.ShortcutInfo; import android.content.pm.ShortcutManager; import android.content.res.Resources; import android.os.Build.VERSION_CODES; import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.os.Process; import android.os.SystemClock; import android.provider.Settings.Global; import android.util.TypedValue; import android.view.View; import android.view.ViewGroup; import android.view.ViewTreeObserver.OnPreDrawListener; import android.view.animation.AccelerateInterpolator; import android.view.animation.Interpolator; import android.view.animation.PathInterpolator; import android.widget.Button; import android.widget.LinearLayout; import android.widget.LinearLayout.LayoutParams; import android.window.SplashScreen; import android.window.SplashScreenView; import androidx.annotation.RequiresApi; import java.time.Instant; import java.time.temporal.ChronoUnit; import java.util.Collections; @RequiresApi(api = VERSION_CODES.S) public class CustomizeExitActivity extends Activity { public static final Interpolator EASE_IN_OUT = new PathInterpolator(.48f, .11f, .53f, .87f); public static final Interpolator ACCELERATE = new AccelerateInterpolator(); public static final int MOCK_DELAY = 200; public static final int MARGIN_ANIMATION_DURATION = 800; public static final int SPLASHSCREEN_ALPHA_ANIMATION_DURATION = 500; public static final int SPLASHSCREEN_TY_ANIMATION_DURATION = 1000; public static final boolean WAIT_FOR_AVD_TO_FINISH = true; public static final boolean DEBUG = false; boolean appReady = false; private float animationScale = 1.0f; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main_activity); getWindow().setDecorFitsSystemWindows(false); // On Android S, this new method has been added to Activity SplashScreen splashScreen = getSplashScreen(); // Setting an OnExitAnimationListener on the SplashScreen indicates // to the system that the application will handle the exit animation. // This means that the SplashScreen will be inflated in the application // process once the process has started. // Otherwise, the splashscreen stays in the SystemUI process and will be // dismissed once the first frame of the app is drawn splashScreen.setOnExitAnimationListener(this::onSplashScreenExit); animationScale = Global.getFloat(getContentResolver(), Global.ANIMATOR_DURATION_SCALE, 1.0f); // Create some artificial delay to simulate some local database fetch for example new Handler(Looper.getMainLooper()) .postDelayed(() -> appReady = true, (long) (MOCK_DELAY * animationScale)); // We use a pre draw listener to delay the removal of the splashscreen // until our app is ready final View content = findViewById(android.R.id.content); content.getViewTreeObserver().addOnPreDrawListener( new OnPreDrawListener() { @Override public boolean onPreDraw() { if (appReady) { content.getViewTreeObserver().removeOnPreDrawListener(this); return true; } return false; } } ); } private void onSplashScreenExit(SplashScreenView view) { // At this point the first frame of the application is drawn and // the SplashScreen is ready to be removed. // It is now up to the application to animate the provided view // since the listener is registered AnimatorSet animatorSet = new AnimatorSet(); animatorSet.setDuration(500); ObjectAnimator translationY = ObjectAnimator.ofFloat(view, "translationY", 0, view.getHeight()); translationY.setInterpolator(ACCELERATE); ObjectAnimator alpha = ObjectAnimator.ofFloat(view, "alpha", 1, 0); alpha.setInterpolator(ACCELERATE); // To get fancy, we'll also animate our content ValueAnimator marginAnimator = createContentAnimation(); animatorSet.playTogether(translationY, alpha, marginAnimator); animatorSet.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { view.remove(); } }); // If we want to wait for our Animated Vector Drawable to finish animating, we can compute // the remaining time to delay the start of the exit animation long delayMillis = Instant.now() .until(view.getIconAnimationStart().plus(view.getIconAnimationDuration()), ChronoUnit.MILLIS); view.postDelayed(animatorSet::start, (long) (delayMillis * animationScale)); } private ValueAnimator createContentAnimation() { Resources r = getResources(); float marginStart = TypedValue.applyDimension( TypedValue.COMPLEX_UNIT_DIP, 30, r.getDisplayMetrics() ); float marginEnd = TypedValue.applyDimension( TypedValue.COMPLEX_UNIT_DIP, 10, r.getDisplayMetrics() ); ValueAnimator marginAnimator = ValueAnimator.ofFloat(marginStart, marginEnd); marginAnimator.addUpdateListener(valueAnimator -> { LinearLayout container = findViewById(R.id.container); int marginTop = Math.round((Float) valueAnimator.getAnimatedValue()); for (int i = 0; i < container.getChildCount(); i++) { View child = container.getChildAt(i); ((LayoutParams) child.getLayoutParams()).setMargins(0, marginTop, 0, 0); } container.requestLayout(); }); marginAnimator.setInterpolator(EASE_IN_OUT); return marginAnimator; } @SuppressLint("SetTextI18n") private void createShortcutButton() { final Button Button = new Button(this); Button.setText("Create shortcut"); Button.setOnClickListener((v) -> createShortcut()); addContentView(Button, new ViewGroup.LayoutParams( ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); } private void createShortcut() { ShortcutManager shortcutManager = getSystemService(ShortcutManager.class); String shortcutId1 = "shortcutId1"; shortcutManager.removeDynamicShortcuts(Collections.singletonList(shortcutId1)); final ShortcutInfo.Builder b = new ShortcutInfo.Builder(this, shortcutId1); final ComponentName name = new ComponentName("com.example.android.startingwindow", "com.example.android.startingwindow.SecondActivity"); final Intent i = new Intent(Intent.ACTION_MAIN) .setComponent(name); ShortcutInfo shortcut = b.setShortLabel("label") .setLongLabel("Long label") .setIntent(i) .setStartingTheme(android.R.style.Theme_Black_NoTitleBar_Fullscreen) .build(); shortcutManager.addDynamicShortcuts(Collections.singletonList(shortcut)); } @Override protected void onPause() { // For the sake of this demo app, we kill the app on pause so // we see a cold start animation for each launch super.onPause(); finishAndRemoveTask(); Process.killProcess(Process.myPid()); } }