From 0b43dd011c13b91aa2cd9827ca9158ac655b05bf Mon Sep 17 00:00:00 2001 From: Alexander Capehart Date: Sat, 27 Aug 2022 15:21:08 -0600 Subject: [PATCH] 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. --- CHANGELOG.md | 1 + .../auxio/playback/AnimatedMaterialButton.kt | 62 +++++++++++++++++++ .../auxio/playback/PlaybackPanelFragment.kt | 7 ++- .../java/org/oxycblt/auxio/widgets/Forms.kt | 23 ++++--- .../ui_remote_fab_container_paused.xml | 20 ++++++ ...ml => ui_remote_fab_container_playing.xml} | 0 .../layout-h600dp/fragment_playback_panel.xml | 2 +- .../fragment_playback_panel.xml | 2 +- .../res/layout/fragment_playback_panel.xml | 2 +- app/src/main/res/values/styles_android.xml | 2 +- 10 files changed, 108 insertions(+), 13 deletions(-) create mode 100644 app/src/main/java/org/oxycblt/auxio/playback/AnimatedMaterialButton.kt create mode 100644 app/src/main/res/drawable/ui_remote_fab_container_paused.xml rename app/src/main/res/drawable/{ui_remote_fab_bg.xml => ui_remote_fab_container_playing.xml} (100%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 341e7e8cf..88897910b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/app/src/main/java/org/oxycblt/auxio/playback/AnimatedMaterialButton.kt b/app/src/main/java/org/oxycblt/auxio/playback/AnimatedMaterialButton.kt new file mode 100644 index 000000000..d0f599a11 --- /dev/null +++ b/app/src/main/java/org/oxycblt/auxio/playback/AnimatedMaterialButton.kt @@ -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 . + */ + +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 + } +} diff --git a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackPanelFragment.kt b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackPanelFragment.kt index 12610ec6f..058be34e5 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackPanelFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackPanelFragment.kt @@ -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(), 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) { diff --git a/app/src/main/java/org/oxycblt/auxio/widgets/Forms.kt b/app/src/main/java/org/oxycblt/auxio/widgets/Forms.kt index 196bb050e..601532157 100644 --- a/app/src/main/java/org/oxycblt/auxio/widgets/Forms.kt +++ b/app/src/main/java/org/oxycblt/auxio/widgets/Forms.kt @@ -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 } diff --git a/app/src/main/res/drawable/ui_remote_fab_container_paused.xml b/app/src/main/res/drawable/ui_remote_fab_container_paused.xml new file mode 100644 index 000000000..599b2b7ca --- /dev/null +++ b/app/src/main/res/drawable/ui_remote_fab_container_paused.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ui_remote_fab_bg.xml b/app/src/main/res/drawable/ui_remote_fab_container_playing.xml similarity index 100% rename from app/src/main/res/drawable/ui_remote_fab_bg.xml rename to app/src/main/res/drawable/ui_remote_fab_container_playing.xml diff --git a/app/src/main/res/layout-h600dp/fragment_playback_panel.xml b/app/src/main/res/layout-h600dp/fragment_playback_panel.xml index 50e604d7c..f095e598c 100644 --- a/app/src/main/res/layout-h600dp/fragment_playback_panel.xml +++ b/app/src/main/res/layout-h600dp/fragment_playback_panel.xml @@ -121,7 +121,7 @@ app:layout_constraintStart_toEndOf="@+id/playback_repeat" app:layout_constraintTop_toTopOf="@+id/playback_play_pause" /> - - - \ No newline at end of file