Настройки, диалоговые окна.

SharedPreferences

  • SharedPreferences - компонент хранения настроек приложения
  • Каталог хранения: /data/data/имя_пакета/shared_prefs/имя_файла_настроек.xml
  • getPreferences() — внутри Activity, чтобы обратиться к определённому для Activity предпочтению;
  • getSharedPreferences() — внутри Activity, чтобы обратиться к предпочтению на уровне приложения;
  • getDefaultSharedPreferences() — из объекта PreferencesManager, чтобы получить общедоступную настройку, предоставляемую Android.

Получение настроек

  • 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)
  • тип double не поддерживается.

SharedPreferences: получение объекта настроек

// это будет именем файла настроек
public static final String APP_PREFERENCES = "mysettings"; 

SharedPreferences mySharedPreferences = 
	getSharedPreferences(APP_PREFERENCES, Context.MODE_PRIVATE);
				    

SharedPreferences: флаги

  • MODE_PRIVATE - только приложение имеет доступ к настройкам.
  • MODE_APPEND - присоединяет новые настройки к существующим
  • MODE_ENABLE_WRITE_AHEAD_LOGGING - включена запись с опережением записи.
  • MODE_MULTI_PROCESS - проверяет изменение настроек, даже если экземпляр sharedpreference уже был загружен
  • MODE_WORLD_READABLE - позволяет другим приложениям читать настройки
  • MODE_WORLD_WRITEABLE - позволяет другим приложениям записывать новые настройки

SharedPreferences: файл

<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<map>
<string name="0005">|000000|000000|000000|0</string>
<string name="0004">|000000|000000|000000|1</string>
<string name="0006">|000000|000000|000000|0</string>
</map>				    
				    

SharedPreferences: пример

// это будет именем файла настроек
public static final String APP_PREFERENCES = "mysettings"; 

public static final String APP_PREFERENCES_NAME = "Nickname";
public static final String APP_PREFERENCES_AGE = "Age";

SharedPreferences mSettings;
mSettings = getSharedPreferences(APP_PREFERENCES, Context.MODE_PRIVATE);

EditText editNickname = findViewById(R.id.editNickname);
String strNickName = editNickname.getText().toString(); // здесь содержится текст, введенный в текстовом поле
Editor editor = mSettings.edit();
editor.putString(APP_PREFERENCES_NAME, strNickName);
editor.apply();
				    

SharedPreferences: сохранение

  • Чтобы внести изменения в настройки (редактировать), нужно использовать класс SharedPreferences.Editor.
  • Получить объект Editor можно через вызов метода edit объекта SharedPreferences, который вы хотите изменить.
  • После того, как вы внесли все необходимые изменения, вызовите метод commit() или apply() объекта Editor
  • Метод apply() появился в API 9 и работает в асинхронном режиме, что является более предпочтительным вариантом.
  • Метод commit() приходится использовать для старых версий и кроме того, он возвращает значение true в успешном случае и false в случае ошибки.

SharedPreferences: дополнительные методы

  • Для очистки значений используйте методы SharedPreferences.Editor.remove(String key)
  • Очистить все значения: SharedPreferences.Editor.clear().
  • Можно получить ассоциативный массив со всеми ключами и значениями через метод getAll()
  • Проверить наличие конкретного ключа с помощью метода contains().
  • Начиная с API 11, у класса SharedPreferences появился новый метод getStringSet()
  • У класса SharedPreferences.Editor родственный ему метод putStringSet().

SharedPreferences: Методы getStringSet() и putStringSet()

SharedPreferences sp;
String catnames;

// записываем имена котов в файл настроек
public void onPutSettings(View v){
	Set catnames = new HashSet();
	catnames.add("Мурзик");
	catnames.add("Барсик");
	catnames.add("Рыжик");
	Editor e = sp.edit();
	e.putStringSet("strSetKey", catnames);
	e.apply();
}

// считываем имена котов обратно
public void onShowSettings(View v)
{
 	Set ret = sp.getStringSet("strSetKey", new HashSet());
	for(String r : ret) {
	    Log.i("Share", "Имя кота: " + r);
	}
}				    
				    

getDefaultSharedPreferences()

  • getSharedPreferences() - необходимо придумывание имени файла для хранения настроек.
  • Этот способ придаёт гибкости в том случае, когда вам нужно создать несколько отдельных файлов.
  • Но если вам нужен один файл, то можно ничего не придумывать, а использовать метод getDefaultSharedPreferences() из объекта PreferencesManager.
  • Система сама сгенерирует имя файла из имени вашего пакета с добавлением слова _preferences.

getDefaultSharedPreferences()

public static SharedPreferences getDefaultSharedPreferences(Context context) {
    return context.getSharedPreferences(getDefaultSharedPreferencesName(context),
        getDefaultSharedPreferencesMode());
}

private static String getDefaultSharedPreferencesName(Context context) {
    return context.getPackageName() + "_preferences";
}

private static int getDefaultSharedPreferencesMode() {
    return Context.MODE_PRIVATE;
}				    
				    

