Como alterar um ícone de aplicativo programaticamente no Android?

153

É possível alterar um ícone de aplicativo diretamente do programa?
Quer dizer, a mudança icon.pngna res\drawablepasta.
Gostaria de permitir que os usuários alterem o ícone do aplicativo no programa, para que da próxima vez vejam o ícone selecionado anteriormente no iniciador.

systempuntoout
fonte

Respostas:

80

É uma pergunta antiga, mas ainda ativa, pois não há um recurso explícito do Android. E os caras do facebook encontraram uma solução - de alguma forma. Hoje, encontrei uma maneira que funciona para mim. Não é perfeito (veja as observações no final desta resposta), mas funciona!

A ideia principal é que eu atualize o ícone do atalho do meu aplicativo, criado pelo iniciador na tela inicial. Quando quero alterar algo no ícone de atalho, removo-o primeiro e o recrio com um novo bitmap.

Aqui está o código. Tem um botão increment. Quando pressionado, o atalho é substituído por um que possui um novo número de contagem.

Primeiro, você precisa dessas duas permissões no seu manifesto:

<uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT" />
<uses-permission android:name="com.android.launcher.permission.UNINSTALL_SHORTCUT" />

Então você precisa destes dois métodos para instalar e desinstalar atalhos. O shortcutAddmétodo cria um bitmap com um número nele. Isso é apenas para demonstrar que realmente muda. Você provavelmente deseja alterar essa parte com algo que deseja no seu aplicativo.

private void shortcutAdd(String name, int number) {
    // Intent to be send, when shortcut is pressed by user ("launched")
    Intent shortcutIntent = new Intent(getApplicationContext(), Play.class);
    shortcutIntent.setAction(Constants.ACTION_PLAY);

    // Create bitmap with number in it -> very default. You probably want to give it a more stylish look
    Bitmap bitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
    Paint paint = new Paint();
    paint.setColor(0xFF808080); // gray
    paint.setTextAlign(Paint.Align.CENTER);
    paint.setTextSize(50);
    new Canvas(bitmap).drawText(""+number, 50, 50, paint);
    ((ImageView) findViewById(R.id.icon)).setImageBitmap(bitmap);

    // Decorate the shortcut
    Intent addIntent = new Intent();
    addIntent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);
    addIntent.putExtra(Intent.EXTRA_SHORTCUT_NAME, name);
    addIntent.putExtra(Intent.EXTRA_SHORTCUT_ICON, bitmap);

    // Inform launcher to create shortcut
    addIntent.setAction("com.android.launcher.action.INSTALL_SHORTCUT");
    getApplicationContext().sendBroadcast(addIntent);
}

private void shortcutDel(String name) {
    // Intent to be send, when shortcut is pressed by user ("launched")
    Intent shortcutIntent = new Intent(getApplicationContext(), Play.class);
    shortcutIntent.setAction(Constants.ACTION_PLAY);

    // Decorate the shortcut
    Intent delIntent = new Intent();
    delIntent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);
    delIntent.putExtra(Intent.EXTRA_SHORTCUT_NAME, name);

    // Inform launcher to remove shortcut
    delIntent.setAction("com.android.launcher.action.UNINSTALL_SHORTCUT");
    getApplicationContext().sendBroadcast(delIntent);
}

E, finalmente, aqui estão dois ouvintes para adicionar o primeiro atalho e atualizar o atalho com um contador de incremento.

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

    setContentView(R.layout.test);
    findViewById(R.id.add).setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View v) {
            shortcutAdd("changeIt!", count);
        }
    });
    findViewById(R.id.increment).setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View v) {
            shortcutDel("changeIt!");
            count++;
            shortcutAdd("changeIt!", count);
        }
    });
}

