У меня есть приложение со строковыми ресурсами для немецкого и английского языков. Я определил отдельный фрагмент для изменения языка, который вы можете увидеть здесь.
public class FR_Options extends Fragment implements View.OnClickListener {
/*
String specifying the language of the App
*/
public static final String LANGUAGE_GERMAN = "German";
public static final String LANGUAGE_ENGLISH = "English";
//Set the default language to GERMAN
public static String currentLanguageOfTheApp = LANGUAGE_ENGLISH;
public FR_Options() {
// Required empty public constructor
}
public static FR_Options newInstance(String param1, String param2) {
FR_Options fragment = new FR_Options();
return fragment;
}
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR1)
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
private FragmentOptionsBinding binding;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
binding = FragmentOptionsBinding.inflate(inflater, container, false);
return binding.getRoot();
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
binding.imageButtonGermany.setOnClickListener(this);
binding.imageButtonUK.setOnClickListener(this);
if (currentLanguageOfTheApp.equals(LANGUAGE_ENGLISH)) {
binding.textViewCurrentLanguageValue.setText(LANGUAGE_ENGLISH);
binding.imageButtonGermany.setAlpha(0.5f);
binding.imageButtonUK.setAlpha(1.0f);
}
if (currentLanguageOfTheApp.equals(LANGUAGE_GERMAN)) {
binding.textViewCurrentLanguageValue.setText(LANGUAGE_GERMAN);
binding.imageButtonGermany.setAlpha(1.0f);
binding.imageButtonUK.setAlpha(0.5f);
}
}
public void onDestroyView() {
super.onDestroyView();
binding = null;
}
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR1)
@Override
public void onClick(View view) {
if (view.getId() == R.id.imageButtonGermany) {
/*
Set the language to "German" for other fragments and database queries
*/
this.currentLanguageOfTheApp = LANGUAGE_GERMAN;
/*
Set the language to "German" for the XML-layout files
*/
Locale locale;
locale = new Locale("de", "DE");
Configuration config = new Configuration(getActivity().getBaseContext().getResources().getConfiguration());
Locale.setDefault(locale);
config.setLocale(locale);
getActivity().recreate();
getActivity().getBaseContext().getResources().updateConfiguration(config,
getActivity().getBaseContext().getResources().getDisplayMetrics());
}
if (view.getId() == R.id.imageButtonUK) {
/*
Set the language to "English" for other fragments and database queries
*/
this.currentLanguageOfTheApp = LANGUAGE_ENGLISH;
/*
Set the language to "English" for the XML-layout files
*/
Locale locale;
locale = new Locale("en", "EN");
Configuration config = new Configuration(getActivity().getBaseContext().getResources().getConfiguration());
Locale.setDefault(locale);
config.setLocale(locale);
getActivity().recreate();
getActivity().getBaseContext().getResources().updateConfiguration(config,
getActivity().getBaseContext().getResources().getDisplayMetrics());
}
}
}
Теперь, когда я перехожу к тестовому фрагменту, файл Java которого выглядит так
public class Test extends Fragment {
int widthDisplay;
int heightDisplay;
private FragmentTestBinding binding;
private ConstraintLayout constraintLayout;
ConstraintSet constraintSet ;
private boolean fragmentViewHasBeenCreated = false;
int helpUpdateCounterProgressBar = 0;//Just for testing
boolean animationIsWindBladRotating = false;
private boolean sunIsShiningForImagewViews = false;
private boolean helpSolarGameRectangleCorrectlyCaughtPreviously = false;
public Test() {
// Required empty public constructor
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
binding = FragmentTestBinding.inflate(inflater, container, false);
WindowManager wm = (WindowManager) getActivity().getWindowManager();
Display display = wm.getDefaultDisplay();
Point size = new Point();
display.getSize(size);
widthDisplay = size.x;
heightDisplay = size.y;
//Test to set the string resources programmatically
String goalText = getString(R.string.goal);
String timeText = getString(R.string.time);
binding.textViewGoal.setText(goalText);
binding.textView3.setText(timeText);
container.getContext();
constraintLayout= binding.constraintLayout;
fragmentViewHasBeenCreated = true;
getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
constraintLayout = binding.constraintLayout;
constraintSet = new ConstraintSet();
return binding.getRoot();
}//end onCreateView
@Override
public void onDestroyView() {
super.onDestroyView();
// Reset your variable to false
fragmentViewHasBeenCreated = false;
}
}
с соответствующим файлом макета XML
<?xml version = "1.0" encoding = "utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android = "http://schemas.android.com/apk/res/android"
xmlns:app = "http://schemas.android.com/apk/res-auto"
xmlns:tools = "http://schemas.android.com/tools"
android:id = "@+id/constraintLayout"
android:layout_width = "match_parent"
android:layout_height = "match_parent"
android:background = "@color/white"
tools:context = ".MainActivity">
<TextView
android:id = "@+id/textView_Goal"
android:layout_width = "wrap_content"
android:layout_height = "wrap_content"
android:text = "@string/goal"
android:textSize = "24dp"
app:layout_constraintBottom_toBottomOf = "parent"
app:layout_constraintEnd_toEndOf = "parent"
app:layout_constraintStart_toStartOf = "parent"
app:layout_constraintTop_toTopOf = "parent" />
<TextView
android:id = "@+id/textView3"
android:layout_width = "wrap_content"
android:layout_height = "wrap_content"
android:text = "@string/time"
android:textSize = "24dp"
app:layout_constraintEnd_toEndOf = "@+id/textView_Goal"
app:layout_constraintStart_toStartOf = "@+id/textView_Goal"
app:layout_constraintTop_toBottomOf = "@+id/textView_Goal" />
</androidx.constraintlayout.widget.ConstraintLayout>
Языки строковых ресурсов android:text = "@string/time"
и android:text = "@string/goal"
никогда не меняются и всегда остаются английскими, которые являются языком по умолчанию.
В папкеvalues/string/strings.xml есть две записи
" <string name = "goal">Goal</string>
<string name = "time">Time</string>"
а в папкеvalues/string/strings.mxl(de-rDE) есть две записи "
<string name = "goal">Ziel</string>
<string name = "time">Zeit</string>"
тем не менее, язык не меняется в классе Test, независимо от того, что я делаю в классе фрагмента FR_Options.
Обновление: я обнаружил, что при смене языка в классе FR_Options я возвращаюсь к своему классу FR_Menu, который выглядит следующим образом.
public class FR_Menu extends Fragment implements View.OnClickListener{
private FragmentMenuBinding binding;
public FR_Menu() {
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
binding = FragmentMenuBinding.inflate(inflater, container, false);
getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
binding.buttonGame.setOnClickListener(this);
binding.buttonOptions.setOnClickListener(this);
binding.buttonHighscores.setOnClickListener(this);
binding.buttonFacts.setOnClickListener(this);
binding.buttonExit.setOnClickListener(this);
binding.buttonTest.setOnClickListener(this);
Log.e("LogTag_Menu", "Method onCreateView - this: " + this);
return binding.getRoot();
}
@Override
public void onClick(View view) {
if (view.getId() == R.id.button_game) {
Navigation.findNavController(getView()).navigate(FR_MenuDirections.actionFRMenuToFRGame());
}
if (view.getId() == R.id.button_highscores) {
Navigation.findNavController(getView()).navigate(FR_MenuDirections.actionFRMenuToFRHighScores());
}
if (view.getId() == R.id.button_facts) {
//Navigation.findNavController(getView()).navigate(FR_MenuDirections.actionFRMenuToFRInterestingFacts());
Navigation.findNavController(getView()).navigate(FR_MenuDirections.actionFRMenuToFRRVLevelSelectionMenu());
}
if (view.getId() == R.id.button_options) {
Navigation.findNavController(getView()).navigate(FR_MenuDirections.actionFRMenuToFROptions());
}
if (view.getId() == R.id.button_test) {
Navigation.findNavController(getView()).navigate(FR_MenuDirections.actionFRMenuToTest());
}
if (view.getId() == R.id.button_exit) {
getActivity().finishAndRemoveTask();
}
}
}
язык строковых ресурсов изменен правильно. Однако при переходе из класса FR_Menu в другой класс язык строковых ресурсов снова меняется на язык по умолчанию (английский). Почему это происходит?
Напоминание: есть ли у кого-нибудь идеи, почему это происходит и как решить эту проблему?
@tomerpacific: Спасибо, Томер, за твой комментарий. Что вы подразумеваете под «делегированием действия, в котором размещен фрагмент»? Насколько я понимаю, я делаю это с помощью кода getActivity().getBaseContext().getResources().updateConfiguration(config, getActivity().getBaseContext().getResources().getDisplayMetrics());
, но, к сожалению, это не меняет язык в других фрагментах.
@tomerpacific: есть какие-нибудь комментарии к моему последнему комментарию? Я буду очень признателен за любые дальнейшие комментарии от вас
Вам нужна контекстная оболочка, чтобы обернуть новый язык в новую конфигурацию при воссоздании действия.
@Zain: Спасибо за ваш комментарий. Что вы на самом деле подразумеваете под «контекстной оберткой» и как я могу использовать ее при воссоздании активности? Может быть, вы можете привести мне пример того, как это может выглядеть в моем коде?
@VanessaF, почему бы вместо этого не использовать официально рекомендованный API языка для каждого приложения? Это проще и понятнее реализовать. Developer.android.com/guide/topics/resources/app-languages#java
@Itp: Спасибо за ваш комментарий. Как я могу использовать этот подход для изменения языка?
currentLanguageOfTheApp
— локальная переменная для FR_Options
; поэтому он не сохраняется глобально в постоянном хранилище.
Вам необходимо постоянно сохранять его, обычно в SharedPrefs
или DataStore
, чтобы при воссоздании действия вы могли получить последний сохраненный пользователем язык.
С SharedPreference
что-то вроде
private static final String LANGUAGE = "LANGUAGE";
private static final String SHARED_PREFS_NAME= "SHARED_PREFS_NAME";
private static String getLanguage(Context context) {
SharedPreferences prefs = context.getSharedPreferences(SHARED_PREFS_NAME, Context.MODE_PRIVATE);
return prefs.getString(LANGUAGE, "en");
}
private static String setLanguage(Context context, String language) {
SharedPreferences prefs = context.getSharedPreferences(SHARED_PREFS_NAME, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = prefs.edit();
editor.putString(LANGUAGE, language);
editor.apply();
}
А при изменении языка пользователем установите для него значение SharedPrefs и перезапустите действие:
@Override
public void onClick(View view) {
if (view.getId() == R.id.imageButtonGermany) {
setLanguage(requireContext(), "de");
}
if (view.getId() == R.id.imageButtonUK) {
setLanguage(requireContext(), "en");
}
// restart the activity
requireActivity().finish();
requireActivity().startActivity(new Intent(requireActivity(), MainActivity.class));
}
На данный момент это просто сохранит новый язык в постоянном хранилище и перезапустит действие, но чтобы применить новый язык при воссоздании действия, новый baseContext
должен сопоставить строковый файл нового языка вместо строки языка по умолчанию. файл. Итак, вам нужна контекстная оболочка, чтобы обернуть новый язык в новую конфигурацию baseContext
при воссоздании активности.
В коде вы должны создать следующее ContextWrapper
:
import android.annotation.TargetApi;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.res.Configuration;
import android.os.Build;
import java.util.Locale;
public class LanguageContextWrapper extends ContextWrapper {
public LanguageContextWrapper(Context base) {
super(base);
}
public static ContextWrapper wrap(Context context, String language) {
Configuration config = context.getResources().getConfiguration();
Locale sysLocale;
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N) {
sysLocale = getSystemLocale(config);
} else {
sysLocale = getSystemLocaleLegacy(config);
}
if (!language.isEmpty() && !sysLocale.getLanguage().equals(language)) {
Locale locale = new Locale(language);
Locale.setDefault(locale);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
setSystemLocale(config, locale);
} else {
setSystemLocaleLegacy(config, locale);
}
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
context = context.createConfigurationContext(config);
} else {
context.getResources().updateConfiguration(config, context.getResources().getDisplayMetrics());
}
return new LanguageContextWrapper(context);
}
public static Locale getSystemLocaleLegacy(Configuration config) {
return config.locale;
}
@TargetApi(Build.VERSION_CODES.N)
public static Locale getSystemLocale(Configuration config) {
return config.getLocales().get(0);
}
public static void setSystemLocaleLegacy(Configuration config, Locale locale) {
config.locale = locale;
}
@TargetApi(Build.VERSION_CODES.N)
public static void setSystemLocale(Configuration config, Locale locale) {
config.setLocale(locale);
}
}
И используйте его в обратном вызове attachBaseContext()
активности:
class MyActivity extends AppCompatActivity {
@Override
protected void attachBaseContext(Context newBase) {
String language = getLanguage(newBase);
super.attachBaseContext(LanguageContextWrapper.wrap(newBase, language));
setLocale(getLanguage(newBase));
}
private void setLocale(String language) {
Locale locale = new Locale(language);
Resources resources = getBaseContext().getResources();
Configuration conf = resources.getConfiguration();
conf.setLocale(locale);
resources.updateConfiguration(conf, resources.getDisplayMetrics());
}
// ........ reset of your activity
}
Спасибо за Ваш ответ. К сожалению, у меня проблемы с пониманием этого, поскольку я не знаю, куда добавить этот код. Например, методы get и setLanguage, должны ли они находиться во фрагменте FR_Options или во фрагменте основного действия?
Нет ли более простого способа сделать это? В другом приложении я просто делаю как в этом приложении. Никакой разницы нет вообще (я на самом деле скопировал и вставил код), и там все работает нормально без всех этих подходов Shared Preferences и Wrapper, которые вы предлагаете. Но я так и не понял, почему в этом приложении это не работает. Может быть, вы можете придумать другой подход, как сделать это без оболочки и общих настроек?
@VanessaF Как сохранить новый язык при следующем запуске другого приложения? вы не используете постоянное хранилище?
Нет, я не использую подход с общими предпочтениями. Кажется, это вносит ненужную сложность. В другом моем приложении пользователь должен при каждом запуске решать, какой язык выбрать. Если бы я хотел иметь постоянное хранилище, я бы предпочел, например, использовать базу данных sql. Таким образом, это намного проще, чем использование ContextWrappers и SharedPreferences.
Где в предложенном вами коде должны быть методы get и setLanguage? Это отдельный класс или они должны быть в основной деятельности?
Это во фрагменте FR_Options
, как он только что оттуда и называется.
Большое спасибо Зейн за ваш ответ. Теперь это работает, и я наградил вас наградой :-). В коде небольшая ошибка. В методе private static String setLanguage(Context context, String language) {
вы ничего не возвращаете. Поэтому мне пришлось изменить объявление метода на void
Просто из любопытства я хотел бы спросить, какие преимущества имеет предлагаемый вами подход с общими настройками по сравнению с простым использованием простого запроса к базе данных sql для постоянного хранения используемого в данный момент языка? При использовании запроса sql вам потребуется гораздо меньше кода, и это снизит сложность, особенно из-за того, что не потребуется никаких дополнительных классов.
Что ж, в этом отношении вы правы... но база данных SQL в основном используется для хранения набора структурированных данных с разными полями и столбцами. Но вам просто нужно сохранить одно значение для языка. Вероятно, поддержание базы данных SQL также потребует некоторых усилий.
Большое спасибо за ваш комментарий. Я использую базы данных SQLite для всего, поскольку это очень просто, быстро и снижает сложность. Вы написали: «Но вам просто нужно сохранить одно значение для языка» --> Я бы сказал наоборот. Если мне просто нужно сохранить одно значение, почему я должен использовать такой очень сложный подход с общими настройками и дополнительными классами, который просто вносит ненужную сложность и много-много строк дополнительного кода, в то время как в SQLite вам просто нужны 2 очень маленьких get и установить методы.
Под капотом SQLite есть накладные расходы на создание базы данных, ее открытие и закрытие, а не просто так, как вы ее кодируете; для доступа требуется даже фоновый поток. Также SharedPreference теперь заменен на DataStore. Я только что добавил SharedPrefs в этот вопрос в качестве примера решения для сохранения данных; но каждое приложение имеет свое собственное управление сохранением данных, которое выходит за рамки этого вопроса. Суть в том, что вам нужен какой-то способ постоянного хранения языка, чтобы его можно было получить при следующем запуске приложения.
Спасибо за ваш ответ и усилия, Зейн. У меня 2 уточняющих вопроса: 1) В предложенном вами решении я не понимаю, зачем нужен LanguageContextWrapper
класс и что там вообще делается? Как уже говорилось ранее, в другом моем приложении в чем-то подобном нет необходимости. При изменении языка он остается таким, каким должен быть, независимо от того, к какому фрагменту вы переходите. 2) Я заметил, что вы используете много методов static
(как в LanguageContextWrapper
, так и в FR_Options
). Мне 1000 раз говорили, что никогда не следует использовать статические методы, поскольку это считается плохим стилем.
У меня еще третий вопрос: 3) Можно ли сменить язык без перезапуска активности? Таким образом, пользователь может просто выбрать язык в классе FR_Options, и после нажатия кнопки языка приложение не должно переходить к другому фрагменту.
Для получения значения строкового ресурса требуется context
, и этот контекст связан только с 1 строковым ресурсом, который загружается при запуске действия. Когда вы меняете язык, вам также необходимо отразить это изменение в контексте, чтобы выбрать новый строковый ресурс этот язык; это то, что делает LanguageContextWrapper
.. Я не могу рассказать больше о другом приложении, так как понятия не имел, как оно работает .. если вы дадите мне образец рабочей облегченной версии, было бы полезно
Is it possible to change the language without restarting the activity?
Насколько я знаю, нет, нельзя. Вы перезапускаете активность в другом приложении?
Спасибо за ответ и извините за поздний ответ (я был в отпуске). В другом моем приложении я действительно не знаю, перезапускается ли действие снова. По крайней мере, я остаюсь в том же фрагменте, чтобы вы могли изменить язык и остаться в том же фрагменте. Возможно ли с вашим подходом сменить язык и остаться в том же фрагменте? Далее вы написали: «Вам также необходимо отразить это изменение в контексте, чтобы выбрать новый строковый ресурс этого языка; это то, что LanguageContextWrapper» -> в другом моем приложении в этом нет необходимости. Он просто меняет язык и отлично работает
Спасибо за ваши ответы. Есть комментарии к моему последнему комментарию? Я буду очень признателен за каждый дальнейший комментарий от вас.
Другое ваше приложение является многофрагментным? если да, то другой фрагмент предоставляет новый языковой ресурс?>>>> Is it possible with your approach to change the language and stay in the same fragment?
Я не думаю, что без ручных усилий не обойтись, признаю, что мне придется тщательно искать обходной путь
Спасибо за ваш комментарий. Другое мое приложение имеет одно основное действие и несколько фрагментов. Я использую navGraph для навигации между ними. Когда я меняю язык в FR_Options
(код которого вы можете увидеть выше в моей первой публикации кода), язык меняется для каждого фрагмента (включая строковые ресурсы). Это работает без проблем
Просто обновите порядок этих строк в своем коде.
//here the activity reload its resources based on the new configuration
getActivity().getBaseContext().getResources().updateConfiguration(config,getActivity().getBaseContext().getResources().getDisplayMetrics());
// restart the current activity
getActivity().recreate();
Спасибо за суровый ответ. Я попробовал, но это ничего не изменило. Однако сейчас я использую решение от Zain, которое работает нормально. Тем не менее, я высоко ценю ваши усилия.
Почему вы обрабатываете изменение языка в самом фрагменте, а не делегируете его действию, в котором размещен фрагмент?