Use MotionLayout to animate bottom sheet opening

This commit is contained in:
Christophe Henry 2023-09-24 11:26:52 +02:00
parent b924a0c655
commit 056e3a4d66
12 changed files with 271 additions and 136 deletions

View File

@ -181,7 +181,7 @@ dependencies {
implementation("androidx.recyclerview:recyclerview:1.2.1")
implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0")
implementation("com.google.android.material:material:1.8.0")
implementation("com.android.support.constraint:constraint-layout:2.0.4")
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
implementation("com.google.android.exoplayer:exoplayer-core:2.18.1")
implementation("com.google.android.exoplayer:exoplayer-ui:2.18.1")

View File

@ -3,6 +3,7 @@ package audio.funkwhale.ffa.activities
import android.animation.ObjectAnimator
import android.animation.ValueAnimator
import android.annotation.SuppressLint
import android.app.Fragment
import android.content.Intent
import android.os.Build
import android.os.Bundle
@ -49,7 +50,6 @@ import audio.funkwhale.ffa.utils.authorize
import audio.funkwhale.ffa.utils.log
import audio.funkwhale.ffa.utils.logError
import audio.funkwhale.ffa.utils.mustNormalizeUrl
import audio.funkwhale.ffa.utils.onApi
import audio.funkwhale.ffa.utils.toast
import audio.funkwhale.ffa.utils.wait
import com.github.kittinunf.fuel.Fuel
@ -60,10 +60,6 @@ import com.google.gson.Gson
import com.preference.PowerPreference
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.channels.consume
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.consumeAsFlow
import kotlinx.coroutines.launch
import org.koin.java.KoinJavaComponent.inject
@ -87,32 +83,28 @@ class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
AppContext.init(this)
binding = ActivityMainBinding.inflate(layoutInflater)
binding.nowPlayingBottomSheet.addBottomSheetCallback(
object : BottomSheetBehavior.BottomSheetCallback() {
override fun onStateChanged(bottomSheet: View, newState: Int) {
// Set the proper margin on the other child
val anim = if (newState == BottomSheetBehavior.STATE_HIDDEN) {
ValueAnimator.ofInt(binding.nowPlayingBottomSheet.peekHeight, 0)
} else {
ValueAnimator.ofInt(0, binding.nowPlayingBottomSheet.peekHeight)
(supportFragmentManager.findFragmentById(R.id.now_playing) as NowPlayingFragment).apply {
onDetailsMenuItemClicked { binding.nowPlayingBottomSheet.close() }
binding.nowPlayingBottomSheet.addBottomSheetCallback(
object : BottomSheetBehavior.BottomSheetCallback() {
override fun onStateChanged(bottomSheet: View, newState: Int) {
// Add padding to the main fragment so that player control don't overlap
// artists and albums
addSiblingFragmentPadding()
}
anim.apply {
duration = 200
addUpdateListener {
val params =
binding.navHostFragmentWrapper.layoutParams as CoordinatorLayout.LayoutParams
params.setMargins(0, 0, 0, it.animatedValue as Int)
}
start()
override fun onSlide(bottomSheet: View, slideOffset: Float) {
// Animate the cover and other elements of the bottom sheet
onBottomSheetDrag(slideOffset)
}
}
)
}
override fun onSlide(bottomSheet: View, slideOffset: Float) {}
}
)
addSiblingFragmentPadding()
setContentView(binding.root)
@ -132,8 +124,11 @@ class MainActivity : AppCompatActivity() {
lifecycleScope.launch {
RequestBus.send(Request.GetQueue).wait<Response.Queue>()?.let {
if(it.queue.isNotEmpty()) binding.nowPlayingBottomSheet.show()
else binding.nowPlayingBottomSheet.hide()
if (it.queue.isNotEmpty() && binding.nowPlayingBottomSheet.isHidden) {
binding.nowPlayingBottomSheet.show()
} else if (it.queue.isEmpty()) {
binding.nowPlayingBottomSheet.hide()
}
}
// Watch the event bus only after to prevent concurrency in displaying the bottom sheet
watchEventBus()
@ -265,6 +260,20 @@ class MainActivity : AppCompatActivity() {
return true
}
private fun addSiblingFragmentPadding() {
val anim = if (binding.nowPlayingBottomSheet.isHidden) {
ValueAnimator.ofInt(binding.nowPlayingBottomSheet.peekHeight, 0)
} else {
ValueAnimator.ofInt(0, binding.nowPlayingBottomSheet.peekHeight)
}
anim.duration = 200
anim.addUpdateListener {
binding.navHostFragmentWrapper.setPadding(0, 0, 0, it.animatedValue as Int)
}
anim.start()
}
private fun launchDialog(fragment: DialogFragment) =
fragment.show(supportFragmentManager.beginTransaction(), "")
@ -297,7 +306,7 @@ class MainActivity : AppCompatActivity() {
CommandBus.get().flowWithLifecycle(
this@MainActivity.lifecycle, Lifecycle.State.RESUMED
).collect { command ->
when(command) {
when (command) {
is Command.StartService -> startService(command.command)
is Command.RefreshTrack -> refreshTrack(command.track)
is Command.AddToPlaylist -> AddToPlaylistDialog.show(
@ -306,6 +315,7 @@ class MainActivity : AppCompatActivity() {
lifecycleScope,
command.tracks
)
else -> {}
}
}

View File

@ -18,7 +18,6 @@ import audio.funkwhale.ffa.model.Track
import audio.funkwhale.ffa.repositories.FavoritedRepository
import audio.funkwhale.ffa.repositories.FavoritesRepository
import audio.funkwhale.ffa.repositories.Repository
import audio.funkwhale.ffa.utils.BottomSheetIneractable
import audio.funkwhale.ffa.utils.Command
import audio.funkwhale.ffa.utils.CommandBus
import audio.funkwhale.ffa.utils.CoverArt
@ -30,24 +29,17 @@ import audio.funkwhale.ffa.utils.maybeNormalizeUrl
import audio.funkwhale.ffa.utils.toIntOrElse
import audio.funkwhale.ffa.utils.untilNetwork
import audio.funkwhale.ffa.viewmodel.NowPlayingViewModel
import jp.wasabeef.picasso.transformations.RoundedCornersTransformation
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.lang.Float.max
class NowPlayingFragment : Fragment(R.layout.fragment_now_playing) {
class NowPlayingFragment: Fragment(R.layout.fragment_now_playing) {
private val binding by lazy { FragmentNowPlayingBinding.bind(requireView()) }
private val viewModel by viewModels<NowPlayingViewModel>()
private val favoriteRepository by lazy { FavoritesRepository(requireContext()) }
private val favoritedRepository by lazy { FavoritedRepository(requireContext()) }
private val bottomSheet: BottomSheetIneractable? by lazy {
var view = this.view?.parent
while (view != null) {
if(view is BottomSheetIneractable) return@lazy view
view = view.parent
}
null
}
private var onDetailsMenuItemClickedCb: () -> Unit = {}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
binding.lifecycleOwner = viewLifecycleOwner
@ -83,7 +75,10 @@ class NowPlayingFragment : Fragment(R.layout.fragment_now_playing) {
nowPlayingDetailsAddToPlaylist.setOnClickListener { onAddToPlaylist() }
}
binding.nowPlayingDetailsInfo.setOnClickListener { openInfoMenu() }
with(binding.header) {
lifecycleOwner = viewLifecycleOwner
isBuffering = viewModel.isBuffering
isPlaying = viewModel.isPlaying
progress = viewModel.progress
@ -100,8 +95,6 @@ class NowPlayingFragment : Fragment(R.layout.fragment_now_playing) {
}
}
binding.nowPlayingDetailsInfo.setOnClickListener { openInfoMenu() }
lifecycleScope.launch(Dispatchers.Main) {
CommandBus.get().collect { onCommand(it) }
}
@ -115,6 +108,14 @@ class NowPlayingFragment : Fragment(R.layout.fragment_now_playing) {
}
}
fun onBottomSheetDrag(value: Float) {
binding.nowPlayingRoot.progress = max(value, 0f)
}
fun onDetailsMenuItemClicked(cb: () -> Unit) {
onDetailsMenuItemClickedCb = cb
}
private fun toggleRepeatMode() {
val cachedRepeatMode = FFACache.getLine(requireContext(), "repeat").toIntOrElse(0)
@ -170,19 +171,10 @@ class NowPlayingFragment : Fragment(R.layout.fragment_now_playing) {
private fun onTrackChange(track: Track?) {
if (track == null) {
binding.header.nowPlayingCover.setImageResource(R.drawable.cover)
binding.nowPlayingDetailCover.setImageResource(R.drawable.cover)
return
}
CoverArt.withContext(requireContext(), maybeNormalizeUrl(track.album?.cover()))
.fit()
.centerCrop()
.into(binding.nowPlayingDetailCover)
CoverArt.withContext(requireContext(), maybeNormalizeUrl(track.album?.cover()))
.fit()
.centerCrop()
.transform(RoundedCornersTransformation(16, 0))
.into(binding.header.nowPlayingCover)
}
@ -199,7 +191,7 @@ class NowPlayingFragment : Fragment(R.layout.fragment_now_playing) {
inflate(R.menu.track_info)
setOnMenuItemClickListener {
bottomSheet?.close()
onDetailsMenuItemClickedCb()
when (it.itemId) {
R.id.track_info_artist -> findNavController().navigate(

View File

@ -2,20 +2,21 @@ package audio.funkwhale.ffa.views
import android.content.Context
import android.util.AttributeSet
import android.util.TypedValue
import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.content.res.use
import audio.funkwhale.ffa.R
import audio.funkwhale.ffa.utils.BottomSheetIneractable
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetBehavior.BottomSheetCallback
import com.google.android.material.card.MaterialCardView
class NowPlayingBottomSheet @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : MaterialCardView(context, attrs), BottomSheetIneractable {
) : FrameLayout(context, attrs, defStyleAttr), BottomSheetIneractable {
private val behavior = BottomSheetBehavior<NowPlayingBottomSheet>()
private val targetHeaderId: Int
@ -27,6 +28,14 @@ class NowPlayingBottomSheet @JvmOverloads constructor(
).use {
it.getResourceId(R.styleable.NowPlaying_target_header, NO_ID)
}
// Put default peek height to actionBarSize so it is not 0
val tv = TypedValue()
if (context.theme.resolveAttribute(android.R.attr.actionBarSize, tv, true)) {
behavior.peekHeight = TypedValue.complexToDimensionPixelSize(
tv.data, resources.displayMetrics
)
}
}
override fun setLayoutParams(params: ViewGroup.LayoutParams?) {
@ -37,7 +46,7 @@ class NowPlayingBottomSheet @JvmOverloads constructor(
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
findViewById<View>(targetHeaderId)?.apply {
behavior.setPeekHeight(this.measuredHeight, false)
behavior.setPeekHeight(this.height, false)
this.setOnClickListener { this@NowPlayingBottomSheet.toggle() }
} ?: hide()
}

View File

@ -2,8 +2,25 @@ package audio.funkwhale.ffa.views
import android.content.Context
import android.util.AttributeSet
import android.view.View
import androidx.appcompat.widget.AppCompatImageButton
import androidx.appcompat.widget.AppCompatImageView
open class SquareView : View {
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet?, style: Int) : super(context, attrs, style)
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
val dimension = if(measuredWidth == 0 && measuredHeight > 0) measuredHeight else measuredWidth
setMeasuredDimension(dimension, dimension)
}
}
open class SquareImageView : AppCompatImageView {
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)

View File

@ -9,9 +9,8 @@
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:background="@color/surface"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_weight="10">
app:layout_constraintBottom_toTopOf="@id/appbar_wrapper">
<LinearLayout
android:id="@+id/nav_host_fragment_wrapper"
android:layout_width="match_parent"
@ -38,21 +37,13 @@
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:layout="@layout/partial_queue" />
</LinearLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="@id/appbar_wrapper"
app:layout_constraintTop_toTopOf="parent">
<audio.funkwhale.ffa.views.NowPlayingBottomSheet
android:id="@+id/now_playing_bottom_sheet"
style="?attr/bottomSheetStyle"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:cardCornerRadius="3dp"
app:cardElevation="12dp"
app:target_header="@id/header">
android:background="@color/elevatedSurface"
app:target_header="@id/constraint_layout_placeholder">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/now_playing"
android:name="audio.funkwhale.ffa.fragments.NowPlayingFragment"

View File

@ -3,56 +3,63 @@
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">
<androidx.constraintlayout.widget.ConstraintLayout
<data>
<import type="androidx.lifecycle.LiveData" />
<import type="android.view.View" />
<import type="android.graphics.drawable.Drawable" />
<variable name="isBuffering" type="LiveData&lt;Boolean>" />
<variable name="isPlaying" type="LiveData&lt;Boolean>" />
<variable name="progress" type="LiveData&lt;Integer>" />
<variable name="currentTrackTitle" type="LiveData&lt;String>" />
<variable name="currentTrackArtist" type="LiveData&lt;String>" />
</data>
<androidx.constraintlayout.motion.widget.MotionLayout
android:id="@+id/now_playing_root"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/elevatedSurface">
app:layoutDescription="@xml/fragment_now_playing_scene">
<include
android:id="@+id/header"
layout="@layout/partial_now_playing_header" />
<include android:id="@+id/header" layout="@layout/partial_now_playing_header" />
<audio.funkwhale.ffa.views.SquareImageView
android:id="@+id/now_playing_detail_cover"
<audio.funkwhale.ffa.views.SquareView
android:id="@+id/detail_image_placeholder"
android:layout_width="0dp"
android:layout_height="0dp"
android:scaleType="fitCenter"
app:layout_constraintTop_toBottomOf="@id/header"
app:layout_constraintTop_toBottomOf="@id/now_playing_progress"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintDimensionRatio="H,1:1"
app:layout_constraintStart_toStartOf="parent"
app:srcCompat="@drawable/cover"
tools:src="@tools:sample/avatars" />
/>
<ImageButton
android:id="@+id/now_playing_details_info"
style="@style/IconButton"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_gravity="top|end"
android:layout_margin="8dp"
android:background="@drawable/circle"
android:contentDescription="@string/alt_track_info"
android:src="@drawable/more"
app:layout_constraintEnd_toEndOf="@id/now_playing_detail_cover"
app:layout_constraintTop_toTopOf="@id/now_playing_detail_cover"
app:layout_constraintEnd_toEndOf="@id/detail_image_placeholder"
app:layout_constraintTop_toTopOf="@id/detail_image_placeholder"
app:tint="@color/controlForeground"
/>
<include
android:id="@+id/controls"
layout="@layout/partial_now_playing_controls"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
app:layout_constraintTop_toBottomOf="@id/header"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
app:layout_constraintTop_toTopOf="@id/detail_image_placeholder"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/now_playing_detail_cover"
app:layout_constraintStart_toEndOf="@id/detail_image_placeholder"
android:alpha="0"
android:background="@color/elevatedSurface"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.motion.widget.MotionLayout>
</layout>

View File

@ -11,11 +11,11 @@
android:layout_height="0dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@id/appbar_wrapper">
<FrameLayout
android:id="@+id/nav_host_fragment_wrapper"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
android:id="@+id/nav_host_fragment_wrapper">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
@ -30,18 +30,17 @@
<audio.funkwhale.ffa.views.NowPlayingBottomSheet
android:id="@+id/now_playing_bottom_sheet"
style="?attr/bottomSheetStyle"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:cardCornerRadius="3dp"
app:cardElevation="12dp"
app:target_header="@id/header">
android:background="@color/elevatedSurface"
app:target_header="@id/constraint_layout_placeholder">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/now_playing"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:name="audio.funkwhale.ffa.fragments.NowPlayingFragment"
tools:layout="@layout/fragment_now_playing"/>
tools:layout="@layout/fragment_now_playing"
/>
</audio.funkwhale.ffa.views.NowPlayingBottomSheet>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -1,25 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<layout
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">
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<import type="androidx.lifecycle.LiveData" />
<import type="android.view.View" />
<import type="android.graphics.drawable.Drawable" />
<variable name="isBuffering" type="LiveData&lt;Boolean>" />
<variable name="isPlaying" type="LiveData&lt;Boolean>" />
<variable name="progress" type="LiveData&lt;Integer>" />
<variable name="currentTrackTitle" type="LiveData&lt;String>" />
<variable name="currentTrackArtist" type="LiveData&lt;String>" />
</data>
<androidx.constraintlayout.motion.widget.MotionLayout
android:id="@+id/now_playing_root"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/elevatedSurface">
app:layoutDescription="@xml/fragment_now_playing_scene">
<include
android:id="@+id/header"
layout="@layout/partial_now_playing_header" />
layout="@layout/partial_now_playing_header"
/>
<audio.funkwhale.ffa.views.SquareImageView
android:id="@+id/now_playing_detail_cover"
<audio.funkwhale.ffa.views.SquareView
android:id="@+id/detail_image_placeholder"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintTop_toBottomOf="@+id/header"
app:srcCompat="@drawable/cover"
tools:src="@tools:sample/avatars"
app:layout_constraintTop_toBottomOf="@id/now_playing_progress"
/>
<ImageButton
@ -27,23 +37,23 @@
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_margin="8dp"
app:layout_constraintEnd_toEndOf="@id/now_playing_detail_cover"
app:layout_constraintTop_toTopOf="@id/now_playing_detail_cover"
app:layout_constraintEnd_toEndOf="@id/detail_image_placeholder"
app:layout_constraintTop_toTopOf="@id/detail_image_placeholder"
style="@style/IconButton"
android:layout_gravity="top|end"
android:background="@drawable/circle"
android:contentDescription="@string/alt_track_info"
android:src="@drawable/more"
app:tint="@color/controlForeground" />
app:tint="@color/controlForeground"
/>
<include
android:id="@+id/controls"
layout="@layout/partial_now_playing_controls"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_height="0dp"
android:layout_margin="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/now_playing_detail_cover"
app:layout_constraintTop_toBottomOf="@id/detail_image_placeholder"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.motion.widget.MotionLayout>
</layout>

View File

@ -16,9 +16,11 @@
<variable name="isPlaying" type="LiveData&lt;Boolean>" />
<variable name="progress" type="LiveData&lt;Integer>" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
android:layout_height="match_parent"
android:alpha="0">
<TextView
android:id="@+id/current_playing_details_title"
android:layout_width="0dp"
@ -84,8 +86,8 @@
android:id="@+id/now_playing_details_progress"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:max="100"
android:layout_margin="8dp"
android:max="100"
android:progress="@{progress, default=40}"
android:progressBackgroundTint="#cacaca"
android:progressTint="@color/controlForeground"

View File

@ -14,29 +14,34 @@
<variable name="currentTrackArtist" type="LiveData&lt;String>" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize">
<merge>
<!-- Placeholder for setting constraints and interacting -->
<View
android:id="@+id/constraint_layout_placeholder"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_constraintTop_toTopOf="parent"
/>
<com.google.android.material.progressindicator.LinearProgressIndicator
android:id="@+id/now_playing_progress"
android:layout_width="0dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/constraint_layout_placeholder"
android:progress="@{progress, default=40}"
android:progressTint="@color/colorPrimaryDark" />
android:progressTint="@color/colorPrimaryDark"
/>
<audio.funkwhale.ffa.views.SquareImageView
android:id="@+id/now_playing_cover"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/now_playing_progress"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="@id/constraint_layout_placeholder"
app:layout_constraintTop_toBottomOf="@id/now_playing_progress"
app:layout_constraintBottom_toBottomOf="@id/constraint_layout_placeholder"
app:srcCompat="@drawable/cover"
tools:src="@tools:sample/avatars" />
tools:src="@tools:sample/avatars"
/>
<ProgressBar
android:id="@+id/now_playing_buffering"
@ -46,9 +51,8 @@
app:layout_constraintTop_toTopOf="@id/now_playing_cover"
app:layout_constraintBottom_toBottomOf="@id/now_playing_cover"
app:layout_constraintEnd_toEndOf="@id/now_playing_cover"
android:indeterminate="true"
android:indeterminateTint="@color/controlForeground"
android:visibility="@{isBuffering ? View.VISIBLE : View.INVISIBLE, default=invisible}" />
android:visibility="@{isBuffering ? View.VISIBLE : View.INVISIBLE, default=invisible}"
/>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/header_controls"
@ -56,9 +60,10 @@
android:layout_height="0dp"
app:layout_constraintHorizontal_weight="10"
app:layout_constraintStart_toEndOf="@id/now_playing_cover"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintEnd_toEndOf="@id/constraint_layout_placeholder"
app:layout_constraintTop_toBottomOf="@id/now_playing_progress"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintBottom_toBottomOf="@id/constraint_layout_placeholder"
android:background="@color/elevatedSurface"
android:padding="4dp">
<TextView
@ -71,7 +76,8 @@
android:text="@{currentTrackTitle}"
android:ellipsize="end"
android:lines="1"
tools:text="Supermassive Black Hole" />
tools:text="Supermassive Black Hole"
/>
<TextView
android:layout_width="0dp"
@ -82,7 +88,8 @@
android:ellipsize="end"
android:lines="1"
android:text="@{currentTrackArtist}"
tools:text="Muse" />
tools:text="Muse"
/>
<com.google.android.material.button.MaterialButton
android:id="@+id/now_playing_toggle"
@ -91,7 +98,8 @@
android:layout_height="match_parent"
app:layout_constraintEnd_toStartOf="@id/now_playing_next"
android:layout_marginEnd="16dp"
app:icon="@{isPlaying ? @drawable/pause : @drawable/play, default=@drawable/play}" />
app:icon="@{isPlaying ? @drawable/pause : @drawable/play, default=@drawable/play}"
/>
<ImageButton
android:id="@+id/now_playing_next"
@ -100,7 +108,9 @@
app:layout_constraintEnd_toEndOf="parent"
style="@style/IconButton"
android:contentDescription="@string/control_next"
android:src="@drawable/next" />
android:src="@drawable/next"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</merge>
</layout>

View File

@ -0,0 +1,88 @@
<?xml version="1.0" encoding="utf-8"?>
<MotionScene
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:motion="http://schemas.android.com/apk/res-auto">
<ConstraintSet android:id="@+id/start">
<Constraint android:id="@id/now_playing_details_info">
<PropertySet android:alpha="0" android:visibility="invisible" />
</Constraint>
<Constraint android:id="@id/header_controls">
<PropertySet android:alpha="1" android:visibility="visible" />
</Constraint>
<Constraint android:id="@id/constraint_layout_placeholder">
<PropertySet android:visibility="visible" />
</Constraint>
</ConstraintSet>
<ConstraintSet android:id="@+id/end">
<Constraint
android:id="@id/now_playing_cover"
motion:layout_constraintEnd_toEndOf="@id/detail_image_placeholder"
motion:layout_constraintStart_toStartOf="@id/detail_image_placeholder"
motion:layout_constraintTop_toBottomOf="@id/detail_image_placeholder"
motion:layout_constraintTop_toTopOf="@id/detail_image_placeholder"
motion:transitionEasing="accelerate"
/>
<Constraint android:id="@id/now_playing_progress">
<PropertySet android:alpha="0" android:visibility="gone" />
</Constraint>
<Constraint android:id="@id/header_controls">
<PropertySet android:alpha="0" android:visibility="invisible" />
</Constraint>
<Constraint android:id="@id/constraint_layout_placeholder">
<PropertySet android:visibility="invisible" />
</Constraint>
<Constraint android:id="@id/now_playing_details_info">
<PropertySet android:alpha="1" android:visibility="visible"/>
</Constraint>
<Constraint android:id="@id/controls">
<PropertySet android:alpha="1" />
</Constraint>
</ConstraintSet>
<Transition
motion:constraintSetEnd="@id/end"
motion:constraintSetStart="@+id/start"
>
<KeyFrameSet>
<KeyPosition
motion:percentX="1"
motion:framePosition="50"
motion:motionTarget="@id/now_playing_cover"
motion:curveFit="spline"
/>
<KeyAttribute
android:alpha="0"
motion:framePosition="10"
motion:motionTarget="@id/header_controls"
/>
<KeyPosition
motion:percentX="1"
motion:framePosition="50"
motion:motionTarget="@id/header_controls"
motion:curveFit="spline"
/>
<KeyAttribute
android:alpha="0"
motion:framePosition="10"
motion:motionTarget="@id/now_playing_progress"
/>
<KeyAttribute
android:alpha="0"
motion:framePosition="90"
motion:motionTarget="@id/now_playing_details_info"
/>
<KeyAttribute
android:alpha="0"
motion:framePosition="90"
motion:motionTarget="@id/controls"
/>
</KeyFrameSet>
</Transition>
</MotionScene>