Введение
Доброго времени суток. В этой статье вы узнаете о том, какие проблемы могут возникнуть при разработке 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>
В результате получаем:
Красивое диалоговое окно

Проблема 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
Я надеюсь, что моя статья была для вас полезна. Всем удачи!