Основные компоненты приложения.
Context
- Context – это объект, который предоставляет доступ к базовым функциям приложения
- доступ к ресурсам, к файловой системе, вызов активности и т.д.
- Activity является подклассом Context
- в коде мы можем использовать её как ИмяАктивности.this (напр. MainActivity.this)
- Доступ к контексту можно получить разными способами: getApplicationContext(), getContext(), getBaseContext()
Context
- Context имеет свои методы, позволяющие получать доступ к ресурсам и другим объектам: getAssets(), getResources(), getPackageManager(), getString(), getSharedPrefsFile().
- getAssets(): приложение может иметь ресурсы в папке assets вашего проекта
- getResources: получить доступ к ресурсу цвета используется конструкция getResources().getColor(), которая может получить доступ к данным из файла res/colors.xml.
Context
- Ключевое слово this не работает в анонимных классах
Button button = (Button) findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Toast.makeText(MainActivity.this, "Мяу", Toast.LENGTH_SHORT).show();
}
});
ContextCompact
- В библиотеки совместимости появился свой класс для контекста ContextCompat.
- Может вам пригодиться, когда студия вдруг подчеркнёт метод в старом проекте и объявит его устаревшим.
// Старый метод
context.getResources().getColor(R.color.some_color_resource_id);
// Студия ругается, что нужно использовать новый вариант getColor(int, Theme)
public void onClick(View view) {
int color = getResources().getColor(R.color.colorPrimary); // ругается
Button button = (Button) findViewById(R.id.button);
button.setTextColor(color);
}
// Теперь не ругается
int color = ContextCompat.getColor(this, R.color.colorPrimary);
Activity
Объявление activity в AndroidManifest
<activity
android:name="com.example.app.ChildActivity"
android:label="@string/title_child_activity"
<!-- Parent activity meta-data to support API level 4+ -->
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="com.example.app.MainActivity" />
<intent-filter>
</intent-filter>
</activity>
Объявление activity в AndroidManifest
- name - java или kotlin класс, который описывает activity
- label - название на верхней панели.
- intent-filter - события, на которые будет реагировать экран.
- множество других параметров (подробнее).
Жизненный цикл activity
Жизненный цикл activity.
- Набор методов управленияе activity
- жёстко контролируется системой и зависит от нужд пользователя, доступных ресурсов и т. д.
- Решение о запуске приложения принимает система.
- Хотя последнее слово и остаётся за системой, она подчиняется определённым заданным и логическим правилам, позволяющим определить, можно ли загрузить, приостановить приложение или прекратить его работу.
- Если в данный момент пользователь работает с определённым окном, система даёт приоритет соответствующему приложению.
- И наоборот, если окно невидимо и система решает, что работу приложения необходимо остановить, чтобы мобилизовать дополнительные ресурсы, будет прекращена работа приложения, имеющего более низкий приоритет.
Жизненный цикл activity. onCreate
- вызывается при создании экземпляра класса activity.
- устанавливается разметка activity setContentView()
- Внутри данного метода настраивают статический интерфейс активности.
- после вызова метода activity переходит в состояние Created.
- Инициализирует статические данные активности, связывают данные со списками и т.д.
- Связывает с необходимыми данными и ресурсами. Задаёт внешний вид через метод setContentView().
Жизненный цикл activity. onCreate
- принимает объект Bundle, содержащий состояние пользовательского интерфейса, сохранённое в последнем вызове обработчика onSaveInstanceState.
- Операции по инициализации, занимающие много времени, следует выполнять в фоновом процессе, а не с помощью метода onCreate().
- В противном случае можно получить диалоговое окно ANR (Application Not Responding, приложение не отвечает).
- В методе можно сделать проверку, запущено ли приложение впервые или восстановлено из памяти.
- Если значение переменной savedInstanceState будет null, приложение запускается первый раз:
Жизненный цикл activity
if ( savedInstanceState == null ) // приложение запущено впервые
{
currentBillTotal = 0.0; // инициализация суммы счета нулем
// другой код
}
else // приложение восстановлено из памяти
{
// инициализация суммы счета сохранённой в памяти суммой
currentBillTotal = savedInstanceState.getDouble(BILL_TOTAL);
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putDouble(BILL_TOTAL, currentBillTotal);
}
Жизненный цикл activity. onStart
- За onCreate() всегда следует вызов onStart()
- перед onStart() не обязательно должен идти onCreate(), так как onStart() может вызываться и для возобновления работы приостановленного приложения (приложение останавливается методом onStop())
- При вызове onStart() окно ещё не видно пользователю, но вскоре будет видно.
- Вызывается непосредственно перед тем, как активность становится видимой пользователю.
- Сопровождается вызовом метода onResume(), если активность получает передний план, или вызовом метода onStop(), если становится скрытой.
Жизненный цикл activity. onResume
- Метод onResume() вызывается после onStart(), даже когда окно работает в приоритетном режиме и пользователь может его наблюдать.
- В этот момент пользователь взаимодействует с созданным вами окном.
- Приложение получает монопольные ресурсы.
- Запускает воспроизведение анимации, аудио и видео. Также может вызываться после onPause().
- система вызывает этот метод каждый раз, когда ваша активность идёт на переднем плане, в том числе, при первом создании.
- Таким образом, вы должны реализовать onResume() для инициализации компонентов, регистрации любых широковещательных приёмников или других процессов, которые вы освободили/приостановили в onPause()
- выполнять любые другие инициализации, которые должны происходить, когда активность вновь активна.
Жизненный цикл activity. onResume
- не нужно перезагружать состояние пользовательского интерфейса внутри него, так как эти функции возложены на обработчики onCreate() и onRestoreInstanceState.
- Пытайтесь размещать относительно быстрый и легковесный код, чтобы ваше приложение оставалось отзывчивым при скрытии с экрана или выходе на передний план.
- Например, после метода onPause(), в котором мы приостановили работу камеры (см. ниже) снова запускаем камеру:
@Override
public void onResume() {
super.onResume();
// Get the Camera instance as the activity achieves full user focus
if (mCamera == null) {
initializeCamera(); // Local method to handle camera init
}
}
Жизненный цикл activity. onPause
- Когда пользователь решает перейти к работе с новым окном, система вызовет для прерываемого окна метод onPause().
- По сути происходит свёртывание активности.
- Деактивирует и выпускает монопольные ресурсы.
- Останавливает воспроизведение видео, аудио и анимацию.
- В этом методе необходимо остановить анимацию и другие действия, которые загружают процессор. Зафиксировать несохранённые данные, например, черновик письма, потому как после его выполнения работа активности может прерваться без предупреждения. Освободить системные ресурсы, например, обработку данных от GPS.
- Исходя из архитектуры своего приложения, вы также можете приостановить выполнение потоков, процессов или широковещательных приёмников, пока активность не появится на переднем плане.
Жизненный цикл activity. onPause
- Когда активность приостановлена, то все компоненты сохраняются в памяти и при возобновления нет необходимости повторно инициализировать их.
- Например, при работе с камерой метод используется следующим образом:
@Override
public void onPause() {
super.onPause();
// Release the Camera because we don't need it when paused
// and other activities might need to use it.
if (mCamera != null) {
mCamera.release()
mCamera = null;
}
}
Жизненный цикл activity. onStop
- Метод onStop() вызывается, когда окно становится невидимым для пользователя.
- Это может произойти при её уничтожении, или если была запущена другая активность (существующая или новая), перекрывшая окно текущей активности.
- Когда ваша активность останавливается, объекты активности хранятся в памяти и восстанавливаются, когда активность возобновляет свою работу.
- Даже если система закрыла вашу активность, когда она была остановлена, она по-прежнему сохраняет состояние объектов, таких как текст в EditText в специальном объекте Bundle (в виде ключ-значение) и восстанавливает их, если пользователь переходит обратно к тому же экземпляру активности.
- При нехватке памяти система может уничтожить скрытую активность, минуя метод onStop() с вызовом метода onDestroy().
Жизненный цикл activity. onRestart
- Если окно возвращается в приоритетный режим после вызова onStop(), то в этом случае вызывается метод onRestart().
- Т.е. вызывается после того, как активность была остановлена и снова была запущена пользователем.
- Всегда сопровождается вызовом метода onStart().
- onRestart предшествует вызовам метода onStart() (кроме самого первого).
- Используйте его для специальных действий, которые должны выполняться только при повторном запуске активности в рамках «полноценного» состояния.
Жизненный цикл activity. onDestroy
- Метод вызывается по окончании работы активности, при вызове метода finish()
- Или в случае, когда система уничтожает этот экземпляр активности для освобождения ресурсов.
- Эти два сценария уничтожения можно определить вызовом метода isFinishing().
- Вызывается перед уничтожением активности.
- Это последний запрос, который получает активность от системы.
- Если определённое окно находится в верхней позиции в стеке, но невидимо пользователю и система решает завершить это окно, вызывается метод onDestroy().
Последовательности вызовов
- Запуск приложения: onCreate() → onStart() → onResume()
- Нажимаем кнопку Назад для выхода из приложения: onPause() → onStop() → onDestroy()
- Нажата кнопка Домой: onPause() → onStop()
- После нажатия кнопки Домой, когда приложение запущено из списка недавно открытых приложений или через значок
onRestart() → onStart() → onResume()
- Когда запускается другое приложение из области уведомлений или открывается приложение Настройки onPause() → onStop()
- Нажата кнопка Назад в другом приложении или в Настройках и ваше приложение стало снова видимым.
onRestart() → onStart() → onResume()
- Открывается диалоговое окно
onPause()
Последовательности вызовов
- Диалоговое окно закрывается onResume()
- Кто-то звонит на телефон onPause() → onResume()
- Пользователь отвечает на звонок onPause()
- Разговор окончен onResume()
- Экран телефона гаснет: onPause() → onStop()
- Экран снова включён onRestart() → onStart() → onResume()
Порядок вызовов
- После onCreate() - onStart()
- После onRestart() - onStart()
- После onStart() - onResume() или onStop()
- После onResume() - onPause()
- После onPause() - onResume() или onStop()
- После onStop() - onRestart() или onDestroy()
- После onDestroy() - ничего
Настройки, диалоговые окна.
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 - адаптер, позволяющий заполнить данными список более сложной структуры, например, два текста в одной строке списка.
Пример ArrayAdapter
String[] countries = {"Россия", "США", "Германия", "Бразилия"};
ArrayAdapter adapter = new ArrayAdapter<>(
this,
android.R.layout.simple_list_item_1,
countries
);
ListView listView = findViewById(R.id.listView);
listView.setAdapter(adapter);
Готовые адаптеры
- SimpleCursorAdapter - дополняет ResourceCursorAdapter и создаёт компоненты TextView/ImageView из столбцов, содержащихся в курсоре. Компоненты определяются в ресурсах
- CursorAdapter - предназначен для работы с ListView, предоставляет данные для списка через курсор, который должен иметь колонку с именем "_id"
- ResourceCursorAdapter - этот адаптер дополняет CursorAdapter и может создавать виды из ресурсов
- HeaderViewListAdapter - расширенный вариант ListAdapter, когда ListView имеет заголовки.
- WrapperListAdapter - еще один адаптер для списков.
BaseAdapter
- Если вам нужен свой собственный адаптер, то в Android есть абстрактный класс BaseAdapter, который можно расширить.
- Собственный адаптер необходим в тех случаях, когда требуется специальное управление данными или дополнительный контроль над отображением дочерних представлений.
- Кроме того, вы можете предусмотреть в своём адаптере элементы кэширования для повышения производительности работы.
- У BaseAdapter есть несколько методов, которые следует переопределить. Например, метод getCount() позволяет узнать количество выводимых объектов.
Пример BaseAdapter
public class MyAdapter extends BaseAdapter {
private List items;
private LayoutInflater inflater;
public MyAdapter(Context context, List items) {
this.items = items;
this.inflater = LayoutInflater.from(context);
}
@Override
public int getCount() {
return items.size();
}
@Override
public Object getItem(int position) {
return items.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null) {
convertView = inflater.inflate(R.layout.my_item_layout, parent, false);
holder = new ViewHolder();
holder.title = convertView.findViewById(R.id.title);
holder.icon = convertView.findViewById(R.id.icon);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
MyItem item = items.get(position);
holder.title.setText(item.getTitle());
holder.icon.setImageResource(item.getIconRes());
return convertView;
}
private static class ViewHolder {
TextView title;
ImageView icon;
}
}
Ключевые методы адаптеров
- getCount() - возвращает общее количество элементов
- getItem(int position) - возвращает объект данных для позиции
- getItemId(int position) - возвращает уникальный ID для позиции
- getView(int position, View convertView, ViewGroup parent) - создаёт/переиспользует view для элемента
- getViewTypeCount() и getItemViewType(int position) - для разных типов элементов в одном списке
ViewHolder
- ViewHolder — это паттерн оптимизации, который значительно ускоряет работу адаптеров в Android, особенно в ListView, GridView и RecyclerView.
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View view = convertView;
if (view == null) {
view = LayoutInflater.from(context).inflate(R.layout.list_item, parent, false);
}
TextView title = view.findViewById(R.id.title);
TextView subtitle = view.findViewById(R.id.subtitle);
ImageView icon = view.findViewById(R.id.icon);
MyItem item = items.get(position);
title.setText(item.getTitle());
subtitle.setText(item.getSubtitle());
icon.setImageResource(item.getIconRes());
return view;
}
ViewHolder
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null) {
convertView = LayoutInflater.from(context).inflate(R.layout.list_item, parent, false);
holder = new ViewHolder();
holder.title = convertView.findViewById(R.id.title);
holder.subtitle = convertView.findViewById(R.id.subtitle);
holder.icon = convertView.findViewById(R.id.icon);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
MyItem item = items.get(position);
holder.title.setText(item.getTitle());
holder.subtitle.setText(item.getSubtitle());
holder.icon.setImageResource(item.getIconRes());
return convertView;
}
Адаптер. Работа с данными и уведомлениями
- После изменения данных нужно уведомить адаптер
- adapter.notifyDataSetChanged(); // Полное обновление (медленно)
- adapter.notifyItemChanged(position); // Точечное обновление (быстро)
- adapter.notifyItemInserted(position);
- adapter.notifyItemRemoved(position);
Разметка
- Компоновка (также используются термины разметка или макет) хранится в виде XML-файла в папке /res/layout.
- компоновка – это некий визуальный шаблон для пользовательского интерфейса вашего приложения, который позволяет управлять элементами управления, их свойствами и расположением.
- Каждый файл разметки должен содержать только один корневой элемент компоновки, который должен быть объектом View или ViewGroup.
- Внутри корневого элемента вы можете добавлять дополнительные объекты разметки или виджеты как дочерние элементы.
Виды разметок
- FrameLayout
- LinearLayout
- TableLayout
- RelativeLayout
- GridLayout
- SwipeRefreshLayout
- ConstraintLayout
- CoordinatorLayout
FrameLayout
- Самая простая разметка.
- Принцип работы: Все дочерние элементы размещаются в левом верхнем углу, друг на друге. Порядок отрисовки определяется порядком в XML: последний элемент в коде будет отображаться поверх предыдущих.
- Простота и эффективность. Подходит для отображения одного элемента или для наложения элементов (например, иконка уведомления на кнопке, прогресс-бар поверх контента).
- Для отображения одного виджета (например, TextView или ImageView).
- Когда вам нужно наложить один элемент на другой.
- Как контейнер для фрагментов (часто используется в FragmentTransaction).
LinearLayout
- Разметка, выстраивающая элементы в линию.
- Элементы располагаются последовательно, в одну строку или в один столбец.
- Направление задается атрибутом android:orientation (horizontal или vertical).Простота и линейность. Легко управлять весом (android:layout_weight) элементов, чтобы они занимали определенную долю доступного пространства.
- Для простых форм, где поля ввода идут друг за другом вертикально.
- Для создания строки или колонки с кнопками.
- Когда нужно равномерно распределить пространство между несколькими элементами с помощью layout_weight.
- Неэффективность при вложенности нескольких LinearLayout для создания сложных интерфейсов (возникает проблема избыточной меры - "measure pass").
TableLayout
- Организует дочерние элементы в строки и столбцы. Каждая строка определяется тегом .
- Количество столбцов определяется строкой с наибольшим количеством ячеек.
- Семантическое представление табличных данных.
- Для отображения данных в табличной форме (например, расписание, список свойств "ключ-значение").
- Негибкость для создания сложных, адаптивных интерфейсов. В современной разработке используется редко, чаще заменяется на GridView или RecyclerView для сложных таблиц или на ConstraintLayout для выравнивания.
RelativeLayout
- Разметка, позиционирующая элементы относительно друг друга или родителя.
- Позиция каждого дочернего элемента задается правилами (rules), например, layout_toLeftOf, layout_alignParentTop, layout_centerInParent.
- Гибкость без глубокой вложенности. Позволяет создавать более сложные интерфейсы, чем LinearLayout, избегая избыточного количества ViewGroup.
- Для интерфейсов средней сложности, где элементы логически связаны друг с другом (например, метка слева от поля ввода, кнопка "Отправить" выровнена по правому краю родителя).
- Производительность может страдать при очень большом количестве правил и зависимостей, такой макет сложнее в отладке и поддержке. Во многом вытеснен более производительным ConstraintLayout.
GridLayout
- Разметка, организующая элементы в сетку.
- Позволяет размещать элементы в ячейках заранее определенной сетки. Вы задаете количество строк и столбцов. Элемент может занимать несколько ячеек (объединять их), используя layout_rowSpan и layout_columnSpan.
- Более мощная и гибкая альтернатива TableLayout. Не требует использования .
- Для создания интерфейсов, которые по своей сути являются сеткой (например, калькулятор, клавиатура, главный экран с иконками приложений).
- Менее распространен и интуитивно понятен, чем LinearLayout или RelativeLayout.
ConstraintLayout
- Современная, гибкая и высокопроизводительная разметка. Рекомендуемая к использованию.
- Позиционирует элементы на основе ограничений (constraints). Каждая сторона элемента (верх, низ, лево, право) может быть "привязана" к другой стороне другого элемента или к границе родителя. Также поддерживает направляющие (Guidelines) и барьеры (Barriers).
- Плоская иерархия (минимальная вложенность), что положительно сказывается на производительности. Огромная гибкость для создания адаптивных интерфейсов, которые хорошо выглядят на экранах разных размеров.
- Практически для любого макета. Это текущий стандарт де-факто в разработке под Android.
- Когда нужно избежать глубокой вложенности LinearLayout и RelativeLayout.
ConstraintLayout
- Каждая сторона элемента (верх, низ, лево, право) и базовые линии могут быть привязаны к:
- Другим элементам
- Границам родителя
- Направляющим (Guidelines)
- Барьерам (Barriers)
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toStartOf="@+id/other_button"
app:layout_constraintBottom_toBottomOf="parent" />
ConstraintLayout
- Типы размеров
- Fixed: конкретный размер в dp
- Wrap Content: под размер содержимого
- Match Constraints (0dp): растягивается согласно ограничениям
<View
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent" />
ConstraintLayout. Цепи (Chains)
- Группируют элементы горизонтально или вертикально, позволяя контролировать как они распределяют пространство.
- Spread - элементы равномерно распределены (по умолчанию)
- Spread Inside - первый и последний элемент прижаты к краям, остальные распределены
- Packed - элементы сгруппированы вместе
- Weighted - с весовыми коэффициентами
ConstraintLayout. Направляющие (Guidelines)
- Вертикальные или горизонтальные линии, невидимые в runtime, к которым можно привязывать элементы.
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.3" />
<Button
app:layout_constraintStart_toStartOf="@+id/guideline"
... />
ConstraintLayout. Барьеры (Barriers)
- "Умные" направляющие, которые перемещаются в зависимости от размеров нескольких элементов.
<Button android:id="@+id/button1" ... />
<Button android:id="@+id/button2" ... />
<androidx.constraintlayout.widget.Barrier
android:id="@+id/barrier"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierDirection="end"
app:constraint_referenced_ids="button1,button2" />
<TextView
app:layout_constraintStart_toEndOf="@+id/barrier"
... />
ConstraintLayout
- Используйте 0dp (match constraints) вместо match_parent
- Избегайте глубокой вложенности - старайтесь укладываться в 2-3 уровня
- Используйте Guidelines для повторяющихся отступов и выравнивания
- Группируйте связанные элементы с помощью Chains
- Тестируйте на разных размерах экранов