Привет, Хабр! Меня зовут Павел Беловол, я Android-разработчик на проекте онлайн-кинотеатра KION в МТС Digital. Это новая часть сериала о внедрении фичи Autoplay в KION, в которой я расскажу про свой личный опыт работы с MotionLayout на примере продакшн-задачи в KION. Из этой статьи вы узнаете, где нужно использовать MotionLayout, а где лучше обойтись без него и писать код анимации самостоятельно.
Небольшая вводная:
MotionLayout – это контейнер, который позволяет просто создавать сложные анимации, для чего требуется лишь описать сцену. Более подробно о MotionLayout вы можете почитать тут.
А теперь поговорим о нашей фиче.
Наверняка все задумывались о том, как классно после окончания просмотра интересного фильма глянуть еще один такой же интересный фильм, но не тратить время на поиск, анализ рейтинга и выбор контента.
Мы в KION подумали, что по окончании просмотра фильма было бы здорово предложить зрителям похожий контент с хорошим рейтингом. Именно тот, который наилучшим образом подходит конкретному зрителю.
Обсудив эту задачу, мы пришли к выводу, что нам нужно следующее:
api, которое вернет похожий фильм;
доработка на клиенте, которая будет предлагать пользователю фильм, полученный от api.
Я не буду раскрывать подробности реализации api, это тема для отдельной статьи. Сейчас просто примем во внимание, что аpi у нас функционирует и находит лучший подобный фильм.
На клиенте мы бы хотели получить такой результат – когда пользователь досмотрел фильм до финальных титров, у него должна появиться такая красивая анимация.
О том, что пользователь досмотрел фильм до финальных титров, мы узнаем от api. И есть слушатель в коде, который сообщит, что настала пора играть анимацию.
Немного расскажу про кнопки. При нажатии на «Смотреть титры» пользователя должно вернуть назад к просмотру фильма.
По нажатию на маленькое окошко с плеером нужно выполнить возврат к просмотру титров, то есть это действие равносильно нажатию на кнопку «Смотреть титры».
По нажатию на кнопку «Следующий фильм» или по окончании обратного отсчета включится следующий похожий фильм.
По нажатию на кнопку «X» выполняется выход из экрана с плеером.
С кликами разобрались, теперь декомпозируем анимацию:
Текущее окошко плеера уменьшается и перемещается в левый нижний угол;
В качестве фона устанавливается постер следующего похожего фильма;
Появляется кнопка «Х» в правом верхнем углу;
Снизу выезжает блок с названием, жанром и кнопками «Смотреть титры» и «Следующий фильм»;
Кнопка «Следующий фильм» имеет обратный отсчет по окончании которого фильм включится автоматически, если пользователь не предпринял никаких действий.
Пункты 1-4 мы будем анимировать полностью с помощью MotionLayout, пункт 5 будем анимировать частично с помощью MotionLayout, частично – вручную. Чуть позже объясню, почему так.
От теории к практике:
Начнем с самого простого, создадим xml-файл activity_main.xml с разметкой наших виджетов.
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.motion.widget.MotionLayout 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/motionLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layoutDescription="@xml/scene"
tools:context=".MainActivity">
<ImageView
android:id="@+id/poster"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
app:srcCompat="@drawable/poster"
tools:ignore="ContentDescription" />
<View
android:id="@+id/shadow"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:background="#99000000"
tools:ignore="ContentDescription" />
<ImageView
android:id="@+id/close"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:layout_marginEnd="24dp"
android:padding="16dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/ic_close_24"
tools:ignore="ContentDescription,ImageContrastCheck" />
<TextView
android:id="@+id/filmTitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginEnd="24dp"
android:layout_marginBottom="12dp"
android:gravity="end"
android:text="@string/vod_title"
android:textColor="#EEEEEE"
android:textSize="24sp"
app:layout_constraintBottom_toTopOf="@id/filmInfo"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/player"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="1.0"
app:layout_constraintVertical_chainStyle="packed" />
<TextView
android:id="@+id/filmInfo"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginEnd="24dp"
android:layout_marginBottom="32dp"
android:gravity="end"
android:text="@string/vod_detail"
android:textColor="#B2C6DB"
android:textSize="14sp"
app:layout_constraintBottom_toTopOf="@id/nextFilm"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/player"
app:layout_constraintTop_toBottomOf="@+id/filmTitle"
app:layout_constraintVertical_chainStyle="packed" />
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/watchCredits"
android:layout_width="wrap_content"
android:layout_height="44dp"
android:layout_marginEnd="10dp"
android:alpha="0.8"
android:background="@drawable/bg_credits"
android:gravity="center"
android:paddingLeft="20dp"
android:paddingTop="13dp"
android:paddingRight="20dp"
android:paddingBottom="13dp"
android:text="@string/watch_credits"
android:textAllCaps="false"
android:textColor="@android:color/white"
android:textSize="12sp"
android:visibility="visible"
app:layout_constraintBottom_toBottomOf="@+id/nextFilm"
app:layout_constraintEnd_toStartOf="@id/nextFilm"
app:layout_constraintTop_toTopOf="@id/nextFilm"
app:lineSpacing="2sp"
tools:ignore="TouchTargetSizeCheck" />
<com.example.motionlayoutsample.ProgressButton
android:id="@+id/nextFilm"
android:layout_width="202dp"
android:layout_height="44dp"
android:layout_marginEnd="24dp"
android:layout_marginBottom="80dp"
android:paddingLeft="20dp"
android:paddingTop="13dp"
android:paddingRight="20dp"
android:paddingBottom="13dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/filmInfo"
app:text="@string/next_film"
tools:ignore="TouchTargetSizeCheck" />
<ImageView
android:id="@+id/player"
android:layout_width="257dp"
android:layout_height="140dp"
android:src="@drawable/content"
android:padding="2dp"
android:layout_marginStart="24dp"
android:layout_marginBottom="80dp"
android:background="@drawable/bg_player"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
tools:ignore="ContentDescription" />
</androidx.constraintlayout.motion.widget.MotionLayout>
Краткое пояснение по идентификаторам :
motionLayout – контейнер выполняющий анимацию
poster – постер следующего фильма
shadow – затемнение
close – иконка «X»
filmTitle – текст с названием фильма
filmInfo – текст с описанием фильма
watchCredits – кнопка с названием «Смотреть титры»
nextFilm – кнопка с названием «Следующий фильм»
player – картинка с имитацией воспроизведения плеера (в продакшн-коде вместо imageView, как правило, контейнер, в который добавляется плеер. Например, FrameLayout).
Также создадим файл сцены scene.xml, именно в нем мы будем описывать, как нужно анимировать виджеты.
<?xml version="1.0" encoding="utf-8"?>
<MotionScene xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<ConstraintSet android:id="@+id/start">
</ConstraintSet>
<ConstraintSet android:id="@+id/end">
</ConstraintSet>
<Transition
android:id="@+id/transition"
app:duration="500"
app:constraintSetEnd="@id/end"
app:constraintSetStart="@+id/start" />
</MotionScene>
Краткое пояснение по идентификаторам :
start – начальное состояние анимации;
end – конечное состояние анимации;
transition – переход между состояниями start и end, параметр duration (длительность анимации) установим в 500 миллисекунд.
Если мы попытаемся проиграть сцену в Android Studio, то ничего не произойдет, так как наши constraintSet пустые, и нет никаких условий анимации. Давайте исправим это.
Полный файл scene.xml
<?xml version="1.0" encoding="utf-8"?>
<MotionScene xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<ConstraintSet android:id="@+id/start">
<Constraint
android:id="@+id/player"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Constraint
android:id="@+id/filmTitle"
app:layout_constraintEnd_toEndOf="parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="24dp"
app:layout_constraintVertical_chainStyle="packed"
android:layout_marginBottom="12dp"
app:layout_constraintBottom_toTopOf="@id/filmInfo"
app:layout_constraintVertical_bias="1.0"
app:layout_constraintTop_toBottomOf="parent" />
<Constraint
android:layout_marginEnd="24dp"
android:layout_height="wrap_content"
android:layout_marginBottom="32dp"
app:layout_constraintBottom_toTopOf="@id/nextFilm"
app:layout_constraintEnd_toEndOf="parent"
android:layout_width="wrap_content"
app:layout_constraintTop_toBottomOf="@+id/filmTitle"
app:layout_constraintVertical_chainStyle="packed"
android:id="@+id/filmInfo" />
<Constraint
android:id="@+id/shadow"
app:layout_constraintEnd_toEndOf="parent"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
android:visibility="invisible" />
<Constraint
android:id="@+id/close"
app:layout_constraintEnd_toEndOf="parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="28dp"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginTop="28dp"
android:visibility="invisible" />
</ConstraintSet>
<ConstraintSet android:id="@+id/end">
<Constraint
android:id="@+id/poster"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<Constraint
android:id="@+id/shadow"
app:layout_constraintEnd_toEndOf="parent"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
android:visibility="visible" />
<Constraint
android:id="@+id/close"
app:layout_constraintEnd_toEndOf="parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="28dp"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginTop="28dp"
android:visibility="visible" />
<Constraint
android:id="@+id/filmTitle"
app:layout_constraintEnd_toEndOf="parent"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="24dp"
android:layout_marginStart="24dp"
app:layout_constraintVertical_chainStyle="packed"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toEndOf="@+id/player"
android:layout_marginBottom="12dp"
app:layout_constraintBottom_toTopOf="@id/filmInfo"
app:layout_constraintVertical_bias="1.0" />
<Constraint
android:id="@+id/filmInfo"
app:layout_constraintEnd_toEndOf="parent"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="24dp"
android:layout_marginStart="24dp"
app:layout_constraintVertical_chainStyle="packed"
app:layout_constraintStart_toEndOf="@+id/player"
android:layout_marginBottom="32dp"
app:layout_constraintBottom_toTopOf="@id/nextFilm"
app:layout_constraintTop_toBottomOf="@+id/filmTitle" />
<Constraint
android:id="@+id/watchCredits"
android:layout_width="wrap_content"
android:layout_height="44dp"
android:visibility="visible"
android:layout_marginEnd="10dp"
app:layout_constraintTop_toTopOf="@id/nextFilm"
app:layout_constraintEnd_toStartOf="@id/nextFilm"
app:layout_constraintBottom_toBottomOf="@+id/nextFilm"
android:alpha="0.8" />
<Constraint
android:id="@+id/nextFilm"
app:layout_constraintEnd_toEndOf="parent"
android:layout_width="202dp"
android:layout_height="44dp"
android:layout_marginEnd="24dp"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginBottom="80dp"
app:layout_constraintTop_toBottomOf="@id/filmInfo" />
<Constraint
android:id="@+id/player"
android:layout_width="257dp"
android:layout_height="140dp"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginBottom="80dp"
android:layout_marginStart="24dp"
app:layout_constraintStart_toStartOf="parent" />
</ConstraintSet>
<Transition
android:id="@+id/transition"
app:duration="500"
app:constraintSetEnd="@id/end"
app:constraintSetStart="@+id/start" />
</MotionScene>
В ConstraintSet с идентификатором start мы описали свойства виджетов в начальном состоянии анимации, а с идентификатором end, – свойства виджетов в конечном состоянии анимации.
Задача практически выполнена, но мы забыли про одну вещь — кнопка «Следующая серия» должна анимироваться, наполняясь индикатором прогресса. Если до этого мы анимировали элементы с помощью MotionLayout, то в этот раз мы должны поступить иначе, так как кнопка с прогрессом — кастомный элемент, и у нас не получится стандартными атрибутами сцены выполнить анимацию заполнения прогресса.
В этом случае нам придется написать код анимации самостоятельно.
Код кнопки
import android.animation.ValueAnimator
import android.content.Context
import android.os.Parcel
import android.os.Parcelable
import android.util.AttributeSet
import android.view.Gravity
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.ProgressBar
import android.widget.TextView
import androidx.cardview.widget.CardView
import androidx.core.animation.addListener
import androidx.core.content.ContextCompat
import androidx.core.content.res.use
class ProgressButton @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : CardView(context, attrs, defStyleAttr) {
var onCountDown: (() -> Unit)? = null
private var clickListener: OnClickListener? = null
private val textView = TextView(context).apply {
gravity = Gravity.CENTER
setTextColor(
ContextCompat.getColor(context, R.color.progress_button_text_color)
)
isAllCaps = false
layoutParams = LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT).apply {
setPadding(0, 0, 12.toPx, 0)
}
}
private val imageView = ImageView(context).apply {
setImageResource(R.drawable.ic_player_next)
layoutParams = LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)
}
private val progressBar =
ProgressBar(context, null, android.R.attr.progressBarStyleHorizontal).apply {
max = 100
progress = if (isInEditMode) 70 else 0
isIndeterminate = false
layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
progressDrawable = ContextCompat.getDrawable(
context, R.drawable.next_episode_progress_bar_states
)
}
private val animator = ValueAnimator.ofInt(0, 100).apply {
addUpdateListener {
progressBar.progress = it.animatedValue as Int
}
addListener(
onEnd = {
if (animatedValue == 100) {
onCountDown?.invoke()
}
}
)
duration = 7000
}
fun startProgress() {
if (!animator.isRunning) {
animator.start()
}
}
fun cancelProgress() {
animator.cancel()
}
init {
addView(progressBar)
addView(
LinearLayout(context).apply {
orientation = LinearLayout.HORIZONTAL
layoutParams =
LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT).apply {
gravity = Gravity.CENTER
}
gravity = Gravity.CENTER
addView(textView)
addView(imageView)
}
)
radius = 7.toPx.toFloat()
super.setOnClickListener {
animator.cancel()
clickListener?.onClick(it)
}
setBackgroundResource(R.drawable.next_episode_button_stroke)
context.theme.obtainStyledAttributes(
attrs,
R.styleable.PlayerNextEpisodeButton,
0, 0
).use {
textView.text = it.getString(R.styleable.PlayerNextEpisodeButton_text)
}
isSaveEnabled = true
}
override fun setOnClickListener(l: OnClickListener?) {
clickListener = l
}
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
cancelProgress()
onCountDown = null
}
override fun onSaveInstanceState(): Parcelable? {
val superState = super.onSaveInstanceState()
superState?.let {
val state = SavedState(superState)
state.progressBarState = progressBar.onSaveInstanceState()
state.isProgressRunning = animator.isRunning
state.currentPlayTime = animator.currentPlayTime
return state
} ?: run {
return superState
}
}
override fun onRestoreInstanceState(state: Parcelable?) {
when (state) {
is SavedState -> {
super.onRestoreInstanceState(state.superState)
progressBar.onRestoreInstanceState(state.progressBarState)
if (state.isProgressRunning) {
animator.currentPlayTime = state.currentPlayTime
startProgress()
}
}
else -> {
super.onRestoreInstanceState(state)
}
}
}
private class SavedState : BaseSavedState {
var progressBarState: Parcelable? = null
var isProgressRunning = false
var currentPlayTime = 0L
constructor(parcel: Parcel) : super(parcel) {
progressBarState = parcel.readParcelable(ProgressBar::class.java.classLoader)
isProgressRunning = parcel.readInt() == 1
currentPlayTime = parcel.readLong()
}
constructor (parcelable: Parcelable?) : super(parcelable)
override fun writeToParcel(out: Parcel?, flags: Int) {
super.writeToParcel(out, flags)
out?.writeParcelable(progressBarState, flags)
out?.writeInt(if (isProgressRunning) 1 else 0)
out?.writeLong(currentPlayTime)
}
companion object {
@Suppress("unused")
@JvmField
val CREATOR = object : Parcelable.Creator<SavedState> {
override fun createFromParcel(source: Parcel): SavedState {
return SavedState(source)
}
override fun newArray(size: Int): Array<SavedState?> {
return arrayOfNulls(size)
}
}
}
}
}
Мы написали код кнопки с прогрессом, в качестве анимирования использовали стандартный ValueAnimator, учитывая сохранение состояния после смены конфигурации.
Ключевые методы:
startProgress()
– запускает анимацию заполнения прогрессаcancelProgress()
– останавливает анимацию заполнения прогресса
Теперь остается самая важная задача — запустить общую анимацию.
Тут все просто.
motionLayout.transitionToEnd()
– запускает анимацию начиная с состояния start к end
motionLayout.transitionToStart()
– запускает анимацию начиная с состояния end к start
Теперь добавим код запуска к слушателям кнопок.
close.setOnClickListener {
nextFilm.cancelProgress()
}
player.setOnClickListener {
motionLayout.transitionToStart()
nextFilm.cancelProgress()
}
watchCredits.setOnClickListener {
motionLayout.transitionToStart()
nextFilm.cancelProgress()
}
nextFilm.setOnClickListener {
motionLayout.transitionToStart()
nextFilm.cancelProgress()
}
nextFilm.onCountDown = {
nextFilm.performClick()
}
Так как кнопка progressButton – это кастомный элемент, который анимируется частично самостоятельно, нужно отдельно вызывать startProgress(), cancelProgress().
И добавим код запуска анимации по достижению титров.
nextFilm.startProgress()
motionLayout.transitionToEnd()
На этом, казалось бы, задача решена, анимация работает. Но после поворота экрана мы обнаруживаем неприятный эффект: анимация всегда возвращается в начальное состояние.
Исправим это:
class MainViewModel : ViewModel() {
var motionLayoutState : Bundle? = null
}
class MainActivity : AppCompatActivity() {
private val viewModel by viewModels<MainViewModel>()
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel.motionLayoutState?.let {
binding.motionLayout.transitionState = it
}
}
override fun onSaveInstanceState(outState: Bundle) {
viewModel.motionLayoutState = binding.motionLayout.transitionState
super.onSaveInstanceState(outState)
}
}
Мы сохранили состояние MotionLayout с помощью viewModel, теперь сцена до и после поворота отображается корректно. Напомню, что progressButton уже содержит в себе код сохранения и восстановления состояния.
Финальный результат на реальном устройстве:
Каков итог?
MotionLayout – мощный и удобный инструмент для написания анимаций на Android, но не всегда и не все получится анимировать c его помощью. Иногда придется писать код анимации самостоятельно, как это сделал я для кнопки с заполнением прогресса.
Спасибо за уделенное время! Если у вас есть вопросы, замечания или истории из личного опыта работы с MotionLayout – добро пожаловать в комментарии.
Мои коллеги с различных платформ написали свои статьи по этой фиче, советую также ознакомиться с их трудами:
Про саму фичу Autoplay в онлайн-кинотеатре
Про нюансы реализации фичи на tvOS
Про разработку фичи на Angular под SmartTV
А еще мы рассказывали на Хабре про другую фичу KION, реализованную с помощью искусственного интеллекта – пропуск титров.
Про проблемы и их решения с помощью Computed Properties в Angular
Комментарии (13)
VitallyCom
12.01.2023 13:43+1Автор молодец, но я не знаю ни одного человека, который этим бы автопредложением воспользовался. Бесит, когда после полнометражного фильма тебе нужно быстрее искать пульт, чтобы отменить просмотр следующего фильма, который ты не собирался смотреть
belovol Автор
12.01.2023 21:05Спасибо вам за комментарий и похвалу :)
Понимаю вас, но есть люди кто по достоинству оценил фичу и нашел много крутого контента :)
VitallyCom
12.01.2023 21:09+2Да, я еще подумал, что это может быть удобно родителям, которые включают ребенку мульт и они идут бесконечно.
Тогда есть новая идея, сделать переключатель в настройках, включающий и отключающий «бесконечный» плей.
belovol Автор
13.01.2023 03:51Понял вас, заберу этот момент и обсужу внутри команды, спасибо за идею ????
kavaynya
13.01.2023 10:16+1Сколько не смотрю туториалы по MotionLayout, все время восторгаюсь, как же все классно выглядит и легко настраивается.
Но как только пытаешься сам, что-то сделать, то выясняется что в AndroidStudio (Canary) MotionEditor не работает, нельзя просмотреть анимацию. А если и смог сделать, то она жутко тормозит. Пока одни негативные впечатления.belovol Автор
13.01.2023 12:23Привет) спасибо за комментарий)
Есть такое, не все идеально работает, постоянно приходится закрывать xml файл и открывать чтобы изменения увидеть в превью, бывает приходится ребилдить)
Но пока что эти все танцы с бубном по времени быстрее, чем проверять на устройстве)
kavaynya
13.01.2023 18:14+1Хорошо, когда так. Но у меня простая анимация по скрытию одного элемента, тормозила ужасно на устройстве
belovol Автор
13.01.2023 19:38А что за устройство если не секрет?) у меня просто на слабеньком redmi 6A жуткие тормоза, а на redmi note 8t все плавно и красиво)
kavaynya
14.01.2023 08:22+1Не секрет. Realme 5 pro. Не новый, но и не слабый. В проекте где пытался его применить, уже используется MotionLayout на одном экране, тормозов при этом у себя я не наблюдаю, но работает немного странно
belovol Автор
14.01.2023 11:54Понял.
Я бы на вашем месте обратил внимание на то как описана сцена. Может быть какие-то вещи избыточны или некорректны. А также обратил бы внимание на виджеты которые анимируете, может быть вложенность большая или элемент содержит логику из-за которой его отрисовка страдает. И еще бы посмотрел в сторону такого параметра
motion:layoutDuringTransition
Может он у вас задан, это может сильно влиять на перфоманс.
belovol Автор
15.01.2023 00:15Расскажите чтобы вам интересно было узнать еще про motion layout. Какие моменты стоило бы подробнее рассмотреть. Учту для будущих статей. Свои предложения можете оставлять под этим комментарием, или написать мне в личку в телеграмм. Контакты на этой странице в конце поста ????
belovol Автор
Расскажите чтобы вам интересно было узнать еще про motion layout. Какие моменты стоило бы подробнее рассмотреть. Учту для будущих статей. Свои предложения можете оставлять под этим комментарием, или написать мне в личку в телеграмм. Контакты на этой странице в конце поста ????