Вот мы и добрались до конца. Осталось сделать только главное меню приложения, а также сделать игре настройки (собственно, меню только для того и нужно, чтобы было откуда настройки вызывать). Ну первое мы еще с прошлой статьи умеем, так что особых сложностей быть не должно. А вот второе следует рассмотреть подробнее. Итак, приступим.
Окно приветствия
В одной из прошлых статей подробно рассматривался вопрос, как создавать формы в приложении для Android и делать переходы между ними. Так что особо останавливаться я не буду, и так все ясно.
На нашей форме приветствия будет какая-нибудь картинка и три кнопки: "Начать игру", "Настройки" и "Выход". Картинку в формате png, которую мы назовем start.png
нужно положить в папку /res/drawable. Названия кнопок нужно вынести в strings.xml
, добавив следующие строки:
res/values/strings.xml
<resources> <string name="app_name">PingPong</string><string name="start_title">Start Game</string> <string name="settings_title">Settings</string> <string name="exit_title">Exit</string></resources>
Тогда разметка для новой формы будет выглядеть так:
res/layout/start.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" android:gravity="bottom" android:background="@drawable/start" android:padding="8dip"> <Button android:id="@+id/StartButton" android:layout_width="fill_parent" android:layout_height="wrap_content" android:textStyle="bold" android:text="@string/start_title" /> <Button android:id="@+id/SettingsButton" android:layout_width="fill_parent" android:layout_height="wrap_content" android:textStyle="bold" android:text="@string/settings_title" /> <Button android:id="@+id/ExitButton" android:layout_width="fill_parent" android:layout_height="wrap_content" android:textStyle="bold" android:text="@string/exit_title" /> </LinearLayout>
Фоновое изображение можно задать экрану с помощью полезного свойства android:background
. Собственно, так можно задавать фон и кнопкам, и вообще чему угодно. Получили вот такую разметку:
Добавим соответствующий этой разметке класс StartScreen.java
. Сразу обработаем нажатия всех кнопок:
StartScreen.java
public class StartScreen extends Activity implements OnClickListener { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.start); // Кнопка "Start" Button startButton = (Button)findViewById(R.id.StartButton); startButton.setOnClickListener(this); // Кнопка "Exit" Button exitButton = (Button)findViewById(R.id.ExitButton); exitButton.setOnClickListener(this); // Кнопка "Settings" Button settingsButton = (Button)findViewById(R.id.SettingsButton); settingsButton.setOnClickListener(this); } /** Обработка нажатия кнопок */ public void onClick(View v) { switch (v.getId()) { case R.id.StartButton: { Intent intent = new Intent(); intent.setClass(this, GameScreen.class); startActivity(intent); break; } case R.id.SettingsButton: { break; } case R.id.ExitButton: finish(); break; default: break; } } }
По нажатию на кнопку Start происходит переход на экран с игрой. Обратите внимание, что StartScreen
при этом не закрывается. Это значит, что, когда закроется StartScreen
, мы попадем обратно на экран приветствия. По нажатию на Settings пока что ничего не происходит, по Exit — приложение закрывается.
Осталось только зарегистрировать эту форму в приложении и сделать ее главной. Для этого идем в AndroidManifest.xml
:
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.pingpong" android:versionCode="1" android:versionName="1.0.0"> <application android:icon="@drawable/icon" android:label="@string/app_name"> <activity android:name=".GameScreen" android:label="@string/app_name"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.SAMPLE_CODE" /> </intent-filter> </activity><activity android:name=".StartScreen"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity></application> </manifest>
Теперь приложение начинается с StartScreen
, все кнопки работают.
Настройки
Я сделаю в настройках два параметра — максимальное количество очков и сложность. Сложность игры будем менять, варьируя скорость мячика и ракеток.
Сама форма с настройками делается достаточно просто. Есть специальный наследник класса Activity
— PreferenceActivity
, который именно для этого и предназначен. Когда мы хотим сделать форму с настройками, нужно унаследоваться именно от него, и он возьмет на себя большую часть рутины.
Разметка
Разметка для формы с настройками выглядит несколько необычно:
res/xml/preferences.xml
<?xml version="1.0" encoding="UTF-8"?> <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" > <PreferenceCategory android:title="@string/prefs_title"> <ListPreference android:key="@string/pref_difficulty" android:title="@string/difficulty_title" android:entries="@array/difficulty" android:entryValues="@array/difficulty" android:defaultValue="1" /> <EditTextPreference android:key="@string/pref_max_score" android:title="@string/score_title" android:defaultValue="10" /> </PreferenceCategory> </PreferenceScreen>
Настолько необычно, что, если поместить этот XML в папку layout, eclipse начнет ругаться, что не может разрезолвить такие классы. Собственно, это не просто разметка: там также содержатся ключи настроек и значения по умолчанию. Поэтому мы создадим отдельную папку xml, и поместим этот файл туда. А теперь обо всем по порядку.
PreferenceCategory
Ну, PreferenceScreen
все понятно, а что такое PreferenceCategory
? Как ни удивительно, это категория настроек. Например, у какой-нибудь игры могут быть настройки графики, настройки звука, настройки сети и т.д.. Удобно отобразить их сгруппированными, вот так:
А можно обойтись без групп: PreferenceScreen
уже сам по себе является контейнером для настроек. В нашем случае, например, настроек мало и группировать нечего. Но это я так, для полноты картины.
Какие можно сделать настройки
Как видно даже не прошлой картинке, настройки могут быть разными. И флажки, и текстовые поля, и списки. Все они происходят от одного класса Preference
, и наследуют от него всякие полезные атрибуты, которые можно задавать в разметке, как то:
- android:title
- Заголовок настройки или контейнера настроек.
- android:summary
- Краткое описание. Проще говоря, это то, что пишется под заголовком мелким шрифтом.
- android:defaultValue
- Значение по умолчанию
- android:key
- Ключ, с которым данная настройка будет храниться и с которым можно будет к ней обращаться.
- android:dependency
- Задает зависимость от другого контрола. Например, можно поставить эдитору зависимость от флажка, и тогда, если флажок не установлен, но эдитор будет неактивен.
Ну и еще кое-что. Рассмотрим некоторые конкретные виды настроек.
CheckBoxPreference
Вот такой флажок:
EditTextPreference
Редактор текста.
Честно говоря, у редактора текста очень хотелось бы валидатор, а еще лучше маску. Например, IP-адрес имеет специфический формат, и хотелось бы запрещать пользователю вводить там что попало. Но мне такую функциональность так и не удалось обнаружить.
ListPreference
Своеобразная реализация Dropdown-а. Хотя, на телефоне наверно и вправду так удобнее.
На этом контроле хотелось бы остановиться подробнее. А конкретнее, рассказать, откуда он, собственно, берет элементы списка. А берет он их из ресурсов с помощью таких атрибутов.
- android:entries
Здесь хранится ссылка на ресурс, в котором хранятся отображаемые элементы списка. Все значения, которые хочется вынести в ресурсы, хранятся в папке values. До сих пор там была только одна XML-ка —
strings.xml
. Но теперь надо добавить еще одну — arrays.xml. И добавить в узелresources
следующее:<string-array name="performance"> <item>Best performance</item> <item>Normal performance and appearance</item> <item>Best appearance</item> </string-array>
После этого можно смело указывать в
android:entries
этот ресурс, список будет загружен.Кстати говоря, в
item
-ах может быть не непосредственно строка, а ссылка на строку вstrings.xml
. Например, в нашем случае будет так (разумеется, стоит добавить соответствующие значения вstrings.xml
):res/values/arrays.xml
<?xml version="1.0" encoding="utf-8"?> <resources> <string-array name="difficulty"> <item>@string/difficulty_easy</item> <item>@string/difficulty_normal</item> <item>@string/difficulty_hard</item> </string-array> </resources>
- android:entryValues
- Список действительных значений. Также ссылка на ресурс, и задавать можно так же. Если кодов будет меньше, чем значений, приложение будет валиться с исключением. Если больше — не будет. В нашем случае можно в entries и entryValues смело задавать одно и то же, но бывает, когда имеет смысл их разделять.
А еще мне никак не удалось победить у ListPreference
атрибут adnroid:defaultValue
. Не работает и все.
RingtonePreference
Настройка звукового сигнала. В нашем приложении, например, можно было бы добавить звук, с которым мячик ударяется о стенку, и выбирать этот звук с помощью такого контрола. Выглядит это так:
Класс формы
Класс для формы с настройками будет выглядеть так:
SettingsScreen.java
public class SettingsScreen extends PreferenceActivity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Настройки и их разметка загружаются из XML-файла addPreferencesFromResource(R.xml.preferences); } }
Кстати, настройки необязательно загружать из XML-ки, можно добавлять все эти настройки прямо в коде конструктора. В сэмплах, которые идут с Android SDK, такие примеры есть.
Добавляем в StartScreen
код для открытия формы настроек, прописываем SettingsScreen
в AndroidManifest.xml
. (Все выглядит точно так же, как и для GameScreen
, так что листинги не привожу). И увидим мы следующее:
Не знаю, кому как, а мне нравится, когда рядом с названием опции написано ее значение. Но как это сделать автоматически, я так и не нашла, пришлось все делать руками, используя для этого поле summary
. Итак, summary настройки должно обновляться при изменении значения. По счастью, есть такое событие OnPreferenceChange
. Итак, пишем:
SettingsScreen.java
public class SettingsScreen extends PreferenceActivity implements Preference.OnPreferenceChangeListener { /* Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Load the preferences from an XML resource addPreferencesFromResource(R.xml.preferences);ListPreference difficulty = (ListPreference)this.findPreference("pref_difficulty"); difficulty.setSummary(difficulty.getEntry()); difficulty.setOnPreferenceChangeListener(this); EditTextPreference maxScore = (EditTextPreference)this.findPreference("pref_max_score"); maxScore.setSummary(maxScore.getText()); maxScore.setOnPreferenceChangeListener(this);}public boolean onPreferenceChange(Preference preference, Object newValue) { preference.setSummary((CharSequence)newValue); return true; }}
Думаю, все понятно без слов. Теперь мы видим такую картину:
И, если мы будем менять настройки, изменения сразу же будут отображаться в summary
.
Использование настроек в других формах
Ну все, настройки мы сделали, они как-то сами где-то сохранились. Теперь возникла необходимость их прочитать и что-нибудь с ними сделать. Читать и делать мы будем в классе GameManager
, а конкретно, в конструкторе. Для работы с сохраненными настройками приложения используется класс SharedPreferences
. Вся работа по чтению и применению настроек выглядит так:
SettingsScreen.java
public GameManager(SurfaceHolder surfaceHolder, Context context) { ... // стили для рисования игрового поля ... // игровые объекты ...// применение настроек SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(context); String difficulty = settings.getString(res.getString(R.string.pref_difficulty), res.getString(R.string.difficulty_normal)); setDifficulty(difficulty); int maxScore = Integer.parseInt(settings.getString(res.getString(R.string.pref_max_score), "10")); setMaxScore(maxScore);}
Метод setDifficulty
приводить не буду, там ничего особо умного не написано.
Настройки из SharedPreferences
можно читать с помощью методов getString
, getInt
, getBoolean
и т.п. Все они принимают два параметра &mdahs; ключ к настройке (то, что мы задавали с помощью атрибута android:key
) и значение по умолчанию. Однако, воспользоваться чем-то кроме getString
мне так и не удалось.
Заключение
Итак, мануал закончен. Получился он огромным, но при этом собственно про андроид оказалось не так уж и много, что самое-то обидное :( Спасибо, если кто дочитал до конца. Буду рада услышать любые мнения.
Отдельное спасибо xeye и std.denis
42 комментария:
Вы - молодец, Даша! :)
Даша, я поддерживаю сергея.
Считаю Вам на эту тему нужно развивать
Согласен с верхними комментариями. удивительно то, что здесь их так мало... должно быть гораздо больше!
Вы сделали титанический труд, всем без исключений понравилось, но почему то не все удосужились Вас похвалить...
Считаю нужно продолжать)
Даша, вы сделали очень полезный и важный труд, для развитии программирования под андроид в русскоязычном инете. Просим продолжения блога! В особенности считаю, что не у каждого опытного программиста хватит терпения и целеустремленности так оформить и объяснить код.
Спасибо огромное Даша!
За развитие Андроида в руНете!
За проделанную работу
Очень надеюсь на продолжение серии статей по теме.
Отличная работа!
Я только начал окучивать направление Java и Android, но с С/С++ перенастроиться на Java, а с PocketPC на Android довольно тяжело. Пока я ломал голову над простенькой начальной игрушкой и над проблемами слоев в памяти/на экране и столкновения спрайтов, тут все уже за меня сделано. Спасибо.
Сделайте, плз, теперь что-нибудь многоэкранное :-).
2 Анонимный
Многоэкранные приложения довольно подробно рассмотрены в прошлой серии статей. Или Вы какую многоэкранность имеете в виду?
Да, Life я видел. Я имел в виду режим плавного скроллинга, когда игровое поле больше ширины экрана. Но это уже сложная штука, согласен. Лично мне бы с нынешним 2D разобраться :-). И кроме как на таких примерах, негде (SDK и книга DiMarzio не катят). Так что на вас, Даша, вся наша надежда ;-). Всяческих вам регардов. Николай.
Спасибо, огромное за проделанную работу. Первый блог в рунете на который я встретил с описанием 2D игры. Интересно было бы прочитать про моделирование игры с игровым полем сверх размеров экрана.
Потрясающая серия. Впечатлен. :)
Честно говоря, у редактора текста очень хотелось бы валидатор, а еще лучше маску. Например, IP-адрес имеет специфический формат, и хотелось бы запрещать пользователю вводить там что попало. Но мне такую функциональность так и не удалось обнаружить.
Можно использовать получение EditText из EditTextPreference и вешать на него обработчики или указывать тип ввода. Например:
((EditTextPreference)this.findPreference(getString(R.string.pref_phone))).getEditText().setInputType(InputType.TYPE_CLASS_NUMBER);
Wow! Bravo!
Так подробно и четко описали все шаги и трудности в процессе разработки...
Очень хороший пример, спасибо.
Спасибо, добрый человек. Весьма полезно.
Спасибо большое за статьи! Жду продолжения :)
да, статья очень подробная, большое спасибо! ждём-с продолжений
а кто-нибудь понял, отчего возникает такая бага (и как её пофиксить) - мячик как бы "зависает", "прилипает" к ракетке иногда?
Спасибо.
а предусмотрена ли для андроида возможность "резать" png-шки?
например, знаю что на С++ для создания анимашки считываем файлик, который условно порезан на кадры. потом во время рисования выводим тот кадр, который необходим. пересмотрел уже много приложений под андроид, и нигде с подобным не стыкался. или все сделано так что достатоочно 1 кадрика или порезано на кучу маленьких кусочков. потом попробуй разберись в тех ошметках что надо выводить...
извините, если туплю просто под андроидом не работал еще. и не знаю разумно ли "резать" файлы. или мы там наткнемся на какие-то проблемы...
Даша,спасибо огромное за понятное объяснение всех программ для андроида.есть маленькая просьба.если не трудно,нужен пример как брать данные из json file в андроиде.например брать координаты с json file и по ним нарисовать треугольник.пыталась сделать сама,но столкнулась с проблемой занесения данных в буфер.прошу помощи.спасибо заранее!!!!
Спасибо! Все наглядно и толково! Хочется продолжения, а потом можно и книжку написать!
Даешь книгу?!?
"Android. Программирование приложений." автор Дарья Ряжских
очень подробно написано. только как заменять изображение ракетки при ударе по мячу. пробовал через mImage, но начинает тормозить.
Дарья, Вы большой молодец, спасибо Вам. Получилось решить проблему android:defaultValue для ListPrefernce
http://paste.org/pastebin/view/23309
Джава огромен. Изучать его с нуля трудно. Эти уроки мне очень помогли. Перечитываю их опять и опять. И, конечно, жду продолжения банкета! :-)
PS Поздравляю с успешным завершением путешествия! :-)
интересно, а можно ли в событии onPreferenceChange узнать о значениях других параметров и изменить их?
Спасибо за мануал, Дарья! Очень пригодится.
молодец, Дарья! респект )
Дарья, большое спасибо!
Отличные статьи, очень аккуратно, внятно и доходчиво написано!
И в этой статье опечаточки....
«Как видно даже не прошлой картинке»
Не «не» а «на».
«если флажок не установлен, но эдитор будет неактивен.»
Не «но» а «то».
Товарищ OnDroid, это блог программиста для программистов, а не для литературный кружок, вот если бы Вы сделали замечания по теме...
С удовольствие прочитал Вашу статью. Кто может подсказать, как определить координаты клика на экране? По сенсору.
"Анонимный комментирует...
С удовольствие прочитал Вашу статью. Кто может подсказать, как определить координаты клика на экране? По сенсору."
Сам нашел ответ на свой вопрос. Реализовал так:
public class GameSurface (тут GameView) extends SurfaceView implements SurfaceHolder.Callback, OnTouchListener
в конструктор добавляем такую строчку:
findViewById(R.id.game).setOnTouchListener(this);
дальше в этом классе добавляем метод:
public boolean onTouch(View v, MotionEvent event)
{
int action = event.getAction();
int x = (int) event.getX();
int y = (int) event.getY();
return mThread.onTouch(v, event);
}
теперь можно выполнять действия по прикосновению на экран (onTouch) или сделать Х и У public и получать доступ из других классов.
Скачал исходники, импортировал в эклипс, импортировал, не компилирует - пишет три ошибки:
Description Resource Path Location Type
Project has no default.properties file! Edit the project properties to set one
The project was not built since its build path is incomplete. Cannot find the class file for java.lang.Object. Fix the build path then try building this project
The type java.lang.Object cannot be resolved. It is indirectly referenced from required .class files
подскажие что делать
Отличный мануал! Спасибо!
Согласна, очень хороший мануал для начинающего. Мне очень помогло. Сижу радуюсь "результатам", вроде сама сделала :)
Дальше уже как-то проще должно быть, есть от чего отталкиваться. А то не знала с какой стороны ко всему этому подходить.
Спасибо большое.
Большое спасибо за статью! Читал с интересом, очень классно написано :)
У Вас талант, Даша. Советую вам развиватсья в этом направлении.Многие скажут, что талантливые люли талантливы во всём, но я думаю, что это особый случай. БОЛЬШОЕ СПАСИБО.
спасибо вам огромное, Даша:)
Огромное спасибо, Дарья за труд. Но я наткнулся на одну ошибку, характерную в java после C# - сравнение строк.
метод
private void setDifficulty(String difficulty)
{
if (difficulty == "Easy")
{
А правильно сравнение строк работает вот так difficulty.equals("Easy")
Помогите, пжл, новичку. Я собрал проект используя ваши статьи, но вот беда - мяч иногда (когда касается угла ракетки) как-бы... начинает через ракетку компьютера проходить... То есть ракетка компьютера загоняет мячик в угол поля, он начинает дергаться и потом проходит через саму ракету... Вообщем, очень страшно выходит... Возможно в логике ракетки что-то не так?
И еще одна проблема. По-моему какой бы режим игры не ставь, параметры не меняются. То есть:
mBall.setSpeed(2);
mUs.setSpeed(3);
mThem.setSpeed(3);
Что так:
mBall.setSpeed(3);
mUs.setSpeed(1);
mThem.setSpeed(1);
Видимо в конструкторе Ball перекрывается: mSpeed = DEFAULT_SPEED; // задали скорость по умолчанию
Согласен с верхним комментарием. private void setDifficulty(String difficulty)
{
if (difficulty == "Easy")
{
difficulty.equals("Easy")
Тогда работает
Отправить комментарий