Привет!
В этой статье мы рассмотрим процесс создания экранов, опираясь на макеты из первой части.
Верстка экранов
Главный экран
На главном экране будут располагаться фрагменты и меню навигации. Напишем меню, предварительно создав иконки. Для этого откроем папку drawable, нажмем New и выберем Vector Assets. В появившемся окне нажмем на Clip Art и выберем иконки Settings, Search и Сontent paste. Размер оставим 24 на 24.
Создадим в папке values файл strings и добавим следующие строчки:
<resources>
<string name="title_home">Home</string>
<string name="title_search">Search</string>
<string name="title_settings">Settings</string>
</resources>
Вынесение всех строк в один файл позволит упростить дальнейшую локализацию приложения.
Если Вы захотите добавить другой язык, например, русский, создайте файл с атрибутом Locale, выберите в списке Language требуемый язык и добавьте эти же строчки, предварительно переведя их.
Теперь перейдем непосредственно к созданию меню. В папке menu создадим Menu resourse file, назовем его "bottom_nav_menu" и добавим в него следующий код:
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/navigation_home"
android:icon="@drawable/ic_content_paste_black_24dp"
android:title="@string/title_home"/>
<item
android:id="@+id/navigation_search"
android:icon="@drawable/ic_search_black_24dp"
android:title="@string/title_search"/>
<item
android:id="@+id/navigation_settings"
android:icon="@drawable/ic_settings_black_24dp"
android:title="@string/title_settings"/>
</menu>
Создадим в папке values файл styles и добавим следующий код:
<resources>
<style name="MyLinearLayout">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">match_parent</item>
<item name="android:orientation">vertical</item>
</style>
</resources>
Созданные стили применяются для представлений, чтобы разделить файлы разметки и файлами стилей. Они содержат атрибуты форматирования, отвечающие за внешний вид и поведение элементов. Сначала можно написать все атрибуты в файле разметки, а потом вынести их в файл styles нажав следующую комбинацию: ПКМ -> Refactor->Extract->Style. Style будет доступно только внутри представлений. Подробнее об этом можно прочитать здесь.
После этого можно приступать непосредственно к созданию layout для Activity. В папке layout создадим Layout resourse file, назовем его "activity_main" и добавим следующий код:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:context=".MainActivity" style="@style/MyLinearLayout">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:id="@+id/fragment_container"
/>
<android.support.design.widget.BottomNavigationView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/nav_view"
app:menu="@menu/bottom_nav_menu"
android:background="#6986c2"
/>
</LinearLayout>
Посмотрим что получилось.
Экран для вкладки Home
Откроем файл styles и добавим стили для Toolbar с дочерним TextView и RecyclerView:
<style name="MyToolbar">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:background">#6986c2</item>
</style>
<style name="Toolbar_TextView">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">match_parent</item>
<item name="android:gravity">center</item>
</style>
<style name="MyRecyclerView">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">match_parent</item>
<item name="android:background">#fff</item>
</style>
В файл strings добавим следующие строчки:
<string name="desc_home">Notification list</string>
<string name="movie_title">Movie title</string>
<string name="default_imdb_rating">IMDb rating: 7,4</string>
Теперь создадим layout с названием "fragment_home" и добавим в него следующий код:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
style="@style/MyLinearLayout">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar_home" style="@style/MyToolbar">
<TextView
android:text="@string/desc_home" style="@style/Toolbar_TextView"/>
</android.support.v7.widget.Toolbar>
<android.support.v7.widget.RecyclerView
android:id="@+id/recView_home" style="@style/MyRecyclerView"/>
</LinearLayout>
Добавим в папку drawable иконки Notification и Notification none.
Перейдем к созданию элемента списка. Добавим файл "item_movie" в папку layout и напишем в нем следующий код:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
>
<android.support.v7.widget.CardView
android:layout_width="match_parent"
android:layout_height="120dp"
app:cardCornerRadius="16dp"
android:layout_marginLeft="16dp"
android:layout_marginTop="8dp"
android:layout_marginRight="16dp"
android:layout_marginBottom="8dp"
app:cardBackgroundColor="#00FFFFFF">
<ImageView
android:layout_width="98dp"
android:layout_height="98dp"
app:srcCompat="@mipmap/ic_launcher"
android:id="@+id/item_image"
android:layout_gravity="center|start"
android:layout_marginLeft="10dp"/>
<TextView
android:text="@string/movie_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/item_title"
android:layout_marginTop="18dp"
android:layout_marginLeft="120dp"
android:textStyle="bold"
android:textColor="@android:color/black"
android:ellipsize="end"
android:maxLines="1"
android:layout_marginRight="16dp"/>
<TextView
android:text="@string/default_imdb_rating"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/item_rating"
android:layout_marginLeft="120dp"
android:layout_marginTop="50dp"/>
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:srcCompat="@drawable/ic_notifications_black_24dp"
android:id="@+id/item_notification"
android:layout_gravity="bottom|right"
android:layout_marginRight="16dp"
android:layout_marginBottom="16dp"
tools:ignore="VectorDrawableCompat"/>
</android.support.v7.widget.CardView>
</FrameLayout>
Посмотрим на результат:
Экран для вкладки Search
В файл strings добавим заголовок для Toolbar:
<string name="desc_settings">Preferences</string>
И создадим layout с именем "fragment_search", попутно добавив следующий блок кода:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
style="@style/MyLinearLayout">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar_search"
style="@style/MyToolbar">
<android.support.v7.widget.SearchView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/border_search_view"
android:layout_marginRight="15dp"
android:id="@+id/search_view"/>
</android.support.v7.widget.Toolbar>
<android.support.v7.widget.RecyclerView
android:id="@+id/recView_search"
style="@style/MyRecyclerView"/>
</LinearLayout>
Посмотрим результат:
Экран для вкладки Settings
Этот экран получился самым объемным, поскольку в нем мало повторяющихся стилей.
Добавим в файл styles код для TextView и CardView:
<style name="MyTextView">
<item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:textStyle">bold</item>
<item name="android:textColor">@android:color/black</item>
<item name="android:layout_marginLeft">16dp</item>
<item name="android:layout_marginTop">8dp</item>
<item name="android:layout_marginBottom">8dp</item>
<item name="android:typeface">normal</item>
</style>
<style name="MyCardView">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:layout_margin">16dp</item>
</style>
Также добавим в файл strings еще пару строчек:
<string name="imdb_rating">IMDb rating</string>
<string name="default_rating">6</string>
<string name="movie_genres">Movie genres</string>
Еще потребуется добавить в папку drawable иконку Star border.
Создадим layout с именем «fragment_settings» и добавим следующий код:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
style="@style/MyLinearLayout"
android:background="#fff">
<android.support.v7.widget.Toolbar
style="@style/MyToolbar"
android:id="@+id/toolbar_home"
app:title="@string/desc_home">
<TextView
android:text="@string/desc_settings"
style="@style/Toolbar_TextView"/>
</android.support.v7.widget.Toolbar>
<android.support.v7.widget.CardView
app:cardCornerRadius="16dp"
app:cardBackgroundColor="#00FFFFFF"
style="@style/MyCardView">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:srcCompat="@drawable/ic_star_border_black_24dp"
android:layout_gravity="right"
android:layout_marginRight="50dp"
android:layout_marginTop="8dp"
tools:ignore="VectorDrawableCompat"/>
<TextView
android:text="@string/default_rating"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/rating_value"
android:layout_gravity="right"
android:layout_marginRight="40dp"
android:layout_marginTop="8dp"
android:textColor="#666666"/>
<TextView
android:text="@string/imdb_rating"
android:id="@+id/textView"
style="@style/MyTextView"/>
<SeekBar
style="@style/Widget.AppCompat.SeekBar.Discrete"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:max="10"
android:progress="6"
android:id="@+id/seek_bar"
android:layout_marginLeft="16dp"
android:layout_marginTop="50dp"
android:layout_marginBottom="20dp"
android:layout_marginRight="16dp"/>
</android.support.v7.widget.CardView>
<android.support.v7.widget.CardView
app:cardCornerRadius="16dp"
app:cardBackgroundColor="#00FFFFFF"
style="@style/MyCardView">
<TextView
android:text="@string/movie_genres"
style="@style/MyTextView"/>
<android.support.v7.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#00FFFFFF"
android:layout_marginLeft="16dp"
android:layout_marginTop="36dp"
android:layout_marginBottom="16dp"
android:layout_marginRight="16dp"
android:id="@+id/recView_settings"/>
</android.support.v7.widget.CardView>
</LinearLayout>
Создадим элемент для списка. Добавим файл "item_genre" в папку layout и напишем следующий код:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_height="wrap_content">
<CheckBox
android:text="@string/movie_genres"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/item_check_box"/>
</FrameLayout>
Посмотрим на результат:
Тестирование дизайна
Посмотрим как часто перерисовывается один и тот же участок экрана, то есть выполняется лишняя работа.
Хорошо | Плохо |
---|---|
|
|
Цвет голубой — перерисовывается один раз. Хорошо.
Цвет зеленый — перерисовывается два раза. Требуется оптимизация.
Цвет светло-красный — перерисовывается три раз. Очень плохо.
Цвет красный — перерисовывается больше 4 раз. Что-то пошло не так.
А если ничем не закрашено, значит участок не перерисовывается. Отличная работа.
Почему на втором скриншоте все красное? К каждому Layout, RecyclerView и CardView добавили белый фон, из-за этого произошло наложение слоев друг на друга. В описанном выше дизайне этого нет, пример лишь демонстрирует неудачное применение стилизации.
На первом скриншоте использовали только один фон на LinearLayout для фрагмента Home, перерисовку которого отменили программно, использовав следующий метод:
window.setBackgroundDrawable(null)
Посмотрим сколько занимает времени отрисовка кадров.
Для первого случая | Для второго случая |
---|---|
|
|
Голубой цвет отвечает за время, используемое на создание и обновление View.
Фиолетовая часть представляет собой время, затраченное на передачу ресурсов рендеринга потока.
Красный цвет представляет собой время для отрисовки.
Оранжевый цвет показывает, сколько времени понадобилось процессору для ожидания, когда GPU завершит свою работу. Он и является источником проблем при больших величинах.
В нашем случае практически все вкладки отрисовываются за время меньше 16мс, то есть ниже зеленой полосы. Если столбик будет больше этой линии, программа может тормозить.
Подробнее о проведении такого тестирования можно прочитать здесь.
Заключение
В следующей статье рассмотрим работу с сервером, а пока автор ее пишет, Вы можете оценить текущий дизайн в опросе ниже.
Если какие-то моменты остались непонятны, задавайте вопросы в комментариях.
Если Вы считаете, что дизайн (XML) можно упростить или сделать лучше, просьба написать об этом.
Автор: Алексей Охотниченко