16

Background

I'm developing a themes chooser feature in my "app manager" app, and I've succeeded setting the theme dynamically for each of the activities.

AGAIN : this is not about setting the theme for the activities. This actually works fine for me.

The problem

The acitivties are showing the correct theme, but the application itself, when launching the app, is showing the wrong one, no matter what I do.

This is a problem because when the user opens the app, he will see the background of the theme of the app, and only after a few moments the activity will be shown with the theme the user has chosen.

So, if the application has a white background, and the user has chosen a theme with a black background, the order will be:

Application shows a white background -> activity is starting and shows a black background.

In screenshots:

enter image description here

So this is wrong. In this case, I need it to show black-to-black background.

Only if the user has chosen a Holo-light based theme (which the app has by default), it works fine, as the color matches the one on the activity that is shown right when opening the app.

What I've tried

I had an idea of setting the app's theme to be empty of everything, hoping that no transition will be shown, using something like:

<application
    ...
    android:theme="@android:style/Theme.Translucent.NoTitleBar" >

In fact, some people here suggested a similar solution.

This works, but it causes a bad experience, since on some devices it takes some time till the first activity is shown, and as such, there is a significant amount of time the user sees nothing at all, as if the app isn't being launched.

The question

How do I solve this?

I've already tried setting the theme in the class that extends from Application, but it doesn't do anything, no matter where in this class I call it.

android developer
  • 114,585
  • 152
  • 739
  • 1,270
  • where you setting the theme in the activity? before setting content or after? – gerbit Apr 26 '14 at 10:31
  • @kEN first line of the "onCreate" method, I call "setTheme()" (after getting which theme to use, of course). – android developer Apr 26 '14 at 10:33
  • 1
    The OP wants the impossible, settings the correct theme right from the very beginning which is right when the user clicks the launcher icon. Since settings the theme programmatically in the Application class doesn't work (search on SO), you'd have to modify the manifest programmatically which is not possible if the solution has to work on regular devices. The OP is also not very open to near-perfect solutions so don't waste your time with this question. – Emanuel Moecklin May 01 '14 at 12:10
  • 1
    @EmanuelMoecklin it's not near perfect, as I've already tried those solutions, and they "work" poorly - nothing is shown. I wanted to ask for a smooth transition. google also has lectures about it. they talk about this tip of having a smooth transition by setting the background of the application to match the one of the first activity. – android developer May 01 '14 at 13:27
  • @EmanuelMoecklin also, it's because I couldn't find a solution that I've written this question. Maybe someone has succeeded doing this before. – android developer May 01 '14 at 13:47
  • My solution would have given you a smooth transition, no color changes, no flashing of the action bar whatsoever but you can't live with the 0.3 seconds delay before the user sees the app. What you want is impossible. – Emanuel Moecklin May 01 '14 at 14:44
  • 1
    @EmanuelMoecklin What is your solution? – sergio91pt May 01 '14 at 15:39
  • 2
    @sergio91pt starting the app with a non visible theme. Once the first activity starts it will set the correct theme so no transition at all. Of course it will take a split second for the Activity to start and until then there's no theme shown at all. I removed my answer though as the OP is obviously not willing to accept any answer short from perfect and as I pointed out this question doesn't have the perfect answer. Basically the same answer Richard Le Mesurier just posted. Told you not to waste time on this question. – Emanuel Moecklin May 01 '14 at 16:02
  • 1
    @emanuelmoecklin seems I've just gone down a similar road to you. – Richard Le Mesurier May 01 '14 at 16:06
  • 1
    @EmanuelMoecklin this is not a good solution since I've told you I tested it, and as an end user, the "no-transition" make a feeling of a super slow device, as if you didn't start the app. There is about 1-2 seconds of nothing being shown. No app has this kind of solution. And adding an activity just makes it slower than just setting the theme of the app to be this way, since it needs to create the activity too. – android developer May 01 '14 at 16:25
  • @android developer: a regular app takes a second to start too so we're talking half a second more. Anyway I'm outta here. We both wasted enough time. You won't get your answer and I'll go back to real problems. – Emanuel Moecklin May 01 '14 at 16:30
  • @EmanuelMoecklin it can take even more than 2 seconds on some devices. I've already tried it and you keep suggesting the same idea. it's a bad user experience to launch something and not seeing any response on the screen. – android developer May 01 '14 at 16:43
  • 1
    @android developer: so you keep telling me and I told you that's the closest you'll get to an answer and since you won't take that as an answer I recommend not to waste any more time on trying to answer the question. Starting an Activity takes what it takes and since the Application itself can't set the theme you're doomed to not have a better answer. – Emanuel Moecklin May 01 '14 at 16:57
  • @EmanuelMoecklin ok, I've updated my question to show what I've tried, and why it's not a good solution. – android developer May 01 '14 at 17:07
  • @EmanuelMoecklin Why in the world are you criticizing the OP for wanting a perfect solution? In the OP's defense, I also would _not_ settle for anything less than perfect in this situation, so I probably wouldn't have liked your answer as well. – Alex Lockwood Nov 20 '14 at 01:46
  • @Alex: Why in the world would you care after 6 month.... Anyway my point was that there is no perfect solution and the accepted answer reflects exactly that: "I've accepted your answer, even though it's not perfect and doesn't solve the issue". Told you so... – Emanuel Moecklin Nov 20 '14 at 13:22
  • @EmanuelMoecklin Oh OK cool, I'm glad you agree with us then! Thanks for clarifying! – Alex Lockwood Nov 20 '14 at 13:27
  • 1
    Please relax people. I didn't intend to offend anyone, and I was just trying to find the best possible solution. that's it... – android developer Nov 20 '14 at 15:32

