Ресурсы и направление верстки отображаются некорректно только на Android 8.0 и выше - PullRequest
0 голосов
/ 05 сентября 2018

У меня есть многоязычное приложение с основным языком английский и дополнительный язык арабский.

Я звоню setLocale() в onCreate() каждого Activity в моем приложении:

public static void setLocale(Locale locale){
    Locale.setDefault(locale);
    Context context = MyApplication.getInstance();
    final Resources resources = context.getResources();
    final Configuration config = resources.getConfiguration();
    config.setLocale(locale);
    context.getResources().updateConfiguration(config,
            resources.getDisplayMetrics());
}

, где locale является одним из следующих:

![enter image description here

Вышеуказанный метод вызывается до вызова super.onCreate(savedInstanceState).

Как описано в документации ,

  • Я добавил android:supportsRtl="true" в манифест.
  • Я изменил все свойства xml с атрибутами left и right на start и end соответственно.
  • Я поместил строки на арабском языке в папку res\values-ar\strings, а ресурсы для рисования - в папку res\drawable-ar (и аналогично для других ресурсов).

Вышеуказанная настройка работает правильно. После изменения Locale на ar-AE арабский текст и ресурсы правильно отображаются в моих действиях.

Однако существует проблема с ресурсами и направлением макета для всех устройств Android с версией 8.0 и выше.

На устройстве с версией менее 8.0 экран RTL правильно выглядит следующим образом:

enter image description here

И на всех устройствах с 8.0+ один и тот же экран выглядит примерно так:

enter image description here

что неправильно .

Оказывается, что направление и ресурсы становятся отображается неправильно.

Здесь две проблемы:

  • Правильный Locale не обновляется в конфигурации приложения.
  • Направление текста и рисования противоположно тому, что должно быть.

Что касается направления, существует любопытный метод, называемый setLayoutDirection(), которого я раньше не замечал.

Я хотел бы знать, что это за проблема, почему она возникает в Oreo и как ее решить. Пожалуйста, помогите / прокомментируйте это.

EDIT

В соответствии с Разницами API отчет , updateConfiguration() метод действительно устарел в Android 7.1 (уровень API 25).

Также найдены все соответствующие посты по этому вопросу. В порядке важности:

1. Android N программно меняет язык .

2. Android context.getResources.updateConfiguration () устарел .

3. Как изменить язык приложения Android O / Oreo / api 26 .

4. Ошибка Android RTL в API 24 и выше при изменении локали

5. Изменение языка программно (Android N 7.0 - API 24) .

6. Android N - изменить языковой стандарт во время выполнения .

7. Ошибка верстки RTL в Android Oreo .

Ответы [ 6 ]

0 голосов
/ 20 июля 2019

Полное решение этой проблемы состоит из трех этапов:

ШАГ 1 :

В onCreate() ваших BaseActivity (или всех ваших Activity с) установите Locale следующим образом:

@Override
protected void onCreate(Bundle savedInstanceState) {

    // set the Locale the very first thing
    Utils.setLocale(Utils.getSavedLocale());
    requestWindowFeature(Window.FEATURE_NO_TITLE);
    super.onCreate(savedInstanceState);

    ......
    ......

}

, где getSavedLocale() - это Locale, соответствующий текущему региону (это будет характерно для вашего проекта ...).

А метод Utils.setLocale(...) определяется следующим образом:

public static void setLocale(Locale locale){
    Context context = MyApplication.getInstance();
    Resources resources = context.getResources();
    Configuration configuration = resources.getConfiguration();
    Locale.setDefault(locale);
    configuration.setLocale(locale);
    configuration.setLayoutDirection(locale);

    // updateConfiguration(...) is deprecated in N
    if (Build.VERSION.SDK_INT >= 25) {
        context = context.getApplicationContext().createConfigurationContext(configuration);
        context = context.createConfigurationContext(configuration);
    }

    context.getResources().updateConfiguration(configuration,
            resources.getDisplayMetrics());
}

Устанавливает правильные значения Locale в каждом Activity. Этого достаточно для приложений, поддерживающих уровень API 25. Для уровня API 26 и выше также требуются ШАГ 2 и ШАГ 3.

ШАГ 2 :

Переопределите следующий метод в вашем BaseActivity:

@Override
protected void attachBaseContext(Context newBase) {
    newBase = Utils.getLanguageAwareContext(newBase);
    super.attachBaseContext(newBase);
}

где функция getLanguageAwareContext(...) определяется следующим образом:

public static Context getLanguageAwareContext(Context context){
    Configuration configuration = context.getResources().getConfiguration();
    Locale locale = getIntendedLocale();
    configuration.setLocale(locale);
    configuration.setLayoutDirection(locale);
    return context.createConfigurationContext(configuration);
}

Это, наряду с ШАГОМ 1, устанавливает правильные значения Locale в каждом Activity вашего приложения для API уровня 26 и выше.

Однако для правильной установки направления языка требуется еще один шаг ...

ШАГ 3 :

В onCreate() вашего BaseActivity добавьте следующий код:

@Override
protected void onCreate(Bundle savedInstanceState) {

    ....
    ....

    // yup, it's a legit bug ... :)
    if (Build.VERSION.SDK_INT >= 26) {
        getWindow().getDecorView().setLayoutDirection(Utils.isRTL()
                ? View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR);


    }

    ....
    ....
}

где функция isRTL() определяется следующим образом:

public static boolean isRTL(){
    return TextUtilsCompat.getLayoutDirectionFromLocale(Locale.getDefault()) == View.LAYOUT_DIRECTION_RTL;
}

Приведенные выше шаги должны решить все проблемы (по крайней мере, относительно установки Locale и направления текста) во всех существующих версиях Android.

0 голосов
/ 13 сентября 2018

Полностью закрыто приложение, потому что я думаю, что оно делает кэш в фоновом режиме.

Используйте приведенный ниже код, вот как я добился этого в моем случае, вы также можете попробовать:

 Intent mStartActivity = new Intent(ctc, SplashActivity.class);
                int mPendingIntentId = 123456;
                PendingIntent mPendingIntent = PendingIntent.getActivity(ctc, mPendingIntentId,    mStartActivity, PendingIntent.FLAG_CANCEL_CURRENT);
                AlarmManager mgr = (AlarmManager)ctc.getSystemService(Context.ALARM_SERVICE);
                mgr.set(AlarmManager.RTC, System.currentTimeMillis() + 100, mPendingIntent);
                System.exit(0);
0 голосов
/ 11 сентября 2018

Метод updateConfiguration() был устарел

Теперь нам нужно использовать createConfigurationContext()

Я справился таким образом

создать новый класс ContextWrapper

import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.os.Build;
import android.os.LocaleList;

import java.util.Locale;

public class ContextWrapper extends android.content.ContextWrapper {

    public ContextWrapper(Context base) {
        super(base);
    }

    public static ContextWrapper wrap(Context context, Locale newLocale) {

        Resources res = context.getResources();
        Configuration configuration = res.getConfiguration();

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            configuration.setLocale(newLocale);

            LocaleList localeList = new LocaleList(newLocale);
            LocaleList.setDefault(localeList);
            configuration.setLocales(localeList);

            context = context.createConfigurationContext(configuration);

        } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
            configuration.setLocale(newLocale);
            context = context.createConfigurationContext(configuration);

        } else {
            configuration.locale = newLocale;
            res.updateConfiguration(configuration, res.getDisplayMetrics());
        }

        return new ContextWrapper(context);
    }}

