Вот мы и добрались до конца. Осталось сделать только главное меню приложения, а также сделать игре настройки (собственно, меню только для того и нужно, чтобы было откуда настройки вызывать). Ну первое мы еще с прошлой статьи умеем, так что особых сложностей быть не должно. А вот второе следует рассмотреть подробнее. Итак, приступим.
Окно приветствия
В одной из прошлых статей подробно рассматривался вопрос, как создавать формы в приложении для 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