Введение
Доброго времени суток. В этой статье вы узнаете о том, какие проблемы могут возникнуть при разработке android-приложений. На написание этой статьи меня побудили комментарии из прошлой статьи, кстати вот она: моя первая статья. Спасибо за советы! Ну, пора начинать…
Проблема 1
Разрабатывая свои приложения мне хотелось сделать их как можно удобнее и красивее. Этому мешали стандартные диалоговые окна. Мне хотелось, чтобы фон в диалоге совпадал с основным фоном приложения.
Решение
В манифесте нужно выбрать собственную тему, за это отвечает атрибут:
android:theme="@style/AppTheme">
Заходим в values/styles.xml:
В этом файле находится тема приложения. Сначала я сделал приложение во весь экран, через наследование темы — android:Theme.Black.NoTitleBar.Fullscreen, потом изменил стиль диалоговых окон таким образом:
<item name="android:alertDialogStyle">@style/alertStyle</item>
но такой вариант не работает… За стиль диалоговых окон отвечает именно такой атрибут:
<item name="android:alertDialogTheme">@style/alertStyle</item>
Теперь уже можно определить сам стиль в том же файле:
<style name="alertStyle" parent="android:Theme.Dialog">
<item name="android:layout_gravity">center</item>
<item name="android:background">#64abcdf5</item>
</style>
Весь файл
<resources>
<style name="AppTheme" parent="android:Theme.Black.NoTitleBar.Fullscreen">
<item name="android:alertDialogTheme">@style/alertStyle</item>
</style>
<style name="alertStyle" parent="android:Theme.Dialog">
<item name="android:layout_gravity">center</item>
<item name="android:background">#64abcdf5</item>
</style>
</resources>
В результате получаем:
Красивое диалоговое окно![](https://habrastorage.org/r/w1560/files/7ee/984/7fc/7ee9847fcce84875ad8bdb1a0a99d8f5.png)
![](https://habrastorage.org/files/7ee/984/7fc/7ee9847fcce84875ad8bdb1a0a99d8f5.png)
Проблема 2
Эта проблема связана с сохранением данных. Сначала я находил решения связанные с созданием баз данных, но это для меня было чем-то страшным. Я искал вариант проще. Наконец набрёл в официальной документации на класс SharedPreferences. С помощью него очень легко сохранять данные. Рассмотрим на примере сохранение времени.
SaveObjectRating
public final class SaveObjectRating {
private Date[] convertedTime;
private SharedPreferences sharedPref;
private long[] time;
SaveObjectRating(Context context) {
time = new long[10];
convertedTime = new Date[10];
sharedPref = context.getSharedPreferences("RatFile", MODE_PRIVATE);
load();
}
public Date[] getConvertedTime() {
return convertedTime;
}
public void setNewTimeOnIndex(long a, int index) {
if (a < time[index] && a != 0) {
time[index] = a;
}
}
//метод сохранения данных
public void save() {
SharedPreferences.Editor editor = sharedPref.edit();
for (int i = 0; i < 10; i++) {
editor.putLong("key" + i, time[i]);
}
editor.apply();
}
//метод загрузки данных из хранилища
private void load() {
for (int i = 0; i < 10; i++) {
time[i] = sharedPref.getLong("key" + i, 3599);
}
}
//Метод преобразования
void createConvertedTime() {
for (int i = 0; i < 10; i++) {
convertedTime[i] = new Date();
long min = time[i] / 60;
long sec = time[i] % 60;
convertedTime[i].setMinutes((int) min);
convertedTime[i].setSeconds((int) sec);
}
}
}
Данный класс используется для сохранения потраченного игроком времени на прохождение уровня. При создании объектов этого класса в них будут уже загруженные данные, потому что в конструкторе класса вызывается метод load().
Новое потраченное время можно задать методом:
setNewTimeOnIndex(long a, int index)
Он изменяет данные только в том случае, если пользователь потратил на прохождение меньше времени.Чтобы сохранить данные нужно вызвать метод save().
И проблема сохранения данных решена без создания баз данных.
Проблема 3
Всё начиналось с много-экранных приложений. Экраны нужно менять, но как. Первое решение которым я долгое время пользовался — это создание для каждого экрана своей активности. Если бы у меня был мощный телефон, как у моего друга, я бы даже не заметил минусов этого решения, но это не так. При переходе на другой экран создавалась ещё одна активность и визуально на слабом телефоне это очень заметно. После осмысления я наконец понял, что активности не самый лучший вариант для переходов между экранами приложения. Нужно было использовать фрагменты. Это даёт нормальный визуальный эффект — совсем не заметно смены экрана.
Проблема 4
Наверное самая сложная проблема, потому что до сих пор каким бы решение я не пользовался приходится ставить куда-нибудь костыль, заключается в том, чтобы реализовать определение текущего фрагмента. Первое, что пришло мне в голову — это использовать статическую переменную. Как вариант сойдёт, но если фрагментов будет очень много, то создаются огромные выборки статической переменной и начитаешь путаться, какое значение отвечает за какой фрагмент. Следующий вариант — это определять на каком мы сейчас фрагменте через его тип (использование ключевого слова языка программирования Java — instanceof). На первый взгляд должно работать, но не работает. Мне до конца так и не удалось разобраться почему это работает не так как надо. (оно работает, но с ошибками...)
Итак, я попробовал ещё один вариант — проверять какой фрагмент виден на экране. Работает так как надо, если использовать его вместе с статической переменной.
MainActivity.class
@Override
public void onBackPressed() {
System.out.println("MainActivity:onBackPressed()");
if (idTheCurrentFragment == 4) {
finish();
}
if (start.isVisible()) {
System.out.println("Вы нажали назад в главном меню приложения");
idTheCurrentFragment = -1;
finish();
} else if (game1vs1.isVisible() || gameCompany.isVisible()) {
confirmation(); //Метод создаёт диалоговое окно с выбором.
} else if (idTheCurrentFragment == -1) {
System.out.println("Вы нажали кнопку назад");
super.onBackPressed();
}
}
Задача состояла в том, чтобы при нажатии системной кнопки назад игрока спрашивали уверен ли он в том, что хочет покинуть игру. Если игрок соглашается, то вызывается метод экземпляра класса FragmentManager popBackStack("..."), статическая переменная выставлялась в значение 4, при котором при нажатии на кнопку ещё раз приложение завершает работу. Такое решение меня устроило, но было бы интересно как это можно сделать ещё лучше.
Проблема 5
Проблема динамической смены фрагментов. К примеру, есть фрагмент А и при нажатии по кнопке, которая находится в размете этого фрагмента, должен открываться следующий экран, то есть фрагмент B. Я попробовал вариант — в классе активности создал обработчик событий нажатия на все кнопки в приложении. Во всей разметке я для всех кнопок указал в атрибуте android:onClick="..." созданный в активности обработчик событий. Для кнопок обязательно нужно указать id, чтобы обработчик понимал с какой кнопкой он имеет дело.
Код обработчика
public void onClick(View v) {
int id = v.getId();
switch (id) {
case R.id.exit: {
onBackPressed();
break;
}
case R.id.info: {
fm.beginTransaction().replace(R.id.fragment_container, infoFragment).addToBackStack(null).commit();
break;
}
case R.id.about_game: {
fm.beginTransaction().replace(R.id.fragment_container, aboutGameFragment).addToBackStack(null).commit();
break;
}
case R.id.about_authors: {
fm.beginTransaction().replace(R.id.fragment_container, aboutAuthorsFragment).addToBackStack(null).commit();
break;
}
}
Сами кнопки не создаются в java коде, только в xml. Да, да это работает. В активности есть экземпляр класса FragmentManager, чтобы выполнить транзакцию, то есть поменять текущий фрагмент.
Всё это хорошо, но я не знаю считается ли это правильным, поэтому от этого варианта я быстро отказался, постепенно убирая из выборки по нескольку значений, пока она совсем не опустела. Ещё один вариант, предлагаемый документацией Google — это через объект интерфейса, то есть фрагмент обрабатывает нажатие на свои кнопки и при нажатии создаётся объект интерфейса, который реализует активность таким образом:
SendingActivity message = (SendingActivity) getActivity();
message.send(''сообщение для активности'');
В реализации активностью этого интерфейса можно сделать switch — case, в котором уже определять какой фрагмент открывать дальше.
@Override
public void send(int a) {
switch (a) {
case 0: {
//Открываем фрагмент первого режима игры
break;
}
case 1: {
//Открываем фрагмент второго режима игры
break;
}
case 2: {
//Открываем фрагмент третьего режима игры
break;
}
case 3: {
//Открываем фрагмент четвёртого режима игр
break;
}
case 4: {
//Закрываем приложение(Кнопка выход)
finish();
break;
}
}
Проблема 6
Это проблема осталась так не решённой. Я надеюсь найдутся какие-нибудь опытные разработчик, которые смогут поделиться своим опытом в комментариях. Дело в том, что часто бывает нужно импортировать свою картинки svg-формата в среду разработки, но делать по одной картинки очень долго, даже если натренироваться… История была такой, я мучился вставлял 80 картинок в среду разработки, а потом оказалось, что их нужно переделывать…
Заключение
Молодцы те, кто дочитал до конца. Итак, я проделал длинный путь обхода этих проблем, чтобы на моих ошибках научились другие и поделился своим опытом с начинающими android-разработчиками. Для примеров я использовал написанных мною приложения. Вот ссылки на них:
play.google.com/store/apps/details?id=com.thekingames.memorize
play.google.com/store/apps/details?id=com.thekingames.warrior
Я надеюсь, что моя статья была для вас полезна. Всем удачи!