Observações:

  • Dessa forma, também funciona se o seu aplicativo controlar mais atalhos na tela inicial, por exemplo, com diferentes extras na tela Intent. Eles só precisam de nomes diferentes para que o correto seja desinstalado e reinstalado.

  • O tratamento programático de atalhos no Android é um recurso Android bem conhecido, amplamente usado, mas não oficialmente suportado. Parece funcionar no iniciador padrão e nunca tentei em nenhum outro lugar. Então, não me culpe, quando você receber este e-mail do usuário "Ele não funciona no meu telefone XYZ, com duas raizes e super explosivo"

  • O iniciador grava um Toastquando um atalho foi instalado e outro quando um atalho foi desinstalado. Então, recebo dois Toastsegundos cada vez que mudo o ícone. Isso não é perfeito, mas bem, desde que o resto do meu aplicativo seja perfeito ...

jboi
fonte
9
O aplicativo Calendário de hoje altera ícones todos os dias sem brindar.
Jim McKeeth
1
@ Jim (eu acho), este é realmente um widget então
JacksOnF1re
3
shortcutDel infelizmente não funciona mais em Marshmallow, ver também stackoverflow.com/a/33731620/1545993
Taifun
2
isso substitui o ícone de atalho, não o ícone do iniciador #
user1506104
3
Nas linhas abaixo, o que é Play.class e Constants.ACTION_PLAY Intent shortcutIntent = new Intent (getApplicationContext (), Play.class); shortcutIntent.setAction (Constants.ACTION_PLAY);
Dasharath Singh Bajroliya
136

Tente isso, funciona bem para mim:

1 Modifique sua MainActivityseção AndroidManifest.xml, exclua-a, alinhe com a MAINcategoria na intent-filterseção

<activity android:name="ru.quickmessage.pa.MainActivity"
    android:configChanges="keyboardHidden|orientation"
    android:screenOrientation="portrait"
    android:label="@string/app_name"
    android:theme="@style/CustomTheme"
    android:launchMode="singleTask">
    <intent-filter>
        ==> <action android:name="android.intent.action.MAIN" /> <== Delete this line
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>

2) Crie <activity-alias>para cada um dos seus ícones. Como isso

<activity-alias android:label="@string/app_name" 
    android:icon="@drawable/icon" 
    android:name=".MainActivity-Red"
    android:enabled="false"
    android:targetActivity=".MainActivity">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>   
</activity-alias>

3) Definir programaticamente: defina o atributo ENABLE para o apropriadoactivity-alias

 getPackageManager().setComponentEnabledSetting(
        new ComponentName("ru.quickmessage.pa", "ru.quickmessage.pa.MainActivity-Red"), 
            PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP);

Observe que pelo menos um deve estar ativado o tempo todo.

PA
fonte
3
infelizmente funciona de maneira diferente nos dispositivos que experimentei. Funciona no HTC Desire 2.2, mas não é confiável no Galaxy Nexus 4.2.2 e Nexus 7 4.3. No Galaxy Nexus, pode levar todos os ícones do aplicativo a desaparecer e também a remover todos os widgets. Pena, então eu tive que remover essa funcionalidade em dispositivos posteriores.
Richard Le Mesurier
7
não consigo iniciar meu aplicativo desde que apaguei isso: <ação android: name = "android.intent.action.MAIN" /> <categoria android: name = "android.intent.category.LAUNCHER" />
noobProgrammer
1
Isso funciona perfeitamente para mim ao alternar entre os ícones do iniciador na bandeja de aplicativos e na tela inicial, mas descobri que o ícone usado no nível do sistema operacional (como multitarefa, pop-up de desinstalação ou na listagem do Application Manager) permanece o original ou se torna o ícone genérico padrão do Android, se você não tiver definido um no nível do aplicativo no manifesto. Você encontrou uma solução para isso ou apenas tolera uma incompatibilidade (ou ausência) no ícone no nível do sistema operacional?
Jokeefe #
2
Erro ao executar o aplicativo. Atividade padrão não encontrada.
CopsOnRoad 15/10
1
Erro ao executar o aplicativo. Atividade padrão não encontrada
Kamil Ibadov 5/11
36

Você não pode alterar o manifesto ou o recurso no APK assinado e selado, exceto por meio de uma atualização de software.

