Skip to content

Commit

Permalink
Merge pull request ionic-team#2 from kpatfln/animated-splashscreen
Browse files Browse the repository at this point in the history
Animate splash screen using multiple images
  • Loading branch information
magyargergo authored Apr 28, 2022
2 parents 0a3d804 + 99fd684 commit 62af1db
Show file tree
Hide file tree
Showing 8 changed files with 150 additions and 3 deletions.
41 changes: 40 additions & 1 deletion splash-screen/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,39 @@ For iOS, `iosSpinnerStyle` has the following options:

To set the color of the spinner use `spinnerColor`, values are either `#RRGGBB` or `#RRGGBBAA`.

## Animated Splash Screen
### iOS
If you want to have an animated splash screen on iOS, set `animated` to `true`, and `launchAnimationDuration` to the desired animation length in milliseconds in your [Capacitor configuration file] (https://capacitorjs.com/docs/config).

Once done, you can add the splash screen assets into your app's `Assets.xcassets` file inside a folder called `Splash`. Each frame should be placed in order, with `Splash_xx` (where xx is the number it belongs to in sequence, e.g. splash_1.png, splash_2.png, ...).

## Android
If you want to have an animated splash screen on Android, create a `splash.xml` in your app's `res/drawable` folder with an animation list showing the location and duration of each frame.

For example:

```
<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android" android:oneshot="false">
<item android:duration="22" android:drawable="@drawable/splash_1">
</item>
<item android:duration="22" android:drawable="@drawable/splash_2">
</item>
<item android:duration="22" android:drawable="@drawable/splash_3">
</item>
<item android:duration="22" android:drawable="@drawable/splash_4">
</item>
<item android:duration="22" android:drawable="@drawable/splash_5">
</item>
<item android:duration="22" android:drawable="@drawable/splash_6">
</item>
<item android:duration="22" android:drawable="@drawable/splash_7">
</item>
</animation-list>
```

Be sure to correctly scale your images as well, as not scaling them may cause significant performance degradation when loading your app.

## Configuration

<docgen-config>
Expand All @@ -86,6 +119,8 @@ These config values are available:
| **`splashImmersive`** | <code>boolean</code> | Hide the status bar and the software navigation buttons on the Splash Screen. Only available on Android. | | 1.0.0 |
| **`layoutName`** | <code>string</code> | If `useDialog` is set to true, configure the Dialog layout. If `useDialog` is not set or false, use a layout instead of the ImageView. Only available on Android. | | 1.1.0 |
| **`useDialog`** | <code>boolean</code> | Use a Dialog instead of an ImageView. If `layoutName` is not configured, it will use a layout that uses the splash image as background. Only available on Android. | | 1.1.0 |
| **`animated`** | <code>boolean</code> | Animate the splash screen using a series of image files. | | 1.2.3 |
| **`launchAnimationDuration`** | <code>number</code> | Play the multiple frames across the amount of milliseconds specified. | | 1.2.3 |

### Examples

Expand All @@ -107,7 +142,9 @@ In `capacitor.config.json`:
"splashFullScreen": true,
"splashImmersive": true,
"layoutName": "launch_screen",
"useDialog": true
"useDialog": true,
"animated": true,
"launchAnimationDuration": 3000
}
}
}
Expand Down Expand Up @@ -136,6 +173,8 @@ const config: CapacitorConfig = {
splashImmersive: true,
layoutName: "launch_screen",
useDialog: true,
animated: true,
launchAnimationDuration: 3000,
},
},
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import android.graphics.drawable.Animatable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.os.Build;
import android.os.Handler;
import android.view.*;
import android.view.animation.LinearInterpolator;
Expand Down Expand Up @@ -268,7 +269,17 @@ private void buildViews() {
}