4 Answers4

10

A bit late, but this may be the answer. I discovered it by chance.

No entrance activity, no custom animations, no hacking. Simply an attribute in theme. Android buried this deep inside its resources.

Add the following attribute to your app theme:

<!--
  ~ From Theme.NoDisplay, this disables the empty preview window probably
  ~ with an incorrect theme.
  -->
<item name="android:windowDisablePreview">true</item>

And your are done. Enjoy it!

Hai Zhang
  • 5,574
  • 1
  • 44
  • 51
  • It actually disables the splash screen phase, so on some devices (like mine), it has a small delay between the time the app gets launched, and the time anything is shown on the screen. – android developer Mar 23 '15 at 20:11
  • 1
    @androiddeveloper Yes, but this is a framework-provided solution. It preserves the original animation (scale up), and this is the minimum delay one can get. – Hai Zhang Mar 24 '15 at 00:57
9

Transparent application theme with fade-in animation

My original suggestion was to use a Transparent full screen application theme (no action bar).

Combined with that, I always suggest an alpha-animation to fade across from the application theme to the activity theme. This prevents jarring to the user when the action bar appears.

OP's code would remain almost identical, except for changing the manifest theme, and adding the alpha animation in your onCreate() method of some base activity class as in examples below:


manifest theme defined as:

android:theme="@android:style/Theme.Translucent.NoTitleBar"

base activity onCreate() method:

@Override
protected void onCreate(Bundle savedInstanceState)
{
    super.onCreate(savedInstanceState);

    // set your custom theme here before setting layout
    super.setTheme(android.R.style.Theme_Holo_Light_DarkActionBar);

    setContentView(R.layout.activity_main);

    overridePendingTransition(R.anim.fade_in, R.anim.fade_out);
}

basic fade in:

<?xml version="1.0" encoding="utf-8"?>
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="2000"
    android:fromAlpha="0.0"
    android:toAlpha="1.0" />

basic fade out (not really needed, but for completeness):

<?xml version="1.0" encoding="utf-8"?>
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="2000"
    android:fromAlpha="1.0"
    android:toAlpha="0.0" />

Of course the animation durations here are way longer than you'd put in production - they are long so you can see them in your development stages.


Update #1:

It has been noted subsequently in comments by @EmanuelMoecklin, @androiddeveloper that this was considered. It is also included in answer by dentex. However, as the OP states, the weakness particularly on older devices is that the user gets no feedback when they try to launch the app. It appears the app takes too long to launch.

On KitKat, this is not the case, since the status bar & soft-keys change from transparent to black, while the rest of the screen is still transparent.

Another take on this approach would be to use a full-screen black background as the application theme. This is what was done by Bitspin for Timely, who were bought by Google apparently on the basis of the stunning UI in that app. It seems this method is therefore quite acceptable in many cases.


Update #2:

In order to speed up the perception of the launch, an alternative to the plain black theme is to use a full-screen image with the app's logo in the centre - "splash screen" style. Again fading across to the activity once launched.

This is not possible for the transparent theme, using a transparent full-screen image. Android ignores the transparency of the image (or overlays the transparent image onto a black background). This was pointed out by OP in the comments.

We can either have a transparent theme without an image, or an opaque theme with an image (an interesting topic for another question perhaps).


A note on using Manifest aliases

