21 |
22 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 28
5 | defaultConfig {
6 | applicationId "com.davidmedenjak.magikarp.sample"
7 | minSdkVersion 18
8 | targetSdkVersion 28
9 | versionCode 1
10 | versionName "1.0"
11 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
12 | }
13 | buildTypes {
14 | release {
15 | minifyEnabled false
16 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
17 | }
18 | }
19 | compileOptions {
20 | sourceCompatibility = '1.8'
21 | targetCompatibility = '1.8'
22 | }
23 | }
24 |
25 | dependencies {
26 | implementation fileTree(dir: 'libs', include: ['*.jar'])
27 | implementation 'androidx.appcompat:appcompat:1.0.2'
28 |
29 | implementation project(":library")
30 | testImplementation 'junit:junit:4.12'
31 | androidTestImplementation 'androidx.test:runner:1.1.1'
32 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
33 | }
34 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 David Medenjak
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx1536m
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app's APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Automatically convert third-party libraries to use AndroidX
19 | android.enableJetifier=true
20 |
21 |
--------------------------------------------------------------------------------
/app/src/main/java/com/davidmedenjak/sample/DemoActivity.java:
--------------------------------------------------------------------------------
1 | package com.davidmedenjak.sample;
2 |
3 | import android.app.Activity;
4 | import android.content.Intent;
5 | import android.os.Bundle;
6 |
7 | import androidx.annotation.Nullable;
8 |
9 | /** Activity to launch and showcase the different modes of splash screens available. */
10 | public class DemoActivity extends Activity {
11 |
12 | @Override
13 | protected void onCreate(@Nullable Bundle savedInstanceState) {
14 | super.onCreate(savedInstanceState);
15 |
16 | setContentView(R.layout.activity_demo);
17 |
18 | findViewById(R.id.action_no_splash)
19 | .setOnClickListener(v -> startActivity(new Intent(this, NoSplashActivity.class)));
20 | findViewById(R.id.action_background_splash)
21 | .setOnClickListener(v -> startActivity(SplashActivity.newIntent(this, false, false)));
22 | findViewById(R.id.action_magikarp)
23 | .setOnClickListener(v -> startActivity(SplashActivity.newIntent(this, true, false)));
24 | findViewById(R.id.action_magikarp_animated)
25 | .setOnClickListener(v -> startActivity(SplashActivity.newIntent(this, true, true)));
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Magikarp
3 |
4 | Magikarp Demo
5 | Lorem Ipsum
6 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
7 |
8 | Magikarp icon from: https://github.com/PokeAPI/sprites
9 | Magikarp (Animated)
10 | Magikarp
11 | Background splash
12 | No splash!
13 | Available splash screen types
14 |
15 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
15 |
16 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
28 |
29 |
30 |
34 |
35 |
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: android
2 | jdk: oraclejdk8
3 | android:
4 | components:
5 | - tools
6 | - build-tools-28.0.3
7 | - android-28
8 | - extra-google-google_play_services
9 | - extra-google-m2repository
10 | - extra-android-m2repository
11 | install: true
12 | before_script:
13 | - touch local.properties
14 | script: "./gradlew build test"
15 | before_cache:
16 | - rm -f $HOME/.gradle/caches/modules-2/modules-2.lock
17 | - rm -fr $HOME/.gradle/caches/*/plugin-resolution/
18 | cache:
19 | directories:
20 | - "$HOME/.gradle/caches/"
21 | - "$HOME/.gradle/wrapper/"
22 | - "$HOME/.android/build-cache"
23 | deploy:
24 | - provider: releases
25 | api_key:
26 | secure: nYZAnnuzxoR7n9MgJuql8fnxfm3MmjlBWxeQtNWtWkJeZNBFoKmvsDb1con083AMG5nD7gN8+0f9ouobgI8HzfbyuzdMsLSLsY1C+Z8hyDLNcnuhzf/SE9KtF5CqBGLl5Exebb/7gECqLNtwd2f7+VrS2O3ly8oemO0SD4FOobbYq04wVXTPuzbIrWuPFHAPkQzmzj+y0Jxl/YrMujU94OmXWZbTo7alnqN6RNzRBCm04do+70muWMQwJCTTvDTVWqYbHwyiZZbTt/rkAjRGlyqVoFUITkXLJ6iJNMRv9T4rCDgBXTxfLXVgsquro9GGobDDEf4lTmCTJAoNba8aKif4wsQn8UL24Ppd67uuCz/1UMC9KfOJFf911M7zeLBpLxUn159xPkMDAIjUq5IzdXTvZvgeUP+Vy7A1tKjxyHhx9Iz0AJcUFaHMdQblSMzfIOFibaNu5wuSGVV0yJyFu66a3c16qrt8IPlWYd27JPMOIPFaGEEWr5ndMXQghCa92Gj7EJndU8SGaZl0yGxTu2JmI9qhhcQtYEi+epGTE249AtmKqyV8j+6K7iko0Fm/U8DdePBPntXaqxPx7K8xWGhxHf5fwroYIHTAskavFpmPJeohkCYiUHXaOCrkxiKJsiVTmRy7PiZeT3wrt5fx6sJq6FBTPcacQO9rjHi6Mac=
27 | file: apk
28 | skip_cleanup: true
29 | on:
30 | repo: bleeding182/magikarp
31 | tags: true
32 | - provider: script
33 | script: "./gradlew bintrayUpload"
34 | skip_cleanup: true
35 | on:
36 | tags: true
37 | env:
38 | global:
39 | secure: OQ7ik210d5Wq8fPxsR+svLl3bFvpgT1dORws98TIgqLQO2Ppzh7tPL7rSsxItgH8GX655zxBjSSfaQbZNtjV2a8zjRY6y+K9I0F0ZxlVgVhEsSA4GiGKZO7+FuSKhlwTVZ6Wa7KTSW9jObv2wfugnwFMW/xDN7kBYh05i0pvsrKYH55PYMZcUmby0I97mJ9Ktq7zTJpNTXh+AKmCpvdk4MEB5kcfDUJharCw/+MORlG21ss1VYyFVFx0weBtO3LigGRFB+oqUN7x/fwYa0egqqiUzNv7E2krQ9WsvHD6yeGt8QRYfTMTELhpMwG/jjIaaq/IHIO+xfKbPzIeG2gbF5oOl+j5Jrvohj6U2YvTV59I9Y4GKdVdQ+/QEK0LricuuwmppmYt749FqBeTWMK3FnWHBeds8R98bBlFz2KnCV0Mvb/J58Xm0vMYmtyqM+16PAtzXl+HUYBcNoir0LpdyC+Y4B4q2baUj87S0QFIuR9s4YI8I754xxRsZcU6Oay3O0zEL6zbhmOZQsS3FLERM/ElXJlAY1WbQkp6w67qYoeqjL9eZWDpVP/mSh45vokcW496Ow15givuBikl+OBqP2j7APGeM+eE06u3oCQ65Dkb7O1WYyLJKeDD17YG2jyt0qJwmptygVfcKATgalkma3/5mcuUMHERNR9HmsZID5Y=
40 |
--------------------------------------------------------------------------------
/library/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id "com.jfrog.bintray" version "1.8.1"
3 | }
4 | apply plugin: 'com.android.library'
5 | apply plugin: 'com.github.dcendents.android-maven'
6 |
7 | android {
8 | compileSdkVersion 28
9 |
10 |
11 | defaultConfig {
12 | minSdkVersion 18
13 | targetSdkVersion 28
14 | versionCode 1
15 | versionName "1.0"
16 |
17 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
18 |
19 | }
20 |
21 | buildTypes {
22 | release {
23 | minifyEnabled false
24 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
25 | }
26 | }
27 | compileOptions {
28 | sourceCompatibility = '1.8'
29 | targetCompatibility = '1.8'
30 | }
31 |
32 | }
33 |
34 | group = 'com.davidmedenjak.magikarp'
35 | version = System.getenv('TRAVIS_TAG')
36 |
37 | bintray {
38 | user = 'bleeding182'
39 | key = System.getenv('BINTRAY_KEY')
40 |
41 | pkg {
42 | repo = 'bleeding182'
43 | name = 'magikarp'
44 | licenses = ['MIT']
45 | vcsUrl = 'https://github.com/bleeding182/magikarp'
46 | version {
47 | name = project.version
48 | desc = ''
49 | vcsTag = System.getenv('TRAVIS_TAG')
50 | }
51 | }
52 | configurations = ['archives']
53 | }
54 |
55 | task generateSourcesJar(type: Jar) {
56 | from android.sourceSets.main.java.srcDirs
57 | classifier 'sources'
58 | }
59 |
60 | task generateJavadocs(type: Javadoc) {
61 | source = android.sourceSets.main.java.srcDirs
62 | classpath += project.files(android.getBootClasspath()
63 | .join(File.pathSeparator))
64 | }
65 |
66 | task generateJavadocsJar(type: Jar) {
67 | from generateJavadocs.destinationDir
68 | classifier 'javadoc'
69 | }
70 |
71 | artifacts {
72 | archives generateSourcesJar, generateJavadocsJar
73 | }
74 |
75 | android {
76 | lintOptions {
77 | abortOnError false
78 | }
79 | }
80 |
81 | dependencies {
82 | implementation fileTree(dir: 'libs', include: ['*.jar'])
83 |
84 | implementation 'androidx.appcompat:appcompat:1.0.2'
85 | testImplementation 'junit:junit:4.12'
86 | androidTestImplementation 'androidx.test:runner:1.1.1'
87 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
88 | }
89 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_demo.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
15 |
16 |
22 |
23 |
28 |
29 |
35 |
36 |
42 |
43 |
49 |
50 |
51 |
60 |
--------------------------------------------------------------------------------
/library/src/main/java/com/davidmedenjak/magikarp/ThemeLifecycleCallback.java:
--------------------------------------------------------------------------------
1 | package com.davidmedenjak.magikarp;
2 |
3 | import android.app.Activity;
4 | import android.app.Application;
5 | import android.content.ComponentName;
6 | import android.content.Context;
7 | import android.content.pm.ActivityInfo;
8 | import android.content.pm.PackageManager;
9 | import android.os.Bundle;
10 |
11 | import androidx.annotation.StyleRes;
12 |
13 | /**
14 | * LifecycleCallback that applies a new theme on activity's {@code onCreate()} to switch to the
15 | * actual theme after showing a splash screen.
16 | *
17 | *
This will apply the default theme to all activities which can be overridden by specifying the
18 | * {@code "theme" } meta-data in your AndroidManifest.xml
19 | */
20 | public class ThemeLifecycleCallback implements Application.ActivityLifecycleCallbacks {
21 |
22 | private static final String META_THEME = "theme";
23 | private static final int FLAG_META_DATA = PackageManager.GET_META_DATA;
24 |
25 | private final PackageManager packageManager;
26 | private final int defaultAppTheme;
27 |
28 | /** @param defaultAppTheme the default theme to use, or {@code 0} for no default. */
29 | public ThemeLifecycleCallback(Context context, @StyleRes int defaultAppTheme) {
30 | this.defaultAppTheme = defaultAppTheme;
31 | packageManager = context.getPackageManager();
32 | }
33 |
34 | @Override
35 | public void onActivityCreated(final Activity activity, final Bundle savedInstanceState) {
36 | final int theme = getTheme(activity);
37 |
38 | if (theme != 0) {
39 | activity.setTheme(theme);
40 | }
41 | }
42 |
43 | @StyleRes
44 | protected int getTheme(final Activity activity) {
45 | final Bundle metaData = readActivityMetadata(activity);
46 | if (metaData != null) {
47 | return metaData.getInt(META_THEME, defaultAppTheme);
48 | }
49 | return defaultAppTheme;
50 | }
51 |
52 | private Bundle readActivityMetadata(final Activity activity) {
53 | try {
54 | final ComponentName name = activity.getComponentName();
55 | final ActivityInfo activityInfo = packageManager.getActivityInfo(name, FLAG_META_DATA);
56 |
57 | return activityInfo.metaData;
58 | } catch (PackageManager.NameNotFoundException e) {
59 | e.printStackTrace();
60 | }
61 | return null;
62 | }
63 |
64 | // region << Unused lifecycle callbacks >>
65 | @Override
66 | public void onActivityStarted(Activity activity) {
67 | // do nothing
68 | }
69 |
70 | @Override
71 | public void onActivityResumed(Activity activity) {
72 | // do nothing
73 | }
74 |
75 | @Override
76 | public void onActivityPaused(Activity activity) {
77 | // do nothing
78 | }
79 |
80 | @Override
81 | public void onActivityStopped(Activity activity) {
82 | // do nothing
83 | }
84 |
85 | @Override
86 | public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
87 | // do nothing
88 | }
89 |
90 | @Override
91 | public void onActivityDestroyed(Activity activity) {
92 | // do nothing
93 | }
94 | // endregion
95 | }
96 |
--------------------------------------------------------------------------------
/library/src/main/java/com/davidmedenjak/magikarp/RevealCallback.java:
--------------------------------------------------------------------------------
1 | package com.davidmedenjak.magikarp;
2 |
3 | import android.animation.Animator;
4 | import android.animation.ValueAnimator;
5 | import android.content.res.Resources;
6 | import android.graphics.drawable.Drawable;
7 | import android.os.Build;
8 | import android.view.View;
9 | import android.view.ViewAnimationUtils;
10 | import android.view.ViewGroup;
11 |
12 | import androidx.annotation.MainThread;
13 | import androidx.annotation.NonNull;
14 |
15 | @MainThread
16 | public final class RevealCallback {
17 |
18 | private final SplashView view;
19 | private boolean revealed = false;
20 |
21 | RevealCallback(@NonNull SplashView view) {
22 | this.view = view;
23 | }
24 |
25 | /** Reveal the content animating away the splash screen overlay. */
26 | public void reveal() {
27 | if (revealed) {
28 | return;
29 | }
30 |
31 | final Animator revealAnimator = createRevealAnimator();
32 |
33 | final int animTime = readDefaultAnimationTime();
34 | revealAnimator.setDuration(animTime);
35 |
36 | reveal(revealAnimator);
37 | }
38 |
39 | /**
40 | * Reveal the content animating away the splash screen overlay using the {@code revealAnimation}.
41 | */
42 | public void reveal(@NonNull final Animator revealAnimation) {
43 | if (revealed) {
44 | return;
45 | }
46 | revealed = true;
47 |
48 | revealAnimation.addListener(new RemoveViewListener(view));
49 | revealAnimation.start();
50 | }
51 |
52 | @NonNull
53 | public Drawable getDrawable() {
54 | return view.getSplashDrawable();
55 | }
56 |
57 | private int readDefaultAnimationTime() {
58 | final Resources resources = view.getResources();
59 | return resources.getInteger(android.R.integer.config_mediumAnimTime);
60 | }
61 |
62 | /**
63 | * Create the default reveal animation to use with {@link #reveal(Animator)}. This is a helper
64 | * method in case you want to chain multiple animations, for the default animation you can use
65 | * {@link #reveal()}.
66 | */
67 | public Animator createRevealAnimator() {
68 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
69 | final int width = view.getWidth();
70 | final int height = view.getHeight();
71 | final int centerX = width / 2;
72 | final int centerY = height / 2;
73 | final int radius = Math.max(width, height);
74 | return ViewAnimationUtils.createCircularReveal(view, centerX, centerY, radius, 0F);
75 | } else {
76 | final Animator animator = ValueAnimator.ofFloat(1F, 0F);
77 | ((ValueAnimator) animator)
78 | .addUpdateListener(it -> view.setAlpha((Float) it.getAnimatedValue()));
79 | return animator;
80 | }
81 | }
82 |
83 | /** Removes the view from parent when the animation ends or gets cancelled. */
84 | private static class RemoveViewListener implements Animator.AnimatorListener {
85 |
86 | private final View view;
87 |
88 | private RemoveViewListener(View view) {
89 | this.view = view;
90 | }
91 |
92 | private void removeView() {
93 | ViewGroup parent = (ViewGroup) view.getParent();
94 | if (parent != null) {
95 | parent.removeView(view);
96 | }
97 | }
98 |
99 | @Override
100 | public void onAnimationEnd(Animator animation) {
101 | removeView();
102 | }
103 |
104 | @Override
105 | public void onAnimationCancel(Animator animation) {
106 | removeView();
107 | }
108 |
109 | @Override
110 | public void onAnimationStart(Animator animation) {}
111 |
112 | @Override
113 | public void onAnimationRepeat(Animator animation) {}
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_lorem_loading.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
14 |
15 |
18 |
19 |
30 |
31 |
36 |
37 |
43 |
44 |
50 |
51 |
57 |
58 |
64 |
65 |
71 |
72 |
78 |
79 |
85 |
86 |
87 |
--------------------------------------------------------------------------------
/library/src/main/java/com/davidmedenjak/magikarp/SplashView.java:
--------------------------------------------------------------------------------
1 | package com.davidmedenjak.magikarp;
2 |
3 | import android.app.Activity;
4 | import android.content.Context;
5 | import android.content.res.Resources;
6 | import android.graphics.Canvas;
7 | import android.graphics.drawable.Drawable;
8 | import android.os.Build;
9 | import android.view.Display;
10 | import android.view.Surface;
11 | import android.view.View;
12 | import android.view.WindowInsets;
13 | import android.view.WindowManager;
14 |
15 | import androidx.annotation.DrawableRes;
16 | import androidx.annotation.NonNull;
17 | import androidx.core.content.ContextCompat;
18 |
19 | /**
20 | * SplashView that aligns {@code splashDrawable} with the window background to make fluid animations
21 | * possible.
22 | *
23 | *
This class should not be used directly. Use {@link Magikarp#splash(Activity, int)} to add a
24 | * splash screen to your activity.
25 | */
26 | public class SplashView extends View {
27 |
28 | private Drawable splashDrawable;
29 |
30 | private WindowManager windowManager;
31 |
32 | public SplashView(@NonNull Context context) {
33 | super(context);
34 | init();
35 | }
36 |
37 | private static int getSize(Resources resources, String resId) {
38 | final int navigationBarSize;
39 | int resourceId = resources.getIdentifier(resId, "dimen", "android");
40 | if (resourceId > 0) {
41 | navigationBarSize = resources.getDimensionPixelSize(resourceId);
42 | } else {
43 | navigationBarSize = 0;
44 | }
45 | return navigationBarSize;
46 | }
47 |
48 | private void init() {
49 | windowManager = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
50 | }
51 |
52 | @NonNull
53 | public Drawable getSplashDrawable() {
54 | return splashDrawable;
55 | }
56 |
57 | void setSplashDrawable(@DrawableRes int drawable) {
58 | //noinspection ConstantConditions
59 | setSplashDrawable(ContextCompat.getDrawable(getContext(), drawable));
60 | }
61 |
62 | void setSplashDrawable(@NonNull Drawable drawable) {
63 | this.splashDrawable = drawable;
64 | splashDrawable.setCallback(this);
65 | }
66 |
67 | @Override
68 | protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
69 | super.onLayout(changed, left, top, right, bottom);
70 | readWindowBounds();
71 | }
72 |
73 | /**
74 | * The window background draws beneath the statusbar and navigationbar. To align the splash
75 | * drawable with the background we need to overdraw as well.
76 | */
77 | private void readWindowBounds() {
78 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
79 | final WindowInsets windowInsets = getRootWindowInsets();
80 | splashDrawable.setBounds(
81 | -windowInsets.getSystemWindowInsetLeft(),
82 | -windowInsets.getSystemWindowInsetTop(),
83 | getWidth() + windowInsets.getSystemWindowInsetRight(),
84 | getHeight() + windowInsets.getSystemWindowInsetBottom());
85 | } else {
86 | final Display defaultDisplay = windowManager.getDefaultDisplay();
87 | final int rotation = defaultDisplay.getRotation();
88 | int statusBarSize = getStatusBarSize();
89 | int navigationBarSize = getNavigationBarSize();
90 | switch (rotation) {
91 | case Surface.ROTATION_180:
92 | // todo Will this align?
93 | case Surface.ROTATION_0:
94 | splashDrawable.setBounds(0, -statusBarSize, getWidth(), getHeight() + navigationBarSize);
95 | break;
96 | case Surface.ROTATION_90:
97 | splashDrawable.setBounds(0, -statusBarSize, getWidth() + navigationBarSize, getHeight());
98 | break;
99 | case Surface.ROTATION_270:
100 | splashDrawable.setBounds(-navigationBarSize, -statusBarSize, getWidth(), getHeight());
101 | break;
102 | }
103 | }
104 | }
105 |
106 | @Override
107 | protected void onDraw(Canvas canvas) {
108 | super.onDraw(canvas);
109 | splashDrawable.draw(canvas);
110 | }
111 |
112 | @Override
113 | protected boolean verifyDrawable(@NonNull Drawable who) {
114 | return who == splashDrawable || super.verifyDrawable(who);
115 | }
116 |
117 | private int getNavigationBarSize() {
118 | return getSize(getResources(), "navigation_bar_height");
119 | }
120 |
121 | private int getStatusBarSize() {
122 | return getSize(getResources(), "status_bar_height");
123 | }
124 | }
125 |
--------------------------------------------------------------------------------
/library/src/main/java/com/davidmedenjak/magikarp/Magikarp.java:
--------------------------------------------------------------------------------
1 | package com.davidmedenjak.magikarp;
2 |
3 | import android.app.Activity;
4 | import android.app.Application;
5 | import android.graphics.drawable.Drawable;
6 | import android.view.ViewGroup;
7 | import android.view.Window;
8 |
9 | import androidx.annotation.DrawableRes;
10 | import androidx.annotation.NonNull;
11 | import androidx.annotation.StyleRes;
12 |
13 | /**
14 | * Offers methods to add splash screens to your application.
15 | *
16 | *
There exist two kinds of splash screens:
17 | *
18 | *
1. The value of {@code windowBackground} from your activity's theme, which will get shown
19 | * while the activity is loading, before the first frame of your content draws. {@link
20 | * #addSplashScreen(Application)} allows you to add a lifecycle callback that switches the splash
21 | * theme to the actual theme used before your Activity starts.
22 | *
23 | *
2. Any overlay that you might add to your content to show the splash screen for a longer
24 | * duration, e.g. while loading or initializing your content. {@link #splash(Activity, int)} allows
25 | * you to add an overlay that you can animate and finally {@link RevealCallback#reveal()} once
26 | * you're ready to show your content.
27 | *
28 | *
Those two approaches can be used together, where the {@code windowBackground} will switch
29 | * seamlessly to the overlay used, as long as the drawables used line up.
30 | */
31 | public final class Magikarp {
32 |
33 | /**
34 | * Register a LifecycleCallback that switches the window background for all activities. Reads the
35 | * meta-inf and switches the theme if {@code "theme"} is set.
36 | *
37 | * @param application the application
38 | */
39 | public static void addSplashScreen(@NonNull Application application) {
40 | addSplashScreen(application, 0);
41 | }
42 |
43 | /**
44 | * Register a LifecycleCallback that switches the window background for all activities. Reads the
45 | * meta-inf and switches the theme if {@code "theme"} is set, otherwise uses the {@code
46 | * defaultAppTheme}.
47 | *
48 | * @param application the application
49 | * @param defaultAppTheme the default theme to use if no meta-inf attribute is set
50 | */
51 | public static void addSplashScreen(
52 | @NonNull Application application, @StyleRes int defaultAppTheme) {
53 | application.registerActivityLifecycleCallbacks(
54 | new ThemeLifecycleCallback(application, defaultAppTheme));
55 | }
56 |
57 | /**
58 | * Add a splash screen overlay to the activity. This will be displayed on top of your content
59 | * until you call {@link RevealCallback#reveal()} on the callback returned.
60 | *
61 | * @param activity the activity that should get a splash screen
62 | * @param splashDrawable the drawable to use for the overlay—this should be the same
63 | * drawable as the {@code windowBackground} of your splash theme
64 | * @return the callback to reveal the content once you are done with the initialization
65 | */
66 | public static RevealCallback splash(
67 | final Activity activity, final @DrawableRes int splashDrawable) {
68 |
69 | final SplashView view = new SplashView(activity);
70 | view.setSplashDrawable(splashDrawable);
71 |
72 | return createRevealCallback(activity, view);
73 | }
74 |
75 | /**
76 | * Add a splash screen overlay to the activity. This will be displayed on top of your content
77 | * until you call {@link RevealCallback#reveal()} on the callback returned.
78 | *
79 | * @param activity the activity that should get a splash screen
80 | * @param splashDrawable the drawable to use for the overlay—this should be the same
81 | * drawable as the {@code windowBackground} of your splash theme
82 | * @return the callback to reveal the content once you are done with the initialization
83 | */
84 | public static RevealCallback splash(
85 | final Activity activity, final @NonNull Drawable splashDrawable) {
86 |
87 | final SplashView view = new SplashView(activity);
88 | view.setSplashDrawable(splashDrawable);
89 |
90 | return createRevealCallback(activity, view);
91 | }
92 |
93 | private static RevealCallback createRevealCallback(Activity activity, SplashView view) {
94 | int matchParent = ViewGroup.LayoutParams.MATCH_PARENT;
95 | final ViewGroup.LayoutParams layoutParams =
96 | new ViewGroup.LayoutParams(matchParent, matchParent);
97 |
98 | final Window window = activity.getWindow();
99 | window.addContentView(view, layoutParams);
100 |
101 | return new RevealCallback(view);
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/app/src/main/java/com/davidmedenjak/sample/SplashActivity.java:
--------------------------------------------------------------------------------
1 | package com.davidmedenjak.sample;
2 |
3 | import android.animation.Animator;
4 | import android.animation.AnimatorSet;
5 | import android.animation.ValueAnimator;
6 | import android.content.Context;
7 | import android.content.Intent;
8 | import android.graphics.Rect;
9 | import android.graphics.drawable.Drawable;
10 | import android.graphics.drawable.LayerDrawable;
11 | import android.os.Bundle;
12 | import android.os.Handler;
13 | import android.util.TypedValue;
14 | import android.view.View;
15 | import android.view.animation.AnticipateOvershootInterpolator;
16 |
17 | import com.davidmedenjak.magikarp.Magikarp;
18 | import com.davidmedenjak.magikarp.RevealCallback;
19 |
20 | import java.util.concurrent.TimeUnit;
21 |
22 | import androidx.fragment.app.FragmentActivity;
23 |
24 | /** Demo Activity showcasing the different modes usable with Magikarp. */
25 | public class SplashActivity extends FragmentActivity {
26 |
27 | public static final String EXTRA_MAGIKARP = "magikarp";
28 | public static final String EXTRA_ANIMATED = "animated";
29 |
30 | /**
31 | * Simulate the time it takes for the app to "load" until the first frame will be drawn. This is
32 | * the time where we would usually see a plain {@code windowBackground} during app start.
33 | */
34 | private static final long APP_LOAD_TIME = TimeUnit.SECONDS.toMillis(1);
35 |
36 | /**
37 | * Simulate the time it takes to load content. This would usually be the time until we finished
38 | * reading the cache or querying the API
39 | */
40 | private static final long CONTENT_LOAD_TIME = TimeUnit.MILLISECONDS.toMillis(500);
41 |
42 | public static Intent newIntent(Context context, boolean magikarp, boolean animated) {
43 | return new Intent(context, SplashActivity.class)
44 | .putExtra(EXTRA_MAGIKARP, magikarp)
45 | .putExtra(EXTRA_ANIMATED, animated);
46 | }
47 |
48 | @Override
49 | protected void onCreate(Bundle savedInstanceState) {
50 | super.onCreate(savedInstanceState);
51 |
52 | try {
53 | // simulate slow app start
54 | Thread.sleep(APP_LOAD_TIME);
55 | } catch (InterruptedException e) {
56 | e.printStackTrace();
57 | }
58 |
59 | setContentView(R.layout.activity_lorem_loading);
60 |
61 | // initialize demo stuff
62 | final boolean magikarp = getIntent().getBooleanExtra(EXTRA_MAGIKARP, false);
63 | final Runnable splash = loadSplashScreen(magikarp);
64 |
65 | // simulate content loading
66 | new Handler().postDelayed(() -> showContent(splash), CONTENT_LOAD_TIME);
67 | }
68 |
69 | /** We finished "loading", now display the "content" ;) */
70 | private void showContent(Runnable splash) {
71 | findViewById(android.R.id.progress).setVisibility(View.GONE);
72 | findViewById(R.id.content).setVisibility(View.VISIBLE);
73 | splash.run();
74 | }
75 |
76 | /**
77 | * Return the splash screen or a dummy Runnable if we don't use one. This is just to make the demo
78 | * work.
79 | */
80 | private Runnable loadSplashScreen(boolean magikarp) {
81 | if (magikarp) {
82 | boolean animated = getIntent().getBooleanExtra(EXTRA_ANIMATED, false);
83 |
84 | final RevealCallback callback = Magikarp.splash(this, R.drawable.splash_screen);
85 |
86 | if (animated) {
87 | return () -> callback.reveal(createSplashAnimation(callback));
88 | } else {
89 | return callback::reveal;
90 | }
91 | } else {
92 | return () -> {};
93 | }
94 | }
95 |
96 | private Animator createSplashAnimation(RevealCallback callback) {
97 | final Drawable drawable =
98 | ((LayerDrawable) callback.getDrawable()).findDrawableByLayerId(R.id.icon);
99 | final float dp100 =
100 | TypedValue.applyDimension(
101 | TypedValue.COMPLEX_UNIT_DIP, 100, getResources().getDisplayMetrics());
102 |
103 | final ValueAnimator splashAnimator = ValueAnimator.ofInt(0, (int) dp100, 0);
104 | splashAnimator.setDuration(450);
105 | splashAnimator.setInterpolator(new AnticipateOvershootInterpolator());
106 | splashAnimator.setRepeatMode(ValueAnimator.RESTART);
107 | splashAnimator.setStartDelay(100);
108 | splashAnimator.setRepeatCount(1);
109 |
110 | final Rect drawableBounds = drawable.copyBounds();
111 | final int top = drawableBounds.top;
112 | final int left = drawableBounds.left;
113 | splashAnimator.addUpdateListener(
114 | animation -> {
115 | drawable.copyBounds(drawableBounds);
116 | int value = (int) animation.getAnimatedValue();
117 | drawableBounds.offsetTo(left, top - value);
118 | drawable.setBounds(drawableBounds);
119 | });
120 |
121 | final AnimatorSet animatorSet = new AnimatorSet();
122 | animatorSet.playSequentially(splashAnimator, callback.createRevealAnimator());
123 | return animatorSet;
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn () {
37 | echo "$*"
38 | }
39 |
40 | die () {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save () {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Magikarp
2 |
3 | _**Magikarp** used splash—and something happened!_
4 |
5 | A small library that has your splash screen needs covered. Check out the
6 | demo app which showcases the the available options! You can also read an
7 | [introductory article](https://blog.davidmedenjak.com/android/2019/05/17/animated-splash-screens.html)
8 | on my blog.
9 |
10 |
11 |
12 |
13 |
14 | ## Why?
15 |
16 | As the name might suggest this library is intended to help you with your
17 | _splash screens_. On Android we can differentiate between two kinds of
18 | splash screen (not counting really bad ideas like splash Actvities):
19 |
20 | 1. The splash screen during app startup time (`windowBackground` of the
21 | activity's theme) which will be visible _until the first frame of the
22 | content view gets drawn_
23 | 2. View overlays while the content is loading
24 |
25 | We can't fully avoid **1.** as our app startup time will get slower the
26 | bigger the project becomes, so this is a great spot to show our splash
27 | screen with _minimal_ impact for the user—otherwise they'd just be
28 | looking at a white screen instead.
29 |
30 | Approach **2.** can be used if we _know_ that we need to do some
31 | unsightly (e.g. getting the user position, moving the map, and waiting
32 | for the tiles to finish loading). We can also use this to have a smooth
33 | animation between our splash screen and our content.
34 |
35 | While we could still use the same approach to _show a splash screen for
36 | 5s_ this is **not recommended** since users usually want to _use_ your
37 | app and not stare at a splash screen—even though some designers
38 | might think of it backwards
39 |
40 | ## Features
41 |
42 | This library offers help with both approaches mentioned:
43 |
44 | * A basic lifecycle callback that can swap themes for easy integration
45 | of a splash theme in your app
46 | * A view overlay to animate your splash screen (while your content is
47 | loading)
48 |
49 | ### Splash Theme
50 |
51 | To use a splash theme start by creating the theme. It is a good idea to
52 | use the same flags that your theme will use (`fullscreen`, etc) but the
53 | important bit is to specify the splash drawable as
54 | `android:windowBackground`
55 |
56 | ```xml
57 |
60 | ```
61 |
62 | A simple splash screen will usually consist of your primary color with
63 | the app icon centered in it:
64 |
65 | ```xml
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
76 |
77 |
78 | ```
79 |
80 | In your Application initialize Magikarp with the default theme of your
81 | app:
82 |
83 | ```java
84 | public class App extends Application {
85 |
86 | @Override
87 | public void onCreate() {
88 | super.onCreate();
89 |
90 | // ...
91 |
92 | Magikarp.addSplashScreen(this, R.style.AppTheme);
93 | }
94 | }
95 | ```
96 |
97 | Finally, register your Application and splash theme in the manifest, and
98 | keep in mind that you should not set any theme on your Activities
99 | directly, or it will use the window background from _that_ theme
100 | instead.
101 |
102 | ```xml
103 |
106 | ```
107 |
108 | That's it! If an Activity needs a different theme you can set declare it
109 | by setting `"theme"` as `` on the Activity.
110 |
111 |
112 | ```xml
113 |
114 |
117 |
118 | ```
119 |
120 | ### Animated Splash Screen
121 |
122 | Magikarp offers a very simple reveal animation to keep showing a splash
123 | screen while your content loads. Pass in the same drawable your splash
124 | screen uses and call `.reveal()` to animate the splash screen away once
125 | you're ready.
126 |
127 | ```java
128 | RevealCallback callback = Magikarp.splash(this, R.drawable.splash_screen);
129 | // ... do some loading ...
130 | callback.reveal();
131 | ```
132 |
133 | You can also create more complex animations with the drawable by using
134 | `callback.getDrawable()`.
135 |
136 | ```java
137 | private Animator createSplashAnimation(RevealCallback callback) {
138 | final Drawable drawable = ((LayerDrawable) callback.getDrawable()).findDrawableByLayerId(R.id.icon);
139 | final float dp100 = 200 // 100 "dp"
140 |
141 | final ValueAnimator splashAnimator = ValueAnimator.ofInt(0, (int) dp100, 0);
142 | splashAnimator.setDuration(450);
143 | splashAnimator.setInterpolator(new AnticipateOvershootInterpolator());
144 | splashAnimator.setRepeatMode(ValueAnimator.RESTART);
145 | splashAnimator.setStartDelay(100);
146 | splashAnimator.setRepeatCount(1);
147 |
148 | final Rect drawableBounds = drawable.copyBounds();
149 | final int top = drawableBounds.top;
150 | final int left = drawableBounds.left;
151 | splashAnimator.addUpdateListener(
152 | animation -> {
153 | drawable.copyBounds(drawableBounds);
154 | int value = (int) animation.getAnimatedValue();
155 | drawableBounds.offsetTo(left, top - value);
156 | drawable.setBounds(drawableBounds);
157 | });
158 |
159 | final AnimatorSet animatorSet = new AnimatorSet();
160 | animatorSet.playSequentially(splashAnimator, callback.createRevealAnimator());
161 | return animatorSet;
162 | }
163 | ```
164 |
165 | Then use `callback.reveal(animator)` to play your custom animation
166 | instead!
167 |
168 | ## Usage
169 |
170 | You can try it out using JitPack by adding the following to your `build.gradle` file:
171 |
172 | ```gradle
173 | repositories {
174 | maven { url 'https://jitpack.io' }
175 | }
176 | dependencies {
177 | implementation 'com.github.bleeding182:magikarp:-SNAPSHOT'
178 | }
179 | ```
180 |
181 | ## Feature Backlog
182 |
183 | This library should remain simple, as such the library should handle the
184 | two use cases mentioned and it should handle them well.
185 |
186 | For now there is only support to animate the drawable as shown in the
187 | example to prevent splash screens with more complexity than the rest of
188 | the app, but view animation support could be added if the need arises.
189 |
190 | ## Contributing
191 |
192 | This library will keep a `0.*` version until I can get some feedback
193 | about how you are using it along with any issues. As such, please feel
194 | free to add suggestions or feedback.
195 |
196 | ## License
197 |
198 | This code is published under MIT, so please feel free to use what you
199 | need.
200 |
--------------------------------------------------------------------------------