private Drawable getSplashDrawable() {
int splashId = context.getResources().getIdentifier(config.getResourceName(), "drawable", context.getPackageName());
int splashId;
// Disables animations on older versions of Android as it may cause OOM issues.
if (config.getAnimated() == true && Build.VERSION.SDK_INT <= Build.VERSION_CODES.N) {
// Uses the first image in the animation sequence in the aforementioned case.
splashId = context.getResources().getIdentifier(config.getResourceName() + "_0", "drawable", context.getPackageName());
} else {
// In all other cases, use the splash resource.
// In the case it is an animation, it would be an Animation List XML which would play in sequence.
// Otherwise, it should be just a standard image file.
splashId = context.getResources().getIdentifier(config.getResourceName(), "drawable", context.getPackageName());
}
try {
Drawable drawable = context.getResources().getDrawable(splashId, context.getTheme());
return drawable;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ public class SplashScreenConfig {
private ScaleType scaleType = ScaleType.FIT_XY;
private boolean usingDialog = false;
private String layoutName;
// This is used to determine whether a series of images should be used to animate the splash screen.
private boolean animated = false;

public Integer getBackgroundColor() {
return backgroundColor;
Expand Down Expand Up @@ -117,4 +119,14 @@ public String getLayoutName() {
public void setLayoutName(String layoutName) {
this.layoutName = layoutName;
}

// This is used to read the config for whether the splash screen should be animated.
public boolean getAnimated() {
return animated;
}

// This is used to set the config for whether the splash screen should be animated.
public void setAnimated(Boolean animated) {
this.animated = animated;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,12 @@ private SplashScreenConfig getSplashScreenConfig() {
config.setLayoutName(getConfig().getString("layoutName"));
}

// This reads the Capacitor config for whether the splash screen should be animated.
// Defaults to false.
Boolean animated = getConfig().getBoolean("animated", false);
// Set the config that the splash screen should/should not be animated.
config.setAnimated(animated);

return config;
}
}
53 changes: 52 additions & 1 deletion splash-screen/ios/Plugin/SplashScreen.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import Capacitor

var parentView: UIView
var viewController = UIViewController()
// This image view / images are used to show the series of images for an animation.
var imageView = UIImageView()
var image: UIImage?
var spinner = UIActivityIndicatorView()
// Used for `updateProgress` function.
// Progress bar will only be shown when `updateProgress` is called by web app.
Expand Down Expand Up @@ -86,6 +89,13 @@ import Capacitor

strongSelf.parentView.addSubview(strongSelf.viewController.view)

// If the config says to animate.
if strongSelf.config.animated {
// Add the animated imageview across to the main view for viewing.
// Done with the subview to ensure the fade is done evenly.
strongSelf.viewController.view.addSubview(strongSelf.imageView)
}

if strongSelf.config.showSpinner {
strongSelf.parentView.addSubview(strongSelf.spinner)
strongSelf.spinner.centerXAnchor.constraint(equalTo: strongSelf.parentView.centerXAnchor).isActive = true
Expand All @@ -95,6 +105,7 @@ import Capacitor
strongSelf.parentView.isUserInteractionEnabled = false

UIView.transition(with: strongSelf.viewController.view, duration: TimeInterval(Double(settings.fadeInDuration) / 1000), options: .curveLinear, animations: {
// The animated imageview (if any) should fade in evenly with this.
strongSelf.viewController.view.alpha = 1

if strongSelf.config.showSpinner {
Expand All @@ -117,6 +128,19 @@ import Capacitor
}
}

// Creates an array of UIImage to play a sequence of images as an animation.
// Ref: https://blog.devgenius.io/how-to-animate-your-images-in-swift-ios-swift-guide-64de30ea616b
func animatedImages(for name: String) -> [UIImage] {
var i = 0
var images = [UIImage]()

while let image = UIImage(named: "\(name)/\(name)_\(i)") {
images.append(image)
i += 1
}
return images
}

private func buildViews() {
let storyboardName = Bundle.main.infoDictionary?["UILaunchStoryboardName"] as? String ?? "LaunchScreen"
if let vc = UIStoryboard(name: storyboardName, bundle: nil).instantiateInitialViewController() {
Expand All @@ -132,6 +156,18 @@ import Capacitor
spinner.translatesAutoresizingMaskIntoConstraints = false
spinner.startAnimating()
}

// If the app config says to animate.
if config.animated {
// Use the first image of the image set as a placeholder until it animates.
imageView.image = UIImage(named: "Splash/Splash_0")
// Create the list of images to make it animated.
imageView.animationImages = self.animatedImages(for: "Splash")
// Set how long to play the images over. e.g. if it's set to 3 sec, then play all images over 3 sec.
imageView.animationDuration = TimeInterval(Double(config.launchAnimationDuration) / 1000)
// Start the animation.
imageView.startAnimating()
}
}

private func tearDown() {
Expand All @@ -142,7 +178,13 @@ import Capacitor
if config.showSpinner {
spinner.removeFromSuperview()
}


// If the splash screen is animated.
if config.animated {
// Remove it from the view.
imageView.removeFromSuperview()
}

// In the case that the progress bar has been activated.
if self.progressBarVisible {
// Remove the progress bar.
Expand All @@ -166,6 +208,14 @@ import Capacitor

if let unwrappedWindow = window {
viewController.view.frame = CGRect(origin: CGPoint(x: 0, y: 0), size: unwrappedWindow.bounds.size)

// If config says it's to be animated.
if config.animated {
// Fit the image view to the screen.
imageView.frame = CGRect(origin: CGPoint(x: 0, y: 0), size: window!.bounds.size)
// Ensure that the image fits the whole screen.
imageView.contentMode = .scaleAspectFit
}
} else {
CAPLog.print("Unable to find root window object for SplashScreen bounds. Please file an issue")
}
Expand All @@ -184,6 +234,7 @@ import Capacitor
if !isVisible { return }
DispatchQueue.main.async {
UIView.transition(with: self.viewController.view, duration: TimeInterval(Double(fadeOutDuration) / 1000), options: .curveLinear, animations: {
// ImageView for animated splash will fade with this.
self.viewController.view.alpha = 0

if self.config.showSpinner {
Expand Down
4 changes: 4 additions & 0 deletions splash-screen/ios/Plugin/SplashScreenConfig.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,8 @@ public struct SplashScreenConfig {
var launchShowDuration = 500
var launchAutoHide = true
let launchFadeInDuration = 0
// How long it should take to flick through all the images for an animation.
var launchAnimationDuration = 3000
// Whether a splash screen should be animated.
var animated = false
}
8 changes: 8 additions & 0 deletions splash-screen/ios/Plugin/SplashScreenPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,14 @@ public class SplashScreenPlugin: CAPPlugin {
if let launchAutoHide = getConfigValue("launchAutoHide") as? Bool {
config.launchAutoHide = launchAutoHide
}
// Animate the splash screen using multiple image files.
if let animated = getConfigValue("animated") as? Bool {
config.animated = animated
}
// Play the multiple image frames across the amount of milliseconds specified.
if let launchAnimationDuration = getConfigValue("launchAnimationDuration") as? Int {
config.launchAnimationDuration = launchAnimationDuration
}
return config
}

Expand Down
16 changes: 16 additions & 0 deletions splash-screen/src/definitions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,22 @@ declare module '@capacitor/cli' {
* @example true
*/
useDialog?: boolean;

/**
* Animate the splash screen using a series of image files.
*
* @since 1.2.3
* @example true
*/
animated?: boolean;

/**
* Play the multiple frames across the amount of milliseconds specified.
*
* @since 1.2.3
* @example 3000
*/
launchAnimationDuration?: number;
};
}
}
Expand Down

0 comments on commit 62af1db

Please sign in to comment.