CommonsWare
fonte
2
@Nanne: Esse é um widget de aplicativo ou recurso da tela inicial, não um ícone de aplicativo. Você ainda não pode alterar o manifesto ou um recurso, exceto por meio de uma atualização de software.
CommonsWare 22/03
1
? Não, quero dizer o contrário: não é (anunciado como) um widget. Eu o adiciono como um atalho do aplicativo. Mas, como você diz, só porque este material NonStock implica a sua apenas um ícone, que não significa que ele é :)
Nanne
2
@NeTeInStEiN: Não funcionará para todas as telas iniciais (por exemplo, aquelas que não prestam atenção às alterações ativadas por componente).
CommonsWare
1
Não é mais verdade. O Google Agenda no Android 6 ou superior é alterado diariamente no iniciador. Hoje, o ícone é um "2", ontem foi um "1". Geralmente, havia um "31" no ícone. Mas não mais, isso muda. Alguém sabe como isso é possível?
UeliDeSchwert 2/17/17
1
@Bobby: quero dizer que existem centenas, se não milhares, de implementações de tela inicial na Play Store, além das centenas de diferentes telas iniciais pré-instaladas nos milhares de modelos de dispositivos Android existentes. Essas implementações da tela inicial são bem-vindas para ter ganchos que permitem a substituição dinâmica do ícone do iniciador. No entanto, nem todas as telas iniciais precisam oferecer isso. Só porque você está vendo esse comportamento de um aplicativo em uma tela inicial em um dispositivo não significa que ele esteja disponível para todos os aplicativos em todas as telas iniciais em todos os dispositivos.
CommonsWare
17

Programaticamente, convém publicar o iniciador de aplicativos:

Nota: este método não funciona mais a partir do Android 8.0 - Oreo

No seu AndroidManifest.xml, adicione:

<uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT"/>

Então você precisa criar sua intenção do iniciador de aplicativos:

Intent myLauncherIntent = new Intent();
myLauncherIntent.setClassName("your.package.name", "YourLauncherActivityName");
myLauncherIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

Crie um atalho de instalação com o iniciador de aplicativos e o ícone personalizado:

Intent intent = new Intent();
intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, myLauncherIntent);
intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, "Application Name");
intent.putExtra
       (
        Intent.EXTRA_SHORTCUT_ICON_RESOURCE,
        Intent.ShortcutIconResource.fromContext
                                    (
                                         getApplicationContext(), 
                                         R.drawable.app_icon
                                    )
       );
intent.setAction("com.android.launcher.action.INSTALL_SHORTCUT");

E, finalmente, ative a intenção de transmissão:

getApplicationContext().sendBroadcast(intent);
Andrei Marcut
fonte
Isso substitui o ícone de atalho, não o ícone lançador
user1506104
10

Supondo que você queira alterar o ícone mostrado na tela inicial, isso pode ser feito facilmente criando um widget que faça exatamente isso. Aqui está um artigo que demonstra como isso pode ser feito para um aplicativo do tipo "novas mensagens" semelhante ao iPhone:

http://www.cnet.com/8301-19736_1-10278814-251.html

Marius Kjeldahl
fonte
9

A solução da @ PA funciona parcialmente para mim. Detalhe minhas descobertas abaixo:

1) O primeiro trecho de código está incorreto, veja abaixo:

<activity
    ...
    <intent-filter>
        ==> <action android:name="android.intent.action.MAIN" /> <== This line shouldn't be deleted, otherwise will have compile error
        <category android:name="android.intent.category.LAUNCHER" /> //DELETE THIS LINE
    </intent-filter>
</activity>

2) Deve usar o código a seguir para desativar todos os ícones antes de ativar outro, caso contrário, ele adicionará um novo ícone, em vez de substituí-lo.

getPackageManager().setComponentEnabledSetting(
        getComponentName(), PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP);

MAS, se você usar o código acima, o atalho na tela inicial será removido! E não será adicionado automaticamente de volta. Você poderá adicionar programaticamente o ícone de volta, mas provavelmente não permanecerá na mesma posição de antes.

3) Observe que o ícone não será alterado imediatamente, pode levar alguns segundos. Se você clicar logo após a alteração, poderá receber um erro dizendo: "O aplicativo não está instalado".

