среда, 11 апреля 2012 г.

Игра для тренировки памяти (часть 3). Хранение настроек.

В части 1 и части 2 мы начали писать игру для тренировки памяти. Изначально в этом приложении я задумывала сделать 5 пунктов, и три из них уже выполнены:

1. Игровое поле 6х6 с картинками (часть 1)
2. Учет количество ходов (или времени) (часть 2)
3. Вывод таблицы рекордов
4. Настройки: выбор цвета фона, набора картинок
5. Начальный экран (часть 2)

Осталось сделать таблицу рекордов и окно настроек. По сути эта и следующая часть будет посвещена хранению данных приложений android.


Данных приложений можно хранить пятью способами:
1. Shared Preferences (общие настройки). Хранилище данных простых типов (boolean, float, int, long и string) в формате ключ-значение.
2. Внутренняя память — хранение данных в файлах в памяти телефона
3. Внешняя память - хранение данных в файлах на внешних носителя (например, на карте памяти)
4. SQLite — хранение данных в базе данных
5. Удаленный ресурс — хранение данных на удаленном ресурсе в сети.
Причем в 1, 2 и 4 случае данные по умолчанию будут доступны только для данного приложения, и при удалении программы из телефона они сотрутся автоматически.

Мы рассмотрим первые два способа. Настройки будем хранить в Shared Preferences, а таблицу рекордов в файле во внутренней памяти телефона.

1. Начнем с настроек.
Класс SharedPreferences — класс для записи и чтения данных основных типов в формате ключ-значение.
Читать данные можно с помощью методов
getBoolean(String key, boolean defValue),
getFloat(String key, float defValue),
getInt(String key, int defValue),
getLong(String key, long defValue),
getString(String key, String defValue)
где key — название ключа, а defValue — значение по умолчанию, то есть значение которое возвращает метод если в SharedPreferences, этот ключ не определен.

Записывать значения можно с помощью подкласса SharedPreferences.Editor. Этот класс инициализируется методом edit() класса SharedPreferences. Сохраняем пары ключ-значение с помощью методов
putBoolean(String key, boolean value),
putFloat(String key, float value),
putInt(String key, int value),
putLong(String key, long value),
putString(String key, String value)
где key — ключ, value — значение.

Для удаления ключа есть метод remove(String key). После всех изменений необходимо вызвать метод commit(), который запишет все изменения.

2. Для того чтобы пользователь мог задавать настройки необходимо окно, его можно написать самостоятельно, а можно воспользоваться классом PreferenceActivity. Это класс наследник от Activity и он берет на себя большую часть работы по созданию окон для редактирования настроек. Вначале создадим разметку для окна, она немного отличается от разметок для простых окон. Создадим директорию res/xml, а в ней файл settings.xml.
Описание настроек начинается с тэга PreferenceScreen, которое описывает окно настроек.

В PreferenceScreen можно вложить другой PreferenceScreen, тогда он будет выглядеть как строчка в списке настроек, а по нажатию открывать другой экран.

Настройки можно группировать с помощью категорий PreferenceCategory. Категорий может быть несколько или не быть совсем. Например на картинке две категории «Web Search» и «Quick Search Box»


Сами настройки могут быть следующих типов:
- CheckBoxPreference - чекбокс

  1. <CheckBoxPreference
  2.  android:key="cbp"
  3.  android:title="CheckBoxPreference"
  4.  android:defaultValue="true"
  5.  android:summary="This is CheckBoxPreference"
  6. />

- ListPreference - список из которого может быть выбран только один элемент



  1. <ListPreference 
  2.  android:key="lp"
  3.  android:title="ListPreference"
  4.  android:entries="@array/prefs"
  5.  android:entryValues="@array/prefsValue"
  6. />

- EditTextPreference - строка



  1. <EditTextPreference
  2.  android:key="etp"
  3.  android:title="EditTextPreference"
  4.  android:defaultValue="My Text"
  5.  android:summary="This is EditTextPreference"
  6. />

- RingtonePreference - выбор рингтона



  1. <RingtonePreference
  2.  android:key="rp"
  3.  android:title="RingtonePreference"
  4.  android:summary="This is RingtonePreference"
  5. />

- Preference - кнопка, которой можно задать какое-то действие
  1. <Preference
  2.  android:key="customPref"
  3.  android:title="Preference"
  4.  android:summary="This is Preference"
  5. />

Атрибуты:
android:key — ключ, по которому затем можно будет получить значение настройки
android:title — название настройки (то что написано крупным шрифтом)
android:summary — описание (то что написано мелким шрифтом). Сюда для удобства можно выводить значение настройки
android:defaultValue — значение по умолчанию
android:entryValues — список значений. Один элемент этого списка будет является значением настройки
android:entries — список значений, который будет видеть пользователь. Этот список может совпадать с android:entryValues.

