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:
parent
282933fb37
commit
0a4b07e583
4 changed files with 99 additions and 19 deletions
|
@ -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
|
||||||
|
|
|
@ -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 ->
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.
|
||||||
|
|
Loading…
Reference in a new issue