Portanto, IMHO esta solução é adequada apenas para alterar o ícone apenas no iniciador de aplicativos, não para atalhos (ou seja, o ícone na tela inicial)

Deqing
fonte
2
Erro ao executar o aplicativo. Atividade padrão não encontrada.
CopsOnRoad 15/10
você excluir o iniciador, como ele encontrará a atividade padrão @Deqing?
J2emanue
5

Experimente esta solução

<activity android:name=".SplashActivity"
        android:label="@string/app_name"
        android:icon="@drawable/ic_launcher">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>

    <activity-alias android:label="ShortCut"
        android:icon="@drawable/ic_short_cut"
        android:name=".SplashActivityAlias"
        android:enabled="false"
        android:targetActivity=".SplashActivity">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity-alias>

Adicione o seguinte código quando desejar alterar o ícone do seu aplicativo

PackageManager pm = getPackageManager();
                    pm.setComponentEnabledSetting(
                            new ComponentName(YourActivity.this,
                                    "your_package_name.SplashActivity"),
                            PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
                            PackageManager.DONT_KILL_APP);

                    pm.setComponentEnabledSetting(
                            new ComponentName(YourActivity.this,
                                    "your_package_name.SplashActivityAlias"),
                            PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
                            PackageManager.DONT_KILL_APP);
Faxriddin Abdullayev
fonte
4

AndroidManifest.xml exemplo:

<application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">

        <activity android:name="com.pritesh.resourceidentifierexample.MainActivity"
                  android:label="@string/app_name"
                  android:launchMode="singleTask">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <!--<category android:name="android.intent.category.LAUNCHER"/>-->
            </intent-filter>
        </activity>

        <activity-alias android:label="RED"
                        android:icon="@drawable/ic_android_red"
                        android:name="com.pritesh.resourceidentifierexample.MainActivity-Red"
                        android:enabled="true"
                        android:targetActivity="com.pritesh.resourceidentifierexample.MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity-alias>

        <activity-alias android:label="GREEN"
                        android:icon="@drawable/ic_android_green"
                        android:name="com.pritesh.resourceidentifierexample.MainActivity-Green"
                        android:enabled="false"
                        android:targetActivity="com.pritesh.resourceidentifierexample.MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity-alias>

        <activity-alias android:label="BLUE"
                        android:icon="@drawable/ic_android_blue"
                        android:name="com.pritesh.resourceidentifierexample.MainActivity-Blue"
                        android:enabled="false"
                        android:targetActivity="com.pritesh.resourceidentifierexample.MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity-alias>

    </application>

Em seguida, siga o código fornecido abaixo em MainActivity:

ImageView imageView = (ImageView)findViewById(R.id.imageView);
            int imageResourceId;
            String currentDateTimeString = DateFormat.getDateTimeInstance().format(new Date());
            int hours = new Time(System.currentTimeMillis()).getHours();
            Log.d("DATE", "onCreate: "  + hours);

            getPackageManager().setComponentEnabledSetting(
                    getComponentName(), PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP);

            if(hours == 13)
            {
                imageResourceId = this.getResources().getIdentifier("ic_android_red", "drawable", this.getPackageName());
                getPackageManager().setComponentEnabledSetting(
                        new ComponentName("com.pritesh.resourceidentifierexample", "com.pritesh.resourceidentifierexample.MainActivity-Red"),
                        PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP);
            }else if(hours == 14)
            {
                imageResourceId = this.getResources().getIdentifier("ic_android_green", "drawable", this.getPackageName());
                getPackageManager().setComponentEnabledSetting(
                        new ComponentName("com.pritesh.resourceidentifierexample", "com.pritesh.resourceidentifierexample.MainActivity-Green"),
                        PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP);

            }else
            {
                imageResourceId = this.getResources().getIdentifier("ic_android_blue", "drawable", this.getPackageName());
                getPackageManager().setComponentEnabledSetting(
                        new ComponentName("com.pritesh.resourceidentifierexample", "com.pritesh.resourceidentifierexample.MainActivity-Blue"),
                        PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP);

            }

            imageView.setImageResource(imageResourceId);
