Как стать автором
Обновить

Пишем Android приложение для киноманов — Часть 2 (Дизайн)

Уровень сложности Простой
Время на прочтение 10 мин
Количество просмотров 9.5K


Привет, Хабр!

В этой статье мы рассмотрим процесс создания экранов, опираясь на макеты из первой части.

Верстка экранов


Главный экран


На главном экране будут располагаться фрагменты и меню навигации. Напишем меню, предварительно создав иконки. Для этого откроем папку 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"?>
<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"
        tools:context=".MainActivity" style="@style/MyLinearLayout">
    <FrameLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:id="@+id/fragment_container"
            android:layout_marginBottom="?attr/actionBarSize"
    />
    <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"
            android:layout_gravity="bottom"/>
</FrameLayout>

Посмотрим что получилось.



Экран для вкладки 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"?>
<android.support.v7.widget.CardView
        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="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>

Посмотрим на результат:



Экран для вкладки 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"?>
<CheckBox
        xmlns:android="http://schemas.android.com/apk/res/android" 
        xmlns:tools="http://schemas.android.com/tools"
        tools:text="@string/movie_genres"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" android:id="@+id/item_check_box"
/>

Посмотрим на результат:



Тестирование дизайна


Посмотрим как часто перерисовывается один и тот же участок экрана, то есть выполняется лишняя работа.

Хорошо Плохо



Цвет голубой — перерисовывается один раз. Хорошо.
Цвет зеленый — перерисовывается два раза. Требуется оптимизация.
Цвет светло-красный — перерисовывается три раз. Очень плохо.
Цвет красный — перерисовывается больше 4 раз. Что-то пошло не так.
А если ничем не закрашено, значит участок не перерисовывается. Отличная работа.

Почему на втором скриншоте все красное? К каждому Layout, RecyclerView и CardView добавили белый фон, из-за этого произошло наложение слоев друг на друга. В описанном выше дизайне этого нет, пример лишь демонстрирует неудачное применение стилизации.

На первом скриншоте использовали только один фон на LinearLayout для фрагмента Home, перерисовку которого отменили программно, использовав следующий метод:

window.setBackgroundDrawable(null)


Посмотрим сколько занимает времени отрисовка кадров.

Для первого случая Для второго случая



Голубой цвет отвечает за время, используемое на создание и обновление View.
Фиолетовая часть представляет собой время, затраченное на передачу ресурсов рендеринга потока.
Красный цвет представляет собой время для отрисовки.
Оранжевый цвет показывает, сколько времени понадобилось процессору для ожидания, когда GPU завершит свою работу. Он и является источником проблем при больших величинах. — Александр Климов


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

Заключение


В следующей статье рассмотрим работу с сервером, а пока автор ее пишет, Вы можете оценить текущий дизайн в опросе ниже.

Если какие-то моменты остались непонятны, задавайте вопросы в комментариях.
Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Оценка дизайна
15.25% Отлично 9
20.34% Хорошо 12
33.9% Можно и лучше 20
13.56% Плохо 8
16.95% Ужасно. Я сделаю лучше. 10
Проголосовали 59 пользователей. Воздержались 15 пользователей.
Теги:
Хабы:
+4
Комментарии 4
Комментарии Комментарии 4

Публикации

Истории

Работа

Ближайшие события

Московский туристический хакатон
Дата 23 марта – 7 апреля
Место
Москва Онлайн
Геймтон «DatsEdenSpace» от DatsTeam
Дата 5 – 6 апреля
Время 17:00 – 20:00
Место
Онлайн