playback: animate playback bar

Make the playback bar animate when it's shown at runtime. This
completes the playback bar layout and honestly it looks amazing.
This commit is contained in:
OxygenCobalt 2021-11-01 20:01:46 -06:00
parent 282933fb37
commit 0a4b07e583
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
4 changed files with 99 additions and 19 deletions

View file

@ -64,7 +64,7 @@ dependencies {
// General // General
implementation "androidx.core:core-ktx:1.7.0" implementation "androidx.core:core-ktx:1.7.0"
implementation "androidx.activity:activity-ktx:1.3.1" implementation "androidx.activity:activity-ktx:1.4.0"
implementation 'androidx.fragment:fragment-ktx:1.3.6' implementation 'androidx.fragment:fragment-ktx:1.3.6'
// UI // UI
@ -85,7 +85,7 @@ dependencies {
implementation "androidx.navigation:navigation-fragment-ktx:$navigation_version" implementation "androidx.navigation:navigation-fragment-ktx:$navigation_version"
// Media // Media
// TODO: Migrate to media2 when I can figure out how to use it // TODO: Migrate to Media3
implementation "androidx.media:media:1.4.3" implementation "androidx.media:media:1.4.3"
// Preferences // Preferences

View file

@ -73,7 +73,7 @@ class MainFragment : Fragment(), PlaybackBarLayout.ActionCallback {
binding.mainBarLayout.setPosition(playbackModel.position.value!!) binding.mainBarLayout.setPosition(playbackModel.position.value!!)
playbackModel.song.observe(viewLifecycleOwner) { song -> playbackModel.song.observe(viewLifecycleOwner) { song ->
binding.mainBarLayout.setSong(song) binding.mainBarLayout.setSong(song, animate = true)
} }
playbackModel.isPlaying.observe(viewLifecycleOwner) { isPlaying -> playbackModel.isPlaying.observe(viewLifecycleOwner) { isPlaying ->

View file

@ -29,6 +29,7 @@ import androidx.annotation.AttrRes
import androidx.annotation.StyleRes import androidx.annotation.StyleRes
import androidx.core.view.children import androidx.core.view.children
import androidx.core.view.updatePadding import androidx.core.view.updatePadding
import androidx.customview.widget.ViewDragHelper
import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.util.systemBarsCompat import org.oxycblt.auxio.util.systemBarsCompat
@ -49,6 +50,7 @@ class PlaybackBarLayout @JvmOverloads constructor(
@StyleRes defStyleRes: Int = 0 @StyleRes defStyleRes: Int = 0
) : ViewGroup(context, attrs, defStyleAttr, defStyleRes) { ) : ViewGroup(context, attrs, defStyleAttr, defStyleRes) {
private val playbackView = CompactPlaybackView(context) private val playbackView = CompactPlaybackView(context)
private var barDragHelper = ViewDragHelper.create(this, ViewDragCallback())
private var lastInsets: WindowInsets? = null private var lastInsets: WindowInsets? = null
init { init {
@ -76,7 +78,11 @@ class PlaybackBarLayout @JvmOverloads constructor(
playbackView.measure(barWidthSpec, barHeightSpec) playbackView.measure(barWidthSpec, barHeightSpec)
updateWindowInsets() updateWindowInsets()
measureContent()
}
private fun measureContent() {
val barParams = playbackView.layoutParams as LayoutParams
val barHeightAdjusted = (playbackView.measuredHeight * barParams.offset).toInt() val barHeightAdjusted = (playbackView.measuredHeight * barParams.offset).toInt()
val contentWidth = measuredWidth val contentWidth = measuredWidth
@ -110,7 +116,17 @@ class PlaybackBarLayout @JvmOverloads constructor(
0, height - barHeightAdjusted, 0, height - barHeightAdjusted,
width, height + (barHeight - barHeightAdjusted) width, height + (barHeight - barHeightAdjusted)
) )
} else { }
}
layoutContent()
}
private fun layoutContent() {
for (child in children) {
val childParams = child.layoutParams as LayoutParams
if (!childParams.isBar) {
child.layout(0, 0, child.measuredWidth, child.measuredHeight) child.layout(0, 0, child.measuredWidth, child.measuredHeight)
} }
} }
@ -125,6 +141,12 @@ class PlaybackBarLayout @JvmOverloads constructor(
return insets return insets
} }
override fun computeScroll() {
if (barDragHelper.continueSettling(true)) {
postInvalidateOnAnimation()
}
}
override fun onDetachedFromWindow() { override fun onDetachedFromWindow() {
super.onDetachedFromWindow() super.onDetachedFromWindow()
@ -174,12 +196,12 @@ class PlaybackBarLayout @JvmOverloads constructor(
} }
} }
fun setSong(song: Song?) { fun setSong(song: Song?, animate: Boolean = false) {
if (song != null) { if (song != null) {
showBar() showBar(animate)
playbackView.setSong(song) playbackView.setSong(song)
} else { } else {
hideBar() hideBar(animate)
} }
} }
@ -195,33 +217,53 @@ class PlaybackBarLayout @JvmOverloads constructor(
playbackView.setCallback(callback) playbackView.setCallback(callback)
} }
private fun showBar() { private fun showBar(animate: Boolean) {
val barParams = playbackView.layoutParams as LayoutParams val barParams = playbackView.layoutParams as LayoutParams
if (barParams.offset == 1f) { if (barParams.shown || barParams.offset == 1f) {
return return
} }
barParams.shown = true
if (animate) {
barDragHelper.smoothSlideViewTo(
playbackView, playbackView.left, height - playbackView.height
)
} else {
barParams.offset = 1f barParams.offset = 1f
if (isLaidOut) { if (isLaidOut) {
updateWindowInsets() updateWindowInsets()
measureContent()
layoutContent()
}
} }
invalidate() invalidate()
} }
private fun hideBar() { private fun hideBar(animate: Boolean) {
val barParams = playbackView.layoutParams as LayoutParams val barParams = playbackView.layoutParams as LayoutParams
if (barParams.offset == 0f) { if (barParams.shown || barParams.offset == 0f) {
return return
} }
barParams.shown = false
if (animate) {
barDragHelper.smoothSlideViewTo(
playbackView, playbackView.left, height
)
} else {
barParams.offset = 0f barParams.offset = 0f
if (isLaidOut) { if (isLaidOut) {
updateWindowInsets() updateWindowInsets()
measureContent()
layoutContent()
}
} }
invalidate() invalidate()
@ -251,6 +293,7 @@ class PlaybackBarLayout @JvmOverloads constructor(
@Suppress("UNUSED") @Suppress("UNUSED")
class LayoutParams : ViewGroup.LayoutParams { class LayoutParams : ViewGroup.LayoutParams {
var isBar = false var isBar = false
var shown = false
var offset = 0f var offset = 0f
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
@ -267,4 +310,42 @@ class PlaybackBarLayout @JvmOverloads constructor(
fun onNavToItem() fun onNavToItem()
fun onNavToPlayback() fun onNavToPlayback()
} }
private inner class ViewDragCallback : ViewDragHelper.Callback() {
override fun tryCaptureView(child: View, pointerId: Int): Boolean = false
override fun onViewPositionChanged(
changedView: View,
left: Int,
top: Int,
dx: Int,
dy: Int
) {
val childRange = getViewVerticalDragRange(changedView)
val childLayoutParams = changedView.layoutParams as LayoutParams
val height = height
childLayoutParams.offset = (height - top).toFloat() / childRange
updateWindowInsets()
measureContent()
layoutContent()
}
override fun getViewVerticalDragRange(child: View): Int {
val childParams = child.layoutParams as LayoutParams
return if (childParams.isBar) {
child.height
} else {
0
}
}
override fun clampViewPositionHorizontal(child: View, left: Int, dx: Int): Int = child.left
override fun clampViewPositionVertical(child: View, top: Int, dy: Int): Int {
return top.coerceIn(height - getViewVerticalDragRange(child)..height)
}
}
} }

View file

@ -179,8 +179,7 @@ class WidgetProvider : AppWidgetProvider() {
updateAppWidget(name, RemoteViews(views)) updateAppWidget(name, RemoteViews(views))
} else { } else {
// Otherwise, we try our best to backport the responsive behavior to older versions. // Otherwise, we try our best to backport the responsive behavior to older versions.
// This is mostly a guess based on RemoteView's documentation. It seems to work well // This seems to work well enough on most launchers.
// enough on most launchers.
// Each widget has independent dimensions, so we iterate through them all // Each widget has independent dimensions, so we iterate through them all
// and do this for each. // and do this for each.