Pritesh Patel
fonte
Estou recebendo com.pritesh.resourceidentifierexample.MainActivity-Red doesn't exist in com.pritesh.resourceidentifierexampleexceção. aqui eu usei o seu nome de manifesto apenas para demonstrar o meu problema
Tejas Pandya
0

Para que a solução de Markus funcionasse, eu precisava da primeira intenção:

Intent myLauncherIntent = new Intent(Intent.ACTION_MAIN);
            myLauncherIntent.setClassName(this,  this.getClass().getName());
            myLauncherIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
PuZZleDucK
fonte
0

Aplicando as sugestões mencionadas, enfrentei a questão do aplicativo ser morto sempre que o ícone padrão é alterado para novo ícone. Então, implementamos o código com alguns ajustes. Passo 1). No arquivo AndroidManifest.xml, crie para a atividade padrão com android: enabled = "true" e outro alias com android: enabled = "false". Você não irá conter, mas anexá-las ao android: enabled = "true".

       <activity
        android:name=".activities.SplashActivity"
        android:label="@string/app_name"
        android:screenOrientation="portrait"
        android:theme="@style/SplashTheme">

    </activity>
    <!-- <activity-alias used to change app icon dynamically>   : default icon, set enabled true    -->
    <activity-alias
        android:label="@string/app_name"
        android:icon="@mipmap/ic_launcher"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:name=".SplashActivityAlias1" <!--put any random name started with dot-->
        android:enabled="true"
        android:targetActivity=".activities.SplashActivity"> <!--target activity class path will be same for all alias-->
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />

            <category android:name="android.intent.category.DEFAULT" />
            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity-alias>
    <!-- <activity-alias used to change app icon dynamically>  : sale icon, set enabled false initially -->
    <activity-alias
        android:label="@string/app_name"
        android:icon="@drawable/ic_store_marker"
        android:roundIcon="@drawable/ic_store_marker"
        android:name=".SplashActivityAlias" <!--put any random name started with dot-->
        android:enabled="false"
        android:targetActivity=".activities.SplashActivity"> <!--target activity class path will be same for all alias-->
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />

            <category android:name="android.intent.category.DEFAULT" />
            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity-alias>

Passo 2). Crie um método que será usado para desativar o 1º alias de atividade que contém o ícone padrão e ativar o 2º alias que contém o ícone.

/**
 * method to change the app icon dynamically
 *
 * @param context
 * @param isNewIcon  : true if new icon need to be set; false to set default 
 * icon
 */

public static void changeAppIconDynamically(Context context, boolean isNewIcon) {
    PackageManager pm = context.getApplicationContext().getPackageManager();
    if (isNewIcon) {
        pm.setComponentEnabledSetting(
                new ComponentName(context,
                        "com.example.dummy.SplashActivityAlias1"), //com.example.dummy will be your package
                PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
                PackageManager.DONT_KILL_APP);

        pm.setComponentEnabledSetting(
                new ComponentName(context,
                        "com.example.dummy.SplashActivityAlias"),
                PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
                PackageManager.DONT_KILL_APP);
    } else {
        pm.setComponentEnabledSetting(
                new ComponentName(context,
                        "com.example.dummy.SplashActivityAlias1"),
                PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
                PackageManager.DONT_KILL_APP);

        pm.setComponentEnabledSetting(
                new ComponentName(context,
                        "com.example.dummy.SplashActivityAlias"),
                PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
                PackageManager.DONT_KILL_APP);
    }
}

Etapa 3). Agora, chame esse método de acordo com sua necessidade, digamos no clique no botão ou na data específica ou na ocasião, como simplesmente -

// Switch app icon to new icon
    GeneralUtils.changeAppIconDynamically(EditProfileActivity.this, true);
// Switch app icon to default icon
            GeneralUtils.changeAppIconDynamically(EditProfileActivity.this, false);

Espero que isso ajude aqueles que enfrentam o problema do aplicativo ser morto na mudança de ícone. Happy Coding :)

Abhijeet
fonte