Cambiar la configuración regional dentro de la aplicación

197

Mis usuarios pueden cambiar la configuración regional dentro de la aplicación (pueden querer mantener la configuración de su teléfono en inglés pero leer el contenido de mi aplicación en francés, holandés o en cualquier otro idioma ...)

¿Por qué esto funciona perfectamente bien en 1.5 / 1.6 pero ya no en 2.0?

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    switch(item.getItemId()) {
    case 201:
        Locale locale2 = new Locale("fr"); 
        Locale.setDefault(locale2);
        Configuration config2 = new Configuration();
        config2.locale = locale2;
        getBaseContext().getResources().updateConfiguration(
            config2, getBaseContext().getResources().getDisplayMetrics());
        // loading data ...
        refresh();
        // refresh the tabs and their content
        refresh_Tab ();   
     break;
     case 201: etc...

El problema es que el MENÚ se "encoge" cada vez más cada vez que el usuario pasa por las líneas de código anteriores ...

Este es el menú que se reduce:

@Override
public boolean onCreateOptionsMenu(Menu menu) {
    menu.add(0, 100, 1, "REFRESH").setIcon(android.R.drawable.ic_menu_compass);
    SubMenu langMenu = menu.addSubMenu(0, 200, 2, "NL-FR").setIcon(android.R.drawable.ic_menu_rotate);
        langMenu.add(1, 201, 0, "Nederlands");
        langMenu.add(1, 202, 0, "Français");
    menu.add(0, 250, 4, R.string.OptionMenu2).setIcon(android.R.drawable.ic_menu_send);
    menu.add(0, 300, 5, R.string.OptionMenu3).setIcon(android.R.drawable.ic_menu_preferences);
    menu.add(0, 350, 3, R.string.OptionMenu4).setIcon(android.R.drawable.ic_menu_more);
    menu.add(0, 400, 6, "Exit").setIcon(android.R.drawable.ic_menu_delete);

    return super.onCreateOptionsMenu(menu);
}

¿Qué debo hacer en API Nivel 5 para que esto funcione nuevamente?

AQUÍ ESTÁ EL CÓDIGO COMPLETO SI DESEA PROBAR ESTO:

import java.util.Locale;

import android.app.Activity;
import android.content.res.Configuration;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.SubMenu;
import android.widget.Toast;

public class Main extends Activity {
    /** Called when the activity is first created. */


    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {

        SubMenu langMenu = menu.addSubMenu(0, 200, 2, "NL-FR").setIcon(android.R.drawable.ic_menu_rotate);
            langMenu.add(1, 201, 0, "Nederlands");
            langMenu.add(1, 202, 0, "Français");

        return super.onCreateOptionsMenu(menu);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch(item.getItemId()){

        case 201:

            Locale locale = new Locale("nl"); 
            Locale.setDefault(locale);
            Configuration config = new Configuration();
            config.locale = locale;
            getBaseContext().getResources().updateConfiguration(config, getBaseContext().getResources().getDisplayMetrics());
            Toast.makeText(this, "Locale in Nederlands !", Toast.LENGTH_LONG).show();
            break;

        case 202:

            Locale locale2 = new Locale("fr"); 
            Locale.setDefault(locale2);
            Configuration config2 = new Configuration();
            config2.locale = locale2;
            getBaseContext().getResources().updateConfiguration(config2, getBaseContext().getResources().getDisplayMetrics());

            Toast.makeText(this, "Locale en Français !", Toast.LENGTH_LONG).show();
            break;  

        }
        return super.onOptionsItemSelected(item);
    }
}

Y aquí está el manifiesto:

<?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="com.cousinHub.ChangeLocale"
          android:versionCode="1"
          android:versionName="1.0">
        <application android:icon="@drawable/icon" android:label="@string/app_name">
            <activity android:name=".Main"
                      android:label="@string/app_name">
                <intent-filter>
                    <action android:name="android.intent.action.MAIN" />
                    <category android:name="android.intent.category.LAUNCHER" />
                </intent-filter>
            </activity>
        </application>
        <uses-sdk android:minSdkVersion="3" /> 
    </manifest>

ESTO ES LO QUE ENCONTRÉ:

<uses-sdk android:minSdkVersion="5" />

=> FUNCIONA SOLO BIEN ...

<uses-sdk android:minSdkVersion="3" />

=> ¡El menú se reduce cada vez que cambia la configuración regional!

Como quiero mantener mi aplicación accesible para los usuarios en 1.5, ¿qué debo hacer?

Hubert
fuente
¿Qué quieres decir con "encogimientos"?
CommonsWare
se hace cada vez más pequeño. ¿Debería usar algo más que getBaseContext?
Hubert
Tal vez no son estas líneas las que crean el problema, ya que cuando hago una aplicación muy básica haciendo solo el cambio en Locale, no tengo el mismo "encogimiento del menú". Pensé que tal vez era porque mi Actividad era en realidad una Actividad de Tabulación, pero incluso con eso, no puedo recrear el problema. Tendré que investigar más a fondo cuál es la causa exacta de este error ... no busque más. Publicaré la respuesta aquí cuando la encuentre. Saludos, H.
Hubert
Edité mi primera publicación, dando un ejemplo de cómo crear el problema. Y noté, de hecho, que cuando cambio la línea <uses-sdk android: minSdkVersion = "5" /> a "3" ... ¡de hecho aparece el problema!
Hubert
1
Puede utilizar la siguiente biblioteca, que proporciona la lista de idiomas, la preferencia para su pantalla de configuración y anula el idioma de su aplicación: github.com/delight-im/Android-Languages
caw

Respuestas:

151

A través de la pregunta original no se trata exactamente de la configuración regional en sí, todas las demás preguntas relacionadas con la configuración regional hacen referencia a esta. Es por eso que quería aclarar el problema aquí. Utilicé esta pregunta como punto de partida para mi propio código de cambio de configuración regional y descubrí que el método no es exactamente correcto. Funciona, pero solo hasta que cambie la configuración (por ejemplo, rotación de pantalla) y solo en esa Actividad en particular. Jugando con un código por un tiempo, terminé con el siguiente enfoque:

He extendido android.app.Application y agregué el siguiente código:

public class MyApplication extends Application
{
    private Locale locale = null;

    @Override
    public void onConfigurationChanged(Configuration newConfig)
    {
        super.onConfigurationChanged(newConfig);
        if (locale != null)
        {
            newConfig.locale = locale;
            Locale.setDefault(locale);
            getBaseContext().getResources().updateConfiguration(newConfig, getBaseContext().getResources().getDisplayMetrics());
        }
    }

    @Override
    public void onCreate()
    {
        super.onCreate();

        SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(this);

        Configuration config = getBaseContext().getResources().getConfiguration();

        String lang = settings.getString(getString(R.string.pref_locale), "");
        if (! "".equals(lang) && ! config.locale.getLanguage().equals(lang))
        {
            locale = new Locale(lang);
            Locale.setDefault(locale);
            config.locale = locale;
            getBaseContext().getResources().updateConfiguration(config, getBaseContext().getResources().getDisplayMetrics());
        }
    }
}

Este código garantiza que cada actividad tendrá un entorno local personalizado y no se restablecerá en la rotación y otros eventos.

También pasé mucho tiempo tratando de hacer que el cambio de preferencia se aplicara de inmediato, pero no tuve éxito: el idioma cambió correctamente al reiniciar la actividad, pero los formatos de números y otras propiedades locales no se aplicaron hasta el reinicio completo de la aplicación.

Cambios a AndroidManifest.xml

No olvide agregar android:configChanges="layoutDirection|locale"a cada actividad en AndroidManifest, así como también android:name=".MyApplication"al <application>elemento.

Andrey Novikov
fuente
1
¿Se llama su actividad de preferencia desde su actividad principal? puede colocar esto en el currículum on ... @Override protected void onResume () {if (! (PreferenceManager.getDefaultSharedPreferences (getApplicationContext ()). getString ("listLanguage", "en") .equals (langPreference))) { actualizar(); } super.onResume (); } privado void refresh () {finish (); Intención myIntent = nueva intención (Main.this, Main.class); startActivity (myIntent); }
trgraglia
1
Esto es realmente lo que necesito, solía llamar a updateConfiguration () en MainActivity pero los menús no se actualizan cuando la aplicación termina y luego comienza. Tu solución es la correcta. Si desea actualizar las cadenas de inmediato, creo que aún debe restablecer las cadenas manualmente. (por ejemplo, restablecer la etiqueta del botón o volver a crear el menú).
emeraldhieu
¿Esto interfiere con la configuración regional del sistema Android?
Kostadin
17
Tenga en cuenta que nunca debe modificar un objeto de configuración que Android le proporciona ( configy newConfig). Debe hacer una copia con config = new Configuration(config);primero. Acabo de pasar una hora depurando este código antes de descubrir el problema (estaba causando que la actividad parpadeara interminablemente).
imgx64
1
Esta técnica tiene problemas técnicos en Android 7+ cuando se inicia la aplicación desde un inicio en frío, con el tamaño de fuente del sistema establecido en grande. Google ha cambiado la forma en que updateConfiguration()funciona, por lo que a veces su Actividad se mostrará en 2 idiomas diferentes (especialmente en Diálogos). Más información: stackoverflow.com/questions/39705739/…
Mr-IDE
61

Después de una buena noche de sueño, encontré la respuesta en la Web (una simple búsqueda en Google en la siguiente línea " getBaseContext().getResources().updateConfiguration(mConfig, getBaseContext().getResources().getDisplayMetrics());"), aquí está:

texto del enlace => ¡este enlace también muestra screenshotslo que está sucediendo!

La densidad era el problema aquí , necesitaba tener esto en AndroidManifest.xml

<supports-screens
android:smallScreens="true"
android:normalScreens="true"
android:largeScreens="true"
android:anyDensity="true"
/>

El más importante es el Android: anyDensity = "true" .

No olvide agregar lo siguiente en AndroidManifest.xmlcada actividad (para Android 4.1 y siguientes):

android:configChanges="locale"

Esta versión es necesaria cuando compila para Android 4.2 (API nivel 17) explicación aquí :

android:configChanges="locale|layoutDirection"
Hubert
fuente
3
Tuve android: anyDensity = "false" debido al consejo de Google (Estrategias para aplicaciones heredadas - punto 9: developer.android.com/intl/fr/guide/practices/… ). ¡Ten cuidado entonces!
Hubert
2
Los textos en los botones no cambian después de cambiar la configuración regional. Me pregunto si hay alguna solución para actualizar todos los widgets que contienen recursos de cadena. Ahora estoy redibujando el texto usando setText (getString (R.string.button_label)) en OnRestart (). (Por supuesto, cambia después de reiniciar la aplicación.)
emeraldhieu
puedes llamar a recrear actividadactivity.recreate()
a Kra
59

En Android M, la mejor solución no funcionará. He escrito una clase auxiliar para arreglar lo que debe llamar desde su clase de Aplicación y todas las Actividades (sugeriría crear una BaseActivity y luego hacer que todas las Actividades hereden de ella.

Nota : Esto también admitirá correctamente la dirección de diseño RTL.

Clase auxiliar:

public class LocaleUtils {

    private static Locale sLocale;

    public static void setLocale(Locale locale) {
        sLocale = locale;
        if(sLocale != null) {
            Locale.setDefault(sLocale);
        }
    }

    public static void updateConfig(ContextThemeWrapper wrapper) {
        if(sLocale != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
            Configuration configuration = new Configuration();
            configuration.setLocale(sLocale);
            wrapper.applyOverrideConfiguration(configuration);
        }
    }

    public static void updateConfig(Application app, Configuration configuration) {
        if (sLocale != null && Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) {
            //Wrapping the configuration to avoid Activity endless loop
            Configuration config = new Configuration(configuration);
            // We must use the now-deprecated config.locale and res.updateConfiguration here,
            // because the replacements aren't available till API level 24 and 17 respectively.
            config.locale = sLocale;
            Resources res = app.getBaseContext().getResources();
            res.updateConfiguration(config, res.getDisplayMetrics());
        }
    }
}

Solicitud:

public class App extends Application {
    public void onCreate(){
        super.onCreate();

        LocaleUtils.setLocale(new Locale("iw"));
        LocaleUtils.updateConfig(this, getBaseContext().getResources().getConfiguration());
    }

    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        LocaleUtils.updateConfig(this, newConfig);
    }
}

Actividad base:

public class BaseActivity extends Activity {
    public BaseActivity() {
        LocaleUtils.updateConfig(this);
    }
}
Roberto B.
fuente
11
Esto debe ser aceptado respuesta. Asegúrese de que updateConfig debería llamar desde el constructor, no desde onCreate of class de actividad, estaba cometiendo este error ..
Hitesh Sahu
1
depende de cómo implementó esto en su programa. Si ha anulado onConfigurationChanged (), no necesita llamar a .updateConfig () y es por eso que está recibiendo el error, porque se llama dos veces. si está diciendo que solo llamar a .setLocale () no funciona, esto se debe a que necesita actualizar su interfaz de usuario, ya sea llamando a recreate () en la actividad o su propio método en actividades / fragmentos (prefiero el primero incluso si no es
sencillo,
3
// importar android.support.v7.view.ContextThemeWrapper; import android.view.ContextThemeWrapper; // correcto espacio de nombres
Mehdi Rostami
1
¿No debería BaseActivity()llamar el constructor super()?
LarsH
1
¿Esta solución incluye implícitamente poniendo <application android:name=".App">en el manifiesto? ¿Qué tal agregar android:configChanges="layoutDirection|locale"a cada actividad en el manifiesto?
LarsH
10

Esto es para mi comentario sobre la respuesta de Andrey, pero no puedo incluir el código en los comentarios.

¿Se llama su actividad de preferencia desde su actividad principal? podrías colocar esto en el currículum vitae ...

@Override
protected void onResume() {
    if (!(PreferenceManager.getDefaultSharedPreferences(
            getApplicationContext()).getString("listLanguage", "en")
            .equals(langPreference))) {
        refresh();
    }
    super.onResume();
}

private void refresh() {
    finish();
    Intent myIntent = new Intent(Main.this, Main.class);
    startActivity(myIntent);
}
Trgraglia
fuente
2
¿Quiere decir que si quiero cambiar el idioma en tiempo de ejecución, entonces tengo que terminar esa actividad y comenzar esa actividad nuevamente para obtener cambios? ¿Hay alguna otra manera de cambiarlo? No es bueno terminar la actividad cada vez y comenzarla de nuevo.
Shreyash Mahajan
1
mejor hacerloactivity.recreate()
A Kra
@trgradlia, quiero usar el código anterior. Por favor dime, ¿qué es langPreference? Creo que estamos comparando el idioma recientemente cambiado y el idioma anterior.
Mahen
1
La actividad tiene un recreate()método, así que úselo recreate()en lugar de refresh()su código. Esto ahorrará su línea de código 3 en el refresh()método
Inzimam Tariq IT
1
@InzimamTariqIT, gracias por el consejo ... La respuesta tiene casi 7 años, así que lo dejaré como está y la gente puede encontrar su mejora aquí en los comentarios.
Trgraglia
6

No pude usar android: anyDensity = "true" porque los objetos en mi juego se posicionarían completamente diferentes ... parece que esto también funciona:

// creando locale
Locale locale2 = new Locale (loc); 
Locale.setDefault (locale2);
Configuración config2 = nueva Configuración ();
config2.locale = locale2;

// actualización de la configuración regional
mContext.getResources (). updateConfiguration (config2, null);
nikib3ro
fuente
Esta solución es la mejor para mí, solo usé estocode Locale.setDefault(mLocale); Configuration config = getBaseContext().getResources().getConfiguration(); if (!config.locale.equals(mLocale)) { config.locale = mLocale; getBaseContext().getResources().updateConfiguration(config, null); }
Huds0nHawk
1

Si desea realizar un efecto en las opciones del menú para cambiar la configuración regional de inmediato, debe hacer esto.

//onCreate method calls only once when menu is called first time.
public boolean onCreateOptionsMenu(Menu menu) {
    super.onCreateOptionsMenu(menu);
    //1.Here you can add your  locale settings . 
    //2.Your menu declaration.
}
//This method is called when your menu is opend to again....
@Override
public boolean onMenuOpened(int featureId, Menu menu) {
    menu.clear();
    onCreateOptionsMenu(menu);
    return super.onMenuOpened(featureId, menu);
}
Ramesh Akula
fuente
¿Pero esta solución no borra el menú en cada menú abierto? Supongo que onMenuOpeneddebería llamarse solo una vez, después del cambio de configuración regional.
trante