• AppTractor.ru
  • Главная
  • iTunes
  • SoundCloud
  • Podster
  • Чат в Telegram
Подкасты Android Dev
Подкасты Android Dev
  • AppTractor.ru
  • Главная
  • iTunes
  • SoundCloud
  • Podster
  • Чат в Telegram
Реализация сложного тулбара на Android

Реализация сложного тулбара на Android

Facebook Twitter Google+ LinkedIn VKontakte

При реализации сложного элемента интерфейса важно уметь схитрить и помнить о том, что в будущем ваш код может измениться, считает разработчик Юхани Лехтимяки, который рассказал о своем опыте создания необычной панели в приложении Social Steps.

В этой статье мы объясним, как и почему мы сделали свою toolbar-панель в приложении Social Steps.

Дизайн

Добавив прекрасных деталей в пользовательский интерфейс, вы можете сильно выделиться среди конкурентов (учитывая, что вся важная функциональность уже сделана, а сам интерфейс хорошо спроектирован). Toolbar – это игровая площадка Android. Мы решили использовать веселые, но значимые анимации и изменения состояния.

Реализация

UI-фреймворк Android удивительно мощен и гибок. Если вы уделите время, чтобы поучиться тому, что вы можете сделать с его помощью, вы получите очень сильный инструмент в свой арсенал. Лично я считаю, что нативный UI Android является самым сильным доступным инструментом прототипирования. Почти все идеи вашего дизайнера можно воплотить за несколько часов (или хотя бы создать приблизительный набросок функции).

Эта гибкость позволяет создавать масштабируемые и готовые к использованию функций. В приложении Social Steps панель инструментов была очевидным местом для продвижения бренда и создания привлекательных для пользователя аспектов приложения.

Для сохранения масштабируемости на экранах Android часто используются прокручивающиеся контейнеры. Поэтому Google представили специальные компоненты для того, чтобы добавлять интересное поведение в панель инстументов Android: AppBarLayout и CollapsingToolbarLayout.

С помощью этих компонентов и некоторых кастомных наработок мы можем сделать волшебный дизайн Toolbar-панели.

Отслеживание скроллинга

AppBarLayout.OnOffsetChangedListener

Этот инструмент вы можете использовать для того, чтобы управлять событиями, когда пользователь пролистывает ваш главный элемент (сжимает toolbar).

Этот код находится у меня в главном Activity, но он работает и в Fragment, если в нем определена ваша панель.

appbarLayout.addOnOffsetChangedListener(object : AppBarLayout.OnOffsetChangedListener {
    internal var scrollRange = -1

    override fun onOffsetChanged(appBarLayout: AppBarLayout,      verticalOffset: Int) {
        //Initialize the size of the scroll
        if (scrollRange == -1) {
            scrollRange = appBarLayout.totalScrollRange
        }


        val scale = 1 + verticalOffset / scrollRange.toFloat()

        toolbarArcBackground.setScale(scale)

        if (scale <= 0) {
            appbarLayout.elevation = toolbarElevation
        } else {
            appbarLayout.elevation = 0f
        }

    }
})

У этого кода очень простая задача – вычислять текущее состояние прокручивающегося контейнера в процентах. Код просто вычисляет этот процент и отправляет это число в кастомный View (смотрите ниже). Также этот код управляет поднятием toolbar, когда он весь оказывается сжатым.

Layout прост (этот можно ещё оптимизировать). Мой кастомный ToolbarArcBackground делает здесь всю сложную работу. Остальное – стандартный код. NonClickableToolbar нужен здесь для сворачивания toolbar-панели.

<?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:id="@+id/rootLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/content_background">


    <android.support.design.widget.CoordinatorLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">


        <android.support.design.widget.AppBarLayout
            android:id="@+id/appbarLayout"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:elevation="0dp">


            <android.support.design.widget.CollapsingToolbarLayout
                android:id="@+id/collapsing_toolbar"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:fitsSystemWindows="true"
                app:layout_scrollFlags="scroll|exitUntilCollapsed">

                <FrameLayout
                    android:id="@+id/collapsing_content"
                    android:layout_width="match_parent"
                    android:layout_height="160dp"
                    app:layout_collapseMode="pin">

                    <com.socialstepsapp.socialsteps.widget.
                     ToolbarArcBackground
                        android:id="@+id/toolbarArcBackground"
                        android:layout_width="match_parent"
                        android:layout_height="match_parent"
                        android:background=
                        "@color/content_background" />


                </FrameLayout>


                <com.socialstepsapp.socialsteps.widget.
                    NonClickableToolbar
                    android:layout_width="match_parent"
                    android:layout_height="?attr/actionBarSize"
                    android:layout_marginTop="24dp" />


            </android.support.design.widget.CollapsingToolbarLayout>


        </android.support.design.widget.AppBarLayout>

        <android.support.v4.widget.NestedScrollView
            android:id="@+id/scroll_view"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:fillViewport="true"
            app:layout_behavior=
            "@string/appbar_scrolling_view_behavior">
