| /* | |
| * 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()); | |
| } | |
| } |