Всем доброго времени суток! Думаю, все смотрели презентацию Android L и видели его революционный для платформы Material Design. Что ж, релиза осталось ждать недолго, а тем временем внимательные пользователи могли заметить появление элементов нового дизайна в некоторых приложениях. Моё внимание приковало к себе обновление «Play Пресса». Выглядит и ощущается действительно революционно, я залип на несколько минут и даже начал читать прессу.
Каждый раздел сопровождается анимированным фоном, который плавно слайдится и, растворяясь, меняет изображения. Много необычных анимаций, которые привносят некоторую свежесть в приложение.
Естественно, у каждого разработчика при виде всех этих сластей возникает желание воплотить данные решения в своих проектах. К сожалению, на момент презентации доступные гайдлайны и документация не пестрили подробностями, они скорее напоминали рекламный прейскурант. К счастью, Google на момент написания статьи удосужились исправить ситуацию, и мы посмотрим что у них получилось.
Итак, гайдлайны достаточно кратко и лаконично описали философию Material Design, советую всем прочитать во избежание ошибок в дальнейшем, дублироваться здесь не буду. Единственное: интригует надпись «This document is a preview», что указывает на то, что, возможно, это еще не конечный вариант.
Далее документация. Что же нам обещают в новой версии? Если вкратце, то все сводится к новой теме, новым виджетам, кастомным теням и анимациям.
Тема Material
Новая тема добавит новые стили для старых виджетов, анимации откликов на касания к элементам интерфейса, новые анимации перехода между активностями. А что нам еще нужно?!)
Новые виджеты
RecyclerView
RecyclerView, по сути, есть логическим продолжением самого востребованного в Android-разработке виджета — ListView. Собственно, предназначение у него ровно то же самое — отображать список элементов, но есть нюансы.
Обязательное использование паттерна ViewHolder. Если при использовании ListView можно было из-за отсутствия опыта использовать адаптер, создающий с нуля отдельное view для каждого элемента списка, что при большом размере списка могло обернуться меньшей отзывчивостью UI и использованием лишней памяти, то при работе с RecyclerView разработчика насильно приводят к имплементации этого паттерна. Посмотрим, как это выглядит в коде адаптера для RecyclerView.
LayoutManager. Для использования RecyclerView кроме адаптера вам необходимо передать ему с помощью метода setLayoutManager() объект класса, реализующего LayoutManager. Этот класс отвечает за работу с адаптером, именно он решает, повторно использовать View или создать новый, и соответственно, именно он дёргает методы onCreateViewHolder(), onBindViewHolder() и getItemCount() адаптера. Пока доступна только одна реализация этого класса — LinearLayoutManager, для создания кастомного LayoutManager необходимо унаследоваться от RecyclerView.LayoutManager.
Анимация операций со списком. Если вы смотрели презентацию дизайна Material, то могли заметить, что одной из основных его особенностей является плавность UI, которая достигается с помощью повсеместного использования анимации. Наверняка, при особом желании можно добавить анимацию и в ListView, мне пока не приходилось этим заниматься, но в RecyclerView это делается парой строчек кода.
Для объекта RecyclerView указывается класс, имплементирующий анимацию:
RecyclerView.ItemAnimator itemAnimator = new DefaultItemAnimator(); recyclerView.setItemAnimator(itemAnimator);
При добавлении или удалении элемента списка вызывается метод адаптера notifyItemInserted(int position) или notifyItemRemoved(int position) соответственно.
При желании можно написать собственную реализацию анимации, унаследовавшись от RecyclerView.ItemAnimator.
Сырость. Нужно помнить, что виджет пока сыроват, и, возможно, его ждут существенные изменения. Например, сейчас в RecyclerView нет возможности задать header и footer списка, да и вообще в интернете о нём пока довольно мало информации.
CardView
CardView — это виджет, имплементирующий такой элемент дизайна Material, как карточка. По сути, это контейнер, у которого можно задавать радиус округленности углов, цвет карточки и высоту по оси z.
Отображение теней
Интересным нововведением, исповедующим Material-философию, является отрисовка теней. Я себе это представил так: если элементы интерфейса являются материальными, они должны отбрасывать тень. А какой размер у этой тени должен быть? Естественно, он будет зависеть от расположения элемента относительно фона и других элементов. Потому у виджетов появился новый параметр, раньше мы им задавали лишь размеры по X и Y, теперь же высоту по Z. Таким образом, виджет, находящийся на более высоком уровне, будет отбрасывать большую тень.
Данный параметр называется android:elevation и применяется так:
<TextView android:id="@+id/my_textview" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/next" android:background="@color/white" android:elevation="5dp" />
С помощью такого решения еще и реализуется управление многослойностью нашего интерфейса, виджеты могут сдвигаться друг на друга, отбрасывая соответствующую тень, перекрывая частично или полностью слои находящиеся под ними. Выглядит это очень эффектно и материально, напоминая коллаж или аппликацию, сшитую из элементов интерфейса.
Анимации
Анимации действительно претерпели больших изменений, и те, кто смотрел гайдлайны, со мной согласятся, а те, кто еще не смотрел — уже побежали это делать).
В новом API нам предоставляют новые анимации отклика на касания пользователя, новые анимации изменения состояния отдельных виджетов и анимации смены активностей. Кратко пройдёмся по каждой группе.
Touch Feedback. Новая анимация отклика на касание, заключается в выделении точки координат, на которую нажал пользователь, и дальнейшем эффекте волны от этой точки по всем направлениям. Анимация имеет логические начало и завершение, выглядит потрясающе и призвана заменить старые селекторы. Реализуется с помощью класса RippleDrawable. Можно реализовать как в xml, так и в коде применить к своему view. Пример реализации в xml, устанавливается как backround:
android:attr/selectableItemBackground android:attr/selectableItemBackgroundBorderless
Существует реализация данного класса для предыдущих версий, её можно посмотреть на github.
Reveal Effect. Данный эффект позволяет красиво показывать и прятать вьюхи, реализуется с помощью класса ViewAnimationUtils.createCircularReveal, для объяснения приведу примеры, взятые из документации.
Чтобы показать ранее скрытый вью:
View myView = findViewById(R.id.my_view); // получаем центр для открывающего круга int cx = (myView.getLeft() + myView.getRight()) / 2; int cy = (myView.getTop() + myView.getBottom()) / 2; // получаем финальный радиус открывающего круга int finalRadius = myView.getWidth(); // старт анимации со значением 0 ValueAnimator anim = ViewAnimationUtils.createCircularReveal(myView, cx, cy, 0, finalRadius); anim.start();
Чтобы скрыть ранее показанный вью:
final View myView = findViewById(R.id.my_view); // получаем центр для закрывающего круга int cx = (myView.getLeft() + myView.getRight()) / 2; int cy = (myView.getTop() + myView.getBottom()) / 2; // получаем начальный радиус открывающего круга int initialRadius = myView.getWidth(); // создаем анимацию с финальным радиусом 0 ValueAnimator anim = ViewAnimationUtils.createCircularReveal(myView, cx, cy, initialRadius, 0); // делаем view невидимым когда анимация закончилась anim.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); myView.setVisibility(View.INVISIBLE); } }); // старт анимации anim.start();
Activity Transitions. Здесь тоже не обошлось без изменений, и Google добавил понятие «общего» элемента между активностями. То есть во время анимации перехода между активностями у нас есть некоторый элемент, который, не покидая экран, может менять своё местоположение и размеры — выглядит прикольно.
Все анимации переходов описываются в стилях, элементы же передаём параметром в интент:
// get the element that receives the click event final View imgContainerView = findViewById(R.id.img_container); // get the common element for the transition in this activity final View androidRobotView = findViewById(R.id.image_small); // define a click listener imgContainerView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Intent intent = new Intent(this, Activity2.class); // create the transition animation - the images in the layouts // of both activities are defined with android:viewName="robot" ActivityOptions options = ActivityOptions .makeSceneTransitionAnimation(this, androidRobotView, "robot"); // start the new activity startActivity(intent, options.toBundle()); } });
Можно передать несколько параметров, если мы используем несколько общих вью:
ActivityOptions options = ActivityOptions.makeSceneTransitionAnimation(this, Pair.create(view1, "agreedName1"), Pair.create(view2, "agreedName2"));
Curved Motion. Все анимации в Material должны происходить плавно, и дифференцироваться во времени, то есть изменять свою скорость. Ранее все было не так, если какая-то анимация начиналась с определенной скоростью, она с ней и завершалась; теперь же, если представить развитие событий в виде графика, он будет напоминать параболу с приплюснутой вершиной. Данное пояснение можно наблюдать на видео в гайдлайнах.
Создавать такие анимации нам помогут классы PathInterpolator и ObjectAnimator, первый используется при анимировании в xml ресурсах, второй же прямо в коде:
ObjectAnimator mAnimator; mAnimator = ObjectAnimator.ofFloat(view, View.X, View.Y, path); ... mAnimator.start();
Animating View State Changes. Анимация изменения состояний наших вью производиться с помощью продвинутого селектора и класса StateListAnimator.
<!-- animate the translationZ property of a view when pressed --> <selector xmlns:android="https://schemas.android.com/apk/res/android"> <item android:state_pressed="true"> <set> <objectAnimator android:propertyName="translationZ" android:duration="100" android:valueTo="2" android:valueType="floatType"/> <!-- you could have other objectAnimator elements here for "x" and "y", or other properties --> </set> </item> <item android:state_enabled="true" android:state_pressed="false" android:state_focused="true"> <set> <objectAnimator android:propertyName="translationZ" android:duration="100" android:valueTo="2" android:valueType="floatType"/> </set> </item> </selector>
Также появился вариант анимированого селектора:
<!-- res/drawable/myanimstatedrawable.xml --> <animated-selector xmlns:android="https://schemas.android.com/apk/res/android"> <!-- provide a different drawable for each state--> <item android:id="@+id/pressed" android:drawable="@drawable/drawableP" android:state_pressed="true"/> <item android:id="@+id/focused" android:drawable="@drawable/drawableF" android:state_focused="true"/> <item android:id="@id/default" android:drawable="@drawable/drawableD"/> <!-- specify a transition --> <transition android:fromId="@+id/default" android:toId="@+id/pressed"> <animation-list> <item android:duration="15" android:drawable="@drawable/dt1"/> <item android:duration="15" android:drawable="@drawable/dt2"/> ... </animation-list> </transition> ... </animated-selector>
Extracting Prominent Colors from an Image. Появился мощный инструмент дизайна в реалтайме, это класс Palette, который возвращает преобладающий цвет из переданного эму изображения, что позволяет в дальнейшем применить маску для этого же изображения для нанесения на неё текста, или в каких-либо иных целях. Таким образом, палитра, которую использует ваш дизайн, может подстраиваться под контент в реалтайме, данный кейс используется в Material версии Play Музыка и выглядит действительно круто.
Рассуждения
К сожалению, не все так радужно, и здесь есть своя ложка дегтя... Практически все нововведения нам доступны только в версии L-preview, потому воспользоваться ими с ходу не получится. Для того, чтобы получить такую возможность, нам нужно использовать последнюю версию Android Studio с новым манифест мержером, это утилита, которая позволяет более гибко контролировать процесс «сшивания» всех манифестов библиотек, задействованных в проекте.
Подключаем новую xml-схему под названием tools:
xmlns:tools="https://schemas.android.com/tools"
Переопределим версию, которая в конечном итоге будет подтягиваться в build.gradle файла:
<uses-sdk tools:node="replace" />
Добавляем библиотеки в зависимости:
dependencies { compile 'com.android.support:cardview-v7:+' compile 'com.android.support:recyclerview-v7:+' compile 'com.android.support:palette-v7:+' }
Таким образом, мы можем уже использовать новые виджеты, но их функционал будет урезан. Что касается темы, она будет доступна только в API версии 21. Написав приложение с использованием нового SDK на девайсе с kitkat, мы увидим, что практически ничего не изменилось, а установив в это же приложение L-preview, все должно приобрести соответствующий вид — ключевое слово «должно», так как на текущий момент все жутко крашится. Можно, конечно, попытаться все исправить, но отсутствие поддержки темы в старых версиях напрочь убивает всякую охоту этим заниматься.
Вывод: Play Пресса — попытка Google адаптировать данное приложение к переходу на новый API, использовав в нём новые виджеты, и придать вид, схожий с Material, задействовав старые инструменты. Возможно, это было сделано для того, чтоб поэкспериментировать с поддержкой новой версии, но пока Play Пресса не готова к этому, так как после установки на Nexus 5 L-preview она тоже крашнулась.
Что касается новых возможностей, то они туманны... Если Google не сделает нормальную поддержку предыдущих версий, а линейка девайсов, которые получат новое обновление, в скором времени не пополнится самыми популярными девайсами других производителей, то следующие полгода наши труды с новым дизайном и не только сможет оценить очень малый процент пользователей... Ну, а я, как всегда, надеюсь на лучшее :).