создать новый класс BaseActivity

import android.content.Context;

import android.support.v7.app.AppCompatActivity;

import java.util.Locale;

/**
 * Created by nilesh on 20/3/18.
 */

public class BaseActivity extends AppCompatActivity {

    @Override
    protected void attachBaseContext(Context newBase) {

        Locale newLocale;

        String lang = new PrefManager(newBase).getLanguage();

        if (lang.equals("zh_CN")) {
            newLocale = new Locale("zh");
        } else {
            newLocale = new Locale(lang);
        }


        Context context = ContextWrapper.wrap(newBase, newLocale);
        super.attachBaseContext(context);
    }
}

Создать PrefManager класс для хранения локали

import android.content.Context;
import android.content.SharedPreferences;

public class PrefManager {

    private SharedPreferences.Editor editor;
    private Context mContext;
    private SharedPreferences prefs;
    private final String LANGUAGE = "language";
    private final String PREF = "user_data";

    public PrefManager(Context mContext) {
        this.mContext = mContext;
    }

    public String getLanguage() {
        this.prefs = this.mContext.getSharedPreferences(PREF, 0);
        return this.prefs.getString(LANGUAGE, "en_US");
    }

    public void setLanguage(String language) {
        this.editor = this.mContext.getSharedPreferences(PREF, 0).edit();
        this.editor.putString(LANGUAGE, language);
        this.editor.apply();
    }

}

