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
|
#### What's Improved
|
||||||
- Playback bar now has a marquee effect
|
- 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.
|
- Added a way to access the system equalizer from the playback menu.
|
||||||
|
|
||||||
#### What's Fixed
|
#### 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
|
package org.oxycblt.auxio.playback
|
||||||
|
|
||||||
|
import android.animation.ValueAnimator
|
||||||
import android.content.ActivityNotFoundException
|
import android.content.ActivityNotFoundException
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.media.audiofx.AudioEffect
|
import android.media.audiofx.AudioEffect
|
||||||
|
@ -49,8 +50,10 @@ class PlaybackPanelFragment :
|
||||||
MenuFragment<FragmentPlaybackPanelBinding>(),
|
MenuFragment<FragmentPlaybackPanelBinding>(),
|
||||||
StyledSeekBar.Callback,
|
StyledSeekBar.Callback,
|
||||||
Toolbar.OnMenuItemClickListener {
|
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.
|
// the contract analogue for this since there is no built-in contract for AudioEffect.
|
||||||
private val activityLauncher by lifecycleObject {
|
private val activityLauncher by lifecycleObject {
|
||||||
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||||
|
@ -179,7 +182,7 @@ class PlaybackPanelFragment :
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updatePlaying(isPlaying: Boolean) {
|
private fun updatePlaying(isPlaying: Boolean) {
|
||||||
requireBinding().playbackPlayPause.apply { isActivated = isPlaying }
|
requireBinding().playbackPlayPause.isActivated = isPlaying
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateShuffled(isShuffled: Boolean) {
|
private fun updateShuffled(isShuffled: Boolean) {
|
||||||
|
|
|
@ -145,13 +145,22 @@ private fun RemoteViews.applyPlayPauseControls(
|
||||||
R.id.widget_play_pause,
|
R.id.widget_play_pause,
|
||||||
context.newBroadcastPendingIntent(PlaybackService.ACTION_PLAY_PAUSE))
|
context.newBroadcastPendingIntent(PlaybackService.ACTION_PLAY_PAUSE))
|
||||||
|
|
||||||
setImageViewResource(
|
// Like the Android 13 media controls, use a circular fab when paused, and a squircle fab
|
||||||
R.id.widget_play_pause,
|
// when playing.
|
||||||
if (state.isPlaying) {
|
|
||||||
R.drawable.ic_pause_24
|
val icon: Int
|
||||||
} else {
|
val container: Int
|
||||||
R.drawable.ic_play_24
|
|
||||||
})
|
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
|
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_constraintStart_toEndOf="@+id/playback_repeat"
|
||||||
app:layout_constraintTop_toTopOf="@+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"
|
android:id="@+id/playback_play_pause"
|
||||||
style="@style/Widget.Auxio.Button.PlayPause"
|
style="@style/Widget.Auxio.Button.PlayPause"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
|
|
|
@ -124,7 +124,7 @@
|
||||||
app:layout_constraintEnd_toStartOf="@+id/playback_play_pause"
|
app:layout_constraintEnd_toStartOf="@+id/playback_play_pause"
|
||||||
app:layout_constraintTop_toTopOf="@+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"
|
android:id="@+id/playback_play_pause"
|
||||||
style="@style/Widget.Auxio.Button.PlayPause"
|
style="@style/Widget.Auxio.Button.PlayPause"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
|
|
|
@ -138,7 +138,7 @@
|
||||||
app:layout_constraintStart_toEndOf="@+id/playback_repeat"
|
app:layout_constraintStart_toEndOf="@+id/playback_repeat"
|
||||||
app:layout_constraintTop_toTopOf="@+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"
|
android:id="@+id/playback_play_pause"
|
||||||
style="@style/Widget.Auxio.Button.PlayPause"
|
style="@style/Widget.Auxio.Button.PlayPause"
|
||||||
android:layout_width="wrap_content"
|
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 -->
|
<!-- 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">
|
<style name="Widget.Auxio.MaterialButton.AppWidget.PlayPause" parent="Widget.AppCompat.Button.Borderless">
|
||||||
<item name="android:minWidth">@dimen/size_btn</item>
|
<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>
|
<item name="android:tint">?attr/colorOnPrimary</item>
|
||||||
</style>
|
</style>
|
||||||
</resources>
|
</resources>
|
Loading…
Reference in a new issue