From 0816fad8a3bbbcf4143d78a6aec82447ab29fc27 Mon Sep 17 00:00:00 2001 From: Kenneth Pahn Date: Tue, 26 Apr 2022 11:48:04 +1000 Subject: [PATCH] Enable iOS to animate splash screen using multiple images Enable iOS to animate splash screen using multiple PNGs more changes fix readme fix name Add comments fix definitions/docs fix doc Some iOS fixes --- splash-screen/README.md | 41 ++++++++++++++- .../plugins/splashscreen/SplashScreen.java | 13 ++++- .../splashscreen/SplashScreenConfig.java | 12 +++++ .../splashscreen/SplashScreenPlugin.java | 6 +++ splash-screen/ios/Plugin/SplashScreen.swift | 51 +++++++++++++++++++ .../ios/Plugin/SplashScreenConfig.swift | 4 ++ .../ios/Plugin/SplashScreenPlugin.swift | 8 +++ splash-screen/src/definitions.ts | 16 ++++++ 8 files changed, 149 insertions(+), 2 deletions(-) diff --git a/splash-screen/README.md b/splash-screen/README.md index eb6ede7b8..e2fe28b2a 100644 --- a/splash-screen/README.md +++ b/splash-screen/README.md @@ -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: + +``` + + + + + + + + + + + + + + + + + +``` + +Be sure to correctly scale your images as well, as not scaling them may cause significant performance degradation when loading your app. + ## Configuration @@ -86,6 +119,8 @@ These config values are available: | **`splashImmersive`** | boolean | Hide the status bar and the software navigation buttons on the Splash Screen. Only available on Android. | | 1.0.0 | | **`layoutName`** | string | 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`** | boolean | 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`** | boolean | Animate the splash screen using a series of image files. | | 1.2.3 | +| **`launchAnimationDuration`** | number | Play the multiple frames across the amount of milliseconds specified. | | 1.2.3 | ### Examples @@ -107,7 +142,9 @@ In `capacitor.config.json`: "splashFullScreen": true, "splashImmersive": true, "layoutName": "launch_screen", - "useDialog": true + "useDialog": true, + "animated": true, + "launchAnimationDuration": 3000 } } } @@ -136,6 +173,8 @@ const config: CapacitorConfig = { splashImmersive: true, layoutName: "launch_screen", useDialog: true, + animated: true, + launchAnimationDuration: 3000, }, }, }; diff --git a/splash-screen/android/src/main/java/com/capacitorjs/plugins/splashscreen/SplashScreen.java b/splash-screen/android/src/main/java/com/capacitorjs/plugins/splashscreen/SplashScreen.java index 6ef6fac08..5c9268eee 100644 --- a/splash-screen/android/src/main/java/com/capacitorjs/plugins/splashscreen/SplashScreen.java +++ b/splash-screen/android/src/main/java/com/capacitorjs/plugins/splashscreen/SplashScreen.java @@ -10,6 +10,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; @@ -266,7 +267,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; diff --git a/splash-screen/android/src/main/java/com/capacitorjs/plugins/splashscreen/SplashScreenConfig.java b/splash-screen/android/src/main/java/com/capacitorjs/plugins/splashscreen/SplashScreenConfig.java index 043bbde2e..214f2bf49 100644 --- a/splash-screen/android/src/main/java/com/capacitorjs/plugins/splashscreen/SplashScreenConfig.java +++ b/splash-screen/android/src/main/java/com/capacitorjs/plugins/splashscreen/SplashScreenConfig.java @@ -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; @@ -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; + } } diff --git a/splash-screen/android/src/main/java/com/capacitorjs/plugins/splashscreen/SplashScreenPlugin.java b/splash-screen/android/src/main/java/com/capacitorjs/plugins/splashscreen/SplashScreenPlugin.java index 72cd636df..bb1cf0930 100644 --- a/splash-screen/android/src/main/java/com/capacitorjs/plugins/splashscreen/SplashScreenPlugin.java +++ b/splash-screen/android/src/main/java/com/capacitorjs/plugins/splashscreen/SplashScreenPlugin.java @@ -155,6 +155,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; } } diff --git a/splash-screen/ios/Plugin/SplashScreen.swift b/splash-screen/ios/Plugin/SplashScreen.swift index ef4256ee8..e72cca225 100644 --- a/splash-screen/ios/Plugin/SplashScreen.swift +++ b/splash-screen/ios/Plugin/SplashScreen.swift @@ -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() var config: SplashScreenConfig = SplashScreenConfig() var hideTask: Any? @@ -56,6 +59,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 @@ -65,6 +75,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 { @@ -87,6 +98,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() { @@ -102,6 +126,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() { @@ -112,6 +148,12 @@ import Capacitor if config.showSpinner { spinner.removeFromSuperview() } + + // If the splash screen is animated. + if config.animated { + // Remove it from the view. + imageView.removeFromSuperview() + } } // Update the bounds for the splash image. This will also be called when @@ -129,6 +171,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") } @@ -147,6 +197,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 { diff --git a/splash-screen/ios/Plugin/SplashScreenConfig.swift b/splash-screen/ios/Plugin/SplashScreenConfig.swift index 6a5d5f578..37f5d0544 100644 --- a/splash-screen/ios/Plugin/SplashScreenConfig.swift +++ b/splash-screen/ios/Plugin/SplashScreenConfig.swift @@ -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 } diff --git a/splash-screen/ios/Plugin/SplashScreenPlugin.swift b/splash-screen/ios/Plugin/SplashScreenPlugin.swift index 1fee828e9..6a6255291 100644 --- a/splash-screen/ios/Plugin/SplashScreenPlugin.swift +++ b/splash-screen/ios/Plugin/SplashScreenPlugin.swift @@ -82,6 +82,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 } diff --git a/splash-screen/src/definitions.ts b/splash-screen/src/definitions.ts index 3a5913759..eb7e0aac3 100644 --- a/splash-screen/src/definitions.ts +++ b/splash-screen/src/definitions.ts @@ -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; }; } }