Диалоговые окна

  • Dialog - базовый класс для всех типов диалоговых окон;
  • AlertDialog — диалоговое окно с кнопками, списком, флажками или переключателями;
  • CharacterPickerDialog - диалоговое окно, позволяющее выбрать символ с ударением, связанный с базовым символом;
  • ProgressDiaiog — диалоговое окно с индикатором прогресса при помощи компонента ProgressBar. В API 26 признан устаревшим.
  • DatePickerDialog — диалоговое окно выбора даты с элементом DatePicker
  • TimePickerDialog — диалоговое окно выбора времени с элементом TimePicker

Класс Dialog

  • Класс Dialog является базовым для всех классов диалоговых окон.
  • ProgressDialog, TimePickerDialog И DatePickerDialog — расширение класса AlertDialog, они также могут иметь командные кнопки.
  • Каждое диалоговое окно должно быть определено внутри активности или фрагмента
  • Диалоговое окно можно открыть один раз или несколько раз.
  • Для отображения диалогового окна необходимо вызвать метод showDialog()

Класс Dialog

  • Метод dismissDialog() прячет диалоговое окно (но не удаляет), не отображая его на экране.
  • Окно остаётся в пуле диалоговых окон данной активности.
  • При повторном отображении при помощи метода showDialog() будет использована кэшированная версия окна.
  • Метод removeDialog() удаляет диалоговое окно из пула окон данной активности.
  • При повторном вызове метода showDialog() диалоговое окно придётся создавать снова.

Dialog: пример

Dialog dialog;

@Override
public void onCreate(Bundle savedInstanceState) {
	super.onCreate(savedInstanceState);
	setContentView(R.layout.activity_main);

	dialog = new Dialog(MainActivity.this);

	// Установите заголовок
	dialog.setTitle("Заголовок диалога");
	// Передайте ссылку на разметку
	dialog.setContentView(R.layout.dialog_view);
	// Найдите элемент TextView внутри вашей разметки
	// и установите ему соответствующий текст
	TextView text = (TextView) dialog.findViewById(R.id.dialogTextView);
	text.setText("Текст в диалоговом окне. Вы любите котов?");
}

public void onClick(View v)
{
    // Выводим диалоговое окно на экран
    dialog.show();
}				    
				    

Методы onCreateDialog() и onPrepareDialog()

  • Метод onCreateDialog() вызывается один раз при создании окна.
  • После начального создания при каждом вызове метода showDialog() будет срабатывать обработчик onPrepareDialog().
  • Переопределив этот метод, вы можете изменять диалоговое окно при каждом его выводе на экран.
  • Это позволит привнести контекст в любое из отображаемых значений.
  • Если требуется перед каждым вызовом диалогового окна изменять его свойства (например, текстовое сообщение или количество кнопок), то можно реализовать внутри этого метода.

Методы onCreateDialog() и onPrepareDialog()

@Override
public void onPrepareDialog(int id, Dialog dialog) {
  switch(id) {
    case (TIME_DIALOG) :
	SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
        Date currentTime = new Date(java.lang.System.currentTimeMillis());
        String dateString = sdf.format(currentTime);
        AlertDialog timeDialog = (AlertDialog)dialog;
        timeDialog.setMessage(dateString);
        break;
    }
}				    
				    

Фрагменты

  • Два способа использования фрагментов
  • Первый способ основан на замещении родительского контейнера.
  • Создаётся стандартная разметка и в том месте, где будут использоваться фрагменты, размещается контейнер, например, FrameLayout.
  • В коде контейнер замещается фрагментом.
  • При использовании подобного сценария в разметке не используется тег fragment, так как его нельзя менять динамически.
  • Также придётся обновлять ActionBar, если он зависит от фрагмента.

Фрагменты

  • Второй вариант - используются отдельные разметки для телефонов и планшетов, которые можно разместить в разных папках ресурсов.
  • Например, если в планшете используется двухпанельная разметка с двумя фрагментами на одной активности
  • используем эту же активность для телефона, но подключаем другую разметку, которая содержит один фрагмент.
  • Когда нам нужно переключиться на второй фрагмент, то запускаем вторую активность.
  • Второй подход является наиболее гибким и в целом предпочтительным способом использования фрагментов.

Фрагменты: классы

  • Сами фрагменты наследуются от androidx.fragment.app.Fragment.
  • Существует подклассы фрагментов: ListFragment, DialogFragment, PreferenceFragment, WebViewFragment и др.
  • Для взаимодействия между фрагментами используется класс android.app.FragmentManager - специальный менеджер по фрагментам.
  • Для транзакций (добавление, удаление, замена) используется класс-помощник android.app.FragmentTransaction.
  • В 2018 году Гугл объявила фрагменты из пакета androd.app устаревшими.
  • Заменяйте везде на версию из библиотеки совместимости. В 2020 году уже используют пакет androidx.fragment.app.

Фрагменты

  • У каждого фрагмента должен быть свой класс.
  • Класс наследуется от класса Fragment или схожих классов
  • Создаются различные методы типа onCreate() и др.
  • Если фрагмент имеет разметку, то используется метод onCreateView() - считайте его аналогом метода setContentView()
  • При этом метод onCreateView() возвращает объект View, который является корневым элементом разметки фрагмента.