В последних версиях Android API Level появились еще интересные типы настроек, такие как MultiSelectListPreference (level 11), SwitchPreference (level 14) или TwoStatePreference (level 14), про них можно почитать тут.

Если стандартных типов настроек не хватает, можно написать класс элемента самостоятельно.

Нам из всего этого нужен только ListPreference. Напишем в settings.xml:
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <PreferenceScreen
  3.   xmlns:android="http://schemas.android.com/apk/res/android" >
  4. <PreferenceCategory
  5.   android:title="@string/setTitle">
  6.  
  7.   <ListPreference android:key="PictureCollection"
  8.     android:title="@string/Collection"
  9.     android:entries="@array/CollectionsTitle"
  10.     android:entryValues="@array/Collections"
  11.     android:defaultValue="animal"
  12.   />
  13.  
  14.   <ListPreference android:key="BackgroundColor"
  15.     android:title="@string/Color"
  16.     android:entries="@array/ColorTitle"
  17.     android:entryValues="@array/Color"
  18.     android:defaultValue="#000000"
  19.   />
  20.   </PreferenceCategory>
  21. </PreferenceScreen>

В /res/values/strings.xml напишем массивы для списков
  1. <?xml version="1.0" encoding="utf-8"?>
  2. <resources>
  3.   <string name="hello">Hello World, MemoriaActivity!</string>
  4.   ...
  5.  
  6.   <string-array name="Collections">
  7.     <item>animal</item>
  8.     ...
  9.     <item>space</item>
  10.   </string-array>
  11.  
  12.   <string-array name="CollectionsTitle">
  13.      <item>Животные</item>
  14.      ...
  15.      <item>Космос</item>
  16.   </string-array>
  17.  
  18.   <string-array name="Color">
  19.     <item>#000000</item>
  20.     ...
  21.     <item>#FFFFFF</item>
  22.   </string-array>
  23.  
  24.   <string-array name="ColorTitle">
  25.     <item>Черный</item>
  26.     ...
  27.     <item>Белый</item>
  28.   </string-array>
  29.  
  30. </resources>

Здесь Collections и Color — массивы, чью элементы будут сохранятся как значение настроек
CollectionsTitle и ColorTitle — списки, которые будет видеть пользователь.

В первой части в пункте 7, мы решили что наборы картинок у нас будут называться как-то так animal0.png, animal1.png, …. animal17.png или people0.png, people1.png, …. people17.png, то есть наборы картинок отличаются префиксом, а внутри набора цифрой. Вот эти префиксы и будут элементами массива Collections. В массиве CollectionsTitle находятся человеческие названия для каждого набора.
С массивами Color и ColorTitle все понятно, это цвета в формате #RRGGBB и их русское название.

3. Теперь напишем класс отвечающий за настройки. Создадим файл Settings.java и напишем в него
  1. public class Settings extends PreferenceActivity implements Preference.OnPreferenceChangeListener
  2. {
  3.   ListPreference collection;
  4.   ListPreference color;
  5.  
  6.   @Override
  7.   public void onCreate(Bundle savedInstanceState)
  8.   {
  9.     super.onCreate(savedInstanceState);
  10.     addPreferencesFromResource(R.xml.settings);
  11.  
  12.     collection = (ListPreference)this.findPreference("PictureCollection");
  13.     color = (ListPreference)this.findPreference("BackgroundColor");
  14.  
  15.     // устанавливаем слушатель
  16.     collection.setOnPreferenceChangeListener(this);
  17.     color.setOnPreferenceChangeListener(this);
  18.  
  19.     // пишем в summary текущее значение
  20.     collection.setSummary(collection.getEntry());
  21.     color.setSummary(color.getEntry());
  22.   }
  23.  
  24.   @Override
  25.   public boolean onPreferenceChange(Preference preference, Object newValue)
  26.   {
  27.     preference.setSummary((CharSequence)newValue);
  28.     return true;
  29.   }
  30. }

Метод onPreferenceChange(Preference preference, Object newValue) — вызывается когда изменилась одна из настроек, где preference — настройка которая изменилась, newValue — новое значение.
При изменении настроек мы пишем в summary новое значение.

4. Запишем этот класс в AndroidManifest.xml и вызываем это окно в MemoriaStart по нажатию на кнопку «Настройка». (Код приводить не буду, там все по аналогии с MemoriaActivity)

