ui: animate play/pause button shape

Make the shape of the play/pause buttons throughout the app morph from
a circle if paused to a square if playing.

Android 13 seemingly does this to their play/pause button, so we copy
it too.

Tangentally related to #162.
This commit is contained in:
Alexander Capehart 2022-08-27 15:21:08 -06:00
parent a34541e95d
commit 0b43dd011c
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
10 changed files with 108 additions and 13 deletions

View file

@ -11,6 +11,7 @@
#### What's Improved
- Playback bar now has a marquee effect
- Play/pause button now changes from square to circle depending on the state
- Added a way to access the system equalizer from the playback menu.
#### What's Fixed

View file

@ -0,0 +1,62 @@
/*
* Copyright (c) 2022 Auxio Project
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.auxio.playback
import android.animation.ValueAnimator
import android.content.Context
import android.util.AttributeSet
import com.google.android.material.button.MaterialButton
/**
* A [MaterialButton] that automatically morphs from a circle to a squircle shape appearance when
* it is activated.
*/
class AnimatedMaterialButton
@JvmOverloads
constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) :
MaterialButton(context, attrs, defStyleAttr) {
private var currentCornerRadiusRatio = 0f
private var animator: ValueAnimator? = null
override fun setActivated(activated: Boolean) {
super.setActivated(activated)
val target = if (activated) 0.3f else 0.5f
if (!isLaidOut) {
updateCornerRadiusRatio(target)
return
}
animator?.cancel()
animator =
ValueAnimator.ofFloat(currentCornerRadiusRatio, target).apply {
duration = ACTIVATION_DURATION
addUpdateListener { updateCornerRadiusRatio(animatedValue as Float) }
start()
}
}
private fun updateCornerRadiusRatio(ratio: Float) {
currentCornerRadiusRatio = ratio
shapeAppearanceModel = shapeAppearanceModel.withCornerSize { it.width() * ratio }
}
companion object {
const val ACTIVATION_DURATION = 150L
}
}

View file

@ -17,6 +17,7 @@
package org.oxycblt.auxio.playback
import android.animation.ValueAnimator
import android.content.ActivityNotFoundException
import android.content.Intent
import android.media.audiofx.AudioEffect
@ -49,8 +50,10 @@ class PlaybackPanelFragment :
MenuFragment<FragmentPlaybackPanelBinding>(),
StyledSeekBar.Callback,
Toolbar.OnMenuItemClickListener {
private var animator: ValueAnimator? = null
private var radius = 0.3f
// AudioEffect expects you to use startActivityFoResult with the panel intent. Use
// AudioEffect expects you to use startActivityForResult with the panel intent. Use
// the contract analogue for this since there is no built-in contract for AudioEffect.
private val activityLauncher by lifecycleObject {
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
@ -179,7 +182,7 @@ class PlaybackPanelFragment :
}
private fun updatePlaying(isPlaying: Boolean) {
requireBinding().playbackPlayPause.apply { isActivated = isPlaying }
requireBinding().playbackPlayPause.isActivated = isPlaying
}
private fun updateShuffled(isShuffled: Boolean) {

View file

@ -145,13 +145,22 @@ private fun RemoteViews.applyPlayPauseControls(
R.id.widget_play_pause,
context.newBroadcastPendingIntent(PlaybackService.ACTION_PLAY_PAUSE))
setImageViewResource(
R.id.widget_play_pause,
if (state.isPlaying) {
R.drawable.ic_pause_24
} else {
R.drawable.ic_play_24
})
// Like the Android 13 media controls, use a circular fab when paused, and a squircle fab
// when playing.
val icon: Int
val container: Int
if (state.isPlaying) {
icon = R.drawable.ic_pause_24
container = R.drawable.ui_remote_fab_container_playing
} else {
icon = R.drawable.ic_play_24
container = R.drawable.ui_remote_fab_container_paused
}
setImageViewResource(R.id.widget_play_pause, icon)
setInt(R.id.widget_play_pause, "setBackgroundResource", container)
return this
}

View file

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape android:shape="rectangle">
<corners android:radius="@dimen/size_corners_mid_large" />
<solid android:color="?attr/colorPrimary" />
</shape>
</item>
<item>
<ripple android:color="@color/sel_remote_fab_ripple">
<item android:id="@android:id/mask">
<shape android:shape="rectangle">
<solid android:color="@android:color/white" />
<corners android:radius="@dimen/size_corners_mid_large" />
</shape>
</item>
</ripple>
</item>
</layer-list>

View file

@ -121,7 +121,7 @@
app:layout_constraintStart_toEndOf="@+id/playback_repeat"
app:layout_constraintTop_toTopOf="@+id/playback_play_pause" />
<com.google.android.material.button.MaterialButton
<org.oxycblt.auxio.playback.AnimatedMaterialButton
android:id="@+id/playback_play_pause"
style="@style/Widget.Auxio.Button.PlayPause"
android:layout_width="wrap_content"

View file

@ -124,7 +124,7 @@
app:layout_constraintEnd_toStartOf="@+id/playback_play_pause"
app:layout_constraintTop_toTopOf="@+id/playback_play_pause" />
<com.google.android.material.button.MaterialButton
<org.oxycblt.auxio.playback.AnimatedMaterialButton
android:id="@+id/playback_play_pause"
style="@style/Widget.Auxio.Button.PlayPause"
android:layout_width="wrap_content"

View file

@ -138,7 +138,7 @@
app:layout_constraintStart_toEndOf="@+id/playback_repeat"
app:layout_constraintTop_toTopOf="@+id/playback_play_pause" />
<com.google.android.material.button.MaterialButton
<org.oxycblt.auxio.playback.AnimatedMaterialButton
android:id="@+id/playback_play_pause"
style="@style/Widget.Auxio.Button.PlayPause"
android:layout_width="wrap_content"

View file

@ -87,7 +87,7 @@
<!-- A variant of button that emulates a FAB-ish button that plays along with widget restrictions -->
<style name="Widget.Auxio.MaterialButton.AppWidget.PlayPause" parent="Widget.AppCompat.Button.Borderless">
<item name="android:minWidth">@dimen/size_btn</item>
<item name="android:background">@drawable/ui_remote_fab_bg</item>
<item name="android:background">@drawable/ui_remote_fab_container_paused</item>
<item name="android:tint">?attr/colorOnPrimary</item>
</style>
</resources>