Теперь вам нужно расширить базовую активность во всех ваших действиях, например

public class OrdersActivity extends BaseActivity

Теперь, когда вам нужно изменить Locale, просто обновите значение в PrefManager и перезапустите свою деятельность

    PrefManager prefManager= new PrefManager(this);
    prefManager.setLanguage("zh_CN");
    //  restart your activity
0 голосов
/ 10 сентября 2018

Resources.updateConfiguration устарела, используйте вместо этого:

 fun setLocale(old: Context, locale: Locale): Context {
    val oldConfig = old.resources.configuration
    oldConfig.setLocale(locale)
    return old.createConfigurationContext(oldConfig)
}

override fun attachBaseContext(newBase: Context?) {
    super.attachBaseContext(newBase?.let { setLocale(it, Locale("ar")) })
}

В Java

private Context setLocale(Context old, Locale locale) {
    Configuration oldConfig = old.getResources().getConfiguration();
    oldConfig.setLocale(locale);
    return old.createConfigurationContext(oldConfig);
}

@Override
protected void attachBaseContext(Context newBase) {
    super.attachBaseContext(setLocale(newBase, new Locale("ar")));
}
0 голосов
/ 10 сентября 2018
public void setLocale(final Context ctx, final String lang) {
    AppSettings.getInstance(ctx).save(PrefKeys.language, lang);
    final Locale loc = new Locale(lang);
    Locale.setDefault(loc);
    final Configuration cfg = new Configuration();
    cfg.locale = loc;
    ctx.getResources().updateConfiguration(cfg, null);
}

Изменить на английский: setLocale(getActivity(), "en";

Изменить на арабский: setLocale(getActivity(), "ar");

После этого вам нужно перезапустить приложение, чтобы получить эффекты смены языка.

0 голосов
/ 07 сентября 2018

Метод Resources#updateConfiguration (Configuration config, DisplayMetrics metrics) является устаревшим на уровне API 25 .

Документ предлагает использовать Context#createConfigurationContext (Configuration overrideConfiguration)


Вы можете просто выполнить базовое действие, которое является общим родителем всех действий, как показано ниже.

public class BaseActivity
        extends AppCompatActivity {

    private static final String LANGUAGE_CODE_ENGLISH = "en";
    private static final String LANGUAGE_CODE_ARABIC = "ar";

    @Override
    protected void attachBaseContext(Context newBase) {
        super.attachBaseContext(CommonUtils.getLanguageAwareContext(newBase));
    }

    private static Context getLanguageAwareContext(Context context) {
        Configuration configuration = context.getResources().getConfiguration();
        configuration.setLocale(new Locale(getLanguageCode));
        return context.createConfigurationContext(configuration);
    }

    // Rewrite this method according to your needs
    private static String getLanguageCode() {
        return LANGUAGE_CODE_ARABIC;
    }
}


Примечания

  • getLanguageCode() должен возвращать код языка. Обычно код языка или любые другие данные, представляющие его, хранятся в настройках.
  • Для динамического изменения языков воссоздайте активность после установки соответствующего кода языка в настройках.
  • Используйте контекст активности, а не контекст приложения, чтобы получить доступ к любым ресурсам, специфичным для локали. Другими словами, используйте this или ActivityName.this из действий и getActivity() из фрагментов вместо getApplicationContext().
...