Как сделать красивый плавный переход между экранами на Android.
План:
1 Вступление
2 Зачем нужна анимация - теория
3 Базовые классы - Scene, Transition, TransitionManager
4 Переходы между Activity/Fragment (Content transitions, Shared elements transitions)
5 Дополнение
25. Scene - о методе enter()
public void enter() {
if (mLayoutId > 0 || mLayout != null) {
getSceneRoot().removeAllViews();
if (mLayoutId > 0) {
LayoutInflater.from(mContext).inflate(mLayoutId, mSceneRoot);
} else {
mSceneRoot.addView(mLayout);
}
}
if (mEnterAction != null) {
mEnterAction.run();
}
setCurrentScene(mSceneRoot, this);
}
О методе enter() подробнее
26. Scene - о методе enter()
public void enter() {
if (mLayoutId > 0 || mLayout != null) {
getSceneRoot().removeAllViews();
if (mLayoutId > 0) {
LayoutInflater.from(mContext).inflate(mLayoutId, mSceneRoot);
} else {
mSceneRoot.addView(mLayout);
}
}
if (mEnterAction != null) {
mEnterAction.run();
}
setCurrentScene(mSceneRoot, this);
}
Приоритет для ContentView
* mLayout - это View для contentView
27. Scene - о методе enter()
public void enter() {
if (mLayoutId > 0 || mLayout != null) {
getSceneRoot().removeAllViews();
if (mLayoutId > 0) {
LayoutInflater.from(mContext).inflate(mLayoutId, mSceneRoot);
} else {
mSceneRoot.addView(mLayout);
}
}
if (mEnterAction != null) {
mEnterAction.run();
}
setCurrentScene(mSceneRoot, this);
}
Битые ссылки
28. Scene - о методе enter()
public void enter() {
if (mLayoutId > 0 || mLayout != null) {
getSceneRoot().removeAllViews();
if (mLayoutId > 0) {
LayoutInflater.from(mContext).inflate(mLayoutId, mSceneRoot);
} else {
mSceneRoot.addView(mLayout);
}
}
if (mEnterAction != null) {
mEnterAction.run();
}
setCurrentScene(mSceneRoot, this);
}
Enter Action
29. Scene - о методе enter()
public void enter() {
if (mLayoutId > 0 || mLayout != null) {
getSceneRoot().removeAllViews();
if (mLayoutId > 0) {
LayoutInflater.from(mContext).inflate(mLayoutId, mSceneRoot);
} else {
mSceneRoot.addView(mLayout);
}
}
if (mEnterAction != null) {
mEnterAction.run();
}
setCurrentScene(mSceneRoot, this);
}
Что такое setCurrentScene?
30. Scene - о методе enter()
setCurrentScene(mSceneRoot, this);
Что такое setCurrentScene?
static void setCurrentScene(View view, Scene scene) {
view.setTagInternal(com.android.internal.R.id.current_scene, scene);
}
Зачем?
Scene previousScene = Scene.getCurrentScene(sceneRoot);
if (previousScene != null) {
previousScene.exit();
}
TransitionManager
36. Scene
Применяем - код
val scene = Scene.getSceneForLayout(cardView,
R.layout.layout_scene_content,
context = this)
Создаем Scene
Применяем Scene
scene.enter()
46. Scene
Custom Transition для Scene
val imageFadeTransition = Fade().addTarget(R.id.ivSea)
val textSlideTransition = Slide().addTarget(R.id.tvSea)
val customAnimTransition = TransitionSet().apply {
duration = 1000L // 1 sec
addTransition(imageFadeTransition)
addTransition(textSlideTransition)
}
TransitionManager.go(scene, customAnimTransition)
47. Scene
Плавное появление для картинки
val imageFadeTransition = Fade().addTarget(R.id.ivSea)
val textSlideTransition = Slide().addTarget(R.id.tvSea)
val customAnimTransition = TransitionSet().apply {
duration = 1000L // 1 sec
addTransition(imageFadeTransition)
addTransition(textSlideTransition)
}
TransitionManager.go(scene, customAnimTransition)
48. Scene
Текст приедет снизу
val imageFadeTransition = Fade().addTarget(R.id.ivSea)
val textSlideTransition = Slide().addTarget(R.id.tvSea)
val customAnimTransition = TransitionSet().apply {
duration = 1000L // 1 sec
addTransition(imageFadeTransition)
addTransition(textSlideTransition)
}
TransitionManager.go(scene, customAnimTransition)
49. Scene
Создаем TransitionSet
val imageFadeTransition = Fade().addTarget(R.id.ivSea)
val textSlideTransition = Slide().addTarget(R.id.tvSea)
val customAnimTransition = TransitionSet().apply {
duration = 1000L // 1 sec
addTransition(imageFadeTransition)
addTransition(textSlideTransition)
}
TransitionManager.go(scene, customAnimTransition)
50. Scene
Можно указать время
val imageFadeTransition = Fade().addTarget(R.id.ivSea)
val textSlideTransition = Slide().addTarget(R.id.tvSea)
val customAnimTransition = TransitionSet().apply {
duration = 1000L // 1 sec
addTransition(imageFadeTransition)
addTransition(textSlideTransition)
}
TransitionManager.go(scene, customAnimTransition)
51. Scene
go() - уже с нашей анимацией
val imageFadeTransition = Fade().addTarget(R.id.ivSea)
val textSlideTransition = Slide().addTarget(R.id.tvSea)
val customAnimTransition = TransitionSet().apply {
duration = 1000L // 1 sec
addTransition(imageFadeTransition)
addTransition(textSlideTransition)
}
TransitionManager.go(scene, customAnimTransition)
57. Принцип работы
Transition - основа
Scene A Scene B
Сохраняем параметры SceneA
Сохраняем параметры SceneB
Только «свои» параметры
Transition
Создание анимации изменения
58. 2 главные задачи
Transition - основа
Сохранение
параметров View в
SceneA и SceneB
Создание анимации
для
изменения View
60. Transition - теория
Как Transition сохраняет параметры
View?
void captureStartValues(@NonNull TransitionValues transitionValues)
void captureEndValues(@NonNull TransitionValues transitionValues)
А что такое TransitionValues?
61. Transition - теория
Что такое TransitionValues?
public class TransitionValues {
/**
* The set of values tracked by transitions for this scene
*/
public final Map<String, Object> values = new HashMap<>(); // properties
//...
public View view; // view
}
67. Transition - теория
Transition - targets
Как Transition определяет какую View анимировать а какую нет?
Плавно появляется только картинка
Приезжает только текст
70. Transition - теория
Match order
public void setMatchOrder(@MatchOrder int... matches)
public static final int MATCH_INSTANCE
public static final int MATCH_NAME
public static final int MATCH_ID
public static final int MATCH_ITEM_ID
- View
- transition name
- View ID
- Item view ID - для android.widget.Adapter
71. Transition - теория
Как добавить target:
<changeBounds>
<targets>
<target android:targetId="@+id/ivSmallAvatar" />
</targets>
</changeBounds>
Код
val changeBounds = ChangeBounds()
changeBounds.addTarget(R.id.ivBigAvatar)
XML
72. Transition - теория
Как удалить target:
val changeBounds = ChangeBounds()
changeBounds.excludeTarget(R.id.ivBigAvatar, exclude = false)
Код
if (exclude) {
list = ArrayListManager.add(list, target);
} else {
list = ArrayListManager.remove(list, target);
}
Transition.java
73. Transition - теория
Неочевидно!
if (exclude) {
list = ArrayListManager.add(list, target);
} else {
list = ArrayListManager.remove(list, target);
}
Transition.java
Роберт Мартин
exclude (исключить) == true -> add?
74. Transition - теория
Как удалить target:
val changeBounds = ChangeBounds()
changeBounds.removeTarget(R.id.ivBigAvatar)
Код
79. Transition - теория
Interpolator для анимации
val transition = Fade()
transition.interpolator = BounceInterpolator()
Код
XML
<fade android:interpolator="@android:interpolator/bounce"/>
86. Transition - базовый набор
Slide
Плавно перемещает View за пределы экрана
Плавно перемещает View на экран
Направление движения устанавливает
Gravity
Код
Slide()
87. Transition - базовый набор
Explode
Двигает View в сторону от центра или
указанного эпицентра
Код
Explode()
89. Transition - базовый набор
Explode
Есть набор View для Explode
Все они одинаковые
Эпицентром будет верхний левый квадрат
90. Transition - базовый набор
Explode - эпицентр
fun createEpicenterCallback(): Transition.EpicenterCallback {
var rect: Rect? = null
val centralView = yourView
if (centralView != null) {
rect = Rect()
centralView.getGlobalVisibleRect(rect)
}
return object : Transition.EpicenterCallback() {
override fun onGetEpicenter(transition: Transition) = rect
}
}
Нужно создать EpicenterCallback
91. Transition - базовый набор
Explode - эпицентр
fun createEpicenterCallback(): Transition.EpicenterCallback {
var rect: Rect? = null
val centralView = yourView
if (centralView != null) {
rect = Rect()
centralView.getGlobalVisibleRect(rect)
}
return object : Transition.EpicenterCallback() {
override fun onGetEpicenter(transition: Transition) = rect
}
}
EpicenterCallback возвращает Rect с координатами эпицентра
92. Transition - базовый набор
Explode - эпицентр
fun createEpicenterCallback(): Transition.EpicenterCallback {
var rect: Rect? = null
val centralView = yourView
if (centralView != null) {
rect = Rect()
centralView.getGlobalVisibleRect(rect)
}
return object : Transition.EpicenterCallback() {
override fun onGetEpicenter(transition: Transition) = rect
}
}
Мы можем взять Rect у нашей View
93. Transition - базовый набор
Explode
val explode = Explode()
explode.epicenterCallback = createEpicenterCallback()
Создаем Explode с EpicenterCallback
101. Transition - базовый набор
ChangeBounds
Анимирует изменение местоположения View
Анимирует изменение размеров View
Код
ChangeBounds()
102. Transition - базовый набор
ArcMotion
Делает перемещение View по окружности
Работает в связке с ChangeBounds
Код
ChangeBounds().apply {
setPathMotion(ArcMotion())
}
Настройка угла окружности
103. Transition - базовый набор
PatternPathMotion
Перемещение View по кастомному пути
Работает в связке с ChangeBounds
104. Transition - базовый набор
PatternPathMotion
Код
val path = createPath()
return ChangeBounds().apply {
setPathMotion(PatternPathMotion(path))
}
private fun createPath() = Path().apply {
moveTo(0f,0f)
quadTo(0f,0f, 1f, 0.0f)
quadTo(1f,0.0f, 0.3f, 0.6f)
quadTo(0.3f,0.6f, 1f, 1f)
}
105. Transition - базовый набор
PatternPathMotion
Код
val path = createPath()
return ChangeBounds().apply {
setPathMotion(PatternPathMotion(path))
}
private fun createPath() = Path().apply {
moveTo(0f,0f)
quadTo(0f,0f, 1f, 0.0f)
quadTo(1f,0.0f, 0.3f, 0.6f)
quadTo(0.3f,0.6f, 1f, 1f)
}
Указываем
свой
путь движения
107. Transition - базовый набор
ChangeClipBounds
Анимирует изменение clipBounds у View
Код
ChangeClipBounds()
108. Transition - базовый набор
ChangeImageTransform
Анимирует матричное изменение
внутри ImageView
Код
ChangeImageTransform() + ChangeBounds
109. Transition - базовый набор
ChangeTransform
Анимирует изменение размера View и
поворот
Код
ChangeTransform() +ChangeBounds
110. Transition - базовый набор
ChangeScroll
Анимирует изменение scrollX и scrollY
Код
ChangeScroll()
111. Transition - базовый набор
AutoTransition
Анимиция появления/исчезновения
Код
AutoTransition()
Анимиция размеров и местоположения
View
Fade + ChangeBounds
112. Transition - базовый набор
TransitionSet
Позволяет группировать Transitions вместе
Код
TransitionSet()
Сам по себе не создает анимацию
113. Transition - базовый набор
TransitionSet
XML
<?xml version="1.0" encoding="utf-8"?>
<transitionSet>
<changeBounds/>
// …
</transitionSet>
114. Transition - базовый набор
TransitionSet
Порядок проигрывания анимации
public static final int ORDERING_TOGETHER = 0;
public static final int ORDERING_SEQUENTIAL = 1;
public TransitionSet setOrdering(int ordering)
117. TransitionManager
Основные методы
setTransition() - сохраняет пару Scene-Transition
transitionTo() - применяет Scene с указанными или по умолчанию Transition-ом
endTransition() - останавливет все текущие и отложенные Transition’s для View
static go() - применяет Scene + Transition (default или custom)
static beginDelayedTransition() - применяет Scene + Transition (default или custom)
119. flTestContent.visibility = View.INVISIBLE
val transition = AutoTransition()
transition.doOnEnd { showWithAlpha() }
TransitionManager.beginDelayedTransition(cardView, transition)
flTestContent.setHeight(height = 500) // your height
TransitionManager
Как это сделать просто
120. flTestContent.visibility = View.INVISIBLE
val transition = AutoTransition()
transition.doOnEnd { showWithAlpha() }
TransitionManager.beginDelayedTransition(cardView, transition)
flTestContent.setHeight(height = 500) // your height
TransitionManager
Как это сделать просто
Скрываем содержимое cardView
121. flTestContent.visibility = View.INVISIBLE
val transition = AutoTransition()
transition.doOnEnd { showWithAlpha() }
TransitionManager.beginDelayedTransition(cardView, transition)
flTestContent.setHeight(height = 500) // your height
TransitionManager
Как это сделать просто
Создаем наш Transition
122. flTestContent.visibility = View.INVISIBLE
val transition = AutoTransition()
transition.doOnEnd { showWithAlpha() }
TransitionManager.beginDelayedTransition(cardView, transition)
flTestContent.setHeight(height = 500) // your height
TransitionManager
Как это сделать просто
Что делает showWithAlpha?
private fun showWithAlpha() {
TransitionManager.beginDelayedTransition(cardView)
flTestContent.visibility = View.VISIBLE
}
123. flTestContent.visibility = View.INVISIBLE
val transition = AutoTransition()
transition.doOnEnd { showWithAlpha() }
TransitionManager.beginDelayedTransition(cardView, transition)
flTestContent.setHeight(height = 500) // your height
TransitionManager
Как это сделать просто
Готовимся анимировать изменения
124. flTestContent.visibility = View.INVISIBLE
val transition = AutoTransition()
transition.doOnEnd { showWithAlpha() }
TransitionManager.beginDelayedTransition(cardView, transition)
flTestContent.setHeight(height = 500) // your height
TransitionManager
Это действительно просто
Делаем изменения
133. Transitions между экранами
4 анимации
Screen BScreen A
ScreenA - уходит - Exit Transition
ScreenB - появляеться - Enter Transition
Переход ScreenA - ScreenB
Возвращаемся ScreenB - ScreenA
ScreenA - снова приходит - Reenter Transition
ScreenB - закрывается - Return Transition
134. Transitions между экранами
4 анимации - в профиль
Screen BScreen A
С ScreenA
открыли ScreenB
Exit Transition Enter Transition
Screen B Screen A
Закрыли ScreenB
вернулись на ScreenA
Return Transition Reenter Transition
151. Content transitions - Fragments
Только код! ;)
fun openScreenB() {
val fragment = FragmentB()
fragment.enterTransition = // your transition
fragment.returnTransition = // your transition
supportFragmentManager
.beginTransaction()
.replace(R.id.flContainer, fragment)
.addToBackStack(fragment::class.java.simpleName)
.commit()
}
152. Content transitions - Fragments
Transitions для FragmentA
val fragment = FragmentA()
fragment.exitTransition = // transition
fragment.reenterTransition = // transition
Бонус
Перед каждой транзакцией можно менять transitions!
156. Shared Elements - теория
Почти тоже самое
Screen B
Screen A
2 экрана
2
Анимации
у Fragments
1
Target
4
Анимации
у Activity
157. Shared Elements - теория
4 анимации - Activity
ScreenA - уходит - Shared Elements Exit Transition
ScreenB - появляеться - Shared Elements Enter Transition
Переход ScreenA - ScreenB
Возвращаемся ScreenB - ScreenA
ScreenA - снова приходит - Shared Elements Reenter Transition
ScreenB - закрывается - Shared Elements Return Transition
158. Shared Elements - теория
4 анимации - Activity
ScreenB - появляеться - Shared Elements Enter Transition
Достаточно указать одну:
159. Shared Elements - теория
2 анимации - Fragments
SharedElementsEnterTransition - при открытии фрагмента
Переход ScreenA - ScreenB
SharedElementsReturnTransition - при удалении (pop) фрагмента из стека
160. Shared Elements - теория
2 анимации - Fragments
SharedElementsEnterTransition - FragmentB
Можно указать только 1
176. Дополнение
Почему не работают sharedElements?
TransitionName - либо не указано либо дубликаты
View measure - View еще не знает свои размеры/положение
SharedElementsEnterTransition - null/не тот Transition
Keyboard - сначала закрой потом анимируй
SharedElementsReturnTransition - null - не сработает переход обратно