Another suggestion by @sergio91pt is to use aliases for different activities in the manifest.

While this can be a useful technique in some circumstances, in this case it has some drawbacks:

  1. Any HOME screen shortcut the user has created for the activity will stop working when the main launcher alias is changed i.e. each time the user changes themes.
  2. Some devices / launchers are quite slow to activate & deactivate the different aliases. In my experience this can take seconds (Galaxy Nexus 4.1 iirc), during which time you either have no visible launch icon, or you have 2 icons.
  3. Each possibly theme requires a different alias - this may prove cumbersome if there are many different themes.
Community
  • 1
  • 1
Richard Le Mesurier
  • 29,432
  • 22
  • 140
  • 255
  • 1
    That was basically my answer but it takes WAY to long before the user sees anything. Not acceptable for the OP... – Emanuel Moecklin May 01 '14 at 16:06
  • 1
    @emanuelmoecklin I hear you. Luckily it works for me in production, so I'm happy to leave it here for future readers. Cheers – Richard Le Mesurier May 01 '14 at 16:09
  • @RichardLeMesurier I will try this, but as Emanuel said, if it takes too long for the user to see anything from the time he launches the app, this is basically the same solution as he offered. I'm beginning to think that as no answer is found anywhere I've searched, this is probably impossible. The weird thing is that Google suggested in their lectures to have a nice transition using a background for the app, but it seems it's not possible in a dynamically manner (set the theme/background in code). I've updated my question to show what I've tried. – android developer May 01 '14 at 16:41
  • 3
    @RichardLeMesurier yes that came to my mind too, but sadly I'm not quite a designer. I also think nobody likes splash screens. so I was thinking about the minimal work, which is just setting the background dynamically, so I asked this question, which shows me this is probably an impossible task. :( – android developer May 01 '14 at 17:16
  • @RichardLeMesurier Will try it out. It seems that's the only way for now. I've already tried so many ways. Thank you for your time. I will leave this question opened in case someone comes with a different idea. for now, I will add +1 for all your comments here. – android developer May 01 '14 at 17:25
  • @RichardLeMesurier do you know by any chance what is the best theme (or set of attributes) for this? I would like to have an image on the center, while the surroundings is transparent (similar to what Ecilpse shows when you open it). – android developer May 01 '14 at 21:35
  • 1
    @RichardLeMesurier But when I use an image file with transparent pixels, I cannot see that they are transparent. Same goes when I use an xml. – android developer May 02 '14 at 06:56
  • @RichardLeMesurier OK, maybe people won't even like a floating splash screen anyway. I think I will just set a holo-light background with my app's logo. hope people will like it, and ignore the bad transition in case they chose a holo-dark theme. – android developer May 02 '14 at 08:49
  • @RichardLeMesurier I've tested the animation part, and for some reason I don't see any animation... Maybe it's because I used my own theme? it just "extends" from "Theme.Sherlock.Light.NoActionBar" , yet with a different background (that has the logo). – android developer May 02 '14 at 09:19
  • @androiddeveloper I have promoted my comments into the answer (since SO is already suggesting I've made too many comments). Feel free to clean up your comments, and let me know if I've missed anything out in my updated answer (other than the last one about animation - I have not addressed that one, and can't see why it would be) – Richard Le Mesurier May 02 '14 at 11:02
  • @RichardLeMesurier I've accepted your answer, even though it's not perfect and doesn't solve the issue. The reason is that I don't think that what I've asked about is possible, and you've just offered multiple alternatives, which others might consider useful. – android developer May 03 '14 at 10:05
  • @androiddeveloper Yes, I think we could all agree early on that it wasn't possible. The FUN part was trying to push the boundaries and figure out the best workaround, bouncing different ideas around. I think between you and me, we've put together a good explanation of the problem & what can be done. Thx for saying "others might consider (it) useful" - that's normally my main goal of contributing on this site. Will let you know if I get any further info... – Richard Le Mesurier May 03 '14 at 13:01
2

To fix any flickering (action bar, title...) upon app's start, I have set into the manifest

android:theme="@android:style/Theme.NoTitleBar"

for both my main activities (a tab container and a settings activity, from where I switch the themes, based on holo dark and light)

If you use some "launcher activity" or "splash activity" apply Theme.NoTitleBar also for them, then:

having declared Theme.NoTitleBar, for each activity, in onCreate you have to:

  1. set the title properly with setTitle(...) and THEN

  2. set the theme with setTheme(R.style.CustomAppTheme) BEFORE setContentView(...)
    (and you already do this);

This will prevent the flashing of the action bar/title when switching theme (if done "on-the-fly") and upon app's start.

If you want a custom action bar appearance, this means that the default holo action bar will not flash before yours.

dentex
  • 3,223
  • 4
  • 28
  • 47
  • I am talking before even showing the activities. I even wrote a note about it : "AGAIN : this is not about setting the theme for the activities. This actually works fine for me." . For example if you set dynamically the theme for all acitivities to have a different background than the one of the application, it will first show the background of the application and then the one of the activity. This is what I ask about. – android developer Apr 29 '14 at 19:09
  • The question was perfectly clear. In fact the suggestion to use `android:theme="@android:style/Theme.NoTitleBar"` is the answer to your question. This fixes the transition from *WHITE-with-ActonBar* to *your activity*, having instead *BLACK-and-no-ActionBar* to *your activity*, which is good when starting the app with dark theme (and is not bad when you use the light theme). – dentex Apr 30 '14 at 13:18
  • But then the transition would be from a screen without an action bar to one that has it. Also, how could this solve the change in the background? – android developer Apr 30 '14 at 14:39
  • *transition would be from a screen without an action bar to one that has it*: Precisely; I found this a nice workaround. *how could this solve the change in the background?*: It will remain. But you will have no changes in the ActionBar and transitions will be: all-black to a dark theme (again, GOOD) and all-black to light theme (BETTER than having light to black, IMHO, also considering many devices having an overall dark theme). – dentex Apr 30 '14 at 14:52
  • Sorry, this isn't how the default transition works. the default one is from black to black (or white to white), and still have the same action bar. I just wish to mimic it but make it work even if the user chooses other themes. :( – android developer Apr 30 '14 at 17:35
1

The transition color is retrieved from the activity theme on the manifest (or the application if not set).

Currently the only way around this limitation is to create a dummy subclass for each real Activity, eg. MyActivityLight, to declare a different theme. Activity alias won't work, the attribute will be ignored.

For activities with IntentFilter's, you should only maintain one of each "type" enabled, using PackageManager#setComponentEnabledSetting(). Note that the change may take some seconds.

For activities that are started by class name, you can infer the correct prefix according to the user's theme.


So lets suppose you have 2 themes: AppTheme.Dark and AppTheme.Light and some activities. The dark theme is the default one.

Original manifest:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example">
    <application android:theme="@style/AppTheme.Dark">
        <activity 
                android:name=".PrivateActivity" 
                android:exported="false" />

        <activity android:name=".ShowActivity">
            <intent-filter>
                <action android:name="android.intent.action.VIEW" />
                <category android:name="android.intent.category.DEFAULT" />
                <data android:mimeType="text/plain" />
             </intent-filter>
        </activity>
    </application>
</manifest>

Change all activities above as abstract classes and create dummy subclasses suffixed by Light and Dark.

Then the manifest should be changed like this:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example">

    <!-- No application theme -->
    <application>
        <activity android:name=".PrivateActivityDark" 
            android:theme="@style/AppTheme.Dark"
            android:exported="false" />
        <activity android:name=".PrivateActivityLight" 
            android:theme="@style/AppTheme.Light"
            android:exported="false"
            android:enabled="false" />

        <activity 
            android:name=".ShowActivityDark"
            android:theme="@style/AppTheme.Dark">
            <intent-filter>
                <action android:name="android.intent.action.VIEW" />
                <category android:name="android.intent.category.DEFAULT" />
                <data android:mimeType="text/plain" />
             </intent-filter>
        </activity>
        <activity 
            android:name=".ShowActivityLight" 
            android:enabled="false"
            android:theme="@style/AppTheme.Light">
            <intent-filter>
                <action android:name="android.intent.action.VIEW" />
                <category android:name="android.intent.category.DEFAULT" />
                <data android:mimeType="text/plain" />
             </intent-filter>
        </activity>
    </application>
</manifest>

Then you could have something like this to get the themed Activity class, given an abstract Activity:

public static ComponentName getThemedActivityName(
        Context ctx, 
        Class<? extends Activity> clazz) {

    // Probably gets some value off SharedPreferences
    boolean darkTheme = isUsingDarkTheme(ctx);

    String baseName = clazz.getName();
    String name += (darkTheme) ? "Dark" : "Light";
    return new ComponentName(ctx, name);
}

public static void startThemedActivity(
        Activity ctx, 
        Class<? extends Activity> clazz) {
    Intent intent = new Intent();
    intent.setComponent(getThemedActivityName(ctx, clazz));
    ctx.startActivity(intent);
}

And also change the enabled status where needed, when the theme is changed.

public void onThemeChanged(Context ctx, boolean dark) {
    // save theme to SharedPreferences or similar and...

    final PackageManager pm = ctx.getPackageManager();
    final String pckgName = ctx.getPackageName();

    final PackageInfo pckgInfo;
    try {
        final int flags = PackageManager.GET_ACTIVITIES 
                             | PackageManager.GET_DISABLED_COMPONENTS;
        pckgInfo = pm.getPackageInfo(pckgName, flags);
    } catch (PackageManager.NameNotFoundException e) {
        throw new RuntimeException(e);
    }

    final ActivityInfo[] activities = pckgInfo.activities;

    for (ActivityInfo info: activities) {
        final boolean enable;
        if (info.theme == R.style.AppTheme_Light) {
            enable = !dark;
        } else if (info.theme == R.style.AppTheme_Dark) {
           enable = dark;
        } else {
           continue;
        }

        final int state = (enable) ? 
                PackageManager.COMPONENT_ENABLED_STATE_ENABLED :
                PackageManager.COMPONENT_ENABLED_STATE_DISABLED;

        final String name = info.targetActivity;
        final ComponentName cmp = new ComponentName(pckgName, name);
        pm.setComponentEnabledSetting(cmp, state, PackageManager.DONT_KILL_APP);
    }
}

If doing IPC on a loop scares you, you can do this asynchronously on a helper thread, as long as multiple calls to onThemeChanged() run sequentially.

Note that in this example I change the enabled status of all activities (that have a known theme) but only had to do that for the ones with intent filters. If the activities aren't hardcoded its easier this way.

Important Note: As Richard Le Mesurier and other have pointed out, using this technique on Launcher Activities removes or disables the shortcut on the home screen, if it exists. This is just a solution for non launcher Activities.

sergio91pt
  • 1,459
  • 18
  • 21
  • Do you mean that I should make a single "mainActivity" for each theme, and let only one of them be enabled at a time, and each time the user wishes to change a theme, I will tell him to remove the old one and create a new one for accessing the other theme? this would work, but it's a big hassle for the user (and it's weird). – android developer Apr 30 '14 at 18:38
  • @androiddeveloper You need one activity per theme per real activity. If you have MyActivity (assuming its by default a black theme), you'll want to create a subclass MyActivityLight. The difference is the (different) theme attribute in the manifest. The burden is on you, to choose the right activity and enable and disable exported activities accordingly. This is not visible to the user in any way (they use the same code, have the same intent filters and icons...) – sergio91pt May 01 '14 at 12:33
  • But if I disable the first activity, it will not be possible to start the app. or you mean I will have a root activity with no theme, that will choose which activity to start? but then this is the same as what I already suggested, which will cause a delay (and it does) that nothing is shown on the screen till an activity is shown. if that's not what you meant, please write a sketch or something visual. – android developer May 01 '14 at 13:24
  • Maybe you didn't understand what I ask about. I wish that the moment the user opens the app via the launcher, the correct theme will show up, and then the activity will be shown. The reason for why it's problematic is that this theme should be set for the application, as the activities are shown after that, but no matter what I try, I can't set the application theme programmatically. – android developer May 01 '14 at 13:46
  • @androiddeveloper The question is clear. You would have 2 launcher activities (for 2 themes), but since only one is enabled at a time only one is visible/can be used. Let me edit the answer with some code, maybe it'll be easier to understand what I'm proposing. – sergio91pt May 01 '14 at 14:11
  • 1
    Enabling and disabling launcher Activitys will result in icons being removed from the home screen on some devices (most Samsung devices e.g. and that's a large part of the devices out there). Been there done it, users are not happy about that. – Emanuel Moecklin May 01 '14 at 15:59
  • @sergio91pt So that's what I thought you've suggested, and as I've written, this can make users unhappy as they suddenly won't see the icon the to app they've just created, only because they've changed the theme. Plus, I need to do this for each theme. :( – android developer May 01 '14 at 16:27
  • @androiddeveloper Sorry, I didn't take into account launcher apps, when I tought of this. Anyway, this will work for non launcher Activities. – sergio91pt May 01 '14 at 16:32
  • 1
    @sergio91pt yes I know, but it's an original solution that i don't think I've thought of. Only solution I've thought of is setting the app theme to be transparent, and that was suggested here in multiple ways. The problem with this solution is that the user sees nothing for too long on some devices, making it appear as if the app isn't launching. – android developer May 01 '14 at 16:36