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:
parent
a34541e95d
commit
0b43dd011c
10 changed files with 108 additions and 13 deletions
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
20
app/src/main/res/drawable/ui_remote_fab_container_paused.xml
Normal file
20
app/src/main/res/drawable/ui_remote_fab_container_paused.xml
Normal 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>
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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>
|
Loading…
Reference in a new issue