Tuesday, October 25, 2011

Animated Splash


An Advanced Splash Screen for Android App

Русский перевод


This my article was originally published on The Code Project web site. You can see it at Codeproject.

Source Code

Introduction


     Everyone wants his/her application to be beautiful and attractive for an eye of a user. And there are a lot of applications, at least desktop applications, mostly games that use splash screens. It’s nice and, moreover, while the splash screen is working, you can initialize your application. Many tutorials exist explaining how to begin Android programming and I won't repeat them here. You can find them all over the internet. So I will show only the programming stuff.


The Beginning


Create a new Android Eclipse project with the following settings:

Project name : AdvancedSplashDemo
Build target: I've set it to Android 2.1
Application name: Advanced Splash Demo
Package name: Advanced Splash Demo
Create Activity: MainActivity – it will be the application itself 
 

So, as we don’t need a splash screen after it’s done, the first thought is to use another activity that will start the main activity and silently die after that. Let’s create a layout for the splash – it will be a linear layout and an Image View inside it. Create a new Android XML file "splash.xml" in appfolder/res/layout folder. Don’t make it fill parent, as we want it to be really as splash screen. The image view has to wrap a content too:

 
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/TheSplashLayout"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="center" >

    <ImageView
        android:id="@+id/SplashImageView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center" >
    </ImageView>
</LinearLayout>
 

Here the gravity attribute value is set to "center" to make the splash to be at the center of the screen. Add some picture to the appfolder/res/drawable folder and press F5 on the project. I’ve added lnxins.png and as you can see, set it as the image view source.

So far, let’s look at the application manifest. It has now just one “.MainActivity” activity set as a launcher. We’ll set it as default category and add another splash activity with a splash layout and will set it as the launcher. Open the manifest and open an application tab. For the main activity, change an Android intent category to default. Near application nodes press an “Add…” button, choose create a new element at top level and double click on Activity. For new activity, click on a “Name*” hyperlink and enter “SplashScreen” class. In sources, a new class will be added for the splash activity. Next, press the “Add…” button again for the SplashScreen node and add the intent filter. Again, for just added intent filter, add an action and a category.

Set the action to android.intent.action.MAIN and the category to android.intent.category.LAUNCHER. So the Splash screen activity will be run first. The manifest should look like the following:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="com.yourname.main"
      android:versionCode="1"
      android:versionName="1.0">
    <application android:icon="@drawable/icon" android:label="@string/app_name">
        <activity android:name=".MainActivity"
                  android:label="@string/app_name">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.DEFAULT"/>
            </intent-filter>
        </activity>    
        <activity android:name="SplashScreen">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"></action>
                <category android:name="android.intent.category.LAUNCHER"></category>
            </intent-filter>
        </activity>
    </application>
</manifest> 
 

A Little Coding

Open SplashScreen.java class. Now it has overridden onCreate method only. Override onTouchEvent method to give the user a possibility to close splash screen at every moment. And don’t forget synchronization or you will have random crashes. Here’s the class code:

public class SplashScreen extends Activity {
    
    /**
     * The thread to process splash screen events
     */
    private Thread mSplashThread;    

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Splash screen view
        setContentView(R.layout.splash);
        
        final SplashScreen sPlashScreen = this;   
        
        // The thread to wait for splash screen events
        mSplashThread =  new Thread(){
            @Override
            public void run(){
                try {
                    synchronized(this){
                        // Wait given period of time or exit on touch
                        wait(5000);
                    }
                }
                catch(InterruptedException ex){                    
                }

                finish();
                
                // Run next activity
                Intent intent = new Intent();
                intent.setClass(sPlashScreen, MainActivity.class);
                startActivity(intent);
                stop();                    
            }
        };
        
        mSplashThread.start();        
    }
        
    /**
     * Processes splash screen touch events
     */
    @Override
    public boolean onTouchEvent(MotionEvent evt)
    {
        if(evt.getAction() == MotionEvent.ACTION_DOWN)
        {
            synchronized(mSplashThread){
                mSplashThread.notifyAll();
            }
        }
        return true;
    }    
}


A Little Beautification

First, let’s make the splash screen transparent. In appfolder/res/values, add new Android XML file styles.xml and add to it a transparent theme:

<resources>
    <style name="Theme.Transparent" parent="android:Theme">
        <item name="android:windowIsTranslucent">true</item>
        <item name="android:windowBackground">@android:color/transparent</item>
        <item name="android:windowContentOverlay">@null</item>
        <item name="android:windowNoTitle">true</item>
        <item name="android:windowIsFloating">true</item>
        <item name="android:backgroundDimEnabled">false</item>
    </style>    
</resources>



Here are some explanations: as you can see, the style’s parent is android:Theme so we could apply it to our activity. And as you can see, the attribute's names are clear and you can understand what they mean.
Next, we’ll apply this theme to our splash. In the manifest file for the splash activity, set a "theme" attribute to the just created theme:

<activity 
    android:name="SplashScreen"
    android:theme="@style/Theme.Transparent"            
>
    <intent-filter>
        <action android:name="android.intent.action.MAIN"></action>
        <category android:name="android.intent.category.LAUNCHER"></category>
    </intent-filter>
</activity> 


