Słów kilka na temat lokalizowania aplikacji swojej…
Fakt, że to czytasz oznacza, że widziałeś na Google Play aplikację, w której można było zmieniać język niezależnie od języka systemu i stwierdziłeś, że coś podobnego zastosujesz u siebie. Pokażę Ci dzisiaj jak to zrobić, ale pragnę zaznaczyć przy tym, że nie jest to dobra praktyka w mojej opinii. Artykuł ten można zaliczyć do grona tutoriali, podzielę go na kroki, które trzeba będzie wykonać, aby zakończyć pracę nad aplikacją. Podejdziemy do tego tutoriala tak samo poważnie jak do każdej innej aplikacji. Do dzieła!
Cel: Naszym celem jest utworzenie aplikacji, która po kliknięciu na jeden z 4 przycisków wyświetli nam tekst “Dzień dobry!” w języku przypisanym do przycisku.
Krok 1. Tworzenie projektu aplikacji
Dobrą praktyką jest wykonywać projekt aplikacji - mnie to motywuje do jej ukończenia + to świetna zabawa i uwielbiam to robić. Ja do tego celu wykorzystuje XD od Adobe.
Otwieramy XD i wybieramy nowy projekt dla androida
Teraz zobaczysz jak ja wykonałem projekt aplikacji
Krok 2. Tworzenie aplikacji
Otwórzmy teraz Android Studio i utwórzmy nowy projekt - ja wybieram Kotlina, Tobie też to polecam.
Pierw utworzymy interfejs, który będzie wyrażać naszą potrzebę - czyli zmianę języka. Oto jego kod:
interface AppLocalization {
fun change(lang: String)
}
Utworzymy teraz fragment, który będzie posiadał 4 przyciski (pierwszy ekran z naszego projektu). Oto jego kod:
class FirstFragment : Fragment() {
companion object {
fun create() = FirstFragment()
const val TAG = "FirstFragment"
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_first, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
val localization = context as AppLocalization
view.bt_polish.setOnClickListener {
localization.change("pl")
}
view.bt_english.setOnClickListener {
localization.change("en")
}
view.bt_german.setOnClickListener {
localization.change("de")
}
view.bt_italian.setOnClickListener {
localization.change("it")
}
}
}
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/bt_polish"
style="@style/DiscoButton.Stroke"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/button_polish"
app:layout_constraintBottom_toTopOf="@+id/bt_english"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/bt_english"
style="@style/DiscoButton.Stroke"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_above="@+id/bt_german"
android:layout_alignParentStart="true"
android:text="@string/button_english"
app:layout_constraintBottom_toTopOf="@+id/bt_german"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/bt_polish" />
<Button
android:id="@+id/bt_german"
style="@style/DiscoButton.Stroke"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_centerVertical="true"
android:text="@string/button_german"
app:layout_constraintBottom_toTopOf="@+id/bt_italian"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/bt_english" />
<Button
android:id="@+id/bt_italian"
style="@style/DiscoButton.Stroke"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:text="@string/button_italian"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/bt_german" />
</android.support.constraint.ConstraintLayout>
Teraz utworzymy kolejny fragment z jednym TextView na środku, czyli drugi ekran z naszego projektu. Oto jego kod:
class SecondFragment : Fragment() {
companion object {
fun create() = SecondFragment()
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_second, container, false)
}
}
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/tv_main_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:padding="@dimen/space_mid"
android:text="@string/label_good_morning"
android:textSize="@dimen/text_main" />
</RelativeLayout>
Jak widzisz każdy z przycisków wywołuje tą samą funkcję jednak z innymi parametrami. Jej implementacja znajduje się w MainActivity, którego kod teraz zobaczysz:
class MainActivity : AppCompatActivity(),
AppLocalization {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val firstFragment = FirstFragment.create()
if (supportFragmentManager.fragments.count() == 0)
supportFragmentManager.beginTransaction()
.add(R.id.fl_content, firstFragment, firstFragment.javaClass.name)
.setPrimaryNavigationFragment(firstFragment)
.commit()
}
override fun change(lang: String) {
changeLocalization(this, lang)
replaceFragment(SecondFragment.create())
}
private fun changeLocalization(context: Context, lang: String) {
val myLocale = Locale(lang)
Locale.setDefault(myLocale)
val config = android.content.res.Configuration()
config.locale = myLocale
context.resources.updateConfiguration(config, context.resources.displayMetrics)
}
private fun replaceFragment(newFragment: Fragment) {
val currentFragment: Fragment = supportFragmentManager.findFragmentById(R.id.fl_content)
val currentFragmentTag: String = currentFragment.javaClass.name
val newFragmentTag: String = newFragment.javaClass.name
if (currentFragmentTag == newFragmentTag) return
supportFragmentManager.beginTransaction()
.replace(R.id.fl_content, newFragment, newFragmentTag)
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
.addToBackStack(newFragmentTag)
.commit()
}
}
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/tv_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/colorPrimary"
android:gravity="center_horizontal"
android:padding="@dimen/space_standard"
android:text="@string/label_great_app"
android:textColor="#fff"
android:textSize="@dimen/button_text_standard" />
<FrameLayout
android:id="@+id/fl_content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_above="@id/tv_emoji"
android:layout_below="@id/tv_title" />
<TextView
android:id="@+id/tv_emoji"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:background="@color/colorPrimary"
android:gravity="center_horizontal"
android:padding="@dimen/space_standard"
android:text="@string/label_emoji"
android:textColor="#fff"
android:textSize="@dimen/button_text_standard" />
</RelativeLayout>
Kilka słów na temat kodu, który widzisz powyżej:
funkcjachangeLocalization
przyjmuje jeden parametr, który posłuży nam do zbudowania Locale - w dużym uproszczeniu możesz to rozumieć tak: urządzenie weźmie tekst (string) z katalogu o tej samej nazwie
zmienia konfigurację aplikacji na taką, która którą ustawiliśmy w Locale
funkcjareplaceFragment
przyjmuje jeden parametr, którym jest fragment który chcemy otworzyć
jeśli będzie to fragment, który już mamy otwarty nic się nie stanie
jeśli będzie to nowy fragment, to podmieniamy go, ustawiamy rodzaj przejścia oraz dodajemy go do stosu fragmentów, a na koniec wdrażamy (#popełniamy)
Dorzucam jeszcze kod "resów" z mojego projektu:
Style
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>
<style name="DiscoButton.Stroke" parent="Base.Widget.AppCompat.Button">
<item name="android:background">@drawable/button_ripple_stroke</item>
<item name="android:textColor">@drawable/button_text_color_stroke</item>
<item name="android:paddingStart">90dp</item>
<item name="android:paddingEnd">90dp</item>
</style>
Drawable
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="@color/colorPrimaryDark">
<item>
<selector>
<item android:state_pressed="true">
<shape
android:padding="10dp"
android:shape="rectangle">
<corners android:radius="10dip" />
<solid android:color="@color/colorAccent" />
</shape>
</item>
<item android:state_enabled="true">
<shape android:shape="rectangle">
<corners android:radius="10dip" />
<stroke
android:width="2dip"
android:color="@color/colorAccent" />
</shape>
</item>
<item android:state_enabled="false">
<shape android:shape="rectangle">
<corners android:radius="10dip" />
<stroke
android:width="2dip"
android:color="@android:color/darker_gray" />
</shape>
</item>
</selector>
</item>
</ripple>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="@android:color/white" android:state_pressed="true" />
<item android:color="@android:color/darker_gray" android:state_enabled="false" />
<item android:color="@color/buttonTextColor" android:state_enabled="true" />
</selector>
Mam nadzieję, że Wam się podobało i wszystko jest dla Was jasne. Dajcie znać co chcielibyście abym poprawił, by kolejny artykuł był lepszy od tego!
Na koniec dorzucam jeszcze zrzuty z mojego urządzenia 😉
Specjalnie dla Android.com.pl
Łukasz Bednarczyk