<!-- Here's some views of the app logic -->

         </android.support.v4.widget.NestedScrollView>
    </android.support.design.widget.CoordinatorLayout>


    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:layout_marginTop="24dp"
        android:background="#00000000"
        android:elevation="0dp">
<!-- Here's couple of irrelevant views ->
       


    </android.support.v7.widget.Toolbar>
</FrameLayout>

Реализация арки

Вся магия происходит в ToolbarArcBackground. Это довольно простой подкласс Android View. Так как у нас есть компонент, вычисляющий масштаб, нам нужно только понять, как получить то, что мы хотим.

Я экспериментировал с несколькими разными подходами. Первый из них – использование Path, чтобы обрезать нижнюю часть макета. К сожалению, в таком случае край получался неровным.

Как и во многих случаях, самым лучшим способом оказался самым простой… то есть, обман. Я воспользовался тем фактом, что фон главного экрана был однотонным. Самым простым способом оказалось нарисовать белый эллипс поверх всего контента toolbar. Чтобы избежать слишком острых концов, я нарисовал эллипс немного за пределами экрана слева и справа.

Метод setScale кастомного View хранит текущее значение и аннулирует контент.

fun setScale(scale: Float) {
    this.scale = if (scale < 0) {
        0f
    } else {
        scale
    }

    invalidate()
}

OnDraw затем просто рисует подходящий эллипс внизу.

override fun onDraw(canvas: Canvas) {
    super.onDraw(canvas)
// draw some other stuff here first
    canvas.drawOval(
        (-extendOverBoundary).toFloat(), height - arcSize * scale,    
        (width + extendOverBoundary).toFloat(),
        height + arcSize * scale,
        ovalPaint)
}

Когда значение достигает нуля, а пользователь долистывает до точки перехода, в которой край toolbar становится прямым, эллипс полностью исчезает.

Перемещение облаков при скроллинге

Облака – это простые bitmap-изображения с зафиксированной начальной локацией (хотя я не удивлюсь, если в будущем они будут двигаться в зависимости от текущего направления ветра ;)). Чтобы облака уходили за пределы панели при её сжатии, я просто вычислил позицию, на которую они должны переместиться в этот момент, а всё остальное – это простое умножение с использованием scale.

canvas.drawBitmap(cloud1Bitmap, cloud1X + cloud1OffsetX * (1 - scale), cloud1Y + cloud1OffsetY * (1 - scale), bitmapPaint)

Реализация изменения времени суток

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

Чтобы все надежно работало, я добавил ещё один scale в компонент View панели, timeScale. Это число от 0 до 1, которое сообщает View, как далеко от левого края находятся солнце или луна. isNight определяет, какую цветовую палитру и какое небесное тело нужно использовать.

fun setTimeScale(isNight: Boolean, timeScale: Float) {
    this.timeScale  = timeScale.coerceIn(0f, 1f)

    this.isNight = isNight
    invalidate()
}

Цвет toolbar создан из нескольких предопределенных цветов, интерполирован при помощи ArgbEvaluator.evaluate() и помещен в качестве фона с использованием шейдера градиента. Чтобы улучшить цвет, мы добавили интерполятор в timeScale перед вычислением цвета. Он добавляет эффект рассвета и заката в ранние и поздние часы, более точно воспроизводя реальное освещение.

private fun calculateColour2(): Int {
    return colourEvaluator.evaluate(scale, 
      ContextCompat.getColor(context, 
      R.color.toolbar_gradient_2_noon), calculateColour2Base())
      as Int

}