Let’s suppose we’re developing a game application. And gamers don’t like when something distracts them from the game process. Most of them prefer fullscreen mode. So, set the fullscreen theme for the main activity:

<activity android:name=".MainActivity"
          android:label="@string/app_name"
          android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
          >
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.DEFAULT"/>
    </intent-filter>
</activity>


Run it. Looks better. Now we’ll make it fade-in and fade-out. Create in appfolder/res folder new folder "anim" and add to it two Android XML files – appear.xml and disappear.xml. They will be alpha animations.

Appear.xml



<set xmlns:android="http://schemas.android.com/apk/res/android">
    <alpha
        android:interpolator="@android:anim/accelerate_interpolator"
        android:fromAlpha="0.0" android:toAlpha="1.0"
        android:duration="800"
    />
</set>


Disappear.xml



<set xmlns:android="http://schemas.android.com/apk/res/android">
    <alpha
        android:interpolator="@android:anim/decelerate_interpolator"
        android:fromAlpha="1.0" android:toAlpha="0.0"
        android:duration="800"
    />
</set> 

In these animations, they just change the alpha channel of an object from fromAlpha value to toAlpha value for a given period of time. Now add new style in styles.xml:

<style name="Animations" parent="@android:Animation" />
    <style name="Animations.SplashScreen">
        <item name="android:windowEnterAnimation">@anim/appear</item>
        <item name="android:windowExitAnimation">@anim/disappear</item> 
    </style>
</style>

So, on the window, enter the “appear” animation will be performed and on the window exit the “disappear” animation will be performed. Add this style to Theme.Transparent theme:

<style name="Theme.Transparent" parent="android:Theme">
        ………
  <item name="android:windowAnimationStyle">
      @style/Animations.SplashScreen
  </item>
</style>


Ok, it’s time to run it again. Now it looks nice. And more…

Don’t Shoot at a Programmer, He Draws As He Can…

Let’s create an animated splash screen. As an artist, I’m not very good so I used Gimp’s Script-Fu to generate a set of frames for animations. First, remove android:src attribute in splash.xml. Then, in the drawable folder, create flag.xml:

<?xml version="1.0" encoding="utf-8"?>
<animation-list     
    xmlns:android="http://schemas.android.com/apk/res/android" 
    android:id="@+id/flaganim"
    android:oneshot="false"
    >
    <item android:drawable="@drawable/f03" android:duration="100" />
    <item android:drawable="@drawable/f04" android:duration="100" />
    <item android:drawable="@drawable/f05" android:duration="100" />
    <item android:drawable="@drawable/f06" android:duration="100" />
    <item android:drawable="@drawable/f07" android:duration="100" />
    <item android:drawable="@drawable/f08" android:duration="100" />
    <item android:drawable="@drawable/f09" android:duration="100" />
    <item android:drawable="@drawable/f10" android:duration="100" />    
</animation-list>

Here is a set of frames and “oneshot” attribute says to loop them. To run animation, we need to change the splash screen class code. To the onCreate method, add the following:

final ImageView splashImageView = 
        (ImageView) findViewById(R.id.SplashImageView);
 splashImageView.setBackgroundResource(R.drawable.flag);
 final AnimationDrawable frameAnimation = 
              (AnimationDrawable)splashImageView.getBackground(); 


We've set the animation for the splash, but here’s a little problem. We can’t start it from the onCreate method. The animation must be started from GUI thread. For this, we’ll use a “post” method of the ImageView class. It will add our runnable to a message queue and when GUI thread will be available, it will start it:

splashImageView.post(new Runnable(){
            @Override
            public void run() {
                frameAnimation.start();                
            }            
        });


And here we are:


That’s all. Have fun with Android programming.

Thanks!

8 comments:

  1. My respect for the article.
    Clear and complete.

    regards
    marcoqf73

    ReplyDelete
  2. This comment has been removed by the author.

    ReplyDelete
  3. You have said that we can load our apps while the splash screen is showing. How can we do that? I don't want to wait my users only for the splash for 1sec

    ReplyDelete
    Replies
    1. You can modify run() method as you wish
      and add your code to init your app data.
      If your app data will be initialized in less than 1 sec
      users will be happy ))

      Delete
  4. Your SplashScreen activity will still be stored in the recent activities stack (even if it's not stored in the back stack activity because you call finish()). So if you press back from your main activity, and launch again your application from the task manager, then your SplashScreen will be shown again.

    It's much better to add the "excludeFromRecents" attribut to your activity:
    <activity android:name="SplashActivity" android:excludeFromRecents="true">
    <intent-filter>
    <action android:name="android.intent.action.MAIN"/>
    <category android:name="android.intent.category.LAUNCHER"/>
    </intent-filter>
    </activity>

    So you longer need to call finish() nor stop() method.
    Also note that stop() is a deprecated method.

    Hope it helps
    Eric

    ReplyDelete
    Replies
    1. Sorry for the delay and thanks for your hint.
      I was too busy. I'll update the code as soon as possible.

      Delete
  5. Thanks for this article! I found it helpful and complete. I compiled a list of some top resources I found around this topic of building android splash screens. I included your post. Check it out, feel free to share. http://www.verious.com/board/Giancarlo-Leonio/building-an-android-splash-screen/

    ReplyDelete