Фрагменты

public class FirstFragment extends Fragment implements OnClickListener {

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.first_fragment,
                container, false);

        Button nextButton = (Button) view.findViewById(R.id.button_first);
        nextButton.setOnClickListener(this);

        return view;
    }

    // ...
}				    
				    

Фрагменты

  • Перед методом findViewById() используется view
  • Этот метод относится к компоненту, а не к активности, как обычно делается в программах, когда просто опускали имя активности.
  • ищем ссылку на кнопку не среди разметки активности, а внутри разметки самого фрагмента.
  • в методе inflate() последний параметр должен иметь значение false в большинстве случаев.

FragmentManager

  • Класс FragmentManager имеет два метода, позволяющих найти фрагмент, который связан с активностью:
  • findFragmentById(int id) - Находит фрагмент по идентификатору
  • findFragmentByTag(String tag) - Находит фрагмент по заданному тегу

Методы транзакции (FragmentTransaction)

  • add() - Добавляет фрагмент к активности
  • remove() - Удаляет фрагмент из активности
  • replace() - Заменяет один фрагмент на другой
  • hide() - Прячет фрагмент (делает невидимым на экране)
  • show() - Выводит скрытый фрагмент на экран
  • detach() (API 13) - Отсоединяет фрагмент от графического интерфейса, но экземпляр класса сохраняется
  • attach() (API 13) - Присоединяет фрагмент, который был отсоединён методом detach()

Методы транзакции (FragmentTransaction)

  • Методы remove(), replace(), detach(), attach() не применимы к статичным фрагментам.
  • Перед началом транзакции нужно получить экземпляр FragmentTransaction через метод FragmentManager.beginTransaction().
  • В конце любой транзакции, которая может состоять из цепочки вышеперечисленных методов, следует вызвать метод commit().
FragmentManager fragmentManager = getFragmentManager()
fragmentManager.beginTransaction()
    .remove(fragment1)
    .add(R.id.fragment_container, fragment2)
    .show(fragment3)
    .hide(fragment4)
    .commit();				    
				    

Адаптеры

  • Адаптеры упрощают связывание данных с элементом управления.
  • Адаптеры используются при работе с виджетами, которые дополняют android.widget.AdapterView: ListView, ExpandableListView, GridView, Spinner, Gallery
  • AdapterView дополняет android.widget.ViewGroup
  • Назначение адаптера заключается в том, чтобы предоставлять дочерние view для контейнера.
  • Адаптер берет данные и метаданные определенного контейнера и строит каждый дочерний вид.
  • Например, мы формируем пункты списка (массив строк) и передаём его списку ListView.

Готовые адаптеры

  • ArrayAdapter - предназначен для работы с ListView. Данные представлены в виде массива, которые размещаются в отдельных элементах TextView
  • ListAdapter - адаптер между ListView и данными. Строго говоря, это класс-интерфейс, который можно использовать и в ArrayAdapter и в SimpleAdapter и т.д.
  • SpinnerAdapter - адаптер для связки данных с элементом Spinner. Это тоже интерфейс, как ListAdapter и работает по схожему принципу
  • SimpleAdapter - адаптер, позволяющий заполнить данными список более сложной структуры, например, два текста в одной строке списка.

Готовые адаптеры

  • SimpleCursorAdapter - дополняет ResourceCursorAdapter и создаёт компоненты TextView/ImageView из столбцов, содержащихся в курсоре. Компоненты определяются в ресурсах
  • CursorAdapter - предназначен для работы с ListView, предоставляет данные для списка через курсор, который должен иметь колонку с именем "_id"
  • ResourceCursorAdapter - этот адаптер дополняет CursorAdapter и может создавать виды из ресурсов
  • HeaderViewListAdapter - расширенный вариант ListAdapter, когда ListView имеет заголовки.
  • WrapperListAdapter - еще один адаптер для списков.

BaseAdapter

  • Если вам нужен свой собственный адаптер, то в Android есть абстрактный класс BaseAdapter, который можно расширить.
  • Собственный адаптер необходим в тех случаях, когда требуется специальное управление данными или дополнительный контроль над отображением дочерних представлений.
  • Кроме того, вы можете предусмотреть в своём адаптере элементы кэширования для повышения производительности работы.
  • У BaseAdapter есть несколько методов, которые следует переопределить. Например, метод getCount() позволяет узнать количество выводимых объектов.

Разметка

  • Компоновка (также используются термины разметка или макет) хранится в виде XML-файла в папке /res/layout.
  • компоновка – это некий визуальный шаблон для пользовательского интерфейса вашего приложения, который позволяет управлять элементами управления, их свойствами и расположением.
  • Каждый файл разметки должен содержать только один корневой элемент компоновки, который должен быть объектом View или ViewGroup.
  • Внутри корневого элемента вы можете добавлять дополнительные объекты разметки или виджеты как дочерние элементы.

Виды разметок

  • FrameLayout
  • LinearLayout
  • TableLayout
  • RelativeLayout
  • GridLayout
  • SwipeRefreshLayout
  • ConstraintLayout
  • CoordinatorLayout