feat: add double tap ripple animation (#401)
* Add double tap seeking animation * Remove unnecessary formatting changes * Order imports correctly * Remove needless blank line * feat: add ripple for playback (play / pause) * refactor: clean up --------- Co-authored-by: Jarne Demeulemeester <jarnedemeulemeester@gmail.com>
This commit is contained in:
parent
57c1e85b11
commit
d86b162d4b
3 changed files with 103 additions and 5 deletions
|
@ -10,9 +10,13 @@ import android.view.GestureDetector
|
|||
import android.view.MotionEvent
|
||||
import android.view.ScaleGestureDetector
|
||||
import android.view.View
|
||||
import android.view.ViewPropertyAnimator
|
||||
import android.view.WindowInsets
|
||||
import android.view.WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_FULL
|
||||
import android.view.WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_OFF
|
||||
import android.view.animation.AccelerateInterpolator
|
||||
import android.view.animation.DecelerateInterpolator
|
||||
import android.widget.ImageView
|
||||
import androidx.media3.ui.AspectRatioFrameLayout
|
||||
import androidx.media3.ui.PlayerView
|
||||
import dev.jdtech.jellyfin.AppPreferences
|
||||
|
@ -72,8 +76,6 @@ class PlayerGestureHelper(
|
|||
val viewWidth = playerView.measuredWidth
|
||||
val areaWidth = viewWidth / 5 // Divide the view into 5 parts: 2:1:2
|
||||
|
||||
val currentPos = playerView.player?.currentPosition ?: 0
|
||||
|
||||
// Define the areas and their boundaries
|
||||
val leftmostAreaStart = 0
|
||||
val middleAreaStart = areaWidth * 2
|
||||
|
@ -82,15 +84,15 @@ class PlayerGestureHelper(
|
|||
when (e.x.toInt()) {
|
||||
in leftmostAreaStart until middleAreaStart -> {
|
||||
// Tapped on the leftmost area (seek backward)
|
||||
playerView.player?.seekTo((currentPos - appPreferences.playerSeekBackIncrement).coerceAtLeast(0))
|
||||
rewind()
|
||||
}
|
||||
in middleAreaStart until rightmostAreaStart -> {
|
||||
// Tapped on the middle area (toggle pause/unpause)
|
||||
playerView.player?.playWhenReady = !playerView.player?.playWhenReady!!
|
||||
togglePlayback()
|
||||
}
|
||||
in rightmostAreaStart until viewWidth -> {
|
||||
// Tapped on the rightmost area (seek forward)
|
||||
playerView.player?.seekTo(currentPos + appPreferences.playerSeekForwardIncrement)
|
||||
fastForward()
|
||||
}
|
||||
}
|
||||
return true
|
||||
|
@ -98,6 +100,68 @@ class PlayerGestureHelper(
|
|||
},
|
||||
)
|
||||
|
||||
private fun fastForward() {
|
||||
val currentPosition = playerView.player?.currentPosition ?: 0
|
||||
val fastForwardPosition = currentPosition + appPreferences.playerSeekForwardIncrement
|
||||
seekTo(fastForwardPosition)
|
||||
animateRipple(activity.binding.imageFfwdAnimationRipple)
|
||||
}
|
||||
|
||||
private fun rewind() {
|
||||
val currentPosition = playerView.player?.currentPosition ?: 0
|
||||
val rewindPosition = currentPosition - appPreferences.playerSeekBackIncrement
|
||||
seekTo(rewindPosition.coerceAtLeast(0))
|
||||
animateRipple(activity.binding.imageRewindAnimationRipple)
|
||||
}
|
||||
|
||||
private fun togglePlayback() {
|
||||
playerView.player?.playWhenReady = !playerView.player?.playWhenReady!!
|
||||
animateRipple(activity.binding.imagePlaybackAnimationRipple)
|
||||
}
|
||||
|
||||
private fun seekTo(position: Long) {
|
||||
playerView.player?.seekTo(position)
|
||||
}
|
||||
|
||||
private fun animateRipple(image: ImageView) {
|
||||
image
|
||||
.animateSeekingRippleStart()
|
||||
.withEndAction {
|
||||
resetRippleImage(image)
|
||||
}
|
||||
.start()
|
||||
}
|
||||
|
||||
private fun ImageView.animateSeekingRippleStart(): ViewPropertyAnimator {
|
||||
val rippleImageHeight = this.height
|
||||
val playerViewHeight = playerView.height.toFloat()
|
||||
val playerViewWidth = playerView.width.toFloat()
|
||||
val scaleDifference = playerViewHeight / rippleImageHeight
|
||||
val playerViewAspectRatio = playerViewWidth / playerViewHeight
|
||||
val scaleValue = scaleDifference * playerViewAspectRatio
|
||||
return animate()
|
||||
.alpha(1f)
|
||||
.scaleX(scaleValue)
|
||||
.scaleY(scaleValue)
|
||||
.setDuration(180)
|
||||
.setInterpolator(DecelerateInterpolator())
|
||||
}
|
||||
|
||||
private fun resetRippleImage(image: ImageView) {
|
||||
image
|
||||
.animateSeekingRippleEnd()
|
||||
.withEndAction {
|
||||
image.scaleX = 1f
|
||||
image.scaleY = 1f
|
||||
}
|
||||
.start()
|
||||
}
|
||||
|
||||
private fun ImageView.animateSeekingRippleEnd() = animate()
|
||||
.alpha(0f)
|
||||
.setDuration(150)
|
||||
.setInterpolator(AccelerateInterpolator())
|
||||
|
||||
private val seekGestureDetector = GestureDetector(
|
||||
playerView.context,
|
||||
object : GestureDetector.SimpleOnGestureListener() {
|
||||
|
|
|
@ -113,4 +113,30 @@
|
|||
tools:ignore="ContentDescription" />
|
||||
</LinearLayout>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/image_ffwd_animation_ripple"
|
||||
android:layout_width="50dp"
|
||||
android:layout_height="50dp"
|
||||
android:layout_gravity="center_vertical|end"
|
||||
android:alpha="0"
|
||||
android:src="@drawable/transparent_circle"
|
||||
tools:ignore="ContentDescription" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/image_rewind_animation_ripple"
|
||||
android:layout_width="50dp"
|
||||
android:layout_height="50dp"
|
||||
android:layout_gravity="center_vertical|start"
|
||||
android:alpha="0"
|
||||
android:src="@drawable/transparent_circle"
|
||||
tools:ignore="ContentDescription" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/image_playback_animation_ripple"
|
||||
android:layout_width="50dp"
|
||||
android:layout_height="50dp"
|
||||
android:layout_gravity="center"
|
||||
android:alpha="0"
|
||||
android:src="@drawable/transparent_circle"
|
||||
tools:ignore="ContentDescription" />
|
||||
</FrameLayout>
|
||||
|
|
8
core/src/main/res/drawable/transparent_circle.xml
Normal file
8
core/src/main/res/drawable/transparent_circle.xml
Normal file
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="oval">
|
||||
<size
|
||||
android:width="24dp"
|
||||
android:height="24dp" />
|
||||
<solid android:color="#22ffffff" />
|
||||
</shape>
|
Loading…
Reference in a new issue