5. Запускаем. Настройки открываются, по умолчанию в summary проставляется дефолтное значение, но при выборе другого значения в списке, оно меняется на английское (из массива Collection), а не на русское (из массива CollectionTitle). Изящного способа писать значение из entries, а не из entryValues я не нашла. Поэтому напишем не особо изящное :)
  1. public class Settings extends PreferenceActivity implements Preference.OnPreferenceChangeListener
  2. {
  3.   ...
  4.  
  5.   // два массива для хранения значений списков
  6.   CharSequence[] pictValue, colValue; 
  7.  
  8.   @Override
  9.   public void onCreate(Bundle savedInstanceState)
  10.   {
  11.      ...
  12.  
  13.      // получаем списки значений для каждой настройки 
  14.      pictValue = collection.getEntries();
  15.      colValue = color.getEntries();
  16.   }
  17.  
  18.   @Override
  19.   public boolean onPreferenceChange(Preference preference, Object newValue)
  20.   {
  21.     // название (android:key) настройки, которая была изменена
  22.     String Key = preference.getKey();
  23.  
  24.     // если это коллекция картинок
  25.     if (Key.equals("PictureCollection"))
  26.     {
  27.       // определяем индекс нового значения
  28.       int i = ((ListPreference)preference).findIndexOfValue(newValue.toString());
  29.       // ищем русское название для значения с этим индексом и записываем в summary
  30.       preference.setSummary(pictValue[i]);
  31.       return true;
  32.     }
  33.  
  34.     // если это цвет
  35.     if (Key.equals("BackgroundColor"))
  36.     {
  37.       int i = ((ListPreference)preference).findIndexOfValue(newValue.toString());
  38.       preference.setSummary(colValue[i]);
  39.       return true;
  40.     }
  41.  
  42.     // для всех остальных настроек (хотя у нас их и нет), ставим пришедшее значение в summary
  43.     preference.setSummary((CharSequence)newValue);
  44.     return true;
  45.   }
  46. }

Добавим в класс два массива pictValue и colValue, в методе onCreate () инициализируем их с помощью метода getEntries() класса ListPreference. Этот метод возвращает массив строк который записан в ListPreference в android:entries. И в методе onPreferenceChange в зависимости от того какая настройка была измнена, ищем в одном из этих массивов нужное нам значение.

6. Все работает. Теперь при изменении настройки у нас пишется русское значение. Писать метод для сохранения настроек не надо, все делается автоматически. Теперь если изменить настройки, выйти из приложения и зайти заново они сохранятся. Значения настроек связаны с нашим приложением и недоступны из других, при удалении программы, они сотрутся.

7. Теперь в классе MemoriaActivity прочитаем значения наших настроек. В методе OnCreate напишем:
  1. public void onCreate(Bundle savedInstanceState) {
  2.   super.onCreate(savedInstanceState);
  3.   setContentView(R.layout.main);
  4.  
  5.   // Настройки
  6.   SharedPreferences settings =  PreferenceManager.getDefaultSharedPreferences(this);
  7.   String PictureCollection = settings.getString("PictureCollection""animal");
  8.   Integer BackgroundColor = Color.parseColor(settings.getString("BackgroundColor""black"));
  9.  
  10.   ...
  11.  
  12.   mGrid = (GridView)findViewById(R.id.field);
  13.   // устанавливаем цвет для фона
  14.   View root = mGrid.getRootView();
  15.   root.setBackgroundColor(BackgroundColor);
  16.   mGrid.setEnabled(true);
  17.  
  18.   ...
  19.  
  20. }

Метод getDefaultSharedPreferences(Context context) класса PreferenceManager возвращает объект SharedPreferences с настройками для данного приложения. Получаем значения для PictureCollection и BackgroundColor. Устанавливаем цвет. Если написать mGrid.setBackgroundColor(BackgroundColor), то цвет установиться только под таблицей, что не очень красиво, поэтому вначале с помощью метода getRootView() получаем View самого верхнего элемента (у нас это LinearLayout) и устанавливаем цвет для него.

Для задания коллекции картинок, немного изменим GridAdapter, а именно добавим еще один параметр в конструктор.
  1. public GridAdapter(Context context, int cols, int rows, String pictCollection)
  2. {
  3.   ...
  4.  
  5.   // определяем префикс
  6.   PictureCollection = pictCollection;
  7.   ...
  8. }

И соответственно изменяем в классе MemoriaActivity:
  1. public void onCreate(Bundle savedInstanceState) {
  2.   super.onCreate(savedInstanceState);
  3.   setContentView(R.layout.main);
  4.  
  5.   ...
  6.  
  7.   mAdapter = new GridAdapter(this, GRID_SIZE, GRID_SIZE, PictureCollection);
  8.  
  9.   ...
  10. }

8. Все, можно запускать. Теперь можно менять в настройках набор картинок и цвет фона игрового поля.

Исходники:
Memoria-3.zip

Все части урока:
1. Игровое поле 6х6 с картинками (часть 1)
2. Учет количество ходов (или времени) (часть 2)
3. Просмотр таблицы рекордов (часть 4)
4. Настройки: выбор цвета фона и набора картинок (часть 3)
5. Начальный экран (Часть 2)

Комментариев нет:

Отправить комментарий