private fun calculateColour2Base(): Int {

    val interpolatedScale = interpolate(timeScale)

    return if (isNight) {
        when (interpolatedScale) {
            in 0.0f..0.5f -> 
                colourEvaluator.evaluate(interpolatedScale * 2,
                ContextCompat.getColor(context, 
                R.color.toolbar_gradient_2_evening), 
                ContextCompat.getColor(context,
                R.color.toolbar_ gradient_2_midnight)) as Int
            else -> colourEvaluator.evaluate((interpolatedScale - 
                0.5f) * 2, ContextCompat.getColor(context, 
                R.color.toolbar_gradient_2_midnight), 
                ContextCompat.getColor(context, 
                R.color.toolbar_gradient_2_morning)) as Int
        }
    } else {
        when (interpolatedScale) {
            in 0.0f..0.5f -> 
                colourEvaluator.evaluate(interpolatedScale * 2, 
                ContextCompat.getColor(context, 
                R.color.toolbar_gradient_2_morning), 
                ContextCompat.getColor(context, 
                R.color.toolbar_gradient_2_noon)) as Int
            in 0.5f..0.75f -> 
                colourEvaluator.evaluate((interpolatedScale - 0.5f) 
                * 4, ContextCompat.getColor(context, 
                R.color.toolbar_gradient_2_noon), 
                ContextCompat.getColor(context, 
                R.color.toolbar_gradient_2_noon_evening)) as Int
            else -> colourEvaluator.evaluate((interpolatedScale - 
                0.75f) * 4, ContextCompat.getColor(context, 
                R.color.toolbar_gradient_2_noon_evening), 
                ContextCompat.getColor(context, 
                R.color.toolbar_gradient_2_evening)) as Int
        }
    }
}

Для вечернего освещения мы добавили одно ручное значение (0,75f), так как интерполированный цвет между днем и вечером выглядел плохо. Чтобы убедиться, что toolbar всегда возвращается к цвету бренда при сжатии, второй цвет градиента также интерполируется к цвету бренда при определенном положении при скроллинге.

LinearGradient(0f, 0f, scale * width, scale * height, calculateColour1(), calculateColour2(), Shader.TileMode.CLAMP)

Сохраняйте масштабируемость

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

 

При создании анимации или градиентов всегда помните о масштабируемости с самого начала. Добавить её потом будет сложнее. Это просто. Вместо того, чтобы использовать фиксированные точки для градиентов, рисуйте их при помощи кода. Вместо того, чтобы думать о ширине экрана, используйте значения, которые предоставляет система Android. При возможности используйте значения в процентах, а если нужны абсолютные значения, помните про DiP.

Вот и все. Как вы видите, в нашем случае приложение не зафиксировано в одной ориентации экрана и мы не запрещаем установку на планшеты.

Заключение

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

Упрощение зависимости размера тулбара от времени и скроллинга до простых диапазонов от 0 до 1 позволило мне сконцентрироваться на реализации в этом ограниченном пространстве проблемы без необходимости переживать о внешних факторах. На самом деле, в приложении день и ночь не обладают одинаковой длительностью, как и должно быть. Реальное время не соотносится с переменной времени идеально. Позже мы сможем без проблем связать настоящее положение солнца с существующим кодом.

Facebook Twitter Google+ LinkedIn VKontakte

Facebook Comments

Другие записи

Трудоустройство Android-разработчиков в России и за рубежом: собеседования, знания, деньги — часть 1.1 Статья
18/06/2018

Трудоустройство Android-разработчиков в России и за рубежом: собеседования, знания, деньги — часть 1.1

38 лучших Open Source Android-проектов на Java Статья
13/04/2018

38 лучших Open Source Android-проектов на Java

Соединяем Android Things со смартфоном при помощи Nearby Connections 2.0 Статья
23/03/2018

Соединяем Android Things со смартфоном при помощи Nearby Connections 2.0

Слушайте
itunes-podcast-app-logo soundcloud-icon thumb17
Подпишитесь на рассылку Дайджест интересных материалов для разработчиков
Facebook
Apptractor

  • AppTractor.ru
  • Главная
  • Обратная связь
  • О проекте
  • RSS
  • Back to top
Android Dev — подкаст о разработке под Android и всем, что с этим связано. Гости программы — разработчики с большим опытом и стажем, которые помнят Android, когда он еще был версии 1.5, и за годы написали приложения для миллионов пользователей по всему миру.
Последние записи
Интересные материалы для Android-разработчика #250

Интересные материалы для Android-разработчика #250

Интересные материалы для Android-разработчика #249

Интересные материалы для Android-разработчика #249

Метки
Android Android O Android Pay Android Things ConstraintLayout Continuous Delivery Continuous Integration Dagger Droidcon Firebase Flutter Google Assistant Google I/O Gradle I/O Instant apps Java Jrebel Kotlin Mobius NDK open source React Native RXJava TensorFlow анимация архитектура аудио безопасность видео дайджест дизайн дополненная реальность интернет вещей исходный код конференция кроссплатформенная разработка машинное обучение новости обучение программирование работа разработка статья тестирование
© AppTractor.ru 2019