Improved slider
This commit is contained in:
parent
437f73b1dc
commit
c42e9cb0df
68 changed files with 621 additions and 3401 deletions
61
.github/stale.yml
vendored
61
.github/stale.yml
vendored
|
@ -1,61 +0,0 @@
|
|||
# Configuration for probot-stale - https://github.com/probot/stale
|
||||
|
||||
# Number of days of inactivity before an Issue or Pull Request becomes stale
|
||||
daysUntilStale: 60
|
||||
|
||||
# Number of days of inactivity before an Issue or Pull Request with the stale label is closed.
|
||||
# Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale.
|
||||
daysUntilClose: 7
|
||||
|
||||
# Only issues or pull requests with all of these labels are check if stale. Defaults to `[]` (disabled)
|
||||
onlyLabels: []
|
||||
|
||||
# Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable
|
||||
exemptLabels:
|
||||
- pinned
|
||||
- security
|
||||
- "[Status] Maybe Later"
|
||||
|
||||
# Set to true to ignore issues in a project (defaults to false)
|
||||
exemptProjects: false
|
||||
|
||||
# Set to true to ignore issues in a milestone (defaults to false)
|
||||
exemptMilestones: false
|
||||
|
||||
# Set to true to ignore issues with an assignee (defaults to false)
|
||||
exemptAssignees: false
|
||||
|
||||
# Label to use when marking as stale
|
||||
staleLabel: stale
|
||||
|
||||
# Comment to post when marking as stale. Set to `false` to disable
|
||||
markComment: >
|
||||
This issue has been automatically marked as stale because it has not had
|
||||
recent activity. It will be closed if no further activity occurs. Thank you
|
||||
for your contributions.
|
||||
|
||||
# Comment to post when removing the stale label.
|
||||
# unmarkComment: >
|
||||
# Your comment here.
|
||||
|
||||
# Comment to post when closing a stale Issue or Pull Request.
|
||||
# closeComment: >
|
||||
# Your comment here.
|
||||
|
||||
# Limit the number of actions per hour, from 1-30. Default is 30
|
||||
limitPerRun: 30
|
||||
|
||||
# Limit to only `issues` or `pulls`
|
||||
# only: issues
|
||||
|
||||
# Optionally, specify configuration settings that are specific to just 'issues' or 'pulls':
|
||||
# pulls:
|
||||
# daysUntilStale: 30
|
||||
# markComment: >
|
||||
# This pull request has been automatically marked as stale because it has not had
|
||||
# recent activity. It will be closed if no further activity occurs. Thank you
|
||||
# for your contributions.
|
||||
|
||||
# issues:
|
||||
# exemptLabels:
|
||||
# - confirmed
|
|
@ -14,6 +14,8 @@ proguardDictionaries {
|
|||
|
||||
android {
|
||||
compileSdkVersion 29
|
||||
buildToolsVersion = '29.0.2'
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 29
|
||||
|
@ -81,7 +83,7 @@ android {
|
|||
sourceCompatibility '1.8'
|
||||
targetCompatibility '1.8'
|
||||
}
|
||||
buildToolsVersion = '29.0.2'
|
||||
|
||||
|
||||
configurations.all {
|
||||
resolutionStrategy.force 'com.google.code.findbugs:jsr305:1.3.9'
|
||||
|
@ -130,10 +132,10 @@ dependencies {
|
|||
|
||||
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
||||
implementation 'androidx.core:core-ktx:1.2.0'
|
||||
implementation 'androidx.fragment:fragment:1.2.1'
|
||||
implementation 'androidx.fragment:fragment:1.2.2'
|
||||
implementation 'androidx.recyclerview:recyclerview:1.1.0'
|
||||
|
||||
implementation 'com.google.android.material:material:1.2.0-alpha04'
|
||||
implementation 'com.google.android.material:material:1.2.0-alpha05'
|
||||
|
||||
def retrofit_version = "2.6.2"
|
||||
implementation "com.squareup.retrofit2:retrofit:$retrofit_version"
|
||||
|
@ -160,7 +162,7 @@ dependencies {
|
|||
|
||||
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
|
||||
def kotlin_coroutines_version = "1.3.0"
|
||||
def kotlin_coroutines_version = "1.3.3"
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_coroutines_version"
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version"
|
||||
|
||||
|
|
|
@ -51,8 +51,8 @@ import code.name.monkey.retromusic.fragments.mainactivity.GenresFragment;
|
|||
import code.name.monkey.retromusic.fragments.mainactivity.PlayingQueueFragment;
|
||||
import code.name.monkey.retromusic.fragments.mainactivity.PlaylistsFragment;
|
||||
import code.name.monkey.retromusic.fragments.mainactivity.SongsFragment;
|
||||
import code.name.monkey.retromusic.fragments.mainactivity.folders.FoldersFragment;
|
||||
import code.name.monkey.retromusic.fragments.mainactivity.home.BannerHomeFragment;
|
||||
import code.name.monkey.retromusic.fragments.mainactivity.FoldersFragment;
|
||||
import code.name.monkey.retromusic.fragments.mainactivity.BannerHomeFragment;
|
||||
import code.name.monkey.retromusic.helper.MusicPlayerRemote;
|
||||
import code.name.monkey.retromusic.helper.SearchQueryHelper;
|
||||
import code.name.monkey.retromusic.helper.SortOrder.AlbumSortOrder;
|
||||
|
|
|
@ -26,7 +26,7 @@ import code.name.monkey.retromusic.fragments.mainactivity.ArtistsFragment
|
|||
import code.name.monkey.retromusic.fragments.mainactivity.GenresFragment
|
||||
import code.name.monkey.retromusic.fragments.mainactivity.PlaylistsFragment
|
||||
import code.name.monkey.retromusic.fragments.mainactivity.SongsFragment
|
||||
import code.name.monkey.retromusic.fragments.mainactivity.home.BannerHomeFragment
|
||||
import code.name.monkey.retromusic.fragments.mainactivity.BannerHomeFragment
|
||||
import dagger.Component
|
||||
import javax.inject.Singleton
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@ import android.widget.EditText
|
|||
import androidx.annotation.LayoutRes
|
||||
import code.name.monkey.appthemehelper.ThemeStore
|
||||
import code.name.monkey.appthemehelper.util.TintHelper
|
||||
|
||||
import com.google.android.material.slider.Slider
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun <T : View> ViewGroup.inflate(@LayoutRes layout: Int): T {
|
||||
|
@ -45,4 +45,10 @@ fun View.showOrHide(show: Boolean) = if (show) show() else hide()
|
|||
fun EditText.appHandleColor(): EditText {
|
||||
TintHelper.colorHandles(this, ThemeStore.accentColor(context))
|
||||
return this
|
||||
}
|
||||
|
||||
fun Slider.setRange(progress: Float, to: Float) {
|
||||
valueFrom = 0F
|
||||
valueTo = to
|
||||
value = progress
|
||||
}
|
|
@ -11,20 +11,19 @@ enum class NowPlayingScreen constructor(
|
|||
val id: Int
|
||||
) {
|
||||
|
||||
NORMAL(R.string.normal, R.drawable.np_normal, 0),
|
||||
FLAT(R.string.flat, R.drawable.np_flat, 1),
|
||||
FIT(R.string.fit, R.drawable.np_fit, 12),
|
||||
TINY(R.string.tiny, R.drawable.np_tiny, 7),
|
||||
PEAK(R.string.peak, R.drawable.np_peak, 14),
|
||||
|
||||
ADAPTIVE(R.string.adaptive, R.drawable.np_adaptive, 10),
|
||||
BLUR(R.string.blur, R.drawable.np_blur, 4),
|
||||
BLUR_CARD(R.string.blur_card, R.drawable.np_blur_card, 9),
|
||||
CARD(R.string.card, R.drawable.np_card, 6),
|
||||
COLOR(R.string.color, R.drawable.np_color, 5),
|
||||
CIRCLE(R.string.circle, R.drawable.np_minimalistic_circle, 15),
|
||||
COLOR(R.string.color, R.drawable.np_color, 5),
|
||||
FIT(R.string.fit, R.drawable.np_fit, 12),
|
||||
FLAT(R.string.flat, R.drawable.np_flat, 1),
|
||||
FULL(R.string.full, R.drawable.np_full, 2),
|
||||
MATERIAL(R.string.material, R.drawable.np_material, 11),
|
||||
NORMAL(R.string.normal, R.drawable.np_normal, 0),
|
||||
PEAK(R.string.peak, R.drawable.np_peak, 14),
|
||||
PLAIN(R.string.plain, R.drawable.np_plain, 3),
|
||||
SIMPLE(R.string.simple, R.drawable.np_simple, 8),
|
||||
TINY(R.string.tiny, R.drawable.np_tiny, 7),
|
||||
}
|
||||
|
|
|
@ -8,7 +8,6 @@ import android.os.Bundle
|
|||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.SeekBar
|
||||
import androidx.fragment.app.Fragment
|
||||
import code.name.monkey.appthemehelper.ThemeStore
|
||||
import code.name.monkey.retromusic.R
|
||||
|
@ -17,12 +16,14 @@ import code.name.monkey.retromusic.util.PreferenceUtil
|
|||
import code.name.monkey.retromusic.util.ViewUtil
|
||||
import code.name.monkey.retromusic.volume.AudioVolumeObserver
|
||||
import code.name.monkey.retromusic.volume.OnAudioVolumeChangedListener
|
||||
import com.google.android.material.slider.Slider
|
||||
import com.google.android.material.slider.Slider.OnChangeListener
|
||||
import kotlinx.android.synthetic.main.fragment_volume.volumeDown
|
||||
import kotlinx.android.synthetic.main.fragment_volume.volumeSeekBar
|
||||
import kotlinx.android.synthetic.main.fragment_volume.volumeUp
|
||||
|
||||
class VolumeFragment : Fragment(), SeekBar.OnSeekBarChangeListener, OnAudioVolumeChangedListener,
|
||||
View.OnClickListener {
|
||||
class VolumeFragment : Fragment(), OnAudioVolumeChangedListener,
|
||||
View.OnClickListener, OnChangeListener {
|
||||
|
||||
private var audioVolumeObserver: AudioVolumeObserver? = null
|
||||
|
||||
|
@ -51,20 +52,22 @@ class VolumeFragment : Fragment(), SeekBar.OnSeekBarChangeListener, OnAudioVolum
|
|||
|
||||
val audioManager = audioManager
|
||||
if (audioManager != null) {
|
||||
volumeSeekBar.max = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC)
|
||||
volumeSeekBar.progress = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC)
|
||||
volumeSeekBar.valueTo = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC).toFloat()
|
||||
volumeSeekBar.value = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC).toFloat()
|
||||
}
|
||||
volumeSeekBar.setOnSeekBarChangeListener(this)
|
||||
volumeSeekBar.addOnChangeListener(this)
|
||||
}
|
||||
|
||||
override fun onAudioVolumeChanged(currentVolume: Int, maxVolume: Int) {
|
||||
override fun onAudioVolumeChanged(currentVolume: Float, maxVolume: Float) {
|
||||
if (volumeSeekBar == null) {
|
||||
return
|
||||
}
|
||||
|
||||
volumeSeekBar.max = maxVolume
|
||||
volumeSeekBar.progress = currentVolume
|
||||
volumeDown.setImageResource(if (currentVolume == 0) R.drawable.ic_volume_off_white_24dp else R.drawable.ic_volume_down_white_24dp)
|
||||
if (maxVolume <= 0) {
|
||||
return
|
||||
}
|
||||
volumeSeekBar.valueTo = maxVolume
|
||||
volumeSeekBar.valueFrom = currentVolume
|
||||
volumeDown.setImageResource(if (currentVolume == 0.0f) R.drawable.ic_volume_off_white_24dp else R.drawable.ic_volume_down_white_24dp)
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
|
@ -74,19 +77,6 @@ class VolumeFragment : Fragment(), SeekBar.OnSeekBarChangeListener, OnAudioVolum
|
|||
}
|
||||
}
|
||||
|
||||
override fun onProgressChanged(seekBar: SeekBar, i: Int, b: Boolean) {
|
||||
val audioManager = audioManager
|
||||
audioManager?.setStreamVolume(AudioManager.STREAM_MUSIC, i, 0)
|
||||
setPauseWhenZeroVolume(i < 1)
|
||||
volumeDown?.setImageResource(if (i == 0) R.drawable.ic_volume_off_white_24dp else R.drawable.ic_volume_down_white_24dp)
|
||||
}
|
||||
|
||||
override fun onStartTrackingTouch(seekBar: SeekBar) {
|
||||
}
|
||||
|
||||
override fun onStopTrackingTouch(seekBar: SeekBar) {
|
||||
}
|
||||
|
||||
override fun onClick(view: View) {
|
||||
val audioManager = audioManager
|
||||
when (view.id) {
|
||||
|
@ -111,10 +101,6 @@ class VolumeFragment : Fragment(), SeekBar.OnSeekBarChangeListener, OnAudioVolum
|
|||
ViewUtil.setProgressDrawable(volumeSeekBar, color, true)
|
||||
}
|
||||
|
||||
fun removeThumb() {
|
||||
volumeSeekBar.thumb = null
|
||||
}
|
||||
|
||||
private fun setPauseWhenZeroVolume(pauseWhenZeroVolume: Boolean) {
|
||||
if (PreferenceUtil.getInstance(requireContext()).pauseOnZeroVolume()) if (MusicPlayerRemote.isPlaying && pauseWhenZeroVolume) {
|
||||
MusicPlayerRemote.pauseSong()
|
||||
|
@ -134,4 +120,14 @@ class VolumeFragment : Fragment(), SeekBar.OnSeekBarChangeListener, OnAudioVolum
|
|||
return VolumeFragment()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onValueChange(slider: Slider, value: Float, fromUser: Boolean) {
|
||||
if (value <= 0) {
|
||||
return
|
||||
}
|
||||
val audioManager = audioManager
|
||||
audioManager?.setStreamVolume(AudioManager.STREAM_MUSIC, value.toInt(), 0)
|
||||
setPauseWhenZeroVolume(value < 1.0f)
|
||||
volumeDown.setImageResource(if (value == 0.0f) R.drawable.ic_volume_off_white_24dp else R.drawable.ic_volume_down_white_24dp)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,18 @@
|
|||
package code.name.monkey.retromusic.fragments.mainactivity.home
|
||||
/*
|
||||
* Copyright (c) 2020 Hemanth Savarala.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3
|
||||
*
|
||||
* This 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 software 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.
|
||||
*/
|
||||
|
||||
package code.name.monkey.retromusic.fragments.mainactivity
|
||||
|
||||
import android.app.ActivityOptions
|
||||
import android.os.Bundle
|
|
@ -1,4 +1,18 @@
|
|||
package code.name.monkey.retromusic.fragments.mainactivity.folders;
|
||||
/*
|
||||
* Copyright (c) 2020 Hemanth Savarala.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3
|
||||
*
|
||||
* This 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 software 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.
|
||||
*/
|
||||
|
||||
package code.name.monkey.retromusic.fragments.mainactivity;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
|
@ -1,13 +1,10 @@
|
|||
package code.name.monkey.retromusic.fragments.player.adaptive
|
||||
|
||||
import android.animation.ObjectAnimator
|
||||
import android.graphics.PorterDuff
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.animation.LinearInterpolator
|
||||
import android.widget.SeekBar
|
||||
import code.name.monkey.appthemehelper.ThemeStore
|
||||
import code.name.monkey.appthemehelper.util.ATHUtil
|
||||
import code.name.monkey.appthemehelper.util.ColorUtil
|
||||
|
@ -16,12 +13,12 @@ import code.name.monkey.appthemehelper.util.TintHelper
|
|||
import code.name.monkey.retromusic.R
|
||||
import code.name.monkey.retromusic.extensions.hide
|
||||
import code.name.monkey.retromusic.extensions.ripAlpha
|
||||
import code.name.monkey.retromusic.extensions.setRange
|
||||
import code.name.monkey.retromusic.extensions.show
|
||||
import code.name.monkey.retromusic.fragments.base.AbsPlayerControlsFragment
|
||||
import code.name.monkey.retromusic.helper.MusicPlayerRemote
|
||||
import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper
|
||||
import code.name.monkey.retromusic.helper.PlayPauseButtonOnClickHandler
|
||||
import code.name.monkey.retromusic.misc.SimpleOnSeekbarChangeListener
|
||||
import code.name.monkey.retromusic.service.MusicService
|
||||
import code.name.monkey.retromusic.util.MusicUtil
|
||||
import code.name.monkey.retromusic.util.PreferenceUtil
|
||||
|
@ -212,13 +209,10 @@ class AdaptivePlaybackControlsFragment : AbsPlayerControlsFragment() {
|
|||
}
|
||||
|
||||
override fun onUpdateProgressViews(progress: Int, total: Int) {
|
||||
progressSlider.max = total
|
||||
|
||||
val animator = ObjectAnimator.ofInt(progressSlider, "progress", progress)
|
||||
animator.duration = SLIDER_ANIMATION_TIME
|
||||
animator.interpolator = LinearInterpolator()
|
||||
animator.start()
|
||||
|
||||
if (total <= 0) {
|
||||
return
|
||||
}
|
||||
progressSlider.setRange(progress.toFloat(), total.toFloat())
|
||||
songTotalTime.text = MusicUtil.getReadableDurationString(total.toLong())
|
||||
songCurrentProgress.text = MusicUtil.getReadableDurationString(progress.toLong())
|
||||
}
|
||||
|
@ -232,16 +226,14 @@ class AdaptivePlaybackControlsFragment : AbsPlayerControlsFragment() {
|
|||
}
|
||||
|
||||
override fun setUpProgressSlider() {
|
||||
progressSlider.setOnSeekBarChangeListener(object : SimpleOnSeekbarChangeListener() {
|
||||
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
|
||||
if (fromUser) {
|
||||
MusicPlayerRemote.seekTo(progress)
|
||||
onUpdateProgressViews(
|
||||
MusicPlayerRemote.songProgressMillis,
|
||||
MusicPlayerRemote.songDurationMillis
|
||||
)
|
||||
}
|
||||
progressSlider.addOnChangeListener { _, value, fromUser ->
|
||||
if (fromUser) {
|
||||
MusicPlayerRemote.seekTo(value.toInt())
|
||||
onUpdateProgressViews(
|
||||
MusicPlayerRemote.songProgressMillis,
|
||||
MusicPlayerRemote.songDurationMillis
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package code.name.monkey.retromusic.fragments.player.blur
|
||||
|
||||
import android.animation.ObjectAnimator
|
||||
import android.graphics.Color
|
||||
import android.graphics.PorterDuff
|
||||
import android.os.Bundle
|
||||
|
@ -9,20 +8,18 @@ import android.view.View
|
|||
import android.view.ViewGroup
|
||||
import android.view.animation.AccelerateInterpolator
|
||||
import android.view.animation.DecelerateInterpolator
|
||||
import android.view.animation.LinearInterpolator
|
||||
import android.widget.SeekBar
|
||||
import androidx.core.content.ContextCompat
|
||||
import code.name.monkey.appthemehelper.util.ColorUtil
|
||||
import code.name.monkey.appthemehelper.util.MaterialValueHelper
|
||||
import code.name.monkey.appthemehelper.util.TintHelper
|
||||
import code.name.monkey.retromusic.R
|
||||
import code.name.monkey.retromusic.extensions.hide
|
||||
import code.name.monkey.retromusic.extensions.setRange
|
||||
import code.name.monkey.retromusic.extensions.show
|
||||
import code.name.monkey.retromusic.fragments.base.AbsPlayerControlsFragment
|
||||
import code.name.monkey.retromusic.helper.MusicPlayerRemote
|
||||
import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper
|
||||
import code.name.monkey.retromusic.helper.PlayPauseButtonOnClickHandler
|
||||
import code.name.monkey.retromusic.misc.SimpleOnSeekbarChangeListener
|
||||
import code.name.monkey.retromusic.service.MusicService
|
||||
import code.name.monkey.retromusic.util.MusicUtil
|
||||
import code.name.monkey.retromusic.util.PreferenceUtil
|
||||
|
@ -235,20 +232,6 @@ class BlurPlaybackControlsFragment : AbsPlayerControlsFragment() {
|
|||
}
|
||||
}
|
||||
|
||||
override fun setUpProgressSlider() {
|
||||
progressSlider.setOnSeekBarChangeListener(object : SimpleOnSeekbarChangeListener() {
|
||||
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
|
||||
if (fromUser) {
|
||||
MusicPlayerRemote.seekTo(progress)
|
||||
onUpdateProgressViews(
|
||||
MusicPlayerRemote.songProgressMillis,
|
||||
MusicPlayerRemote.songDurationMillis
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun showBonceAnimation() {
|
||||
playPauseButton.apply {
|
||||
clearAnimation()
|
||||
|
@ -273,14 +256,23 @@ class BlurPlaybackControlsFragment : AbsPlayerControlsFragment() {
|
|||
}
|
||||
|
||||
override fun onUpdateProgressViews(progress: Int, total: Int) {
|
||||
progressSlider.max = total
|
||||
|
||||
val animator = ObjectAnimator.ofInt(progressSlider, "progress", progress)
|
||||
animator.duration = SLIDER_ANIMATION_TIME
|
||||
animator.interpolator = LinearInterpolator()
|
||||
animator.start()
|
||||
|
||||
if (total <= 0) {
|
||||
return
|
||||
}
|
||||
progressSlider.setRange(progress.toFloat(), total.toFloat())
|
||||
songTotalTime.text = MusicUtil.getReadableDurationString(total.toLong())
|
||||
songCurrentProgress.text = MusicUtil.getReadableDurationString(progress.toLong())
|
||||
}
|
||||
|
||||
override fun setUpProgressSlider() {
|
||||
progressSlider.addOnChangeListener { _, value, fromUser ->
|
||||
if (fromUser) {
|
||||
MusicPlayerRemote.seekTo(value.toInt())
|
||||
onUpdateProgressViews(
|
||||
MusicPlayerRemote.songProgressMillis,
|
||||
MusicPlayerRemote.songDurationMillis
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package code.name.monkey.retromusic.fragments.player.cardblur
|
||||
|
||||
import android.animation.ObjectAnimator
|
||||
import android.graphics.Color
|
||||
import android.graphics.PorterDuff
|
||||
import android.os.Bundle
|
||||
|
@ -8,19 +7,17 @@ import android.view.LayoutInflater
|
|||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.animation.DecelerateInterpolator
|
||||
import android.view.animation.LinearInterpolator
|
||||
import android.widget.SeekBar
|
||||
import code.name.monkey.appthemehelper.util.ColorUtil
|
||||
import code.name.monkey.appthemehelper.util.MaterialValueHelper
|
||||
import code.name.monkey.appthemehelper.util.TintHelper
|
||||
import code.name.monkey.retromusic.R
|
||||
import code.name.monkey.retromusic.extensions.hide
|
||||
import code.name.monkey.retromusic.extensions.setRange
|
||||
import code.name.monkey.retromusic.extensions.show
|
||||
import code.name.monkey.retromusic.fragments.base.AbsPlayerControlsFragment
|
||||
import code.name.monkey.retromusic.helper.MusicPlayerRemote
|
||||
import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper
|
||||
import code.name.monkey.retromusic.helper.PlayPauseButtonOnClickHandler
|
||||
import code.name.monkey.retromusic.misc.SimpleOnSeekbarChangeListener
|
||||
import code.name.monkey.retromusic.service.MusicService
|
||||
import code.name.monkey.retromusic.util.MusicUtil
|
||||
import code.name.monkey.retromusic.util.PreferenceUtil
|
||||
|
@ -210,29 +207,24 @@ class CardBlurPlaybackControlsFragment : AbsPlayerControlsFragment() {
|
|||
}
|
||||
}
|
||||
|
||||
override fun setUpProgressSlider() {
|
||||
progressSlider.setOnSeekBarChangeListener(object : SimpleOnSeekbarChangeListener() {
|
||||
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
|
||||
if (fromUser) {
|
||||
MusicPlayerRemote.seekTo(progress)
|
||||
onUpdateProgressViews(
|
||||
MusicPlayerRemote.songProgressMillis,
|
||||
MusicPlayerRemote.songDurationMillis
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
override fun onUpdateProgressViews(progress: Int, total: Int) {
|
||||
progressSlider.max = total
|
||||
|
||||
val animator = ObjectAnimator.ofInt(progressSlider, "progress", progress)
|
||||
animator.duration = SLIDER_ANIMATION_TIME
|
||||
animator.interpolator = LinearInterpolator()
|
||||
animator.start()
|
||||
|
||||
if (total <= 0) {
|
||||
return
|
||||
}
|
||||
progressSlider.setRange(progress.toFloat(), total.toFloat())
|
||||
songTotalTime.text = MusicUtil.getReadableDurationString(total.toLong())
|
||||
songCurrentProgress.text = MusicUtil.getReadableDurationString(progress.toLong())
|
||||
}
|
||||
|
||||
override fun setUpProgressSlider() {
|
||||
progressSlider.addOnChangeListener { _, value, fromUser ->
|
||||
if (fromUser) {
|
||||
MusicPlayerRemote.seekTo(value.toInt())
|
||||
onUpdateProgressViews(
|
||||
MusicPlayerRemote.songProgressMillis,
|
||||
MusicPlayerRemote.songDurationMillis
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,7 +14,6 @@
|
|||
|
||||
package code.name.monkey.retromusic.fragments.player.circle
|
||||
|
||||
import android.animation.ObjectAnimator
|
||||
import android.content.Context
|
||||
import android.graphics.Color
|
||||
import android.graphics.PorterDuff
|
||||
|
@ -23,8 +22,6 @@ import android.os.Bundle
|
|||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.animation.LinearInterpolator
|
||||
import android.widget.SeekBar
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
import code.name.monkey.appthemehelper.ThemeStore
|
||||
import code.name.monkey.appthemehelper.util.ATHUtil
|
||||
|
@ -32,14 +29,13 @@ import code.name.monkey.appthemehelper.util.TintHelper
|
|||
import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper
|
||||
import code.name.monkey.retromusic.R
|
||||
import code.name.monkey.retromusic.extensions.hide
|
||||
import code.name.monkey.retromusic.extensions.setRange
|
||||
import code.name.monkey.retromusic.extensions.show
|
||||
import code.name.monkey.retromusic.fragments.base.AbsPlayerControlsFragment
|
||||
import code.name.monkey.retromusic.fragments.base.AbsPlayerFragment
|
||||
import code.name.monkey.retromusic.helper.MusicPlayerRemote
|
||||
import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper
|
||||
import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper.Callback
|
||||
import code.name.monkey.retromusic.helper.PlayPauseButtonOnClickHandler
|
||||
import code.name.monkey.retromusic.misc.SimpleOnSeekbarChangeListener
|
||||
import code.name.monkey.retromusic.util.MusicUtil
|
||||
import code.name.monkey.retromusic.util.PreferenceUtil
|
||||
import code.name.monkey.retromusic.util.ViewUtil
|
||||
|
@ -100,14 +96,7 @@ class CirclePlayerFragment : AbsPlayerFragment(), Callback, OnAudioVolumeChanged
|
|||
}
|
||||
|
||||
private fun setupViews() {
|
||||
progressSlider.setOnSeekBarChangeListener(object : SimpleOnSeekbarChangeListener() {
|
||||
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
|
||||
if (fromUser) {
|
||||
MusicPlayerRemote.seekTo(progress)
|
||||
onUpdateProgressViews(MusicPlayerRemote.songProgressMillis, MusicPlayerRemote.songDurationMillis)
|
||||
}
|
||||
}
|
||||
})
|
||||
setUpProgressSlider()
|
||||
ViewUtil.setProgressDrawable(progressSlider, ThemeStore.accentColor(requireContext()))
|
||||
volumeSeekBar.progressColor = ThemeStore.accentColor(requireContext())
|
||||
setUpPlayPauseFab()
|
||||
|
@ -205,18 +194,6 @@ class CirclePlayerFragment : AbsPlayerFragment(), Callback, OnAudioVolumeChanged
|
|||
}
|
||||
}
|
||||
|
||||
override fun onUpdateProgressViews(progress: Int, total: Int) {
|
||||
progressSlider.max = total
|
||||
|
||||
val animator = ObjectAnimator.ofInt(progressSlider, "progress", progress)
|
||||
animator.duration = AbsPlayerControlsFragment.SLIDER_ANIMATION_TIME
|
||||
animator.interpolator = LinearInterpolator()
|
||||
animator.start()
|
||||
|
||||
songTotalTime.text = MusicUtil.getReadableDurationString(total.toLong())
|
||||
songCurrentProgress.text = MusicUtil.getReadableDurationString(progress.toLong())
|
||||
}
|
||||
|
||||
private fun updatePlayPauseDrawableState() {
|
||||
when {
|
||||
MusicPlayerRemote.isPlaying -> playPauseButton.setImageResource(R.drawable.ic_pause_white_24dp)
|
||||
|
@ -224,12 +201,13 @@ class CirclePlayerFragment : AbsPlayerFragment(), Callback, OnAudioVolumeChanged
|
|||
}
|
||||
}
|
||||
|
||||
override fun onAudioVolumeChanged(currentVolume: Int, maxVolume: Int) {
|
||||
override fun onAudioVolumeChanged(currentVolume: Float, maxVolume: Float) {
|
||||
if (volumeSeekBar == null) {
|
||||
return
|
||||
}
|
||||
volumeSeekBar.max = maxVolume
|
||||
volumeSeekBar.progress = currentVolume
|
||||
|
||||
volumeSeekBar.max = maxVolume.toInt()
|
||||
volumeSeekBar.progress = currentVolume.toInt()
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
|
@ -249,4 +227,25 @@ class CirclePlayerFragment : AbsPlayerFragment(), Callback, OnAudioVolumeChanged
|
|||
|
||||
override fun onStopTrackingTouch(seekArc: SeekArc?) {
|
||||
}
|
||||
|
||||
override fun onUpdateProgressViews(progress: Int, total: Int) {
|
||||
if (total <= 0) {
|
||||
return
|
||||
}
|
||||
progressSlider.setRange(progress.toFloat(), total.toFloat())
|
||||
songTotalTime.text = MusicUtil.getReadableDurationString(total.toLong())
|
||||
songCurrentProgress.text = MusicUtil.getReadableDurationString(progress.toLong())
|
||||
}
|
||||
|
||||
fun setUpProgressSlider() {
|
||||
progressSlider.addOnChangeListener { _, value, fromUser ->
|
||||
if (fromUser) {
|
||||
MusicPlayerRemote.seekTo(value.toInt())
|
||||
onUpdateProgressViews(
|
||||
MusicPlayerRemote.songProgressMillis,
|
||||
MusicPlayerRemote.songDurationMillis
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +1,5 @@
|
|||
package code.name.monkey.retromusic.fragments.player.color
|
||||
|
||||
import android.animation.ObjectAnimator
|
||||
import android.graphics.Color
|
||||
import android.graphics.PorterDuff
|
||||
import android.os.Bundle
|
||||
|
@ -8,33 +7,21 @@ import android.view.LayoutInflater
|
|||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.animation.DecelerateInterpolator
|
||||
import android.view.animation.LinearInterpolator
|
||||
import android.widget.SeekBar
|
||||
import code.name.monkey.appthemehelper.util.ColorUtil
|
||||
import code.name.monkey.appthemehelper.util.TintHelper
|
||||
import code.name.monkey.retromusic.R
|
||||
import code.name.monkey.retromusic.extensions.hide
|
||||
import code.name.monkey.retromusic.extensions.setRange
|
||||
import code.name.monkey.retromusic.extensions.show
|
||||
import code.name.monkey.retromusic.fragments.base.AbsPlayerControlsFragment
|
||||
import code.name.monkey.retromusic.helper.MusicPlayerRemote
|
||||
import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper
|
||||
import code.name.monkey.retromusic.helper.PlayPauseButtonOnClickHandler
|
||||
import code.name.monkey.retromusic.misc.SimpleOnSeekbarChangeListener
|
||||
import code.name.monkey.retromusic.service.MusicService
|
||||
import code.name.monkey.retromusic.util.MusicUtil
|
||||
import code.name.monkey.retromusic.util.PreferenceUtil
|
||||
import code.name.monkey.retromusic.util.ViewUtil
|
||||
import kotlinx.android.synthetic.main.fragment_color_player_playback_controls.nextButton
|
||||
import kotlinx.android.synthetic.main.fragment_color_player_playback_controls.playPauseButton
|
||||
import kotlinx.android.synthetic.main.fragment_color_player_playback_controls.previousButton
|
||||
import kotlinx.android.synthetic.main.fragment_color_player_playback_controls.progressSlider
|
||||
import kotlinx.android.synthetic.main.fragment_color_player_playback_controls.repeatButton
|
||||
import kotlinx.android.synthetic.main.fragment_color_player_playback_controls.shuffleButton
|
||||
import kotlinx.android.synthetic.main.fragment_color_player_playback_controls.songCurrentProgress
|
||||
import kotlinx.android.synthetic.main.fragment_color_player_playback_controls.songInfo
|
||||
import kotlinx.android.synthetic.main.fragment_color_player_playback_controls.songTotalTime
|
||||
import kotlinx.android.synthetic.main.fragment_color_player_playback_controls.text
|
||||
import kotlinx.android.synthetic.main.fragment_color_player_playback_controls.title
|
||||
import kotlinx.android.synthetic.main.fragment_color_player_playback_controls.*
|
||||
|
||||
class ColorPlaybackControlsFragment : AbsPlayerControlsFragment() {
|
||||
|
||||
|
@ -47,7 +34,11 @@ class ColorPlaybackControlsFragment : AbsPlayerControlsFragment() {
|
|||
progressViewUpdateHelper = MusicProgressViewUpdateHelper(this)
|
||||
}
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
return inflater.inflate(R.layout.fragment_color_player_playback_controls, container, false)
|
||||
}
|
||||
|
||||
|
@ -173,7 +164,10 @@ class ColorPlaybackControlsFragment : AbsPlayerControlsFragment() {
|
|||
lastPlaybackControlsColor,
|
||||
PorterDuff.Mode.SRC_IN
|
||||
)
|
||||
else -> shuffleButton.setColorFilter(lastDisabledPlaybackControlsColor, PorterDuff.Mode.SRC_IN)
|
||||
else -> shuffleButton.setColorFilter(
|
||||
lastDisabledPlaybackControlsColor,
|
||||
PorterDuff.Mode.SRC_IN
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -185,7 +179,10 @@ class ColorPlaybackControlsFragment : AbsPlayerControlsFragment() {
|
|||
when (MusicPlayerRemote.repeatMode) {
|
||||
MusicService.REPEAT_MODE_NONE -> {
|
||||
repeatButton.setImageResource(R.drawable.ic_repeat_white_24dp)
|
||||
repeatButton.setColorFilter(lastDisabledPlaybackControlsColor, PorterDuff.Mode.SRC_IN)
|
||||
repeatButton.setColorFilter(
|
||||
lastDisabledPlaybackControlsColor,
|
||||
PorterDuff.Mode.SRC_IN
|
||||
)
|
||||
}
|
||||
MusicService.REPEAT_MODE_ALL -> {
|
||||
repeatButton.setImageResource(R.drawable.ic_repeat_white_24dp)
|
||||
|
@ -215,26 +212,24 @@ class ColorPlaybackControlsFragment : AbsPlayerControlsFragment() {
|
|||
}
|
||||
}
|
||||
|
||||
override fun setUpProgressSlider() {
|
||||
progressSlider.setOnSeekBarChangeListener(object : SimpleOnSeekbarChangeListener() {
|
||||
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
|
||||
if (fromUser) {
|
||||
MusicPlayerRemote.seekTo(progress)
|
||||
onUpdateProgressViews(MusicPlayerRemote.songProgressMillis, MusicPlayerRemote.songDurationMillis)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
override fun onUpdateProgressViews(progress: Int, total: Int) {
|
||||
progressSlider!!.max = total
|
||||
|
||||
val animator = ObjectAnimator.ofInt(progressSlider, "progress", progress)
|
||||
animator.duration = SLIDER_ANIMATION_TIME
|
||||
animator.interpolator = LinearInterpolator()
|
||||
animator.start()
|
||||
|
||||
if (total <= 0) {
|
||||
return
|
||||
}
|
||||
progressSlider.setRange(progress.toFloat(), total.toFloat())
|
||||
songTotalTime.text = MusicUtil.getReadableDurationString(total.toLong())
|
||||
songCurrentProgress.text = MusicUtil.getReadableDurationString(progress.toLong())
|
||||
}
|
||||
|
||||
override fun setUpProgressSlider() {
|
||||
progressSlider.addOnChangeListener { _, value, fromUser ->
|
||||
if (fromUser) {
|
||||
MusicPlayerRemote.seekTo(value.toInt())
|
||||
onUpdateProgressViews(
|
||||
MusicPlayerRemote.songProgressMillis,
|
||||
MusicPlayerRemote.songDurationMillis
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,17 +27,7 @@ import code.name.monkey.retromusic.misc.SimpleOnSeekbarChangeListener
|
|||
import code.name.monkey.retromusic.service.MusicService
|
||||
import code.name.monkey.retromusic.util.MusicUtil
|
||||
import code.name.monkey.retromusic.util.PreferenceUtil
|
||||
import kotlinx.android.synthetic.main.fragment_fit_playback_controls.nextButton
|
||||
import kotlinx.android.synthetic.main.fragment_fit_playback_controls.playPauseButton
|
||||
import kotlinx.android.synthetic.main.fragment_fit_playback_controls.previousButton
|
||||
import kotlinx.android.synthetic.main.fragment_fit_playback_controls.progressSlider
|
||||
import kotlinx.android.synthetic.main.fragment_fit_playback_controls.repeatButton
|
||||
import kotlinx.android.synthetic.main.fragment_fit_playback_controls.shuffleButton
|
||||
import kotlinx.android.synthetic.main.fragment_fit_playback_controls.songCurrentProgress
|
||||
import kotlinx.android.synthetic.main.fragment_fit_playback_controls.songInfo
|
||||
import kotlinx.android.synthetic.main.fragment_fit_playback_controls.songTotalTime
|
||||
import kotlinx.android.synthetic.main.fragment_fit_playback_controls.text
|
||||
import kotlinx.android.synthetic.main.fragment_fit_playback_controls.title
|
||||
import kotlinx.android.synthetic.main.fragment_fit_playback_controls.*
|
||||
|
||||
class FitPlaybackControlsFragment : AbsPlayerControlsFragment() {
|
||||
|
||||
|
@ -126,10 +116,12 @@ class FitPlaybackControlsFragment : AbsPlayerControlsFragment() {
|
|||
val colorBg = ATHUtil.resolveColor(activity, android.R.attr.colorBackground)
|
||||
if (ColorUtil.isColorLight(colorBg)) {
|
||||
lastPlaybackControlsColor = MaterialValueHelper.getSecondaryTextColor(activity, true)
|
||||
lastDisabledPlaybackControlsColor = MaterialValueHelper.getSecondaryDisabledTextColor(activity, true)
|
||||
lastDisabledPlaybackControlsColor =
|
||||
MaterialValueHelper.getSecondaryDisabledTextColor(activity, true)
|
||||
} else {
|
||||
lastPlaybackControlsColor = MaterialValueHelper.getPrimaryTextColor(activity, false)
|
||||
lastDisabledPlaybackControlsColor = MaterialValueHelper.getPrimaryDisabledTextColor(activity, false)
|
||||
lastDisabledPlaybackControlsColor =
|
||||
MaterialValueHelper.getPrimaryDisabledTextColor(activity, false)
|
||||
}
|
||||
|
||||
val colorFinal = if (PreferenceUtil.getInstance(requireContext()).adaptiveColor) {
|
||||
|
@ -196,7 +188,10 @@ class FitPlaybackControlsFragment : AbsPlayerControlsFragment() {
|
|||
lastPlaybackControlsColor,
|
||||
PorterDuff.Mode.SRC_IN
|
||||
)
|
||||
else -> shuffleButton.setColorFilter(lastDisabledPlaybackControlsColor, PorterDuff.Mode.SRC_IN)
|
||||
else -> shuffleButton.setColorFilter(
|
||||
lastDisabledPlaybackControlsColor,
|
||||
PorterDuff.Mode.SRC_IN
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -208,7 +203,10 @@ class FitPlaybackControlsFragment : AbsPlayerControlsFragment() {
|
|||
when (MusicPlayerRemote.repeatMode) {
|
||||
MusicService.REPEAT_MODE_NONE -> {
|
||||
repeatButton.setImageResource(R.drawable.ic_repeat_white_24dp)
|
||||
repeatButton.setColorFilter(lastDisabledPlaybackControlsColor, PorterDuff.Mode.SRC_IN)
|
||||
repeatButton.setColorFilter(
|
||||
lastDisabledPlaybackControlsColor,
|
||||
PorterDuff.Mode.SRC_IN
|
||||
)
|
||||
}
|
||||
MusicService.REPEAT_MODE_ALL -> {
|
||||
repeatButton.setImageResource(R.drawable.ic_repeat_white_24dp)
|
||||
|
|
|
@ -1,14 +1,11 @@
|
|||
package code.name.monkey.retromusic.fragments.player.flat
|
||||
|
||||
import android.animation.ObjectAnimator
|
||||
import android.graphics.PorterDuff
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.animation.DecelerateInterpolator
|
||||
import android.view.animation.LinearInterpolator
|
||||
import android.widget.SeekBar
|
||||
import code.name.monkey.appthemehelper.ThemeStore
|
||||
import code.name.monkey.appthemehelper.util.ATHUtil
|
||||
import code.name.monkey.appthemehelper.util.ColorUtil
|
||||
|
@ -17,26 +14,18 @@ import code.name.monkey.appthemehelper.util.TintHelper
|
|||
import code.name.monkey.retromusic.R
|
||||
import code.name.monkey.retromusic.extensions.hide
|
||||
import code.name.monkey.retromusic.extensions.ripAlpha
|
||||
import code.name.monkey.retromusic.extensions.setRange
|
||||
import code.name.monkey.retromusic.extensions.show
|
||||
import code.name.monkey.retromusic.fragments.base.AbsPlayerControlsFragment
|
||||
import code.name.monkey.retromusic.helper.MusicPlayerRemote
|
||||
import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper
|
||||
import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper.Callback
|
||||
import code.name.monkey.retromusic.helper.PlayPauseButtonOnClickHandler
|
||||
import code.name.monkey.retromusic.misc.SimpleOnSeekbarChangeListener
|
||||
import code.name.monkey.retromusic.service.MusicService
|
||||
import code.name.monkey.retromusic.util.MusicUtil
|
||||
import code.name.monkey.retromusic.util.PreferenceUtil
|
||||
import code.name.monkey.retromusic.util.ViewUtil
|
||||
import kotlinx.android.synthetic.main.fragment_flat_player_playback_controls.playPauseButton
|
||||
import kotlinx.android.synthetic.main.fragment_flat_player_playback_controls.progressSlider
|
||||
import kotlinx.android.synthetic.main.fragment_flat_player_playback_controls.repeatButton
|
||||
import kotlinx.android.synthetic.main.fragment_flat_player_playback_controls.shuffleButton
|
||||
import kotlinx.android.synthetic.main.fragment_flat_player_playback_controls.songCurrentProgress
|
||||
import kotlinx.android.synthetic.main.fragment_flat_player_playback_controls.songInfo
|
||||
import kotlinx.android.synthetic.main.fragment_flat_player_playback_controls.songTotalTime
|
||||
import kotlinx.android.synthetic.main.fragment_flat_player_playback_controls.text
|
||||
import kotlinx.android.synthetic.main.fragment_flat_player_playback_controls.title
|
||||
import kotlinx.android.synthetic.main.fragment_flat_player_playback_controls.*
|
||||
|
||||
class FlatPlaybackControlsFragment : AbsPlayerControlsFragment(), Callback {
|
||||
|
||||
|
@ -73,18 +62,6 @@ class FlatPlaybackControlsFragment : AbsPlayerControlsFragment(), Callback {
|
|||
progressViewUpdateHelper.stop()
|
||||
}
|
||||
|
||||
override fun onUpdateProgressViews(progress: Int, total: Int) {
|
||||
progressSlider.max = total
|
||||
|
||||
val animator = ObjectAnimator.ofInt(progressSlider, "progress", progress)
|
||||
animator.duration = SLIDER_ANIMATION_TIME
|
||||
animator.interpolator = LinearInterpolator()
|
||||
animator.start()
|
||||
|
||||
songTotalTime.text = MusicUtil.getReadableDurationString(total.toLong())
|
||||
songCurrentProgress.text = MusicUtil.getReadableDurationString(progress.toLong())
|
||||
}
|
||||
|
||||
public override fun show() {
|
||||
playPauseButton!!.animate()
|
||||
.scaleX(1f)
|
||||
|
@ -106,10 +83,12 @@ class FlatPlaybackControlsFragment : AbsPlayerControlsFragment(), Callback {
|
|||
val isDark = ColorUtil.isColorLight(colorBg)
|
||||
if (isDark) {
|
||||
lastPlaybackControlsColor = MaterialValueHelper.getSecondaryTextColor(activity, true)
|
||||
lastDisabledPlaybackControlsColor = MaterialValueHelper.getSecondaryDisabledTextColor(activity, true)
|
||||
lastDisabledPlaybackControlsColor =
|
||||
MaterialValueHelper.getSecondaryDisabledTextColor(activity, true)
|
||||
} else {
|
||||
lastPlaybackControlsColor = MaterialValueHelper.getPrimaryTextColor(activity, false)
|
||||
lastDisabledPlaybackControlsColor = MaterialValueHelper.getPrimaryDisabledTextColor(activity, false)
|
||||
lastDisabledPlaybackControlsColor =
|
||||
MaterialValueHelper.getPrimaryDisabledTextColor(activity, false)
|
||||
}
|
||||
|
||||
val colorFinal = if (PreferenceUtil.getInstance(requireContext()).adaptiveColor) {
|
||||
|
@ -131,7 +110,8 @@ class FlatPlaybackControlsFragment : AbsPlayerControlsFragment(), Callback {
|
|||
val isDark = ColorUtil.isColorLight(color)
|
||||
val darkColor = ColorUtil.darkenColor(color)
|
||||
val colorPrimary = MaterialValueHelper.getPrimaryTextColor(context, isDark)
|
||||
val colorSecondary = MaterialValueHelper.getSecondaryTextColor(context, ColorUtil.isColorLight(darkColor))
|
||||
val colorSecondary =
|
||||
MaterialValueHelper.getSecondaryTextColor(context, ColorUtil.isColorLight(darkColor))
|
||||
|
||||
TintHelper.setTintAuto(playPauseButton!!, colorPrimary, false)
|
||||
TintHelper.setTintAuto(playPauseButton!!, color, true)
|
||||
|
@ -191,18 +171,25 @@ class FlatPlaybackControlsFragment : AbsPlayerControlsFragment(), Callback {
|
|||
}
|
||||
}
|
||||
|
||||
override fun onUpdateProgressViews(progress: Int, total: Int) {
|
||||
if (total <= 0) {
|
||||
return
|
||||
}
|
||||
progressSlider.setRange(progress.toFloat(), total.toFloat())
|
||||
songTotalTime.text = MusicUtil.getReadableDurationString(total.toLong())
|
||||
songCurrentProgress.text = MusicUtil.getReadableDurationString(progress.toLong())
|
||||
}
|
||||
|
||||
override fun setUpProgressSlider() {
|
||||
progressSlider!!.setOnSeekBarChangeListener(object : SimpleOnSeekbarChangeListener() {
|
||||
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
|
||||
if (fromUser) {
|
||||
MusicPlayerRemote.seekTo(progress)
|
||||
onUpdateProgressViews(
|
||||
MusicPlayerRemote.songProgressMillis,
|
||||
MusicPlayerRemote.songDurationMillis
|
||||
)
|
||||
}
|
||||
progressSlider.addOnChangeListener { _, value, fromUser ->
|
||||
if (fromUser) {
|
||||
MusicPlayerRemote.seekTo(value.toInt())
|
||||
onUpdateProgressViews(
|
||||
MusicPlayerRemote.songProgressMillis,
|
||||
MusicPlayerRemote.songDurationMillis
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
override fun onRepeatModeChanged() {
|
||||
|
@ -221,7 +208,10 @@ class FlatPlaybackControlsFragment : AbsPlayerControlsFragment(), Callback {
|
|||
when (MusicPlayerRemote.repeatMode) {
|
||||
MusicService.REPEAT_MODE_NONE -> {
|
||||
repeatButton.setImageResource(R.drawable.ic_repeat_white_24dp)
|
||||
repeatButton.setColorFilter(lastDisabledPlaybackControlsColor, PorterDuff.Mode.SRC_IN)
|
||||
repeatButton.setColorFilter(
|
||||
lastDisabledPlaybackControlsColor,
|
||||
PorterDuff.Mode.SRC_IN
|
||||
)
|
||||
}
|
||||
MusicService.REPEAT_MODE_ALL -> {
|
||||
repeatButton.setImageResource(R.drawable.ic_repeat_white_24dp)
|
||||
|
@ -244,7 +234,10 @@ class FlatPlaybackControlsFragment : AbsPlayerControlsFragment(), Callback {
|
|||
lastPlaybackControlsColor,
|
||||
PorterDuff.Mode.SRC_IN
|
||||
)
|
||||
else -> shuffleButton.setColorFilter(lastDisabledPlaybackControlsColor, PorterDuff.Mode.SRC_IN)
|
||||
else -> shuffleButton.setColorFilter(
|
||||
lastDisabledPlaybackControlsColor,
|
||||
PorterDuff.Mode.SRC_IN
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package code.name.monkey.retromusic.fragments.player.full
|
||||
|
||||
import android.animation.ObjectAnimator
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.res.ColorStateList
|
||||
import android.graphics.Color
|
||||
|
@ -12,9 +11,7 @@ import android.view.MenuItem
|
|||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.animation.DecelerateInterpolator
|
||||
import android.view.animation.LinearInterpolator
|
||||
import android.widget.PopupMenu
|
||||
import android.widget.SeekBar
|
||||
import androidx.core.content.ContextCompat
|
||||
import code.name.monkey.appthemehelper.ThemeStore
|
||||
import code.name.monkey.appthemehelper.util.ColorUtil
|
||||
|
@ -23,36 +20,25 @@ import code.name.monkey.appthemehelper.util.TintHelper
|
|||
import code.name.monkey.retromusic.R
|
||||
import code.name.monkey.retromusic.extensions.hide
|
||||
import code.name.monkey.retromusic.extensions.ripAlpha
|
||||
import code.name.monkey.retromusic.extensions.setRange
|
||||
import code.name.monkey.retromusic.extensions.show
|
||||
import code.name.monkey.retromusic.fragments.base.AbsPlayerControlsFragment
|
||||
import code.name.monkey.retromusic.helper.MusicPlayerRemote
|
||||
import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper
|
||||
import code.name.monkey.retromusic.helper.PlayPauseButtonOnClickHandler
|
||||
import code.name.monkey.retromusic.misc.SimpleOnSeekbarChangeListener
|
||||
import code.name.monkey.retromusic.model.Song
|
||||
import code.name.monkey.retromusic.service.MusicService
|
||||
import code.name.monkey.retromusic.util.MusicUtil
|
||||
import code.name.monkey.retromusic.util.PreferenceUtil
|
||||
import code.name.monkey.retromusic.util.ViewUtil
|
||||
import kotlinx.android.synthetic.main.fragment_full_player_controls.nextButton
|
||||
import kotlinx.android.synthetic.main.fragment_full_player_controls.playPauseButton
|
||||
import kotlinx.android.synthetic.main.fragment_full_player_controls.playerMenu
|
||||
import kotlinx.android.synthetic.main.fragment_full_player_controls.previousButton
|
||||
import kotlinx.android.synthetic.main.fragment_full_player_controls.progressSlider
|
||||
import kotlinx.android.synthetic.main.fragment_full_player_controls.repeatButton
|
||||
import kotlinx.android.synthetic.main.fragment_full_player_controls.shuffleButton
|
||||
import kotlinx.android.synthetic.main.fragment_full_player_controls.songCurrentProgress
|
||||
import kotlinx.android.synthetic.main.fragment_full_player_controls.songFavourite
|
||||
import kotlinx.android.synthetic.main.fragment_full_player_controls.songInfo
|
||||
import kotlinx.android.synthetic.main.fragment_full_player_controls.songTotalTime
|
||||
import kotlinx.android.synthetic.main.fragment_full_player_controls.text
|
||||
import kotlinx.android.synthetic.main.fragment_full_player_controls.title
|
||||
import kotlinx.android.synthetic.main.fragment_full_player_controls.*
|
||||
|
||||
/**
|
||||
* Created by hemanths on 20/09/17.
|
||||
*/
|
||||
|
||||
class FullPlaybackControlsFragment : AbsPlayerControlsFragment(), PopupMenu.OnMenuItemClickListener {
|
||||
class FullPlaybackControlsFragment : AbsPlayerControlsFragment(),
|
||||
PopupMenu.OnMenuItemClickListener {
|
||||
|
||||
private var lastPlaybackControlsColor: Int = 0
|
||||
private var lastDisabledPlaybackControlsColor: Int = 0
|
||||
|
@ -91,17 +77,6 @@ class FullPlaybackControlsFragment : AbsPlayerControlsFragment(), PopupMenu.OnMe
|
|||
progressViewUpdateHelper!!.stop()
|
||||
}
|
||||
|
||||
override fun onUpdateProgressViews(progress: Int, total: Int) {
|
||||
progressSlider.max = total
|
||||
|
||||
val animator = ObjectAnimator.ofInt(progressSlider, "progress", progress)
|
||||
animator.duration = SLIDER_ANIMATION_TIME
|
||||
animator.interpolator = LinearInterpolator()
|
||||
animator.start()
|
||||
|
||||
songTotalTime.text = MusicUtil.getReadableDurationString(total.toLong())
|
||||
songCurrentProgress.text = MusicUtil.getReadableDurationString(progress.toLong())
|
||||
}
|
||||
|
||||
public override fun show() {
|
||||
playPauseButton!!.animate()
|
||||
|
@ -228,15 +203,25 @@ class FullPlaybackControlsFragment : AbsPlayerControlsFragment(), PopupMenu.OnMe
|
|||
previousButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN)
|
||||
}
|
||||
|
||||
override fun onUpdateProgressViews(progress: Int, total: Int) {
|
||||
if (total <= 0) {
|
||||
return
|
||||
}
|
||||
progressSlider.setRange(progress.toFloat(), total.toFloat())
|
||||
songTotalTime.text = MusicUtil.getReadableDurationString(total.toLong())
|
||||
songCurrentProgress.text = MusicUtil.getReadableDurationString(progress.toLong())
|
||||
}
|
||||
|
||||
override fun setUpProgressSlider() {
|
||||
progressSlider!!.setOnSeekBarChangeListener(object : SimpleOnSeekbarChangeListener() {
|
||||
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
|
||||
if (fromUser) {
|
||||
MusicPlayerRemote.seekTo(progress)
|
||||
onUpdateProgressViews(MusicPlayerRemote.songProgressMillis, MusicPlayerRemote.songDurationMillis)
|
||||
}
|
||||
progressSlider.addOnChangeListener { _, value, fromUser ->
|
||||
if (fromUser) {
|
||||
MusicPlayerRemote.seekTo(value.toInt())
|
||||
onUpdateProgressViews(
|
||||
MusicPlayerRemote.songProgressMillis,
|
||||
MusicPlayerRemote.songDurationMillis
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
override fun onRepeatModeChanged() {
|
||||
|
@ -257,7 +242,10 @@ class FullPlaybackControlsFragment : AbsPlayerControlsFragment(), PopupMenu.OnMe
|
|||
lastPlaybackControlsColor,
|
||||
PorterDuff.Mode.SRC_IN
|
||||
)
|
||||
else -> shuffleButton.setColorFilter(lastDisabledPlaybackControlsColor, PorterDuff.Mode.SRC_IN)
|
||||
else -> shuffleButton.setColorFilter(
|
||||
lastDisabledPlaybackControlsColor,
|
||||
PorterDuff.Mode.SRC_IN
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -269,7 +257,10 @@ class FullPlaybackControlsFragment : AbsPlayerControlsFragment(), PopupMenu.OnMe
|
|||
when (MusicPlayerRemote.repeatMode) {
|
||||
MusicService.REPEAT_MODE_NONE -> {
|
||||
repeatButton.setImageResource(R.drawable.ic_repeat_white_24dp)
|
||||
repeatButton.setColorFilter(lastDisabledPlaybackControlsColor, PorterDuff.Mode.SRC_IN)
|
||||
repeatButton.setColorFilter(
|
||||
lastDisabledPlaybackControlsColor,
|
||||
PorterDuff.Mode.SRC_IN
|
||||
)
|
||||
}
|
||||
MusicService.REPEAT_MODE_ALL -> {
|
||||
repeatButton.setImageResource(R.drawable.ic_repeat_white_24dp)
|
||||
|
@ -300,7 +291,7 @@ class FullPlaybackControlsFragment : AbsPlayerControlsFragment(), PopupMenu.OnMe
|
|||
@SuppressLint("StaticFieldLeak")
|
||||
fun updateIsFavorite() {
|
||||
if (updateIsFavoriteTask != null) {
|
||||
updateIsFavoriteTask!!.cancel(false)
|
||||
updateIsFavoriteTask?.cancel(false)
|
||||
}
|
||||
updateIsFavoriteTask = object : AsyncTask<Song, Void, Boolean>() {
|
||||
override fun doInBackground(vararg params: Song): Boolean? {
|
||||
|
|
|
@ -1,41 +1,24 @@
|
|||
package code.name.monkey.retromusic.fragments.player.material
|
||||
|
||||
import android.animation.ObjectAnimator
|
||||
import android.graphics.PorterDuff
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.animation.LinearInterpolator
|
||||
import android.widget.SeekBar
|
||||
import code.name.monkey.appthemehelper.util.ATHUtil
|
||||
import code.name.monkey.appthemehelper.util.ColorUtil
|
||||
import code.name.monkey.appthemehelper.util.MaterialValueHelper
|
||||
import code.name.monkey.retromusic.R
|
||||
import code.name.monkey.retromusic.extensions.hide
|
||||
import code.name.monkey.retromusic.extensions.ripAlpha
|
||||
import code.name.monkey.retromusic.extensions.show
|
||||
import code.name.monkey.retromusic.extensions.textColorSecondary
|
||||
import code.name.monkey.retromusic.extensions.*
|
||||
import code.name.monkey.retromusic.fragments.base.AbsPlayerControlsFragment
|
||||
import code.name.monkey.retromusic.helper.MusicPlayerRemote
|
||||
import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper
|
||||
import code.name.monkey.retromusic.helper.PlayPauseButtonOnClickHandler
|
||||
import code.name.monkey.retromusic.misc.SimpleOnSeekbarChangeListener
|
||||
import code.name.monkey.retromusic.service.MusicService
|
||||
import code.name.monkey.retromusic.util.MusicUtil
|
||||
import code.name.monkey.retromusic.util.PreferenceUtil
|
||||
import code.name.monkey.retromusic.util.ViewUtil
|
||||
import kotlinx.android.synthetic.main.fragment_material_playback_controls.nextButton
|
||||
import kotlinx.android.synthetic.main.fragment_material_playback_controls.playPauseButton
|
||||
import kotlinx.android.synthetic.main.fragment_material_playback_controls.previousButton
|
||||
import kotlinx.android.synthetic.main.fragment_material_playback_controls.progressSlider
|
||||
import kotlinx.android.synthetic.main.fragment_material_playback_controls.repeatButton
|
||||
import kotlinx.android.synthetic.main.fragment_material_playback_controls.shuffleButton
|
||||
import kotlinx.android.synthetic.main.fragment_material_playback_controls.songCurrentProgress
|
||||
import kotlinx.android.synthetic.main.fragment_material_playback_controls.songInfo
|
||||
import kotlinx.android.synthetic.main.fragment_material_playback_controls.songTotalTime
|
||||
import kotlinx.android.synthetic.main.fragment_material_playback_controls.text
|
||||
import kotlinx.android.synthetic.main.fragment_material_playback_controls.title
|
||||
import kotlinx.android.synthetic.main.fragment_material_playback_controls.*
|
||||
|
||||
/**
|
||||
* @author Hemanth S (h4h13).
|
||||
|
@ -115,11 +98,13 @@ class MaterialControlsFragment : AbsPlayerControlsFragment() {
|
|||
override fun setDark(color: Int) {
|
||||
val colorBg = ATHUtil.resolveColor(requireContext(), R.attr.colorSurface)
|
||||
if (ColorUtil.isColorLight(colorBg)) {
|
||||
lastPlaybackControlsColor = MaterialValueHelper.getSecondaryTextColor(requireContext(), true)
|
||||
lastPlaybackControlsColor =
|
||||
MaterialValueHelper.getSecondaryTextColor(requireContext(), true)
|
||||
lastDisabledPlaybackControlsColor =
|
||||
MaterialValueHelper.getSecondaryDisabledTextColor(requireContext(), true)
|
||||
} else {
|
||||
lastPlaybackControlsColor = MaterialValueHelper.getPrimaryTextColor(requireContext(), false)
|
||||
lastPlaybackControlsColor =
|
||||
MaterialValueHelper.getPrimaryTextColor(requireContext(), false)
|
||||
lastDisabledPlaybackControlsColor =
|
||||
MaterialValueHelper.getPrimaryDisabledTextColor(requireContext(), false)
|
||||
}
|
||||
|
@ -187,7 +172,10 @@ class MaterialControlsFragment : AbsPlayerControlsFragment() {
|
|||
lastPlaybackControlsColor,
|
||||
PorterDuff.Mode.SRC_IN
|
||||
)
|
||||
else -> shuffleButton.setColorFilter(lastDisabledPlaybackControlsColor, PorterDuff.Mode.SRC_IN)
|
||||
else -> shuffleButton.setColorFilter(
|
||||
lastDisabledPlaybackControlsColor,
|
||||
PorterDuff.Mode.SRC_IN
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -199,7 +187,10 @@ class MaterialControlsFragment : AbsPlayerControlsFragment() {
|
|||
when (MusicPlayerRemote.repeatMode) {
|
||||
MusicService.REPEAT_MODE_NONE -> {
|
||||
repeatButton.setImageResource(R.drawable.ic_repeat_white_24dp)
|
||||
repeatButton.setColorFilter(lastDisabledPlaybackControlsColor, PorterDuff.Mode.SRC_IN)
|
||||
repeatButton.setColorFilter(
|
||||
lastDisabledPlaybackControlsColor,
|
||||
PorterDuff.Mode.SRC_IN
|
||||
)
|
||||
}
|
||||
MusicService.REPEAT_MODE_ALL -> {
|
||||
repeatButton.setImageResource(R.drawable.ic_repeat_white_24dp)
|
||||
|
@ -218,29 +209,24 @@ class MaterialControlsFragment : AbsPlayerControlsFragment() {
|
|||
public override fun hide() {
|
||||
}
|
||||
|
||||
override fun setUpProgressSlider() {
|
||||
progressSlider.setOnSeekBarChangeListener(object : SimpleOnSeekbarChangeListener() {
|
||||
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
|
||||
if (fromUser) {
|
||||
MusicPlayerRemote.seekTo(progress)
|
||||
onUpdateProgressViews(
|
||||
MusicPlayerRemote.songProgressMillis,
|
||||
MusicPlayerRemote.songDurationMillis
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
override fun onUpdateProgressViews(progress: Int, total: Int) {
|
||||
progressSlider!!.max = total
|
||||
|
||||
val animator = ObjectAnimator.ofInt(progressSlider, "progress", progress)
|
||||
animator.duration = SLIDER_ANIMATION_TIME
|
||||
animator.interpolator = LinearInterpolator()
|
||||
animator.start()
|
||||
|
||||
if (total <= 0) {
|
||||
return
|
||||
}
|
||||
progressSlider.setRange(progress.toFloat(), total.toFloat())
|
||||
songTotalTime.text = MusicUtil.getReadableDurationString(total.toLong())
|
||||
songCurrentProgress.text = MusicUtil.getReadableDurationString(progress.toLong())
|
||||
}
|
||||
|
||||
override fun setUpProgressSlider() {
|
||||
progressSlider.addOnChangeListener { _, value, fromUser ->
|
||||
if (fromUser) {
|
||||
MusicPlayerRemote.seekTo(value.toInt())
|
||||
onUpdateProgressViews(
|
||||
MusicPlayerRemote.songProgressMillis,
|
||||
MusicPlayerRemote.songDurationMillis
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package code.name.monkey.retromusic.fragments.player.normal
|
||||
|
||||
import android.animation.ObjectAnimator
|
||||
import android.content.SharedPreferences
|
||||
import android.content.SharedPreferences.OnSharedPreferenceChangeListener
|
||||
import android.graphics.PorterDuff
|
||||
|
@ -9,8 +8,6 @@ import android.view.LayoutInflater
|
|||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.animation.DecelerateInterpolator
|
||||
import android.view.animation.LinearInterpolator
|
||||
import android.widget.SeekBar
|
||||
import code.name.monkey.appthemehelper.ThemeStore
|
||||
import code.name.monkey.appthemehelper.util.ATHUtil
|
||||
import code.name.monkey.appthemehelper.util.ColorUtil
|
||||
|
@ -19,29 +16,20 @@ import code.name.monkey.appthemehelper.util.TintHelper
|
|||
import code.name.monkey.retromusic.R
|
||||
import code.name.monkey.retromusic.extensions.hide
|
||||
import code.name.monkey.retromusic.extensions.ripAlpha
|
||||
import code.name.monkey.retromusic.extensions.setRange
|
||||
import code.name.monkey.retromusic.extensions.show
|
||||
import code.name.monkey.retromusic.fragments.base.AbsPlayerControlsFragment
|
||||
import code.name.monkey.retromusic.helper.MusicPlayerRemote
|
||||
import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper
|
||||
import code.name.monkey.retromusic.helper.PlayPauseButtonOnClickHandler
|
||||
import code.name.monkey.retromusic.misc.SimpleOnSeekbarChangeListener
|
||||
import code.name.monkey.retromusic.service.MusicService
|
||||
import code.name.monkey.retromusic.util.MusicUtil
|
||||
import code.name.monkey.retromusic.util.PreferenceUtil
|
||||
import code.name.monkey.retromusic.util.ViewUtil
|
||||
import kotlinx.android.synthetic.main.fragment_player_playback_controls.nextButton
|
||||
import kotlinx.android.synthetic.main.fragment_player_playback_controls.playPauseButton
|
||||
import kotlinx.android.synthetic.main.fragment_player_playback_controls.previousButton
|
||||
import kotlinx.android.synthetic.main.fragment_player_playback_controls.progressSlider
|
||||
import kotlinx.android.synthetic.main.fragment_player_playback_controls.repeatButton
|
||||
import kotlinx.android.synthetic.main.fragment_player_playback_controls.shuffleButton
|
||||
import kotlinx.android.synthetic.main.fragment_player_playback_controls.songCurrentProgress
|
||||
import kotlinx.android.synthetic.main.fragment_player_playback_controls.songInfo
|
||||
import kotlinx.android.synthetic.main.fragment_player_playback_controls.songTotalTime
|
||||
import kotlinx.android.synthetic.main.fragment_player_playback_controls.text
|
||||
import kotlinx.android.synthetic.main.fragment_player_playback_controls.title
|
||||
import kotlinx.android.synthetic.main.fragment_player_playback_controls.*
|
||||
|
||||
class PlayerPlaybackControlsFragment : AbsPlayerControlsFragment(), OnSharedPreferenceChangeListener {
|
||||
class PlayerPlaybackControlsFragment : AbsPlayerControlsFragment(),
|
||||
OnSharedPreferenceChangeListener {
|
||||
|
||||
private var lastPlaybackControlsColor: Int = 0
|
||||
private var lastDisabledPlaybackControlsColor: Int = 0
|
||||
|
@ -77,11 +65,13 @@ class PlayerPlaybackControlsFragment : AbsPlayerControlsFragment(), OnSharedPref
|
|||
override fun setDark(color: Int) {
|
||||
val colorBg = ATHUtil.resolveColor(requireContext(), android.R.attr.colorBackground)
|
||||
if (ColorUtil.isColorLight(colorBg)) {
|
||||
lastPlaybackControlsColor = MaterialValueHelper.getSecondaryTextColor(requireContext(), true)
|
||||
lastPlaybackControlsColor =
|
||||
MaterialValueHelper.getSecondaryTextColor(requireContext(), true)
|
||||
lastDisabledPlaybackControlsColor =
|
||||
MaterialValueHelper.getSecondaryDisabledTextColor(requireContext(), true)
|
||||
} else {
|
||||
lastPlaybackControlsColor = MaterialValueHelper.getPrimaryTextColor(requireContext(), false)
|
||||
lastPlaybackControlsColor =
|
||||
MaterialValueHelper.getPrimaryTextColor(requireContext(), false)
|
||||
lastDisabledPlaybackControlsColor =
|
||||
MaterialValueHelper.getPrimaryDisabledTextColor(requireContext(), false)
|
||||
}
|
||||
|
@ -94,15 +84,15 @@ class PlayerPlaybackControlsFragment : AbsPlayerControlsFragment(), OnSharedPref
|
|||
|
||||
TintHelper.setTintAuto(
|
||||
playPauseButton,
|
||||
MaterialValueHelper.getPrimaryTextColor(requireContext(), ColorUtil.isColorLight(colorFinal)),
|
||||
MaterialValueHelper.getPrimaryTextColor(
|
||||
requireContext(),
|
||||
ColorUtil.isColorLight(colorFinal)
|
||||
),
|
||||
false
|
||||
)
|
||||
TintHelper.setTintAuto(playPauseButton, colorFinal, true)
|
||||
|
||||
ViewUtil.setProgressDrawable(progressSlider, colorFinal, false)
|
||||
|
||||
ViewUtil.setProgressDrawable(progressSlider, colorFinal, true)
|
||||
volumeFragment?.setTintable(colorFinal)
|
||||
|
||||
updateRepeatState()
|
||||
updateShuffleState()
|
||||
updatePrevNextColor()
|
||||
|
@ -196,7 +186,10 @@ class PlayerPlaybackControlsFragment : AbsPlayerControlsFragment(), OnSharedPref
|
|||
lastPlaybackControlsColor,
|
||||
PorterDuff.Mode.SRC_IN
|
||||
)
|
||||
else -> shuffleButton.setColorFilter(lastDisabledPlaybackControlsColor, PorterDuff.Mode.SRC_IN)
|
||||
else -> shuffleButton.setColorFilter(
|
||||
lastDisabledPlaybackControlsColor,
|
||||
PorterDuff.Mode.SRC_IN
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -208,7 +201,10 @@ class PlayerPlaybackControlsFragment : AbsPlayerControlsFragment(), OnSharedPref
|
|||
when (MusicPlayerRemote.repeatMode) {
|
||||
MusicService.REPEAT_MODE_NONE -> {
|
||||
repeatButton.setImageResource(R.drawable.ic_repeat_white_24dp)
|
||||
repeatButton.setColorFilter(lastDisabledPlaybackControlsColor, PorterDuff.Mode.SRC_IN)
|
||||
repeatButton.setColorFilter(
|
||||
lastDisabledPlaybackControlsColor,
|
||||
PorterDuff.Mode.SRC_IN
|
||||
)
|
||||
}
|
||||
MusicService.REPEAT_MODE_ALL -> {
|
||||
repeatButton.setImageResource(R.drawable.ic_repeat_white_24dp)
|
||||
|
@ -240,32 +236,27 @@ class PlayerPlaybackControlsFragment : AbsPlayerControlsFragment(), OnSharedPref
|
|||
}
|
||||
}
|
||||
|
||||
override fun setUpProgressSlider() {
|
||||
progressSlider.setOnSeekBarChangeListener(object : SimpleOnSeekbarChangeListener() {
|
||||
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
|
||||
if (fromUser) {
|
||||
MusicPlayerRemote.seekTo(progress)
|
||||
onUpdateProgressViews(
|
||||
MusicPlayerRemote.songProgressMillis,
|
||||
MusicPlayerRemote.songDurationMillis
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
override fun onUpdateProgressViews(progress: Int, total: Int) {
|
||||
progressSlider.max = total
|
||||
|
||||
val animator = ObjectAnimator.ofInt(progressSlider, "progress", progress)
|
||||
animator.duration = SLIDER_ANIMATION_TIME
|
||||
animator.interpolator = LinearInterpolator()
|
||||
animator.start()
|
||||
|
||||
if (total <= 0) {
|
||||
return
|
||||
}
|
||||
progressSlider.setRange(progress.toFloat(), total.toFloat())
|
||||
songTotalTime.text = MusicUtil.getReadableDurationString(total.toLong())
|
||||
songCurrentProgress.text = MusicUtil.getReadableDurationString(progress.toLong())
|
||||
}
|
||||
|
||||
override fun setUpProgressSlider() {
|
||||
progressSlider.addOnChangeListener { _, value, fromUser ->
|
||||
if (fromUser) {
|
||||
MusicPlayerRemote.seekTo(value.toInt())
|
||||
onUpdateProgressViews(
|
||||
MusicPlayerRemote.songProgressMillis,
|
||||
MusicPlayerRemote.songDurationMillis
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
|
||||
|
||||
if (key == PreferenceUtil.EXTRA_SONG_INFO) {
|
||||
|
@ -275,6 +266,7 @@ class PlayerPlaybackControlsFragment : AbsPlayerControlsFragment(), OnSharedPref
|
|||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
PreferenceUtil.getInstance(requireContext()).unregisterOnSharedPreferenceChangedListener(this)
|
||||
PreferenceUtil.getInstance(requireContext())
|
||||
.unregisterOnSharedPreferenceChangedListener(this)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,39 +14,29 @@
|
|||
|
||||
package code.name.monkey.retromusic.fragments.player.peak
|
||||
|
||||
import android.animation.ObjectAnimator
|
||||
import android.graphics.Color
|
||||
import android.graphics.PorterDuff
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.animation.LinearInterpolator
|
||||
import android.widget.SeekBar
|
||||
import code.name.monkey.appthemehelper.util.ATHUtil
|
||||
import code.name.monkey.appthemehelper.util.ColorUtil
|
||||
import code.name.monkey.appthemehelper.util.MaterialValueHelper
|
||||
import code.name.monkey.appthemehelper.util.TintHelper
|
||||
import code.name.monkey.retromusic.R
|
||||
import code.name.monkey.retromusic.extensions.ripAlpha
|
||||
import code.name.monkey.retromusic.extensions.setRange
|
||||
import code.name.monkey.retromusic.extensions.textColorSecondary
|
||||
import code.name.monkey.retromusic.fragments.base.AbsPlayerControlsFragment
|
||||
import code.name.monkey.retromusic.helper.MusicPlayerRemote
|
||||
import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper
|
||||
import code.name.monkey.retromusic.helper.PlayPauseButtonOnClickHandler
|
||||
import code.name.monkey.retromusic.misc.SimpleOnSeekbarChangeListener
|
||||
import code.name.monkey.retromusic.service.MusicService
|
||||
import code.name.monkey.retromusic.util.MusicUtil
|
||||
import code.name.monkey.retromusic.util.PreferenceUtil
|
||||
import code.name.monkey.retromusic.util.ViewUtil
|
||||
import kotlinx.android.synthetic.main.fragment_peak_control_player.nextButton
|
||||
import kotlinx.android.synthetic.main.fragment_peak_control_player.playPauseButton
|
||||
import kotlinx.android.synthetic.main.fragment_peak_control_player.previousButton
|
||||
import kotlinx.android.synthetic.main.fragment_peak_control_player.progressSlider
|
||||
import kotlinx.android.synthetic.main.fragment_peak_control_player.repeatButton
|
||||
import kotlinx.android.synthetic.main.fragment_peak_control_player.shuffleButton
|
||||
import kotlinx.android.synthetic.main.fragment_peak_control_player.songCurrentProgress
|
||||
import kotlinx.android.synthetic.main.fragment_peak_control_player.songTotalTime
|
||||
import kotlinx.android.synthetic.main.fragment_peak_control_player.*
|
||||
|
||||
/**
|
||||
* Created by hemanths on 2019-10-04.
|
||||
|
@ -98,11 +88,13 @@ class PeakPlayerControlFragment : AbsPlayerControlsFragment() {
|
|||
override fun setDark(color: Int) {
|
||||
val colorBg = ATHUtil.resolveColor(requireContext(), android.R.attr.colorBackground)
|
||||
if (ColorUtil.isColorLight(colorBg)) {
|
||||
lastPlaybackControlsColor = MaterialValueHelper.getSecondaryTextColor(requireContext(), true)
|
||||
lastPlaybackControlsColor =
|
||||
MaterialValueHelper.getSecondaryTextColor(requireContext(), true)
|
||||
lastDisabledPlaybackControlsColor =
|
||||
MaterialValueHelper.getSecondaryDisabledTextColor(requireContext(), true)
|
||||
} else {
|
||||
lastPlaybackControlsColor = MaterialValueHelper.getPrimaryTextColor(requireContext(), false)
|
||||
lastPlaybackControlsColor =
|
||||
MaterialValueHelper.getPrimaryTextColor(requireContext(), false)
|
||||
lastDisabledPlaybackControlsColor =
|
||||
MaterialValueHelper.getPrimaryDisabledTextColor(requireContext(), false)
|
||||
}
|
||||
|
@ -122,18 +114,6 @@ class PeakPlayerControlFragment : AbsPlayerControlsFragment() {
|
|||
updatePrevNextColor()
|
||||
}
|
||||
|
||||
override fun onUpdateProgressViews(progress: Int, total: Int) {
|
||||
progressSlider.max = total
|
||||
|
||||
val animator = ObjectAnimator.ofInt(progressSlider, "progress", progress)
|
||||
animator.duration = SLIDER_ANIMATION_TIME
|
||||
animator.interpolator = LinearInterpolator()
|
||||
animator.start()
|
||||
|
||||
songTotalTime.text = MusicUtil.getReadableDurationString(total.toLong())
|
||||
songCurrentProgress.text = MusicUtil.getReadableDurationString(progress.toLong())
|
||||
}
|
||||
|
||||
private fun updatePlayPauseDrawableState() {
|
||||
if (MusicPlayerRemote.isPlaying) {
|
||||
playPauseButton.setImageResource(R.drawable.ic_pause_white_24dp)
|
||||
|
@ -162,15 +142,25 @@ class PeakPlayerControlFragment : AbsPlayerControlsFragment() {
|
|||
}
|
||||
}
|
||||
|
||||
override fun onUpdateProgressViews(progress: Int, total: Int) {
|
||||
if (total <= 0) {
|
||||
return
|
||||
}
|
||||
progressSlider.setRange(progress.toFloat(), total.toFloat())
|
||||
songTotalTime.text = MusicUtil.getReadableDurationString(total.toLong())
|
||||
songCurrentProgress.text = MusicUtil.getReadableDurationString(progress.toLong())
|
||||
}
|
||||
|
||||
override fun setUpProgressSlider() {
|
||||
progressSlider.setOnSeekBarChangeListener(object : SimpleOnSeekbarChangeListener() {
|
||||
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
|
||||
if (fromUser) {
|
||||
MusicPlayerRemote.seekTo(progress)
|
||||
onUpdateProgressViews(MusicPlayerRemote.songProgressMillis, MusicPlayerRemote.songDurationMillis)
|
||||
}
|
||||
progressSlider.addOnChangeListener { _, value, fromUser ->
|
||||
if (fromUser) {
|
||||
MusicPlayerRemote.seekTo(value.toInt())
|
||||
onUpdateProgressViews(
|
||||
MusicPlayerRemote.songProgressMillis,
|
||||
MusicPlayerRemote.songDurationMillis
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private fun setUpPlayPauseFab() {
|
||||
|
@ -196,7 +186,10 @@ class PeakPlayerControlFragment : AbsPlayerControlsFragment() {
|
|||
lastPlaybackControlsColor,
|
||||
PorterDuff.Mode.SRC_IN
|
||||
)
|
||||
else -> shuffleButton.setColorFilter(lastDisabledPlaybackControlsColor, PorterDuff.Mode.SRC_IN)
|
||||
else -> shuffleButton.setColorFilter(
|
||||
lastDisabledPlaybackControlsColor,
|
||||
PorterDuff.Mode.SRC_IN
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -204,7 +197,10 @@ class PeakPlayerControlFragment : AbsPlayerControlsFragment() {
|
|||
when (MusicPlayerRemote.repeatMode) {
|
||||
MusicService.REPEAT_MODE_NONE -> {
|
||||
repeatButton.setImageResource(R.drawable.ic_repeat_white_24dp)
|
||||
repeatButton.setColorFilter(lastDisabledPlaybackControlsColor, PorterDuff.Mode.SRC_IN)
|
||||
repeatButton.setColorFilter(
|
||||
lastDisabledPlaybackControlsColor,
|
||||
PorterDuff.Mode.SRC_IN
|
||||
)
|
||||
}
|
||||
MusicService.REPEAT_MODE_ALL -> {
|
||||
repeatButton.setImageResource(R.drawable.ic_repeat_white_24dp)
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package code.name.monkey.retromusic.fragments.player.plain
|
||||
|
||||
import android.animation.ObjectAnimator
|
||||
import android.graphics.PorterDuff
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
|
@ -8,8 +7,6 @@ import android.view.View
|
|||
import android.view.ViewGroup
|
||||
import android.view.animation.AccelerateInterpolator
|
||||
import android.view.animation.DecelerateInterpolator
|
||||
import android.view.animation.LinearInterpolator
|
||||
import android.widget.SeekBar
|
||||
import code.name.monkey.appthemehelper.ThemeStore
|
||||
import code.name.monkey.appthemehelper.util.ATHUtil
|
||||
import code.name.monkey.appthemehelper.util.ColorUtil
|
||||
|
@ -18,12 +15,12 @@ import code.name.monkey.appthemehelper.util.TintHelper
|
|||
import code.name.monkey.retromusic.R
|
||||
import code.name.monkey.retromusic.extensions.hide
|
||||
import code.name.monkey.retromusic.extensions.ripAlpha
|
||||
import code.name.monkey.retromusic.extensions.setRange
|
||||
import code.name.monkey.retromusic.extensions.show
|
||||
import code.name.monkey.retromusic.fragments.base.AbsPlayerControlsFragment
|
||||
import code.name.monkey.retromusic.helper.MusicPlayerRemote
|
||||
import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper
|
||||
import code.name.monkey.retromusic.helper.PlayPauseButtonOnClickHandler
|
||||
import code.name.monkey.retromusic.misc.SimpleOnSeekbarChangeListener
|
||||
import code.name.monkey.retromusic.service.MusicService
|
||||
import code.name.monkey.retromusic.util.MusicUtil
|
||||
import code.name.monkey.retromusic.util.PreferenceUtil
|
||||
|
@ -86,7 +83,11 @@ class PlainPlaybackControlsFragment : AbsPlayerControlsFragment() {
|
|||
progressViewUpdateHelper = MusicProgressViewUpdateHelper(this)
|
||||
}
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
|
||||
return inflater.inflate(R.layout.fragment_plain_controls_fragment, container, false)
|
||||
}
|
||||
|
@ -142,10 +143,12 @@ class PlainPlaybackControlsFragment : AbsPlayerControlsFragment() {
|
|||
val colorBg = ATHUtil.resolveColor(context!!, android.R.attr.colorBackground)
|
||||
if (ColorUtil.isColorLight(colorBg)) {
|
||||
lastPlaybackControlsColor = MaterialValueHelper.getSecondaryTextColor(context!!, true)
|
||||
lastDisabledPlaybackControlsColor = MaterialValueHelper.getSecondaryDisabledTextColor(context!!, true)
|
||||
lastDisabledPlaybackControlsColor =
|
||||
MaterialValueHelper.getSecondaryDisabledTextColor(context!!, true)
|
||||
} else {
|
||||
lastPlaybackControlsColor = MaterialValueHelper.getPrimaryTextColor(context!!, false)
|
||||
lastDisabledPlaybackControlsColor = MaterialValueHelper.getPrimaryDisabledTextColor(context!!, false)
|
||||
lastDisabledPlaybackControlsColor =
|
||||
MaterialValueHelper.getPrimaryDisabledTextColor(context!!, false)
|
||||
}
|
||||
|
||||
val colorFinal = if (PreferenceUtil.getInstance(requireContext()).adaptiveColor) {
|
||||
|
@ -179,7 +182,10 @@ class PlainPlaybackControlsFragment : AbsPlayerControlsFragment() {
|
|||
lastPlaybackControlsColor,
|
||||
PorterDuff.Mode.SRC_IN
|
||||
)
|
||||
else -> shuffleButton.setColorFilter(lastDisabledPlaybackControlsColor, PorterDuff.Mode.SRC_IN)
|
||||
else -> shuffleButton.setColorFilter(
|
||||
lastDisabledPlaybackControlsColor,
|
||||
PorterDuff.Mode.SRC_IN
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -191,7 +197,10 @@ class PlainPlaybackControlsFragment : AbsPlayerControlsFragment() {
|
|||
when (MusicPlayerRemote.repeatMode) {
|
||||
MusicService.REPEAT_MODE_NONE -> {
|
||||
repeatButton.setImageResource(R.drawable.ic_repeat_white_24dp)
|
||||
repeatButton.setColorFilter(lastDisabledPlaybackControlsColor, PorterDuff.Mode.SRC_IN)
|
||||
repeatButton.setColorFilter(
|
||||
lastDisabledPlaybackControlsColor,
|
||||
PorterDuff.Mode.SRC_IN
|
||||
)
|
||||
}
|
||||
MusicService.REPEAT_MODE_ALL -> {
|
||||
repeatButton.setImageResource(R.drawable.ic_repeat_white_24dp)
|
||||
|
@ -223,20 +232,6 @@ class PlainPlaybackControlsFragment : AbsPlayerControlsFragment() {
|
|||
}
|
||||
}
|
||||
|
||||
override fun setUpProgressSlider() {
|
||||
progressSlider.setOnSeekBarChangeListener(object : SimpleOnSeekbarChangeListener() {
|
||||
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
|
||||
if (fromUser) {
|
||||
MusicPlayerRemote.seekTo(progress)
|
||||
onUpdateProgressViews(
|
||||
MusicPlayerRemote.songProgressMillis,
|
||||
MusicPlayerRemote.songDurationMillis
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun showBonceAnimation() {
|
||||
playPauseButton.apply {
|
||||
clearAnimation()
|
||||
|
@ -269,14 +264,23 @@ class PlainPlaybackControlsFragment : AbsPlayerControlsFragment() {
|
|||
}
|
||||
|
||||
override fun onUpdateProgressViews(progress: Int, total: Int) {
|
||||
progressSlider.max = total
|
||||
|
||||
val animator = ObjectAnimator.ofInt(progressSlider, "progress", progress)
|
||||
animator.duration = SLIDER_ANIMATION_TIME
|
||||
animator.interpolator = LinearInterpolator()
|
||||
animator.start()
|
||||
|
||||
if (total <= 0) {
|
||||
return
|
||||
}
|
||||
progressSlider.setRange(progress.toFloat(), total.toFloat())
|
||||
songTotalTime.text = MusicUtil.getReadableDurationString(total.toLong())
|
||||
songCurrentProgress.text = MusicUtil.getReadableDurationString(progress.toLong())
|
||||
}
|
||||
|
||||
override fun setUpProgressSlider() {
|
||||
progressSlider.addOnChangeListener { _, value, fromUser ->
|
||||
if (fromUser) {
|
||||
MusicPlayerRemote.seekTo(value.toInt())
|
||||
onUpdateProgressViews(
|
||||
MusicPlayerRemote.songProgressMillis,
|
||||
MusicPlayerRemote.songDurationMillis
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,12 +25,7 @@ import code.name.monkey.retromusic.model.Song
|
|||
import code.name.monkey.retromusic.util.MusicUtil
|
||||
import code.name.monkey.retromusic.util.PreferenceUtil
|
||||
import code.name.monkey.retromusic.util.ViewUtil
|
||||
import kotlinx.android.synthetic.main.fragment_tiny_player.playerSongTotalTime
|
||||
import kotlinx.android.synthetic.main.fragment_tiny_player.playerToolbar
|
||||
import kotlinx.android.synthetic.main.fragment_tiny_player.progressBar
|
||||
import kotlinx.android.synthetic.main.fragment_tiny_player.songInfo
|
||||
import kotlinx.android.synthetic.main.fragment_tiny_player.text
|
||||
import kotlinx.android.synthetic.main.fragment_tiny_player.title
|
||||
import kotlinx.android.synthetic.main.fragment_tiny_player.*
|
||||
|
||||
class TinyPlayerFragment : AbsPlayerFragment(), MusicProgressViewUpdateHelper.Callback {
|
||||
override fun onUpdateProgressViews(progress: Int, total: Int) {
|
||||
|
@ -86,10 +81,12 @@ class TinyPlayerFragment : AbsPlayerFragment(), MusicProgressViewUpdateHelper.Ca
|
|||
|
||||
if (ColorUtil.isColorLight(colorFinal)) {
|
||||
textColorPrimary = MaterialValueHelper.getSecondaryTextColor(requireContext(), true)
|
||||
textColorPrimaryDisabled = MaterialValueHelper.getSecondaryTextColor(requireContext(), true)
|
||||
textColorPrimaryDisabled =
|
||||
MaterialValueHelper.getSecondaryTextColor(requireContext(), true)
|
||||
} else {
|
||||
textColorPrimary = MaterialValueHelper.getPrimaryTextColor(requireContext(), false)
|
||||
textColorPrimaryDisabled = MaterialValueHelper.getSecondaryTextColor(requireContext(), false)
|
||||
textColorPrimaryDisabled =
|
||||
MaterialValueHelper.getSecondaryTextColor(requireContext(), false)
|
||||
}
|
||||
|
||||
this.lastColor = colorFinal
|
||||
|
@ -143,7 +140,11 @@ class TinyPlayerFragment : AbsPlayerFragment(), MusicProgressViewUpdateHelper.Ca
|
|||
}
|
||||
}
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
return inflater.inflate(R.layout.fragment_tiny_player, container, false)
|
||||
}
|
||||
|
||||
|
|
|
@ -17,7 +17,6 @@ package code.name.monkey.retromusic.helper
|
|||
import android.os.Handler
|
||||
import android.os.Message
|
||||
|
||||
|
||||
class MusicProgressViewUpdateHelper : Handler {
|
||||
|
||||
private var callback: Callback? = null
|
||||
|
@ -54,8 +53,8 @@ class MusicProgressViewUpdateHelper : Handler {
|
|||
private fun refreshProgressViews(): Int {
|
||||
val progressMillis = MusicPlayerRemote.songProgressMillis
|
||||
val totalMillis = MusicPlayerRemote.songDurationMillis
|
||||
|
||||
callback!!.onUpdateProgressViews(progressMillis, totalMillis)
|
||||
println("$progressMillis $totalMillis")
|
||||
callback?.onUpdateProgressViews(progressMillis, totalMillis)
|
||||
|
||||
if (!MusicPlayerRemote.isPlaying) {
|
||||
return intervalPaused
|
||||
|
|
|
@ -32,7 +32,7 @@ import code.name.monkey.retromusic.R;
|
|||
import code.name.monkey.retromusic.dialogs.OptionsSheetDialogFragment;
|
||||
import code.name.monkey.retromusic.fragments.AlbumCoverStyle;
|
||||
import code.name.monkey.retromusic.fragments.NowPlayingScreen;
|
||||
import code.name.monkey.retromusic.fragments.mainactivity.folders.FoldersFragment;
|
||||
import code.name.monkey.retromusic.fragments.mainactivity.FoldersFragment;
|
||||
import code.name.monkey.retromusic.helper.SortOrder;
|
||||
import code.name.monkey.retromusic.helper.SortOrder.AlbumSongSortOrder;
|
||||
import code.name.monkey.retromusic.model.CategoryInfo;
|
||||
|
|
|
@ -28,6 +28,7 @@ import androidx.core.view.ViewCompat
|
|||
import code.name.monkey.appthemehelper.util.ATHUtil
|
||||
import code.name.monkey.appthemehelper.util.ColorUtil
|
||||
import code.name.monkey.appthemehelper.util.MaterialValueHelper
|
||||
import com.google.android.material.slider.Slider
|
||||
|
||||
object ViewUtil {
|
||||
|
||||
|
@ -47,6 +48,16 @@ object ViewUtil {
|
|||
}
|
||||
}
|
||||
|
||||
fun setProgressDrawable(progressSlider: Slider, color: Int, thumbTint: Boolean = false) {
|
||||
if (thumbTint) {
|
||||
progressSlider.thumbColor = ColorStateList.valueOf(color)
|
||||
}
|
||||
val colorWithAlpha = ColorUtil.withAlpha(color, 0.25f)
|
||||
progressSlider.haloColor = ColorStateList.valueOf(colorWithAlpha)
|
||||
progressSlider.trackColorActive = ColorStateList.valueOf(color)
|
||||
progressSlider.trackColorInactive = ColorStateList.valueOf(colorWithAlpha)
|
||||
}
|
||||
|
||||
fun setProgressDrawable(progressSlider: ProgressBar, newColor: Int) {
|
||||
|
||||
val layerDrawable = progressSlider.progressDrawable as LayerDrawable
|
||||
|
|
|
@ -16,12 +16,11 @@ package code.name.monkey.retromusic.views
|
|||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import code.name.monkey.retromusic.R
|
||||
import com.google.android.material.imageview.ExperimentalImageView
|
||||
import com.google.android.material.imageview.ShapeableImageView
|
||||
import com.google.android.material.shape.CornerFamily
|
||||
import com.google.android.material.shape.ShapeAppearanceModel
|
||||
|
||||
@ExperimentalImageView
|
||||
|
||||
class RetroShapeableImageView @JvmOverloads constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
|
@ -29,8 +28,10 @@ class RetroShapeableImageView @JvmOverloads constructor(
|
|||
) : ShapeableImageView(context, attrs, defStyle) {
|
||||
|
||||
init {
|
||||
val typedArray = context.obtainStyledAttributes(attrs, R.styleable.RetroShapeableImageView, defStyle, -1)
|
||||
val cornerSize = typedArray.getDimension(R.styleable.RetroShapeableImageView_retroCornerSize, 0f);
|
||||
val typedArray =
|
||||
context.obtainStyledAttributes(attrs, R.styleable.RetroShapeableImageView, defStyle, -1)
|
||||
val cornerSize =
|
||||
typedArray.getDimension(R.styleable.RetroShapeableImageView_retroCornerSize, 0f);
|
||||
shapeAppearanceModel = ShapeAppearanceModel.Builder()
|
||||
.setAllCorners(CornerFamily.ROUNDED, cornerSize)
|
||||
.build()
|
||||
|
|
|
@ -23,13 +23,16 @@ import androidx.annotation.NonNull;
|
|||
public class AudioVolumeContentObserver extends ContentObserver {
|
||||
|
||||
private final OnAudioVolumeChangedListener mListener;
|
||||
|
||||
private final AudioManager mAudioManager;
|
||||
|
||||
private final int mAudioStreamType;
|
||||
private int mLastVolume;
|
||||
|
||||
private float mLastVolume;
|
||||
|
||||
AudioVolumeContentObserver(@NonNull Handler handler, @NonNull AudioManager audioManager,
|
||||
int audioStreamType,
|
||||
@NonNull OnAudioVolumeChangedListener listener) {
|
||||
int audioStreamType,
|
||||
@NonNull OnAudioVolumeChangedListener listener) {
|
||||
|
||||
super(handler);
|
||||
mAudioManager = audioManager;
|
||||
|
@ -44,8 +47,8 @@ public class AudioVolumeContentObserver extends ContentObserver {
|
|||
@Override
|
||||
public void onChange(boolean selfChange, Uri uri) {
|
||||
if (mAudioManager != null && mListener != null) {
|
||||
int maxVolume = mAudioManager.getStreamMaxVolume(mAudioStreamType);
|
||||
int currentVolume = mAudioManager.getStreamVolume(mAudioStreamType);
|
||||
float maxVolume = mAudioManager.getStreamMaxVolume(mAudioStreamType);
|
||||
float currentVolume = mAudioManager.getStreamVolume(mAudioStreamType);
|
||||
if (currentVolume != mLastVolume) {
|
||||
mLastVolume = currentVolume;
|
||||
mListener.onAudioVolumeChanged(currentVolume, maxVolume);
|
||||
|
|
|
@ -11,10 +11,8 @@
|
|||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*/
|
||||
package code.name.monkey.retromusic.volume
|
||||
|
||||
package code.name.monkey.retromusic.volume;
|
||||
|
||||
public interface OnAudioVolumeChangedListener {
|
||||
|
||||
void onAudioVolumeChanged(int currentVolume, int maxVolume);
|
||||
interface OnAudioVolumeChangedListener {
|
||||
fun onAudioVolumeChanged(currentVolume: Float, maxVolume: Float)
|
||||
}
|
|
@ -54,39 +54,6 @@
|
|||
app:navigationIcon="@drawable/ic_keyboard_arrow_down_black_24dp"
|
||||
tools:layout_editor_absoluteY="24dp" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/titleContainer"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintBottom_toTopOf="@+id/text"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/guideline"
|
||||
app:layout_constraintTop_toBottomOf="@+id/playerToolbar">
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:maxLines="1"
|
||||
android:textAppearance="@style/TextViewHeadline4"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:textStyle="bold"
|
||||
tools:text="@tools:sample/lorem/random" />
|
||||
</LinearLayout>
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/text"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:maxLines="2"
|
||||
android:textAppearance="@style/TextViewBody1"
|
||||
app:layout_constraintBottom_toTopOf="@+id/progressContainer"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/guideline"
|
||||
app:layout_constraintTop_toBottomOf="@+id/titleContainer"
|
||||
tools:text="@tools:sample/lorem/random" />
|
||||
|
||||
<code.name.monkey.retromusic.views.SeekArc
|
||||
android:id="@+id/volumeSeekBar"
|
||||
|
@ -98,7 +65,7 @@
|
|||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="@id/guideline"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/playerToolbar"
|
||||
app:progressWidth="4dp"
|
||||
app:rotation="180"
|
||||
app:roundEdges="true"
|
||||
|
@ -152,57 +119,88 @@
|
|||
app:layout_constraintEnd_toEndOf="@+id/volumeSeekBar"
|
||||
app:layout_constraintStart_toStartOf="@+id/volumeSeekBar" />
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/progressContainer"
|
||||
<LinearLayout
|
||||
android:id="@+id/titleContainer"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="28dp"
|
||||
android:paddingStart="12dp"
|
||||
android:paddingEnd="12dp"
|
||||
app:layout_constraintBottom_toTopOf="@+id/songInfo"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintBottom_toTopOf="@+id/text"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/guideline"
|
||||
app:layout_constraintTop_toBottomOf="@+id/text">
|
||||
app:layout_constraintTop_toBottomOf="@+id/playerToolbar">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatSeekBar
|
||||
android:id="@+id/progressSlider"
|
||||
style="@style/Widget.MaterialProgressBar.ProgressBar.Horizontal"
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_toLeftOf="@id/songTotalTime"
|
||||
android:layout_toRightOf="@id/songCurrentProgress"
|
||||
android:maxHeight="3dp"
|
||||
android:progressDrawable="@drawable/color_progress_seek"
|
||||
android:splitTrack="false"
|
||||
android:thumb="@drawable/switch_thumb_material"
|
||||
tools:ignore="RtlHardcoded,UnusedAttribute"
|
||||
tools:progress="20" />
|
||||
android:gravity="center"
|
||||
android:maxLines="1"
|
||||
android:paddingStart="0dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:textAppearance="@style/TextViewHeadline4"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:textStyle="bold"
|
||||
tools:text="@tools:sample/lorem/random" />
|
||||
</LinearLayout>
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/songTotalTime"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_alignParentRight="true"
|
||||
android:gravity="center_vertical|right|end"
|
||||
android:paddingRight="8dp"
|
||||
android:singleLine="true"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:textSize="12sp"
|
||||
tools:ignore="RtlHardcoded,RtlSymmetry"
|
||||
tools:text="00:22" />
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/text"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:maxLines="2"
|
||||
android:paddingStart="0dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:textAppearance="@style/TextViewBody1"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintBottom_toTopOf="@id/progressSlider"
|
||||
app:layout_constraintStart_toEndOf="@id/guideline"
|
||||
app:layout_constraintTop_toBottomOf="@+id/titleContainer"
|
||||
tools:text="@tools:sample/lorem/random" />
|
||||
|
||||
<com.google.android.material.slider.Slider
|
||||
android:id="@+id/progressSlider"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
app:labelBehavior="gone"
|
||||
app:layout_constraintBottom_toTopOf="@id/songInfo"
|
||||
app:layout_constraintEnd_toStartOf="@id/songTotalTime"
|
||||
app:layout_constraintStart_toEndOf="@id/songCurrentProgress"
|
||||
app:layout_constraintTop_toBottomOf="@id/text"
|
||||
app:thumbColor="?attr/colorSurface"
|
||||
app:trackColorInactive="?attr/colorControlNormal"
|
||||
tools:ignore="RtlHardcoded,UnusedAttribute" />
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/songTotalTime"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical|right|end"
|
||||
android:paddingRight="8dp"
|
||||
android:singleLine="true"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:textSize="12sp"
|
||||
app:layout_constraintBottom_toBottomOf="@id/progressSlider"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@id/progressSlider"
|
||||
tools:ignore="RtlHardcoded,RtlSymmetry"
|
||||
tools:text="00:22" />
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/songCurrentProgress"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:gravity="center_vertical|left|end"
|
||||
android:paddingLeft="8dp"
|
||||
android:singleLine="true"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:textSize="12sp"
|
||||
app:layout_constraintBottom_toBottomOf="@id/progressSlider"
|
||||
app:layout_constraintStart_toEndOf="@id/guideline"
|
||||
app:layout_constraintTop_toTopOf="@id/progressSlider"
|
||||
tools:ignore="RtlHardcoded,RtlSymmetry"
|
||||
tools:text="00:22" />
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/songCurrentProgress"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:gravity="center_vertical|left|end"
|
||||
android:paddingLeft="8dp"
|
||||
android:singleLine="true"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:textSize="12sp"
|
||||
tools:ignore="RtlHardcoded,RtlSymmetry"
|
||||
tools:text="00:22" />
|
||||
</RelativeLayout>
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/songInfo"
|
||||
|
@ -220,6 +218,5 @@
|
|||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/guideline"
|
||||
app:layout_constraintTop_toBottomOf="@+id/progressContainer"
|
||||
tools:text="@tools:sample/lorem" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -128,17 +128,14 @@
|
|||
app:layout_constraintStart_toEndOf="@+id/nextButton"
|
||||
app:layout_constraintTop_toTopOf="@+id/playPauseButton">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatSeekBar
|
||||
<com.google.android.material.slider.Slider
|
||||
android:id="@+id/progressSlider"
|
||||
style="@style/Widget.MaterialProgressBar.ProgressBar.Horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_toLeftOf="@id/songTotalTime"
|
||||
android:layout_toRightOf="@id/songCurrentProgress"
|
||||
android:maxHeight="3dp"
|
||||
android:progressDrawable="@drawable/color_progress_seek"
|
||||
android:splitTrack="false"
|
||||
android:thumb="@drawable/switch_thumb_material"
|
||||
app:labelBehavior="gone"
|
||||
app:thumbColor="?attr/colorSurface"
|
||||
tools:ignore="RtlHardcoded,UnusedAttribute"
|
||||
tools:progress="20" />
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<androidx.constraintlayout.widget.ConstraintLayout android:id="@+id/playbackControls"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/playbackControls"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
|
@ -18,28 +18,27 @@
|
|||
android:minWidth="40dp"
|
||||
android:singleLine="true"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:textSize="12sp"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/progressSlider"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@id/progressSlider"
|
||||
tools:ignore="RtlHardcoded,RtlSymmetry"
|
||||
tools:text="00:22" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatSeekBar
|
||||
<com.google.android.material.slider.Slider
|
||||
android:id="@+id/progressSlider"
|
||||
style="@style/Widget.MaterialProgressBar.ProgressBar.Horizontal"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_weight="8"
|
||||
android:maxHeight="1.5dp"
|
||||
android:progressDrawable="@drawable/color_progress_seek"
|
||||
android:splitTrack="false"
|
||||
android:value="1.0f"
|
||||
android:valueFrom="0.0f"
|
||||
android:valueTo="1.0f"
|
||||
app:labelBehavior="gone"
|
||||
app:layout_constraintEnd_toStartOf="@id/songTotalTime"
|
||||
app:layout_constraintStart_toEndOf="@id/songCurrentProgress"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:ignore="RtlHardcoded,UnusedAttribute"
|
||||
tools:progress="20" />
|
||||
app:trackHeight="3dp"
|
||||
tools:valueFrom="0.0"
|
||||
tools:valueTo="11.0" />
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/songTotalTime"
|
||||
|
@ -50,7 +49,6 @@
|
|||
android:minWidth="40dp"
|
||||
android:singleLine="true"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:textSize="12sp"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/progressSlider"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@id/progressSlider"
|
||||
|
@ -138,8 +136,8 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="0"
|
||||
android:paddingStart="8dp"
|
||||
android:paddingEnd="8dp"
|
||||
android:paddingStart="8dp"
|
||||
app:layout_constraintBottom_toTopOf="@id/songInfo"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
|
@ -155,9 +153,9 @@
|
|||
android:ellipsize="end"
|
||||
android:gravity="center"
|
||||
android:maxLines="1"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:paddingBottom="8dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:paddingStart="16dp"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:textSize="12sp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
|
|
|
@ -31,27 +31,34 @@
|
|||
android:minWidth="40dp"
|
||||
android:singleLine="true"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:textSize="12sp"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/progressSlider"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@id/progressSlider"
|
||||
tools:ignore="RtlHardcoded,RtlSymmetry"
|
||||
tools:text="00:22" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatSeekBar
|
||||
<com.google.android.material.slider.Slider
|
||||
android:id="@+id/progressSlider"
|
||||
style="@style/Widget.MaterialProgressBar.ProgressBar.Horizontal"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="8"
|
||||
android:maxHeight="1.5dp"
|
||||
android:progressDrawable="@drawable/color_progress_seek"
|
||||
android:splitTrack="false"
|
||||
android:value="1.0f"
|
||||
android:valueFrom="0.0f"
|
||||
android:valueTo="1.0f"
|
||||
app:haloColor="@color/md_white_semi_transparent"
|
||||
app:labelBehavior="gone"
|
||||
app:layout_constraintEnd_toStartOf="@id/songTotalTime"
|
||||
app:layout_constraintStart_toEndOf="@id/songCurrentProgress"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:thumbColor="@color/md_white_1000"
|
||||
app:trackColorActive="@color/md_white_1000"
|
||||
app:trackColorInactive="@color/md_white_semi_transparent"
|
||||
app:trackHeight="3dp"
|
||||
tools:ignore="RtlHardcoded,UnusedAttribute"
|
||||
tools:progress="20" />
|
||||
tools:progress="20"
|
||||
tools:valueFrom="0.0"
|
||||
tools:valueTo="11.0" />
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/songTotalTime"
|
||||
|
@ -62,7 +69,6 @@
|
|||
android:minWidth="40dp"
|
||||
android:singleLine="true"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:textSize="12sp"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/progressSlider"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@id/progressSlider"
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/playback_controls"
|
||||
android:layout_width="match_parent"
|
||||
|
@ -53,18 +54,18 @@
|
|||
tools:text="22.00" />
|
||||
</LinearLayout>
|
||||
|
||||
<androidx.appcompat.widget.AppCompatSeekBar
|
||||
<com.google.android.material.slider.Slider
|
||||
android:id="@+id/progressSlider"
|
||||
style="@style/Widget.MaterialProgressBar.ProgressBar.Horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:maxHeight="2dp"
|
||||
android:paddingStart="20dp"
|
||||
android:paddingEnd="20dp"
|
||||
android:progressDrawable="@drawable/color_progress_seek"
|
||||
android:progressTint="@color/md_white_1000"
|
||||
android:splitTrack="false"
|
||||
android:thumbTint="@color/md_white_1000"
|
||||
app:haloColor="@color/md_white_semi_transparent"
|
||||
app:labelBehavior="gone"
|
||||
app:thumbColor="@color/md_white_1000"
|
||||
app:trackColorActive="@color/md_white_1000"
|
||||
app:trackColorInactive="@color/md_white_semi_transparent"
|
||||
app:trackHeight="2dp"
|
||||
tools:progress="20" />
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
|
|
|
@ -62,8 +62,8 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:maxLines="2"
|
||||
android:paddingStart="8dp"
|
||||
android:paddingEnd="8dp"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:textAppearance="@style/TextViewHeadline5"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:textStyle="bold"
|
||||
|
@ -76,6 +76,8 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:maxLines="2"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:textAppearance="@style/TextViewBody1"
|
||||
app:layout_constraintBottom_toTopOf="@+id/volumeSeekBar"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
|
@ -147,55 +149,48 @@
|
|||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="@+id/volumeSeekBar" />
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/progressContainer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="28dp"
|
||||
android:paddingStart="12dp"
|
||||
android:paddingEnd="12dp"
|
||||
|
||||
<com.google.android.material.slider.Slider
|
||||
android:id="@+id/progressSlider"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
app:labelBehavior="gone"
|
||||
app:layout_constraintBottom_toTopOf="@+id/songInfo"
|
||||
app:layout_constraintTop_toBottomOf="@+id/volumeSeekBar">
|
||||
app:layout_constraintEnd_toStartOf="@id/songTotalTime"
|
||||
app:layout_constraintStart_toEndOf="@id/songCurrentProgress"
|
||||
app:layout_constraintTop_toBottomOf="@+id/volumeSeekBar"
|
||||
app:thumbColor="?attr/colorSurface"
|
||||
app:trackColorInactive="?attr/colorControlNormal"
|
||||
tools:ignore="RtlHardcoded,UnusedAttribute"
|
||||
tools:progress="20" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatSeekBar
|
||||
android:id="@+id/progressSlider"
|
||||
style="@style/Widget.MaterialProgressBar.ProgressBar.Horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_toLeftOf="@id/songTotalTime"
|
||||
android:layout_toRightOf="@id/songCurrentProgress"
|
||||
android:maxHeight="3dp"
|
||||
android:progressDrawable="@drawable/color_progress_seek"
|
||||
android:splitTrack="false"
|
||||
android:thumb="@drawable/switch_thumb_material"
|
||||
tools:ignore="RtlHardcoded,UnusedAttribute"
|
||||
tools:progress="20" />
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/songCurrentProgress"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical|left|end"
|
||||
android:paddingLeft="16dp"
|
||||
android:singleLine="true"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/progressSlider"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@id/progressSlider"
|
||||
tools:ignore="RtlHardcoded,RtlSymmetry"
|
||||
tools:text="00:22" />
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/songTotalTime"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_alignParentRight="true"
|
||||
android:gravity="center_vertical|right|end"
|
||||
android:paddingRight="8dp"
|
||||
android:singleLine="true"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:textSize="12sp"
|
||||
tools:ignore="RtlHardcoded,RtlSymmetry"
|
||||
tools:text="00:22" />
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/songCurrentProgress"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:gravity="center_vertical|left|end"
|
||||
android:paddingLeft="8dp"
|
||||
android:singleLine="true"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:textSize="12sp"
|
||||
tools:ignore="RtlHardcoded,RtlSymmetry"
|
||||
tools:text="00:22" />
|
||||
</RelativeLayout>
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/songTotalTime"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical|right|end"
|
||||
android:paddingRight="16dp"
|
||||
android:singleLine="true"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/progressSlider"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@id/progressSlider"
|
||||
tools:ignore="RtlHardcoded,RtlSymmetry"
|
||||
tools:text="00:22" />
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/songInfo"
|
||||
|
|
|
@ -17,23 +17,19 @@
|
|||
android:minWidth="40dp"
|
||||
android:singleLine="true"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:textSize="12sp"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/progressSlider"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@id/progressSlider"
|
||||
tools:ignore="RtlHardcoded,RtlSymmetry"
|
||||
tools:text="00:22" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatSeekBar
|
||||
<com.google.android.material.slider.Slider
|
||||
android:id="@+id/progressSlider"
|
||||
style="@style/Widget.MaterialProgressBar.ProgressBar.Horizontal"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_weight="8"
|
||||
android:maxHeight="2dp"
|
||||
android:progressDrawable="@drawable/color_progress_seek"
|
||||
android:splitTrack="false"
|
||||
app:labelBehavior="gone"
|
||||
app:trackHeight="3dp"
|
||||
app:layout_constraintEnd_toStartOf="@id/songTotalTime"
|
||||
app:layout_constraintStart_toEndOf="@id/songCurrentProgress"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
|
@ -49,7 +45,6 @@
|
|||
android:minWidth="40dp"
|
||||
android:singleLine="true"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:textSize="12sp"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/progressSlider"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@id/progressSlider"
|
||||
|
|
|
@ -17,23 +17,26 @@
|
|||
android:minWidth="40dp"
|
||||
android:singleLine="true"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:textSize="12sp"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/progressSlider"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@id/progressSlider"
|
||||
tools:ignore="RtlHardcoded,RtlSymmetry"
|
||||
tools:text="00:22" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatSeekBar
|
||||
<com.google.android.material.slider.Slider
|
||||
android:id="@+id/progressSlider"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="8"
|
||||
android:splitTrack="false"
|
||||
android:thumb="@drawable/switch_square"
|
||||
app:labelBehavior="gone"
|
||||
app:haloRadius="0dp"
|
||||
app:layout_constraintEnd_toStartOf="@id/songTotalTime"
|
||||
app:layout_constraintStart_toEndOf="@id/songCurrentProgress"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:thumbRadius="8dp"
|
||||
app:trackHeight="3dp"
|
||||
tools:ignore="RtlHardcoded,UnusedAttribute"
|
||||
tools:progress="20" />
|
||||
|
||||
|
@ -46,7 +49,6 @@
|
|||
android:minWidth="40dp"
|
||||
android:singleLine="true"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:textSize="12sp"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/progressSlider"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@id/progressSlider"
|
||||
|
|
|
@ -83,19 +83,20 @@
|
|||
tools:ignore="RtlHardcoded,RtlSymmetry"
|
||||
tools:text="00:22" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatSeekBar
|
||||
<com.google.android.material.slider.Slider
|
||||
android:id="@+id/progressSlider"
|
||||
style="@style/Widget.MaterialProgressBar.ProgressBar.Horizontal"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_weight="8"
|
||||
android:maxHeight="3dp"
|
||||
android:progressDrawable="@drawable/color_progress_seek"
|
||||
android:splitTrack="false"
|
||||
app:haloColor="@color/md_white_semi_transparent"
|
||||
app:labelBehavior="gone"
|
||||
app:layout_constraintEnd_toStartOf="@id/songTotalTime"
|
||||
app:layout_constraintStart_toEndOf="@id/songCurrentProgress"
|
||||
app:layout_constraintTop_toBottomOf="@id/text"
|
||||
app:thumbColor="@color/md_white_1000"
|
||||
app:trackColorActive="@color/md_white_1000"
|
||||
app:trackColorInactive="@color/md_white_semi_transparent"
|
||||
app:trackHeight="3dp"
|
||||
tools:ignore="RtlHardcoded,UnusedAttribute"
|
||||
tools:progress="20" />
|
||||
|
||||
|
|
|
@ -18,26 +18,22 @@
|
|||
android:minWidth="40dp"
|
||||
android:singleLine="true"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:textSize="12sp"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/progressSlider"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@id/progressSlider"
|
||||
tools:ignore="RtlHardcoded,RtlSymmetry"
|
||||
tools:text="00:22" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatSeekBar
|
||||
<com.google.android.material.slider.Slider
|
||||
android:id="@+id/progressSlider"
|
||||
style="@style/Widget.MaterialProgressBar.ProgressBar.Horizontal"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_weight="8"
|
||||
android:maxHeight="3dp"
|
||||
android:progressDrawable="@drawable/color_progress_seek"
|
||||
android:splitTrack="false"
|
||||
app:labelBehavior="gone"
|
||||
app:layout_constraintEnd_toStartOf="@id/songTotalTime"
|
||||
app:layout_constraintStart_toEndOf="@id/songCurrentProgress"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:trackHeight="3dp"
|
||||
tools:ignore="RtlHardcoded,UnusedAttribute"
|
||||
tools:progress="20" />
|
||||
|
||||
|
@ -50,7 +46,6 @@
|
|||
android:minWidth="40dp"
|
||||
android:singleLine="true"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:textSize="12sp"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/progressSlider"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@id/progressSlider"
|
||||
|
|
|
@ -28,23 +28,19 @@
|
|||
android:minWidth="40dp"
|
||||
android:singleLine="true"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:textSize="12sp"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/progressSlider"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@id/progressSlider"
|
||||
tools:ignore="RtlHardcoded,RtlSymmetry"
|
||||
tools:text="00:22" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatSeekBar
|
||||
<com.google.android.material.slider.Slider
|
||||
android:id="@+id/progressSlider"
|
||||
style="@style/Widget.MaterialProgressBar.ProgressBar.Horizontal"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_weight="8"
|
||||
android:maxHeight="3dp"
|
||||
android:progressDrawable="@drawable/color_progress_seek"
|
||||
android:splitTrack="false"
|
||||
app:labelBehavior="gone"
|
||||
app:trackHeight="3dp"
|
||||
app:layout_constraintEnd_toStartOf="@id/songTotalTime"
|
||||
app:layout_constraintStart_toEndOf="@id/songCurrentProgress"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
|
@ -60,7 +56,6 @@
|
|||
android:minWidth="40dp"
|
||||
android:singleLine="true"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:textSize="12sp"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/progressSlider"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@id/progressSlider"
|
||||
|
|
|
@ -16,26 +16,22 @@
|
|||
android:minWidth="40dp"
|
||||
android:singleLine="true"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:textSize="12sp"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/progressSlider"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@id/progressSlider"
|
||||
tools:ignore="RtlHardcoded,RtlSymmetry"
|
||||
tools:text="00:22" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatSeekBar
|
||||
<com.google.android.material.slider.Slider
|
||||
android:id="@+id/progressSlider"
|
||||
style="@style/Widget.MaterialProgressBar.ProgressBar.Horizontal"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="8"
|
||||
android:maxHeight="3dp"
|
||||
android:progressDrawable="@drawable/color_progress_seek"
|
||||
android:splitTrack="false"
|
||||
app:labelBehavior="gone"
|
||||
app:layout_constraintBottom_toTopOf="@+id/playPauseButton"
|
||||
app:layout_constraintEnd_toStartOf="@id/songTotalTime"
|
||||
app:layout_constraintStart_toEndOf="@id/songCurrentProgress"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:trackHeight="3dp"
|
||||
tools:ignore="RtlHardcoded,UnusedAttribute"
|
||||
tools:progress="20" />
|
||||
|
||||
|
@ -48,7 +44,6 @@
|
|||
android:minWidth="40dp"
|
||||
android:singleLine="true"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:textSize="12sp"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/progressSlider"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@+id/progressSlider"
|
||||
|
|
|
@ -18,26 +18,21 @@
|
|||
android:minWidth="40dp"
|
||||
android:singleLine="true"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:textSize="12sp"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/progressSlider"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@id/progressSlider"
|
||||
tools:ignore="RtlHardcoded,RtlSymmetry"
|
||||
tools:text="00:22" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatSeekBar
|
||||
<com.google.android.material.slider.Slider
|
||||
android:id="@+id/progressSlider"
|
||||
style="@style/Widget.MaterialProgressBar.ProgressBar.Horizontal"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:maxHeight="3dp"
|
||||
android:padding="0dp"
|
||||
android:progressDrawable="@drawable/color_progress_seek"
|
||||
android:splitTrack="false"
|
||||
android:thumb="@drawable/switch_thumb_material"
|
||||
app:labelBehavior="gone"
|
||||
app:layout_constraintEnd_toStartOf="@id/songTotalTime"
|
||||
app:layout_constraintStart_toEndOf="@id/songCurrentProgress"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:trackHeight="3dp"
|
||||
tools:ignore="RtlHardcoded,UnusedAttribute"
|
||||
tools:progress="20" />
|
||||
|
||||
|
@ -50,7 +45,6 @@
|
|||
android:minWidth="40dp"
|
||||
android:singleLine="true"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:textSize="12sp"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/progressSlider"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
|
|
|
@ -45,7 +45,6 @@
|
|||
<include layout="@layout/status_bar" />
|
||||
</FrameLayout>
|
||||
|
||||
|
||||
<code.name.monkey.retromusic.views.VerticalTextView
|
||||
android:id="@+id/playerSongTotalTime"
|
||||
android:layout_width="wrap_content"
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/viewGroup"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -25,22 +24,21 @@
|
|||
app:srcCompat="@drawable/ic_volume_down_white_24dp"
|
||||
app:tint="?attr/colorControlNormal" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatSeekBar
|
||||
<com.google.android.material.slider.Slider
|
||||
android:id="@+id/volumeSeekBar"
|
||||
style="@style/Widget.MaterialProgressBar.ProgressBar.Horizontal"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:max="100"
|
||||
android:maxHeight="2dp"
|
||||
android:progressDrawable="@drawable/color_progress_seek"
|
||||
app:labelBehavior="gone"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/volumeDown"
|
||||
app:layout_constraintEnd_toStartOf="@+id/volumeUp"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintStart_toEndOf="@+id/volumeDown"
|
||||
app:layout_constraintTop_toTopOf="@+id/volumeDown"
|
||||
tools:progress="20"
|
||||
tools:progressTint="?attr/colorControlNormal" />
|
||||
app:thumbRadius="8dp"
|
||||
app:trackHeight="2dp" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/volumeUp"
|
||||
|
|
|
@ -28,7 +28,7 @@ dependencies {
|
|||
implementation fileTree(include: ['*.jar'], dir: 'libs')
|
||||
implementation project(':fonts')
|
||||
implementation 'androidx.appcompat:appcompat:1.1.0'
|
||||
implementation 'com.google.android.material:material:1.2.0-alpha04'
|
||||
implementation 'com.google.android.material:material:1.2.0-alpha05'
|
||||
implementation 'androidx.preference:preference:1.1.0'
|
||||
implementation 'androidx.cardview:cardview:1.0.0'
|
||||
// Used for the list preference classes
|
||||
|
|
|
@ -47,6 +47,7 @@
|
|||
|
||||
<color name="md_black_1000">#000000</color>
|
||||
<color name="md_white_1000">#FFFFFF</color>
|
||||
<color name="md_white_semi_transparent">#40FFFFFF</color>
|
||||
|
||||
|
||||
<color name="md_teal_A400">#1DE9B6</color>
|
||||
|
|
|
@ -3,10 +3,8 @@ org.gradle.daemon=true
|
|||
org.gradle.parallel=true
|
||||
jvmArgs='-Xmx2048m'
|
||||
android.useAndroidX=true
|
||||
android.enabelR8=true
|
||||
android.enableR8.fullMode=false
|
||||
android.enableJetifier=true
|
||||
android.debug.obsoleteApi=true
|
||||
android.enableBuildCache=true
|
||||
android.jetifier.blacklist = butterknife.*\\.jar
|
||||
kotlin.code.style=official
|
4
gradle/wrapper/gradle-wrapper.properties
vendored
4
gradle/wrapper/gradle-wrapper.properties
vendored
|
@ -1,6 +1,6 @@
|
|||
#Fri Sep 20 00:21:23 IST 2019
|
||||
#Mon Feb 24 22:35:41 IST 2020
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.1-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip
|
||||
|
|
1
liboverscroll/.gitignore
vendored
1
liboverscroll/.gitignore
vendored
|
@ -1 +0,0 @@
|
|||
/build
|
|
@ -1,49 +0,0 @@
|
|||
apply plugin: 'com.android.library'
|
||||
|
||||
android {
|
||||
compileSdkVersion 29
|
||||
defaultConfig {
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 28
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
}
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'androidx.recyclerview:recyclerview:1.2.0-alpha01'
|
||||
|
||||
testCompile 'junit:junit:4.12'
|
||||
testCompile "org.mockito:mockito-core:1.9.5"
|
||||
testCompile "org.robolectric:robolectric:3.0"
|
||||
}
|
||||
|
||||
|
||||
// Running from Gradle tab in IDE would create liboverscroll/build/lib/liboverscroll-sources.jar
|
||||
task sourcesJar(type: Jar) {
|
||||
from android.sourceSets.main.java.srcDirs
|
||||
classifier = 'sources'
|
||||
}
|
||||
|
||||
task javadoc(type: Javadoc) {
|
||||
failOnError false
|
||||
source = android.sourceSets.main.java.srcDirs
|
||||
classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
|
||||
}
|
||||
|
||||
// Running from Gradle tab in IDE would create liboverscroll/build/lib/liboverscroll-javadoc.jar
|
||||
task javadocJar(type: Jar, dependsOn: javadoc) {
|
||||
classifier = 'javadoc'
|
||||
from javadoc.destinationDir
|
||||
}
|
||||
|
||||
artifacts {
|
||||
archives javadocJar
|
||||
archives sourcesJar
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest
|
||||
package="me.everything" >
|
||||
|
||||
</manifest>
|
|
@ -1,99 +0,0 @@
|
|||
package me.everything.android.ui.overscroll;
|
||||
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
|
||||
import me.everything.android.ui.overscroll.adapters.IOverScrollDecoratorAdapter;
|
||||
|
||||
/**
|
||||
* A concrete implementation of {@link OverScrollBounceEffectDecoratorBase} for a horizontal orientation.
|
||||
*
|
||||
* @author amit
|
||||
*/
|
||||
public class HorizontalOverScrollBounceEffectDecorator extends OverScrollBounceEffectDecoratorBase {
|
||||
|
||||
protected static class MotionAttributesHorizontal extends MotionAttributes {
|
||||
|
||||
public boolean init(View view, MotionEvent event) {
|
||||
|
||||
// We must have history available to calc the dx. Normally it's there - if it isn't temporarily,
|
||||
// we declare the event 'invalid' and expect it in consequent events.
|
||||
if (event.getHistorySize() == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Allow for counter-orientation-direction operations (e.g. item swiping) to run fluently.
|
||||
final float dy = event.getY(0) - event.getHistoricalY(0, 0);
|
||||
final float dx = event.getX(0) - event.getHistoricalX(0, 0);
|
||||
if (Math.abs(dx) < Math.abs(dy)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
mAbsOffset = view.getTranslationX();
|
||||
mDeltaOffset = dx;
|
||||
mDir = mDeltaOffset > 0;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
protected static class AnimationAttributesHorizontal extends AnimationAttributes {
|
||||
|
||||
public AnimationAttributesHorizontal() {
|
||||
mProperty = View.TRANSLATION_X;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void init(View view) {
|
||||
mAbsOffset = view.getTranslationX();
|
||||
mMaxOffset = view.getWidth();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* C'tor, creating the effect with default arguments:
|
||||
* <br/>Touch-drag ratio in 'forward' direction will be set to DEFAULT_TOUCH_DRAG_MOVE_RATIO_FWD.
|
||||
* <br/>Touch-drag ratio in 'backwards' direction will be set to DEFAULT_TOUCH_DRAG_MOVE_RATIO_BCK.
|
||||
* <br/>Deceleration factor (for the bounce-back effect) will be set to DEFAULT_DECELERATE_FACTOR.
|
||||
*
|
||||
* @param viewAdapter The view's encapsulation.
|
||||
*/
|
||||
public HorizontalOverScrollBounceEffectDecorator(IOverScrollDecoratorAdapter viewAdapter) {
|
||||
this(viewAdapter, DEFAULT_TOUCH_DRAG_MOVE_RATIO_FWD, DEFAULT_TOUCH_DRAG_MOVE_RATIO_BCK, DEFAULT_DECELERATE_FACTOR);
|
||||
}
|
||||
|
||||
/**
|
||||
* C'tor, creating the effect with explicit arguments.
|
||||
* @param viewAdapter The view's encapsulation.
|
||||
* @param touchDragRatioFwd Ratio of touch distance to actual drag distance when in 'forward' direction.
|
||||
* @param touchDragRatioBck Ratio of touch distance to actual drag distance when in 'backward'
|
||||
* direction (opposite to initial one).
|
||||
* @param decelerateFactor Deceleration factor used when decelerating the motion to create the
|
||||
* bounce-back effect.
|
||||
*/
|
||||
public HorizontalOverScrollBounceEffectDecorator(IOverScrollDecoratorAdapter viewAdapter,
|
||||
float touchDragRatioFwd, float touchDragRatioBck, float decelerateFactor) {
|
||||
super(viewAdapter, decelerateFactor, touchDragRatioFwd, touchDragRatioBck);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected MotionAttributes createMotionAttributes() {
|
||||
return new MotionAttributesHorizontal();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AnimationAttributes createAnimationAttributes() {
|
||||
return new AnimationAttributesHorizontal();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void translateView(View view, float offset) {
|
||||
view.setTranslationX(offset);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void translateViewAndEvent(View view, float offset, MotionEvent event) {
|
||||
view.setTranslationX(offset);
|
||||
event.offsetLocation(offset - event.getX(0), 0f);
|
||||
}
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
package me.everything.android.ui.overscroll;
|
||||
|
||||
import android.view.View;
|
||||
|
||||
/**
|
||||
* @author amit
|
||||
*/
|
||||
public interface IOverScrollDecor {
|
||||
View getView();
|
||||
|
||||
void setOverScrollStateListener(IOverScrollStateListener listener);
|
||||
void setOverScrollUpdateListener(IOverScrollUpdateListener listener);
|
||||
|
||||
/**
|
||||
* Get the current decorator's runtime state, i.e. one of the values specified by {@link IOverScrollState}.
|
||||
* @return The state.
|
||||
*/
|
||||
int getCurrentState();
|
||||
|
||||
/**
|
||||
* Detach the decorator from its associated view, thus disabling it entirely.
|
||||
*
|
||||
* <p>It is best to call this only when over-scroll isn't currently in-effect - i.e. verify that
|
||||
* <code>getCurrentState()==IOverScrollState.STATE_IDLE</code> as a precondition, or otherwise
|
||||
* use a state listener previously installed using
|
||||
* {@link #setOverScrollStateListener(IOverScrollStateListener)}.</p>
|
||||
*
|
||||
* <p>Note: Upon detachment completion, the view in question will return to the default
|
||||
* Android over-scroll configuration (i.e. {@link View.OVER_SCROLL_ALWAYS} mode). This can be
|
||||
* overridden by calling <code>View.setOverScrollMode(mode)</code> immediately thereafter.</p>
|
||||
*/
|
||||
void detach();
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
package me.everything.android.ui.overscroll;
|
||||
|
||||
/**
|
||||
* @author amit
|
||||
*/
|
||||
public interface IOverScrollState {
|
||||
|
||||
/** No over-scroll is in-effect. */
|
||||
int STATE_IDLE = 0;
|
||||
|
||||
/** User is actively touch-dragging, thus enabling over-scroll at the view's <i>start</i> side. */
|
||||
int STATE_DRAG_START_SIDE = 1;
|
||||
|
||||
/** User is actively touch-dragging, thus enabling over-scroll at the view's <i>end</i> side. */
|
||||
int STATE_DRAG_END_SIDE = 2;
|
||||
|
||||
/** User has released their touch, thus throwing the view back into place via bounce-back animation. */
|
||||
int STATE_BOUNCE_BACK = 3;
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
package me.everything.android.ui.overscroll;
|
||||
|
||||
/**
|
||||
* A callback-listener enabling over-scroll effect clients to be notified of effect state transitions.
|
||||
* <br/>Invoked whenever state is transitioned onto one of {@link IOverScrollState#STATE_IDLE},
|
||||
* {@link IOverScrollState#STATE_DRAG_START_SIDE}, {@link IOverScrollState#STATE_DRAG_END_SIDE}
|
||||
* or {@link IOverScrollState#STATE_BOUNCE_BACK}.
|
||||
*
|
||||
* @author amit
|
||||
*
|
||||
* @see IOverScrollUpdateListener
|
||||
*/
|
||||
public interface IOverScrollStateListener {
|
||||
|
||||
/**
|
||||
* The invoked callback.
|
||||
*
|
||||
* @param decor The associated over-scroll 'decorator'.
|
||||
* @param oldState The old over-scroll state; ID's specified by {@link IOverScrollState}, e.g.
|
||||
* {@link IOverScrollState#STATE_IDLE}.
|
||||
* @param newState The <b>new</b> over-scroll state; ID's specified by {@link IOverScrollState},
|
||||
* e.g. {@link IOverScrollState#STATE_IDLE}.
|
||||
*/
|
||||
void onOverScrollStateChange(IOverScrollDecor decor, int oldState, int newState);
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
package me.everything.android.ui.overscroll;
|
||||
|
||||
/**
|
||||
* A callback-listener enabling over-scroll effect clients to subscribe to <b>real-time</b> updates
|
||||
* of over-scrolling intensity, provided as the view-translation offset from pre-scroll position.
|
||||
*
|
||||
* @author amit
|
||||
*
|
||||
* @see IOverScrollStateListener
|
||||
*/
|
||||
public interface IOverScrollUpdateListener {
|
||||
|
||||
/**
|
||||
* The invoked callback.
|
||||
*
|
||||
* @param decor The associated over-scroll 'decorator'.
|
||||
* @param state One of: {@link IOverScrollState#STATE_IDLE}, {@link IOverScrollState#STATE_DRAG_START_SIDE},
|
||||
* {@link IOverScrollState#STATE_DRAG_START_SIDE} or {@link IOverScrollState#STATE_BOUNCE_BACK}.
|
||||
* @param offset The currently visible offset created due to over-scroll.
|
||||
*/
|
||||
void onOverScrollUpdate(IOverScrollDecor decor, int state, float offset);
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
package me.everything.android.ui.overscroll;
|
||||
|
||||
/**
|
||||
* @author amit
|
||||
*/
|
||||
public interface ListenerStubs {
|
||||
|
||||
class OverScrollStateListenerStub implements IOverScrollStateListener {
|
||||
@Override
|
||||
public void onOverScrollStateChange(IOverScrollDecor decor, int oldState, int newState) { }
|
||||
}
|
||||
|
||||
class OverScrollUpdateListenerStub implements IOverScrollUpdateListener {
|
||||
@Override
|
||||
public void onOverScrollUpdate(IOverScrollDecor decor, int state, float offset) { }
|
||||
}
|
||||
}
|
|
@ -1,483 +0,0 @@
|
|||
package me.everything.android.ui.overscroll;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorSet;
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.animation.ValueAnimator;
|
||||
import android.util.Log;
|
||||
import android.util.Property;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.animation.DecelerateInterpolator;
|
||||
import android.view.animation.Interpolator;
|
||||
|
||||
import me.everything.android.ui.overscroll.adapters.IOverScrollDecoratorAdapter;
|
||||
import me.everything.android.ui.overscroll.adapters.RecyclerViewOverScrollDecorAdapter;
|
||||
|
||||
import static me.everything.android.ui.overscroll.IOverScrollState.*;
|
||||
import static me.everything.android.ui.overscroll.ListenerStubs.*;
|
||||
|
||||
/**
|
||||
* A standalone view decorator adding over-scroll with a smooth bounce-back effect to (potentially) any view -
|
||||
* provided that an appropriate {@link IOverScrollDecoratorAdapter} implementation exists / can be written
|
||||
* for that view type (e.g. {@link RecyclerViewOverScrollDecorAdapter}).
|
||||
*
|
||||
* <p>Design-wise, being a standalone class, this decorator powerfully provides the ability to add
|
||||
* the over-scroll effect over any view without adjusting the view's implementation. In essence, this
|
||||
* eliminates the need to repeatedly implement the effect per each view type (list-view,
|
||||
* recycler-view, image-view, etc.). Therefore, using it is highly recommended compared to other
|
||||
* more intrusive solutions.</p>
|
||||
*
|
||||
* <p>Note that this class is abstract, having {@link HorizontalOverScrollBounceEffectDecorator} and
|
||||
* {@link VerticalOverScrollBounceEffectDecorator} providing concrete implementations that are
|
||||
* view-orientation specific.</p>
|
||||
*
|
||||
* <hr width="97%"/>
|
||||
* <h2>Implementation Notes</h2>
|
||||
*
|
||||
* <p>At it's core, the class simply registers itself as a touch-listener over the decorated view and
|
||||
* intercepts touch events as needed.</p>
|
||||
*
|
||||
* <p>Internally, it delegates the over-scrolling calculations onto 3 state-based classes:
|
||||
* <ol>
|
||||
* <li><b>Idle state</b> - monitors view state and touch events to intercept over-scrolling initiation
|
||||
* (in which case it hands control over to the Over-scrolling state).</li>
|
||||
* <li><b>Over-scrolling state</b> - handles motion events to apply the over-scroll effect as users
|
||||
* interact with the view.</li>
|
||||
* <li><b>Bounce-back state</b> - runs the bounce-back animation, all-the-while blocking all
|
||||
* touch events till the animation completes (in which case it hands control back to the idle
|
||||
* state).</li>
|
||||
* </ol>
|
||||
* </p>
|
||||
*
|
||||
* @author amit
|
||||
*
|
||||
* @see RecyclerViewOverScrollDecorAdapter
|
||||
* @see IOverScrollDecoratorAdapter
|
||||
*/
|
||||
public abstract class OverScrollBounceEffectDecoratorBase implements IOverScrollDecor, View.OnTouchListener {
|
||||
|
||||
public static final String TAG = "OverScrollDecor";
|
||||
|
||||
public static final float DEFAULT_TOUCH_DRAG_MOVE_RATIO_FWD = 3f;
|
||||
public static final float DEFAULT_TOUCH_DRAG_MOVE_RATIO_BCK = 1f;
|
||||
public static final float DEFAULT_DECELERATE_FACTOR = -2f;
|
||||
|
||||
protected static final int MAX_BOUNCE_BACK_DURATION_MS = 800;
|
||||
protected static final int MIN_BOUNCE_BACK_DURATION_MS = 200;
|
||||
|
||||
protected final OverScrollStartAttributes mStartAttr = new OverScrollStartAttributes();
|
||||
protected final IOverScrollDecoratorAdapter mViewAdapter;
|
||||
|
||||
protected final IdleState mIdleState;
|
||||
protected final OverScrollingState mOverScrollingState;
|
||||
protected final BounceBackState mBounceBackState;
|
||||
protected IDecoratorState mCurrentState;
|
||||
|
||||
protected IOverScrollStateListener mStateListener = new OverScrollStateListenerStub();
|
||||
protected IOverScrollUpdateListener mUpdateListener = new OverScrollUpdateListenerStub();
|
||||
|
||||
/**
|
||||
* When in over-scroll mode, keep track of dragging velocity to provide a smooth slow-down
|
||||
* for the bounce-back effect.
|
||||
*/
|
||||
protected float mVelocity;
|
||||
|
||||
/**
|
||||
* Motion attributes: keeps data describing current motion event.
|
||||
* <br/>Orientation agnostic: subclasses provide either horizontal or vertical
|
||||
* initialization of the agnostic attributes.
|
||||
*/
|
||||
protected abstract static class MotionAttributes {
|
||||
public float mAbsOffset;
|
||||
public float mDeltaOffset;
|
||||
public boolean mDir; // True = 'forward', false = 'backwards'.
|
||||
|
||||
protected abstract boolean init(View view, MotionEvent event);
|
||||
}
|
||||
|
||||
protected static class OverScrollStartAttributes {
|
||||
protected int mPointerId;
|
||||
protected float mAbsOffset;
|
||||
protected boolean mDir; // True = 'forward', false = 'backwards'.
|
||||
}
|
||||
|
||||
protected abstract static class AnimationAttributes {
|
||||
public Property<View, Float> mProperty;
|
||||
public float mAbsOffset;
|
||||
public float mMaxOffset;
|
||||
|
||||
protected abstract void init(View view);
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface of decorator-state delegation classes. Defines states as handles of two fundamental
|
||||
* touch events: actual movement, up/cancel.
|
||||
*/
|
||||
protected interface IDecoratorState {
|
||||
|
||||
/**
|
||||
* Handle a motion (touch) event.
|
||||
*
|
||||
* @param event The event from onTouch.
|
||||
* @return Return value for onTouch.
|
||||
*/
|
||||
boolean handleMoveTouchEvent(MotionEvent event);
|
||||
|
||||
/**
|
||||
* Handle up / touch-cancel events.
|
||||
*
|
||||
* @param event The event from onTouch.
|
||||
* @return Return value for onTouch.
|
||||
*/
|
||||
boolean handleUpOrCancelTouchEvent(MotionEvent event);
|
||||
|
||||
/**
|
||||
* Handle a transition onto this state, as it becomes 'current' state.
|
||||
* @param fromState
|
||||
*/
|
||||
void handleEntryTransition(IDecoratorState fromState);
|
||||
|
||||
/**
|
||||
* The client-perspective ID of the state associated with this (internal) one. ID's
|
||||
* are as specified in {@link IOverScrollState}.
|
||||
*
|
||||
* @return The ID, e.g. {@link IOverScrollState#STATE_IDLE}.
|
||||
*/
|
||||
int getStateId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Idle state: monitors move events, trying to figure out whether over-scrolling should be
|
||||
* initiated (i.e. when scrolled further when the view is at one of its displayable ends).
|
||||
* <br/>When such is the case, it hands over control to the over-scrolling state.
|
||||
*/
|
||||
protected class IdleState implements IDecoratorState {
|
||||
|
||||
final MotionAttributes mMoveAttr;
|
||||
|
||||
public IdleState() {
|
||||
mMoveAttr = createMotionAttributes();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getStateId() {
|
||||
return STATE_IDLE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handleMoveTouchEvent(MotionEvent event) {
|
||||
|
||||
final View view = mViewAdapter.getView();
|
||||
if (!mMoveAttr.init(view, event)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Has over-scrolling officially started?
|
||||
if ((mViewAdapter.isInAbsoluteStart() && mMoveAttr.mDir) ||
|
||||
(mViewAdapter.isInAbsoluteEnd() && !mMoveAttr.mDir)) {
|
||||
|
||||
// Save initial over-scroll attributes for future reference.
|
||||
mStartAttr.mPointerId = event.getPointerId(0);
|
||||
mStartAttr.mAbsOffset = mMoveAttr.mAbsOffset;
|
||||
mStartAttr.mDir = mMoveAttr.mDir;
|
||||
|
||||
issueStateTransition(mOverScrollingState);
|
||||
return mOverScrollingState.handleMoveTouchEvent(event);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handleUpOrCancelTouchEvent(MotionEvent event) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleEntryTransition(IDecoratorState fromState) {
|
||||
mStateListener.onOverScrollStateChange(OverScrollBounceEffectDecoratorBase.this, fromState.getStateId(), this.getStateId());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the actual over-scrolling: thus translating the view according to configuration
|
||||
* and user interactions, dynamically.
|
||||
*
|
||||
* <br/><br/>The state is exited - thus completing over-scroll handling, in one of two cases:
|
||||
* <br/>When user lets go of the view, it transitions control to the bounce-back state.
|
||||
* <br/>When user moves the view back onto a potential 'under-scroll' state, it abruptly
|
||||
* transitions control to the idle-state, so as to return touch-events management to the
|
||||
* normal over-scroll-less environment (thus preventing under-scrolling and potentially regaining
|
||||
* regular scrolling).
|
||||
*/
|
||||
protected class OverScrollingState implements IDecoratorState {
|
||||
|
||||
protected final float mTouchDragRatioFwd;
|
||||
protected final float mTouchDragRatioBck;
|
||||
|
||||
final MotionAttributes mMoveAttr;
|
||||
int mCurrDragState;
|
||||
|
||||
public OverScrollingState(float touchDragRatioFwd, float touchDragRatioBck) {
|
||||
mMoveAttr = createMotionAttributes();
|
||||
mTouchDragRatioFwd = touchDragRatioFwd;
|
||||
mTouchDragRatioBck = touchDragRatioBck;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getStateId() {
|
||||
// This is really a single class that implements 2 states, so our ID depends on what
|
||||
// it was during the last invocation.
|
||||
return mCurrDragState;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handleMoveTouchEvent(MotionEvent event) {
|
||||
|
||||
// Switching 'pointers' (e.g. fingers) on-the-fly isn't supported -- abort over-scroll
|
||||
// smoothly using the default bounce-back animation in this case.
|
||||
if (mStartAttr.mPointerId != event.getPointerId(0)) {
|
||||
issueStateTransition(mBounceBackState);
|
||||
return true;
|
||||
}
|
||||
|
||||
final View view = mViewAdapter.getView();
|
||||
if (!mMoveAttr.init(view, event)) {
|
||||
// Keep intercepting the touch event as long as we're still over-scrolling...
|
||||
return true;
|
||||
}
|
||||
|
||||
float deltaOffset = mMoveAttr.mDeltaOffset / (mMoveAttr.mDir == mStartAttr.mDir ? mTouchDragRatioFwd : mTouchDragRatioBck);
|
||||
float newOffset = mMoveAttr.mAbsOffset + deltaOffset;
|
||||
|
||||
// If moved in counter direction onto a potential under-scroll state -- don't. Instead, abort
|
||||
// over-scrolling abruptly, thus returning control to which-ever touch handlers there
|
||||
// are waiting (e.g. regular scroller handlers).
|
||||
if ( (mStartAttr.mDir && !mMoveAttr.mDir && (newOffset <= mStartAttr.mAbsOffset)) ||
|
||||
(!mStartAttr.mDir && mMoveAttr.mDir && (newOffset >= mStartAttr.mAbsOffset)) ) {
|
||||
translateViewAndEvent(view, mStartAttr.mAbsOffset, event);
|
||||
mUpdateListener.onOverScrollUpdate(OverScrollBounceEffectDecoratorBase.this, mCurrDragState, 0);
|
||||
|
||||
issueStateTransition(mIdleState);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (view.getParent() != null) {
|
||||
view.getParent().requestDisallowInterceptTouchEvent(true);
|
||||
}
|
||||
|
||||
long dt = event.getEventTime() - event.getHistoricalEventTime(0);
|
||||
if (dt > 0) { // Sometimes (though rarely) dt==0 cause originally timing is in nanos, but is presented in millis.
|
||||
mVelocity = deltaOffset / dt;
|
||||
}
|
||||
|
||||
translateView(view, newOffset);
|
||||
mUpdateListener.onOverScrollUpdate(OverScrollBounceEffectDecoratorBase.this, mCurrDragState, newOffset);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handleUpOrCancelTouchEvent(MotionEvent event) {
|
||||
issueStateTransition(mBounceBackState);
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleEntryTransition(IDecoratorState fromState) {
|
||||
mCurrDragState = (mStartAttr.mDir ? STATE_DRAG_START_SIDE : STATE_DRAG_END_SIDE);
|
||||
mStateListener.onOverScrollStateChange(OverScrollBounceEffectDecoratorBase.this, fromState.getStateId(), this.getStateId());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* When entered, starts the bounce-back animation.
|
||||
* <br/>Upon animation completion, transitions control onto the idle state; Does so by
|
||||
* registering itself as an animation listener.
|
||||
* <br/>In the meantime, blocks (intercepts) all touch events.
|
||||
*/
|
||||
protected class BounceBackState implements IDecoratorState, Animator.AnimatorListener, ValueAnimator.AnimatorUpdateListener {
|
||||
|
||||
protected final Interpolator mBounceBackInterpolator = new DecelerateInterpolator();
|
||||
protected final float mDecelerateFactor;
|
||||
protected final float mDoubleDecelerateFactor;
|
||||
|
||||
protected final AnimationAttributes mAnimAttributes;
|
||||
|
||||
public BounceBackState(float decelerateFactor) {
|
||||
mDecelerateFactor = decelerateFactor;
|
||||
mDoubleDecelerateFactor = 2f * decelerateFactor;
|
||||
|
||||
mAnimAttributes = createAnimationAttributes();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getStateId() {
|
||||
return STATE_BOUNCE_BACK;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleEntryTransition(IDecoratorState fromState) {
|
||||
|
||||
mStateListener.onOverScrollStateChange(OverScrollBounceEffectDecoratorBase.this, fromState.getStateId(), this.getStateId());
|
||||
|
||||
Animator bounceBackAnim = createAnimator();
|
||||
bounceBackAnim.addListener(this);
|
||||
|
||||
bounceBackAnim.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handleMoveTouchEvent(MotionEvent event) {
|
||||
// Flush all touches down the drain till animation is over.
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handleUpOrCancelTouchEvent(MotionEvent event) {
|
||||
// Flush all touches down the drain till animation is over.
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
issueStateTransition(mIdleState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationUpdate(ValueAnimator animation) {
|
||||
mUpdateListener.onOverScrollUpdate(OverScrollBounceEffectDecoratorBase.this, STATE_BOUNCE_BACK, (Float) animation.getAnimatedValue());
|
||||
}
|
||||
|
||||
@Override public void onAnimationStart(Animator animation) {}
|
||||
@Override public void onAnimationCancel(Animator animation) {}
|
||||
@Override public void onAnimationRepeat(Animator animation) {}
|
||||
|
||||
protected Animator createAnimator() {
|
||||
|
||||
final View view = mViewAdapter.getView();
|
||||
|
||||
mAnimAttributes.init(view);
|
||||
|
||||
// Set up a low-duration slow-down animation IN the drag direction.
|
||||
|
||||
// Exception: If wasn't dragging in 'forward' direction (or velocity=0 -- i.e. not dragging at all),
|
||||
// skip slow-down anim directly to the bounce-back.
|
||||
if (mVelocity == 0f || (mVelocity < 0 && mStartAttr.mDir) || (mVelocity > 0 && !mStartAttr.mDir)) {
|
||||
return createBounceBackAnimator(mAnimAttributes.mAbsOffset);
|
||||
}
|
||||
|
||||
// dt = (Vt - Vo) / a; Vt=0 ==> dt = -Vo / a
|
||||
float slowdownDuration = -mVelocity / mDecelerateFactor;
|
||||
slowdownDuration = (slowdownDuration < 0 ? 0 : slowdownDuration); // Happens in counter-direction dragging
|
||||
|
||||
// dx = (Vt^2 - Vo^2) / 2a; Vt=0 ==> dx = -Vo^2 / 2a
|
||||
float slowdownDistance = -mVelocity * mVelocity / mDoubleDecelerateFactor;
|
||||
float slowdownEndOffset = mAnimAttributes.mAbsOffset + slowdownDistance;
|
||||
|
||||
ObjectAnimator slowdownAnim = createSlowdownAnimator(view, (int) slowdownDuration, slowdownEndOffset);
|
||||
|
||||
// Set up the bounce back animation, bringing the view back into the original, pre-overscroll position (translation=0).
|
||||
|
||||
ObjectAnimator bounceBackAnim = createBounceBackAnimator(slowdownEndOffset);
|
||||
|
||||
// Play the 2 animations as a sequence.
|
||||
AnimatorSet wholeAnim = new AnimatorSet();
|
||||
wholeAnim.playSequentially(slowdownAnim, bounceBackAnim);
|
||||
return wholeAnim;
|
||||
}
|
||||
|
||||
protected ObjectAnimator createSlowdownAnimator(View view, int slowdownDuration, float slowdownEndOffset) {
|
||||
ObjectAnimator slowdownAnim = ObjectAnimator.ofFloat(view, mAnimAttributes.mProperty, slowdownEndOffset);
|
||||
slowdownAnim.setDuration(slowdownDuration);
|
||||
slowdownAnim.setInterpolator(mBounceBackInterpolator);
|
||||
slowdownAnim.addUpdateListener(this);
|
||||
return slowdownAnim;
|
||||
}
|
||||
|
||||
protected ObjectAnimator createBounceBackAnimator(float startOffset) {
|
||||
|
||||
final View view = mViewAdapter.getView();
|
||||
|
||||
// Duration is proportional to the view's size.
|
||||
float bounceBackDuration = (Math.abs(startOffset) / mAnimAttributes.mMaxOffset) * MAX_BOUNCE_BACK_DURATION_MS;
|
||||
ObjectAnimator bounceBackAnim = ObjectAnimator.ofFloat(view, mAnimAttributes.mProperty, mStartAttr.mAbsOffset);
|
||||
bounceBackAnim.setDuration(Math.max((int) bounceBackDuration, MIN_BOUNCE_BACK_DURATION_MS));
|
||||
bounceBackAnim.setInterpolator(mBounceBackInterpolator);
|
||||
bounceBackAnim.addUpdateListener(this);
|
||||
return bounceBackAnim;
|
||||
}
|
||||
}
|
||||
|
||||
public OverScrollBounceEffectDecoratorBase(IOverScrollDecoratorAdapter viewAdapter, float decelerateFactor, float touchDragRatioFwd, float touchDragRatioBck) {
|
||||
mViewAdapter = viewAdapter;
|
||||
|
||||
mBounceBackState = new BounceBackState(decelerateFactor);
|
||||
mOverScrollingState = new OverScrollingState(touchDragRatioFwd, touchDragRatioBck);
|
||||
mIdleState = new IdleState();
|
||||
|
||||
mCurrentState = mIdleState;
|
||||
|
||||
attach();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouch(View v, MotionEvent event) {
|
||||
switch (event.getAction()) {
|
||||
case MotionEvent.ACTION_MOVE:
|
||||
return mCurrentState.handleMoveTouchEvent(event);
|
||||
|
||||
case MotionEvent.ACTION_CANCEL:
|
||||
case MotionEvent.ACTION_UP:
|
||||
return mCurrentState.handleUpOrCancelTouchEvent(event);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOverScrollStateListener(IOverScrollStateListener listener) {
|
||||
mStateListener = (listener != null ? listener : new OverScrollStateListenerStub());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOverScrollUpdateListener(IOverScrollUpdateListener listener) {
|
||||
mUpdateListener = (listener != null ? listener : new OverScrollUpdateListenerStub());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCurrentState() {
|
||||
return mCurrentState.getStateId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView() {
|
||||
return mViewAdapter.getView();
|
||||
}
|
||||
|
||||
protected void issueStateTransition(IDecoratorState state) {
|
||||
IDecoratorState oldState = mCurrentState;
|
||||
mCurrentState = state;
|
||||
mCurrentState.handleEntryTransition(oldState);
|
||||
}
|
||||
|
||||
protected void attach() {
|
||||
getView().setOnTouchListener(this);
|
||||
getView().setOverScrollMode(View.OVER_SCROLL_NEVER);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void detach() {
|
||||
if (mCurrentState != mIdleState) {
|
||||
Log.w(TAG, "Decorator detached while over-scroll is in effect. You might want to add a precondition of that getCurrentState()==STATE_IDLE, first.");
|
||||
}
|
||||
getView().setOnTouchListener(null);
|
||||
getView().setOverScrollMode(View.OVER_SCROLL_ALWAYS);
|
||||
}
|
||||
|
||||
protected abstract MotionAttributes createMotionAttributes();
|
||||
protected abstract AnimationAttributes createAnimationAttributes();
|
||||
protected abstract void translateView(View view, float offset);
|
||||
protected abstract void translateViewAndEvent(View view, float offset, MotionEvent event);
|
||||
}
|
|
@ -1,78 +0,0 @@
|
|||
package me.everything.android.ui.overscroll;
|
||||
|
||||
import android.view.View;
|
||||
import android.widget.ScrollView;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.widget.NestedScrollView;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import me.everything.android.ui.overscroll.adapters.NestedScrollViewOverScrollDecorAdapter;
|
||||
import me.everything.android.ui.overscroll.adapters.RecyclerViewOverScrollDecorAdapter;
|
||||
import me.everything.android.ui.overscroll.adapters.ScrollViewOverScrollDecorAdapter;
|
||||
import me.everything.android.ui.overscroll.adapters.StaticOverScrollDecorAdapter;
|
||||
|
||||
/**
|
||||
* @author amit
|
||||
*/
|
||||
public class OverScrollDecoratorHelper {
|
||||
|
||||
public static final int ORIENTATION_VERTICAL = 0;
|
||||
|
||||
public static final int ORIENTATION_HORIZONTAL = 1;
|
||||
|
||||
/**
|
||||
* Set up the over-scroll effect over a specified {@link RecyclerView} view.
|
||||
* <br/>Only recycler-views using <b>native</b> Android layout managers (i.e. {@link LinearLayoutManager},
|
||||
* {@link GridLayoutManager} and {@link StaggeredGridLayoutManager}) are currently supported
|
||||
* by this convenience method.
|
||||
*
|
||||
* @param recyclerView The view.
|
||||
* @param orientation Either {@link #ORIENTATION_HORIZONTAL} or {@link #ORIENTATION_VERTICAL}.
|
||||
* @return The over-scroll effect 'decorator', enabling further effect configuration.
|
||||
*/
|
||||
@NonNull
|
||||
public static IOverScrollDecor setUpOverScroll(@NonNull RecyclerView recyclerView, int orientation) {
|
||||
switch (orientation) {
|
||||
case ORIENTATION_HORIZONTAL:
|
||||
return new HorizontalOverScrollBounceEffectDecorator(
|
||||
new RecyclerViewOverScrollDecorAdapter(recyclerView));
|
||||
case ORIENTATION_VERTICAL:
|
||||
return new VerticalOverScrollBounceEffectDecorator(
|
||||
new RecyclerViewOverScrollDecorAdapter(recyclerView));
|
||||
default:
|
||||
throw new IllegalArgumentException("orientation");
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static IOverScrollDecor setUpOverScroll(@NonNull ScrollView scrollView) {
|
||||
return new VerticalOverScrollBounceEffectDecorator(new ScrollViewOverScrollDecorAdapter(scrollView));
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static IOverScrollDecor setUpOverScroll(@NonNull NestedScrollView nestedScrollView) {
|
||||
return new VerticalOverScrollBounceEffectDecorator(
|
||||
new NestedScrollViewOverScrollDecorAdapter(nestedScrollView));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up the over-scroll over a generic view, assumed to always be over-scroll ready (e.g.
|
||||
* a plain text field, image view).
|
||||
*
|
||||
* @param view The view.
|
||||
* @param orientation One of {@link #ORIENTATION_HORIZONTAL} or {@link #ORIENTATION_VERTICAL}.
|
||||
* @return The over-scroll effect 'decorator', enabling further effect configuration.
|
||||
*/
|
||||
public static IOverScrollDecor setUpStaticOverScroll(View view, int orientation) {
|
||||
switch (orientation) {
|
||||
case ORIENTATION_HORIZONTAL:
|
||||
return new HorizontalOverScrollBounceEffectDecorator(new StaticOverScrollDecorAdapter(view));
|
||||
|
||||
case ORIENTATION_VERTICAL:
|
||||
return new VerticalOverScrollBounceEffectDecorator(new StaticOverScrollDecorAdapter(view));
|
||||
|
||||
default:
|
||||
throw new IllegalArgumentException("orientation");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,100 +0,0 @@
|
|||
package me.everything.android.ui.overscroll;
|
||||
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import me.everything.android.ui.overscroll.adapters.IOverScrollDecoratorAdapter;
|
||||
|
||||
/**
|
||||
* A concrete implementation of {@link OverScrollBounceEffectDecoratorBase} for a vertical orientation.
|
||||
*
|
||||
* @author amit
|
||||
*/
|
||||
public class VerticalOverScrollBounceEffectDecorator extends OverScrollBounceEffectDecoratorBase {
|
||||
|
||||
protected static class MotionAttributesVertical extends MotionAttributes {
|
||||
|
||||
public boolean init(View view, MotionEvent event) {
|
||||
|
||||
// We must have history available to calc the dx. Normally it's there - if it isn't temporarily,
|
||||
// we declare the event 'invalid' and expect it in consequent events.
|
||||
if (event.getHistorySize() == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Allow for counter-orientation-direction operations (e.g. item swiping) to run fluently.
|
||||
final float dy = event.getY(0) - event.getHistoricalY(0, 0);
|
||||
final float dx = event.getX(0) - event.getHistoricalX(0, 0);
|
||||
if (Math.abs(dx) > Math.abs(dy)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
mAbsOffset = view.getTranslationY();
|
||||
mDeltaOffset = dy;
|
||||
mDir = mDeltaOffset > 0;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
protected static class AnimationAttributesVertical extends AnimationAttributes {
|
||||
|
||||
public AnimationAttributesVertical() {
|
||||
mProperty = View.TRANSLATION_Y;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void init(View view) {
|
||||
mAbsOffset = view.getTranslationY();
|
||||
mMaxOffset = view.getHeight();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* C'tor, creating the effect with default arguments:
|
||||
* <br/>Touch-drag ratio in 'forward' direction will be set to DEFAULT_TOUCH_DRAG_MOVE_RATIO_FWD.
|
||||
* <br/>Touch-drag ratio in 'backwards' direction will be set to DEFAULT_TOUCH_DRAG_MOVE_RATIO_BCK.
|
||||
* <br/>Deceleration factor (for the bounce-back effect) will be set to DEFAULT_DECELERATE_FACTOR.
|
||||
*
|
||||
* @param viewAdapter The view's encapsulation.
|
||||
*/
|
||||
public VerticalOverScrollBounceEffectDecorator(IOverScrollDecoratorAdapter viewAdapter) {
|
||||
this(viewAdapter, DEFAULT_TOUCH_DRAG_MOVE_RATIO_FWD, DEFAULT_TOUCH_DRAG_MOVE_RATIO_BCK,
|
||||
DEFAULT_DECELERATE_FACTOR);
|
||||
}
|
||||
|
||||
/**
|
||||
* C'tor, creating the effect with explicit arguments.
|
||||
*
|
||||
* @param viewAdapter The view's encapsulation.
|
||||
* @param touchDragRatioFwd Ratio of touch distance to actual drag distance when in 'forward' direction.
|
||||
* @param touchDragRatioBck Ratio of touch distance to actual drag distance when in 'backward'
|
||||
* direction (opposite to initial one).
|
||||
* @param decelerateFactor Deceleration factor used when decelerating the motion to create the
|
||||
* bounce-back effect.
|
||||
*/
|
||||
public VerticalOverScrollBounceEffectDecorator(IOverScrollDecoratorAdapter viewAdapter,
|
||||
float touchDragRatioFwd, float touchDragRatioBck, float decelerateFactor) {
|
||||
super(viewAdapter, decelerateFactor, touchDragRatioFwd, touchDragRatioBck);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AnimationAttributes createAnimationAttributes() {
|
||||
return new AnimationAttributesVertical();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected MotionAttributes createMotionAttributes() {
|
||||
return new MotionAttributesVertical();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void translateView(View view, float offset) {
|
||||
view.setTranslationY(offset);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void translateViewAndEvent(View view, float offset, MotionEvent event) {
|
||||
view.setTranslationY(offset);
|
||||
event.offsetLocation(offset - event.getY(0), 0f);
|
||||
}
|
||||
}
|
|
@ -1,57 +0,0 @@
|
|||
package me.everything.android.ui.overscroll.adapters;
|
||||
|
||||
import android.view.View;
|
||||
import android.widget.AbsListView;
|
||||
|
||||
import me.everything.android.ui.overscroll.HorizontalOverScrollBounceEffectDecorator;
|
||||
import me.everything.android.ui.overscroll.VerticalOverScrollBounceEffectDecorator;
|
||||
|
||||
/**
|
||||
* An adapter to enable over-scrolling over object of {@link AbsListView}, namely {@link
|
||||
* android.widget.ListView} and it's extensions, and {@link android.widget.GridView}.
|
||||
*
|
||||
* @author amit
|
||||
*
|
||||
* @see HorizontalOverScrollBounceEffectDecorator
|
||||
* @see VerticalOverScrollBounceEffectDecorator
|
||||
*/
|
||||
public class AbsListViewOverScrollDecorAdapter implements IOverScrollDecoratorAdapter {
|
||||
|
||||
protected final AbsListView mView;
|
||||
|
||||
public AbsListViewOverScrollDecorAdapter(AbsListView view) {
|
||||
mView = view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView() {
|
||||
return mView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInAbsoluteStart() {
|
||||
return mView.getChildCount() > 0 && !canScrollListUp();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInAbsoluteEnd() {
|
||||
return mView.getChildCount() > 0 && !canScrollListDown();
|
||||
}
|
||||
|
||||
public boolean canScrollListUp() {
|
||||
// Ported from AbsListView#canScrollList() which isn't compatible to all API levels
|
||||
final int firstTop = mView.getChildAt(0).getTop();
|
||||
final int firstPosition = mView.getFirstVisiblePosition();
|
||||
return firstPosition > 0 || firstTop < mView.getListPaddingTop();
|
||||
}
|
||||
|
||||
public boolean canScrollListDown() {
|
||||
// Ported from AbsListView#canScrollList() which isn't compatible to all API levels
|
||||
final int childCount = mView.getChildCount();
|
||||
final int itemsCount = mView.getCount();
|
||||
final int firstPosition = mView.getFirstVisiblePosition();
|
||||
final int lastPosition = firstPosition + childCount;
|
||||
final int lastBottom = mView.getChildAt(childCount - 1).getBottom();
|
||||
return lastPosition < itemsCount || lastBottom > mView.getHeight() - mView.getListPaddingBottom();
|
||||
}
|
||||
}
|
|
@ -1,41 +0,0 @@
|
|||
package me.everything.android.ui.overscroll.adapters;
|
||||
|
||||
import android.view.View;
|
||||
import android.widget.HorizontalScrollView;
|
||||
|
||||
import me.everything.android.ui.overscroll.HorizontalOverScrollBounceEffectDecorator;
|
||||
import me.everything.android.ui.overscroll.VerticalOverScrollBounceEffectDecorator;
|
||||
|
||||
/**
|
||||
* An adapter that enables over-scrolling support over a {@link HorizontalScrollView}.
|
||||
* <br/>Seeing that {@link HorizontalScrollView} only supports horizontal scrolling, this adapter
|
||||
* should only be used with a {@link HorizontalOverScrollBounceEffectDecorator}.
|
||||
*
|
||||
* @author amit
|
||||
*
|
||||
* @see HorizontalOverScrollBounceEffectDecorator
|
||||
* @see VerticalOverScrollBounceEffectDecorator
|
||||
*/
|
||||
public class HorizontalScrollViewOverScrollDecorAdapter implements IOverScrollDecoratorAdapter {
|
||||
|
||||
protected final HorizontalScrollView mView;
|
||||
|
||||
public HorizontalScrollViewOverScrollDecorAdapter(HorizontalScrollView view) {
|
||||
mView = view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView() {
|
||||
return mView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInAbsoluteStart() {
|
||||
return !mView.canScrollHorizontally(-1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInAbsoluteEnd() {
|
||||
return !mView.canScrollHorizontally(1);
|
||||
}
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
package me.everything.android.ui.overscroll.adapters;
|
||||
|
||||
import android.view.View;
|
||||
|
||||
import me.everything.android.ui.overscroll.HorizontalOverScrollBounceEffectDecorator;
|
||||
|
||||
/**
|
||||
* @author amitd
|
||||
*
|
||||
* @see HorizontalOverScrollBounceEffectDecorator
|
||||
*/
|
||||
public interface IOverScrollDecoratorAdapter {
|
||||
|
||||
View getView();
|
||||
|
||||
/**
|
||||
* Is view in it's absolute start position - such that a negative over-scroll can potentially
|
||||
* be initiated. For example, in list-views, this is synonymous with the first item being
|
||||
* fully visible.
|
||||
*
|
||||
* @return Whether in absolute start position.
|
||||
*/
|
||||
boolean isInAbsoluteStart();
|
||||
|
||||
/**
|
||||
* Is view in it's absolute end position - such that an over-scroll can potentially
|
||||
* be initiated. For example, in list-views, this is synonymous with the last item being
|
||||
* fully visible.
|
||||
*
|
||||
* @return Whether in absolute end position.
|
||||
*/
|
||||
boolean isInAbsoluteEnd();
|
||||
}
|
|
@ -1,40 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2020 Hemanth Savarala.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3
|
||||
*
|
||||
* This 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 software 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.
|
||||
*/
|
||||
|
||||
package me.everything.android.ui.overscroll.adapters;
|
||||
|
||||
import android.view.View;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.widget.NestedScrollView;
|
||||
|
||||
public class NestedScrollViewOverScrollDecorAdapter implements IOverScrollDecoratorAdapter {
|
||||
|
||||
protected final NestedScrollView mView;
|
||||
|
||||
public NestedScrollViewOverScrollDecorAdapter(@NonNull NestedScrollView view) {
|
||||
this.mView = view;
|
||||
}
|
||||
|
||||
public View getView() {
|
||||
return this.mView;
|
||||
}
|
||||
|
||||
public boolean isInAbsoluteEnd() {
|
||||
return !this.mView.canScrollVertically(1);
|
||||
}
|
||||
|
||||
public boolean isInAbsoluteStart() {
|
||||
return !this.mView.canScrollVertically(-1);
|
||||
}
|
||||
}
|
|
@ -1,237 +0,0 @@
|
|||
package me.everything.android.ui.overscroll.adapters;
|
||||
|
||||
import android.graphics.Canvas;
|
||||
import android.view.View;
|
||||
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.recyclerview.widget.StaggeredGridLayoutManager;
|
||||
import java.util.List;
|
||||
import me.everything.android.ui.overscroll.HorizontalOverScrollBounceEffectDecorator;
|
||||
import me.everything.android.ui.overscroll.VerticalOverScrollBounceEffectDecorator;
|
||||
|
||||
/**
|
||||
* @author amitd
|
||||
* @see HorizontalOverScrollBounceEffectDecorator
|
||||
* @see VerticalOverScrollBounceEffectDecorator
|
||||
*/
|
||||
public class RecyclerViewOverScrollDecorAdapter implements IOverScrollDecoratorAdapter {
|
||||
|
||||
private static class ItemTouchHelperCallbackWrapper extends ItemTouchHelper.Callback {
|
||||
|
||||
final ItemTouchHelper.Callback mCallback;
|
||||
|
||||
private ItemTouchHelperCallbackWrapper(ItemTouchHelper.Callback callback) {
|
||||
mCallback = callback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canDropOver(RecyclerView recyclerView, RecyclerView.ViewHolder current,
|
||||
RecyclerView.ViewHolder target) {
|
||||
return mCallback.canDropOver(recyclerView, current, target);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RecyclerView.ViewHolder chooseDropTarget(RecyclerView.ViewHolder selected,
|
||||
List<RecyclerView.ViewHolder> dropTargets, int curX, int curY) {
|
||||
return mCallback.chooseDropTarget(selected, dropTargets, curX, curY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
|
||||
mCallback.clearView(recyclerView, viewHolder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int convertToAbsoluteDirection(int flags, int layoutDirection) {
|
||||
return mCallback.convertToAbsoluteDirection(flags, layoutDirection);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getAnimationDuration(RecyclerView recyclerView, int animationType, float animateDx,
|
||||
float animateDy) {
|
||||
return mCallback.getAnimationDuration(recyclerView, animationType, animateDx, animateDy);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBoundingBoxMargin() {
|
||||
return mCallback.getBoundingBoxMargin();
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getMoveThreshold(RecyclerView.ViewHolder viewHolder) {
|
||||
return mCallback.getMoveThreshold(viewHolder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
|
||||
return mCallback.getMovementFlags(recyclerView, viewHolder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getSwipeThreshold(RecyclerView.ViewHolder viewHolder) {
|
||||
return mCallback.getSwipeThreshold(viewHolder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int interpolateOutOfBoundsScroll(RecyclerView recyclerView, int viewSize, int viewSizeOutOfBounds,
|
||||
int totalSize, long msSinceStartScroll) {
|
||||
return mCallback.interpolateOutOfBoundsScroll(recyclerView, viewSize, viewSizeOutOfBounds, totalSize,
|
||||
msSinceStartScroll);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isItemViewSwipeEnabled() {
|
||||
return mCallback.isItemViewSwipeEnabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLongPressDragEnabled() {
|
||||
return mCallback.isLongPressDragEnabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX,
|
||||
float dY, int actionState, boolean isCurrentlyActive) {
|
||||
mCallback.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChildDrawOver(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX,
|
||||
float dY, int actionState, boolean isCurrentlyActive) {
|
||||
mCallback.onChildDrawOver(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder,
|
||||
RecyclerView.ViewHolder target) {
|
||||
return mCallback.onMove(recyclerView, viewHolder, target);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMoved(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, int fromPos,
|
||||
RecyclerView.ViewHolder target, int toPos, int x, int y) {
|
||||
mCallback.onMoved(recyclerView, viewHolder, fromPos, target, toPos, x, y);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {
|
||||
mCallback.onSelectedChanged(viewHolder, actionState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
|
||||
mCallback.onSwiped(viewHolder, direction);
|
||||
}
|
||||
}
|
||||
|
||||
protected class ImplHorizLayout implements Impl {
|
||||
|
||||
@Override
|
||||
public boolean isInAbsoluteEnd() {
|
||||
return !mRecyclerView.canScrollHorizontally(1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInAbsoluteStart() {
|
||||
return !mRecyclerView.canScrollHorizontally(-1);
|
||||
}
|
||||
}
|
||||
|
||||
protected class ImplVerticalLayout implements Impl {
|
||||
|
||||
@Override
|
||||
public boolean isInAbsoluteEnd() {
|
||||
return !mRecyclerView.canScrollVertically(1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInAbsoluteStart() {
|
||||
return !mRecyclerView.canScrollVertically(-1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A delegation of the adapter implementation of this view that should provide the processing
|
||||
* of {@link #isInAbsoluteStart()} and {@link #isInAbsoluteEnd()}. Essentially needed simply
|
||||
* because the implementation depends on the layout manager implementation being used.
|
||||
*/
|
||||
protected interface Impl {
|
||||
|
||||
boolean isInAbsoluteEnd();
|
||||
|
||||
boolean isInAbsoluteStart();
|
||||
}
|
||||
|
||||
protected final Impl mImpl;
|
||||
|
||||
protected boolean mIsItemTouchInEffect = false;
|
||||
|
||||
protected final RecyclerView mRecyclerView;
|
||||
|
||||
public RecyclerViewOverScrollDecorAdapter(RecyclerView recyclerView) {
|
||||
|
||||
mRecyclerView = recyclerView;
|
||||
|
||||
final RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
|
||||
if (layoutManager instanceof LinearLayoutManager ||
|
||||
layoutManager instanceof StaggeredGridLayoutManager) {
|
||||
final int orientation =
|
||||
(layoutManager instanceof LinearLayoutManager
|
||||
? ((LinearLayoutManager) layoutManager).getOrientation()
|
||||
: ((StaggeredGridLayoutManager) layoutManager).getOrientation());
|
||||
|
||||
if (orientation == LinearLayoutManager.HORIZONTAL) {
|
||||
mImpl = new ImplHorizLayout();
|
||||
} else {
|
||||
mImpl = new ImplVerticalLayout();
|
||||
}
|
||||
} else {
|
||||
throw new IllegalArgumentException(
|
||||
"Recycler views with custom layout managers are not supported by this adapter out of the box." +
|
||||
"Try implementing and providing an explicit 'impl' parameter to the other c'tors, or otherwise create a custom adapter subclass of your own.");
|
||||
}
|
||||
}
|
||||
|
||||
public RecyclerViewOverScrollDecorAdapter(RecyclerView recyclerView, Impl impl) {
|
||||
mRecyclerView = recyclerView;
|
||||
mImpl = impl;
|
||||
}
|
||||
|
||||
public RecyclerViewOverScrollDecorAdapter(RecyclerView recyclerView,
|
||||
ItemTouchHelper.Callback itemTouchHelperCallback) {
|
||||
this(recyclerView);
|
||||
setUpTouchHelperCallback(itemTouchHelperCallback);
|
||||
}
|
||||
|
||||
public RecyclerViewOverScrollDecorAdapter(RecyclerView recyclerView, Impl impl,
|
||||
ItemTouchHelper.Callback itemTouchHelperCallback) {
|
||||
this(recyclerView, impl);
|
||||
setUpTouchHelperCallback(itemTouchHelperCallback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView() {
|
||||
return mRecyclerView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInAbsoluteEnd() {
|
||||
return !mIsItemTouchInEffect && mImpl.isInAbsoluteEnd();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInAbsoluteStart() {
|
||||
return !mIsItemTouchInEffect && mImpl.isInAbsoluteStart();
|
||||
}
|
||||
|
||||
protected void setUpTouchHelperCallback(final ItemTouchHelper.Callback itemTouchHelperCallback) {
|
||||
new ItemTouchHelper(new ItemTouchHelperCallbackWrapper(itemTouchHelperCallback) {
|
||||
@Override
|
||||
public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {
|
||||
mIsItemTouchInEffect = actionState != 0;
|
||||
super.onSelectedChanged(viewHolder, actionState);
|
||||
}
|
||||
}).attachToRecyclerView(mRecyclerView);
|
||||
}
|
||||
}
|
|
@ -1,43 +0,0 @@
|
|||
package me.everything.android.ui.overscroll.adapters;
|
||||
|
||||
import android.view.View;
|
||||
import android.widget.ScrollView;
|
||||
|
||||
import me.everything.android.ui.overscroll.HorizontalOverScrollBounceEffectDecorator;
|
||||
import me.everything.android.ui.overscroll.VerticalOverScrollBounceEffectDecorator;
|
||||
|
||||
/**
|
||||
* An adapter that enables over-scrolling over a {@link ScrollView}.
|
||||
* <br/>Seeing that {@link ScrollView} only supports vertical scrolling, this adapter
|
||||
* should only be used with a {@link VerticalOverScrollBounceEffectDecorator}. For horizontal
|
||||
* over-scrolling, use {@link HorizontalScrollViewOverScrollDecorAdapter} in conjunction with
|
||||
* a {@link android.widget.HorizontalScrollView}.
|
||||
*
|
||||
* @author amit
|
||||
*
|
||||
* @see HorizontalOverScrollBounceEffectDecorator
|
||||
* @see VerticalOverScrollBounceEffectDecorator
|
||||
*/
|
||||
public class ScrollViewOverScrollDecorAdapter implements IOverScrollDecoratorAdapter {
|
||||
|
||||
protected final ScrollView mView;
|
||||
|
||||
public ScrollViewOverScrollDecorAdapter(ScrollView view) {
|
||||
mView = view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView() {
|
||||
return mView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInAbsoluteStart() {
|
||||
return !mView.canScrollVertically(-1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInAbsoluteEnd() {
|
||||
return !mView.canScrollVertically(1);
|
||||
}
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
package me.everything.android.ui.overscroll.adapters;
|
||||
|
||||
import android.view.View;
|
||||
|
||||
import me.everything.android.ui.overscroll.HorizontalOverScrollBounceEffectDecorator;
|
||||
import me.everything.android.ui.overscroll.VerticalOverScrollBounceEffectDecorator;
|
||||
|
||||
/**
|
||||
* A static adapter for views that are ALWAYS over-scroll-able (e.g. image view).
|
||||
*
|
||||
* @author amit
|
||||
*
|
||||
* @see HorizontalOverScrollBounceEffectDecorator
|
||||
* @see VerticalOverScrollBounceEffectDecorator
|
||||
*/
|
||||
public class StaticOverScrollDecorAdapter implements IOverScrollDecoratorAdapter {
|
||||
|
||||
protected final View mView;
|
||||
|
||||
public StaticOverScrollDecorAdapter(View view) {
|
||||
mView = view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView() {
|
||||
return mView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInAbsoluteStart() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInAbsoluteEnd() {
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -1,640 +0,0 @@
|
|||
package me.everything.android.ui.overscroll;
|
||||
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.annotation.Config;
|
||||
|
||||
import me.everything.android.ui.overscroll.adapters.IOverScrollDecoratorAdapter;
|
||||
|
||||
import static me.everything.android.ui.overscroll.HorizontalOverScrollBounceEffectDecorator.DEFAULT_DECELERATE_FACTOR;
|
||||
import static me.everything.android.ui.overscroll.HorizontalOverScrollBounceEffectDecorator.DEFAULT_TOUCH_DRAG_MOVE_RATIO_FWD;
|
||||
import static me.everything.android.ui.overscroll.IOverScrollState.*;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.Matchers.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
/**
|
||||
* @author amitd
|
||||
*/
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@Config(manifest = Config.NONE)
|
||||
public class HorizontalOverScrollBounceEffectDecoratorTest {
|
||||
|
||||
View mView;
|
||||
IOverScrollDecoratorAdapter mViewAdapter;
|
||||
IOverScrollStateListener mStateListener;
|
||||
IOverScrollUpdateListener mUpdateListener;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
mView = mock(View.class);
|
||||
mViewAdapter = mock(IOverScrollDecoratorAdapter.class);
|
||||
when(mViewAdapter.getView()).thenReturn(mView);
|
||||
|
||||
mStateListener = mock(IOverScrollStateListener.class);
|
||||
mUpdateListener = mock(IOverScrollUpdateListener.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void detach_decoratorIsAttached_detachFromView() throws Exception {
|
||||
|
||||
// Arrange
|
||||
|
||||
HorizontalOverScrollBounceEffectDecorator uut = new HorizontalOverScrollBounceEffectDecorator(mViewAdapter);
|
||||
|
||||
// Act
|
||||
|
||||
uut.detach();
|
||||
|
||||
// Assert
|
||||
|
||||
verify(mView).setOnTouchListener(eq((View.OnTouchListener) null));
|
||||
verify(mView).setOverScrollMode(View.OVER_SCROLL_ALWAYS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void detach_overScrollInEffect_detachFromView() throws Exception {
|
||||
|
||||
when(mViewAdapter.isInAbsoluteStart()).thenReturn(true);
|
||||
when(mViewAdapter.isInAbsoluteEnd()).thenReturn(false);
|
||||
|
||||
HorizontalOverScrollBounceEffectDecorator uut = getUUT();
|
||||
uut.onTouch(mView, createShortRightMoveEvent());
|
||||
|
||||
// Act
|
||||
|
||||
uut.detach();
|
||||
|
||||
// Assert
|
||||
|
||||
verify(mView).setOnTouchListener(eq((View.OnTouchListener) null));
|
||||
verify(mView).setOverScrollMode(View.OVER_SCROLL_ALWAYS);
|
||||
}
|
||||
|
||||
/*
|
||||
* Move-action event
|
||||
*/
|
||||
|
||||
@Test
|
||||
public void onTouchMoveAction_notInViewEnds_ignoreTouchEvent() throws Exception {
|
||||
|
||||
// Arrange
|
||||
|
||||
MotionEvent event = createShortRightMoveEvent();
|
||||
|
||||
when(mViewAdapter.isInAbsoluteStart()).thenReturn(false);
|
||||
when(mViewAdapter.isInAbsoluteEnd()).thenReturn(false);
|
||||
|
||||
HorizontalOverScrollBounceEffectDecorator uut = getUUT();
|
||||
|
||||
// Act
|
||||
|
||||
boolean ret = uut.onTouch(mView, event);
|
||||
|
||||
// Assert
|
||||
|
||||
verify(mView, never()).setTranslationX(anyFloat());
|
||||
verify(mView, never()).setTranslationY(anyFloat());
|
||||
assertFalse(ret);
|
||||
assertEquals(STATE_IDLE, uut.getCurrentState());
|
||||
|
||||
verify(mStateListener, never()).onOverScrollStateChange(eq(uut), anyInt(), anyInt());
|
||||
verify(mUpdateListener, never()).onOverScrollUpdate(eq(uut), anyInt(), anyFloat());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onTouchMoveAction_dragRightInLeftEnd_overscrollRight() throws Exception {
|
||||
|
||||
// Arrange
|
||||
|
||||
MotionEvent event = createShortRightMoveEvent();
|
||||
|
||||
when(mViewAdapter.isInAbsoluteStart()).thenReturn(true);
|
||||
when(mViewAdapter.isInAbsoluteEnd()).thenReturn(false);
|
||||
|
||||
HorizontalOverScrollBounceEffectDecorator uut = getUUT();
|
||||
|
||||
// Act
|
||||
|
||||
final boolean ret = uut.onTouch(mView, event);
|
||||
|
||||
// Assert
|
||||
|
||||
final float expectedTransX = (event.getX() - event.getHistoricalX(0)) / DEFAULT_TOUCH_DRAG_MOVE_RATIO_FWD;
|
||||
verify(mView).setTranslationX(expectedTransX);
|
||||
verify(mView, never()).setTranslationY(anyFloat());
|
||||
assertTrue(ret);
|
||||
assertEquals(STATE_DRAG_START_SIDE, uut.getCurrentState());
|
||||
|
||||
verify(mStateListener).onOverScrollStateChange(eq(uut), eq(STATE_IDLE), eq(STATE_DRAG_START_SIDE));
|
||||
verify(mUpdateListener).onOverScrollUpdate(eq(uut), eq(STATE_DRAG_START_SIDE), eq(expectedTransX));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onTouchMoveAction_dragLeftInRightEnd_overscrollLeft() throws Exception {
|
||||
|
||||
// Arrange
|
||||
|
||||
MotionEvent event = createShortLeftMoveEvent();
|
||||
|
||||
when(mViewAdapter.isInAbsoluteStart()).thenReturn(false);
|
||||
when(mViewAdapter.isInAbsoluteEnd()).thenReturn(true);
|
||||
|
||||
HorizontalOverScrollBounceEffectDecorator uut = getUUT();
|
||||
|
||||
// Act
|
||||
|
||||
final boolean ret = uut.onTouch(mView, event);
|
||||
|
||||
// Assert
|
||||
|
||||
final float expectedTransX = (event.getX() - event.getHistoricalX(0)) / DEFAULT_TOUCH_DRAG_MOVE_RATIO_FWD;
|
||||
verify(mView).setTranslationX(expectedTransX);
|
||||
verify(mView, never()).setTranslationY(anyFloat());
|
||||
assertTrue(ret);
|
||||
assertEquals(STATE_DRAG_END_SIDE, uut.getCurrentState());
|
||||
|
||||
verify(mStateListener).onOverScrollStateChange(eq(uut), eq(STATE_IDLE), eq(STATE_DRAG_END_SIDE));
|
||||
verify(mUpdateListener).onOverScrollUpdate(eq(uut), eq(STATE_DRAG_END_SIDE), eq(expectedTransX));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onTouchMoveAction_dragLeftInLeftEnd_ignoreTouchEvent() throws Exception {
|
||||
|
||||
// Arrange
|
||||
|
||||
MotionEvent event = createShortLeftMoveEvent();
|
||||
|
||||
when(mViewAdapter.isInAbsoluteStart()).thenReturn(true);
|
||||
when(mViewAdapter.isInAbsoluteEnd()).thenReturn(false);
|
||||
|
||||
HorizontalOverScrollBounceEffectDecorator uut = getUUT();
|
||||
|
||||
// Act
|
||||
|
||||
final boolean ret = uut.onTouch(mView, event);
|
||||
|
||||
// Assert
|
||||
|
||||
verify(mView, never()).setTranslationX(anyFloat());
|
||||
verify(mView, never()).setTranslationY(anyFloat());
|
||||
assertFalse(ret);
|
||||
assertEquals(STATE_IDLE, uut.getCurrentState());
|
||||
|
||||
verify(mStateListener, never()).onOverScrollStateChange(eq(uut), anyInt(), anyInt());
|
||||
verify(mUpdateListener, never()).onOverScrollUpdate(eq(uut), anyInt(), anyFloat());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onTouchMoveAction_dragRightInRightEnd_ignoreTouchEvent() throws Exception {
|
||||
|
||||
// Arrange
|
||||
|
||||
MotionEvent event = createShortRightMoveEvent();
|
||||
|
||||
when(mViewAdapter.isInAbsoluteStart()).thenReturn(false);
|
||||
when(mViewAdapter.isInAbsoluteEnd()).thenReturn(true);
|
||||
|
||||
HorizontalOverScrollBounceEffectDecorator uut = getUUT();
|
||||
|
||||
// Act
|
||||
|
||||
boolean ret = uut.onTouch(mView, event);
|
||||
|
||||
// Assert
|
||||
|
||||
verify(mView, never()).setTranslationX(anyFloat());
|
||||
verify(mView, never()).setTranslationY(anyFloat());
|
||||
assertFalse(ret);
|
||||
assertEquals(STATE_IDLE, uut.getCurrentState());
|
||||
|
||||
verify(mStateListener, never()).onOverScrollStateChange(eq(uut), anyInt(), anyInt());
|
||||
verify(mUpdateListener, never()).onOverScrollUpdate(eq(uut), anyInt(), anyFloat());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onTouchMoveAction_2ndRightDragInLeftEnd_overscrollRightFurther() throws Exception {
|
||||
|
||||
// Arrange
|
||||
|
||||
// Bring UUT to a right-overscroll state
|
||||
MotionEvent event1 = createShortRightMoveEvent();
|
||||
|
||||
when(mViewAdapter.isInAbsoluteStart()).thenReturn(true);
|
||||
when(mViewAdapter.isInAbsoluteEnd()).thenReturn(false);
|
||||
|
||||
HorizontalOverScrollBounceEffectDecorator uut = getUUT();
|
||||
uut.onTouch(mView, event1);
|
||||
reset(mView);
|
||||
|
||||
// Create 2nd right-drag event
|
||||
MotionEvent event2 = createLongRightMoveEvent();
|
||||
|
||||
// Act
|
||||
|
||||
final boolean ret = uut.onTouch(mView, event2);
|
||||
|
||||
// Assert
|
||||
|
||||
final float expectedTransX1 = (event1.getX() - event1.getHistoricalX(0)) / DEFAULT_TOUCH_DRAG_MOVE_RATIO_FWD;
|
||||
final float expectedTransX2 = (event2.getX() - event2.getHistoricalX(0)) / DEFAULT_TOUCH_DRAG_MOVE_RATIO_FWD;
|
||||
verify(mView).setTranslationX(expectedTransX2);
|
||||
verify(mView, never()).setTranslationY(anyFloat());
|
||||
assertTrue(ret);
|
||||
assertEquals(STATE_DRAG_START_SIDE, uut.getCurrentState());
|
||||
|
||||
// State-change listener called only once?
|
||||
verify(mStateListener).onOverScrollStateChange(eq(uut), eq(STATE_IDLE), eq(STATE_DRAG_START_SIDE));
|
||||
verify(mStateListener).onOverScrollStateChange(eq(uut), anyInt(), anyInt());
|
||||
// Update-listener called exactly twice?
|
||||
verify(mUpdateListener).onOverScrollUpdate(eq(uut), eq(STATE_DRAG_START_SIDE), eq(expectedTransX1));
|
||||
verify(mUpdateListener).onOverScrollUpdate(eq(uut), eq(STATE_DRAG_START_SIDE), eq(expectedTransX2));
|
||||
verify(mUpdateListener, times(2)).onOverScrollUpdate(eq(uut), anyInt(), anyFloat());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onTouchMoveAction_2ndLeftDragInRightEnd_overscrollLeftFurther() throws Exception {
|
||||
|
||||
// Arrange
|
||||
|
||||
// Bring UUT to a left-overscroll state
|
||||
MotionEvent event1 = createShortLeftMoveEvent();
|
||||
|
||||
when(mViewAdapter.isInAbsoluteStart()).thenReturn(false);
|
||||
when(mViewAdapter.isInAbsoluteEnd()).thenReturn(true);
|
||||
|
||||
HorizontalOverScrollBounceEffectDecorator uut = getUUT();
|
||||
uut.onTouch(mView, event1);
|
||||
reset(mView);
|
||||
|
||||
// Create 2nd left-drag event
|
||||
MotionEvent event2 = createLongLeftMoveEvent();
|
||||
|
||||
// Act
|
||||
|
||||
final boolean ret = uut.onTouch(mView, event2);
|
||||
|
||||
// Assert
|
||||
|
||||
final float expectedTransX1 = (event1.getX() - event1.getHistoricalX(0)) / DEFAULT_TOUCH_DRAG_MOVE_RATIO_FWD;
|
||||
final float expectedTransX2 = (event2.getX() - event2.getHistoricalX(0)) / DEFAULT_TOUCH_DRAG_MOVE_RATIO_FWD;
|
||||
verify(mView).setTranslationX(expectedTransX2);
|
||||
verify(mView, never()).setTranslationY(anyFloat());
|
||||
assertTrue(ret);
|
||||
assertEquals(STATE_DRAG_END_SIDE, uut.getCurrentState());
|
||||
|
||||
// State-change listener called only once?
|
||||
verify(mStateListener).onOverScrollStateChange(eq(uut), eq(STATE_IDLE), eq(STATE_DRAG_END_SIDE));
|
||||
verify(mStateListener).onOverScrollStateChange(eq(uut), anyInt(), anyInt());
|
||||
// Update-listener called exactly twice?
|
||||
verify(mUpdateListener).onOverScrollUpdate(eq(uut), eq(STATE_DRAG_END_SIDE), eq(expectedTransX1));
|
||||
verify(mUpdateListener).onOverScrollUpdate(eq(uut), eq(STATE_DRAG_END_SIDE), eq(expectedTransX2));
|
||||
verify(mUpdateListener, times(2)).onOverScrollUpdate(eq(uut), anyInt(), anyFloat());
|
||||
}
|
||||
|
||||
/**
|
||||
* When over-scroll has already started (to the right in this case) and suddenly the user changes
|
||||
* their mind and scrolls a bit in the other direction:
|
||||
* <br/>We expect the <b>touch to still be intercepted</b> in that case, and the <b>overscroll to
|
||||
* remain in effect</b>.
|
||||
*/
|
||||
@Test
|
||||
public void onTouchMoveAction_dragLeftWhenRightOverscolled_continueOverscrollingLeft() throws Exception {
|
||||
|
||||
// Arrange
|
||||
|
||||
// In left & right tests we use equal ratios to avoid the effect's under-scroll handling
|
||||
final float touchDragRatioFwd = 3f;
|
||||
final float touchDragRatioBck = 3f;
|
||||
|
||||
// Bring UUT to a right-overscroll state
|
||||
when(mViewAdapter.isInAbsoluteStart()).thenReturn(true);
|
||||
when(mViewAdapter.isInAbsoluteEnd()).thenReturn(false);
|
||||
|
||||
HorizontalOverScrollBounceEffectDecorator uut = getUUT(touchDragRatioFwd, touchDragRatioBck);
|
||||
MotionEvent eventMoveRight = createLongRightMoveEvent();
|
||||
uut.onTouch(mView, eventMoveRight);
|
||||
reset(mView);
|
||||
float startTransX = (eventMoveRight.getX() - eventMoveRight.getHistoricalX(0)) / touchDragRatioFwd;
|
||||
when(mView.getTranslationX()).thenReturn(startTransX);
|
||||
|
||||
// Create the left-drag event
|
||||
MotionEvent eventMoveLeft = createShortLeftMoveEvent();
|
||||
|
||||
// Act
|
||||
|
||||
boolean ret = uut.onTouch(mView, eventMoveLeft);
|
||||
|
||||
// Assert
|
||||
|
||||
float expectedTransX = startTransX +
|
||||
(eventMoveLeft.getX() - eventMoveLeft.getHistoricalX(0)) / touchDragRatioBck;
|
||||
verify(mView).setTranslationX(expectedTransX);
|
||||
verify(mView, never()).setTranslationY(anyFloat());
|
||||
assertTrue(ret);
|
||||
assertEquals(STATE_DRAG_START_SIDE, uut.getCurrentState());
|
||||
|
||||
// State-change listener called only once?
|
||||
verify(mStateListener).onOverScrollStateChange(eq(uut), eq(STATE_IDLE), eq(STATE_DRAG_START_SIDE));
|
||||
verify(mStateListener).onOverScrollStateChange(eq(uut), anyInt(), anyInt());
|
||||
// Update-listener called exactly twice?
|
||||
verify(mUpdateListener).onOverScrollUpdate(eq(uut), eq(STATE_DRAG_START_SIDE), eq(startTransX));
|
||||
verify(mUpdateListener).onOverScrollUpdate(eq(uut), eq(STATE_DRAG_START_SIDE), eq(expectedTransX));
|
||||
verify(mUpdateListener, times(2)).onOverScrollUpdate(eq(uut), anyInt(), anyFloat());
|
||||
}
|
||||
|
||||
/**
|
||||
* When over-scroll has already started (to the left in this case) and suddenly the user changes
|
||||
* their mind and scrolls a bit in the other direction:
|
||||
* <br/>We expect the <b>touch to still be intercepted</b> in that case, and the <b>overscroll to remain in effect</b>.
|
||||
*/
|
||||
@Test
|
||||
public void onTouchMoveAction_dragRightWhenLeftOverscolled_continueOverscrollingRight() throws Exception {
|
||||
|
||||
// Arrange
|
||||
|
||||
// In left & right tests we use equal ratios to avoid the effect's under-scroll handling
|
||||
final float touchDragRatioFwd = 3f;
|
||||
final float touchDragRatioBck = 3f;
|
||||
|
||||
// Bring UUT to a left-overscroll state
|
||||
when(mViewAdapter.isInAbsoluteStart()).thenReturn(false);
|
||||
when(mViewAdapter.isInAbsoluteEnd()).thenReturn(true);
|
||||
|
||||
HorizontalOverScrollBounceEffectDecorator uut = getUUT(touchDragRatioFwd, touchDragRatioBck);
|
||||
MotionEvent eventMoveLeft = createLongLeftMoveEvent();
|
||||
uut.onTouch(mView, eventMoveLeft);
|
||||
reset(mView);
|
||||
|
||||
float startTransX = (eventMoveLeft.getX() - eventMoveLeft.getHistoricalX(0)) / touchDragRatioFwd;
|
||||
when(mView.getTranslationX()).thenReturn(startTransX);
|
||||
|
||||
// Create the right-drag event
|
||||
MotionEvent eventMoveRight = createShortRightMoveEvent();
|
||||
|
||||
// Act
|
||||
|
||||
boolean ret = uut.onTouch(mView, eventMoveRight);
|
||||
|
||||
// Assert
|
||||
|
||||
float expectedTransX = startTransX + (eventMoveRight.getX() - eventMoveRight.getHistoricalX(0)) / touchDragRatioBck;
|
||||
verify(mView).setTranslationX(expectedTransX);
|
||||
verify(mView, never()).setTranslationY(anyFloat());
|
||||
assertTrue(ret);
|
||||
assertEquals(STATE_DRAG_END_SIDE, uut.getCurrentState());
|
||||
|
||||
// State-change listener called only once?
|
||||
verify(mStateListener).onOverScrollStateChange(eq(uut), eq(STATE_IDLE), eq(STATE_DRAG_END_SIDE));
|
||||
verify(mStateListener).onOverScrollStateChange(eq(uut), anyInt(), anyInt());
|
||||
// Update-listener called exactly twice?
|
||||
verify(mUpdateListener).onOverScrollUpdate(eq(uut), eq(STATE_DRAG_END_SIDE), eq(startTransX));
|
||||
verify(mUpdateListener).onOverScrollUpdate(eq(uut), eq(STATE_DRAG_END_SIDE), eq(expectedTransX));
|
||||
verify(mUpdateListener, times(2)).onOverScrollUpdate(eq(uut), anyInt(), anyFloat());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onTouchMoveAction_undragWhenRightOverscrolled_endOverscrolling() throws Exception {
|
||||
|
||||
// Arrange
|
||||
|
||||
// In left & right tests we use equal ratios to avoid the effect's under-scroll handling
|
||||
final float touchDragRatioFwd = 3f;
|
||||
final float touchDragRatioBck = 3f;
|
||||
|
||||
// Bring UUT to a right-overscroll state
|
||||
when(mViewAdapter.isInAbsoluteStart()).thenReturn(true);
|
||||
when(mViewAdapter.isInAbsoluteEnd()).thenReturn(false);
|
||||
|
||||
HorizontalOverScrollBounceEffectDecorator uut = getUUT(touchDragRatioFwd, touchDragRatioBck);
|
||||
MotionEvent eventMoveRight = createLongRightMoveEvent();
|
||||
uut.onTouch(mView, eventMoveRight);
|
||||
reset(mView);
|
||||
float startTransX = (eventMoveRight.getX() - eventMoveRight.getHistoricalX(0)) / touchDragRatioFwd;
|
||||
when(mView.getTranslationX()).thenReturn(startTransX);
|
||||
|
||||
// Create the left-drag event
|
||||
MotionEvent eventMoveLeft = createLongLeftMoveEvent();
|
||||
|
||||
// Act
|
||||
|
||||
boolean ret = uut.onTouch(mView, eventMoveLeft);
|
||||
|
||||
// Assert
|
||||
|
||||
verify(mView).setTranslationX(0);
|
||||
verify(mView, never()).setTranslationY(anyFloat());
|
||||
assertTrue(ret);
|
||||
assertEquals(STATE_IDLE, uut.getCurrentState());
|
||||
|
||||
// State-change listener invoked to say drag-on and drag-off (idle).
|
||||
verify(mStateListener).onOverScrollStateChange(eq(uut), eq(STATE_IDLE), eq(STATE_DRAG_START_SIDE));
|
||||
verify(mStateListener).onOverScrollStateChange(eq(uut), eq(STATE_DRAG_START_SIDE), eq(STATE_IDLE));
|
||||
verify(mStateListener, times(2)).onOverScrollStateChange(eq(uut), anyInt(), anyInt());
|
||||
// Update-listener called exactly twice?
|
||||
verify(mUpdateListener).onOverScrollUpdate(eq(uut), eq(STATE_DRAG_START_SIDE), eq(startTransX));
|
||||
verify(mUpdateListener).onOverScrollUpdate(eq(uut), eq(STATE_DRAG_START_SIDE), eq(0f));
|
||||
verify(mUpdateListener, times(2)).onOverScrollUpdate(eq(uut), anyInt(), anyFloat());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onTouchMoveAction_undragWhenLeftOverscrolled_endOverscrolling() throws Exception {
|
||||
|
||||
// Arrange
|
||||
|
||||
// In left & right tests we use equal ratios to avoid the effect's under-scroll handling
|
||||
final float touchDragRatioFwd = 3f;
|
||||
final float touchDragRatioBck = 3f;
|
||||
|
||||
// Bring UUT to a left-overscroll state
|
||||
when(mViewAdapter.isInAbsoluteStart()).thenReturn(false);
|
||||
when(mViewAdapter.isInAbsoluteEnd()).thenReturn(true);
|
||||
|
||||
HorizontalOverScrollBounceEffectDecorator uut = getUUT(touchDragRatioFwd, touchDragRatioBck);
|
||||
MotionEvent eventMoveLeft = createLongLeftMoveEvent();
|
||||
uut.onTouch(mView, eventMoveLeft);
|
||||
reset(mView);
|
||||
float startTransX = (eventMoveLeft.getX() - eventMoveLeft.getHistoricalX(0)) / touchDragRatioFwd;
|
||||
when(mView.getTranslationX()).thenReturn(startTransX);
|
||||
|
||||
// Create the left-drag event
|
||||
MotionEvent eventMoveRight = createLongRightMoveEvent();
|
||||
|
||||
// Act
|
||||
|
||||
boolean ret = uut.onTouch(mView, eventMoveRight);
|
||||
|
||||
// Assert
|
||||
|
||||
verify(mView).setTranslationX(0);
|
||||
verify(mView, never()).setTranslationY(anyFloat());
|
||||
assertTrue(ret);
|
||||
assertEquals(STATE_IDLE, uut.getCurrentState());
|
||||
|
||||
// State-change listener invoked to say drag-on and drag-off (idle).
|
||||
verify(mStateListener).onOverScrollStateChange(eq(uut), eq(STATE_IDLE), eq(STATE_DRAG_END_SIDE));
|
||||
verify(mStateListener).onOverScrollStateChange(eq(uut), eq(STATE_DRAG_END_SIDE), eq(STATE_IDLE));
|
||||
verify(mStateListener, times(2)).onOverScrollStateChange(eq(uut), anyInt(), anyInt());
|
||||
// Update-listener called exactly twice?
|
||||
verify(mUpdateListener).onOverScrollUpdate(eq(uut), eq(STATE_DRAG_END_SIDE), eq(startTransX));
|
||||
verify(mUpdateListener).onOverScrollUpdate(eq(uut), eq(STATE_DRAG_END_SIDE), eq(0f));
|
||||
verify(mUpdateListener, times(2)).onOverScrollUpdate(eq(uut), anyInt(), anyFloat());
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Up action event
|
||||
*/
|
||||
|
||||
@Test
|
||||
public void onTouchUpAction_eventWhenNotOverscrolled_ignoreTouchEvent() throws Exception {
|
||||
|
||||
// Arrange
|
||||
|
||||
MotionEvent event = createDefaultUpActionEvent();
|
||||
|
||||
when(mViewAdapter.isInAbsoluteStart()).thenReturn(true);
|
||||
when(mViewAdapter.isInAbsoluteEnd()).thenReturn(true);
|
||||
|
||||
HorizontalOverScrollBounceEffectDecorator uut = getUUT();
|
||||
|
||||
// Act
|
||||
|
||||
boolean ret = uut.onTouch(mView, event);
|
||||
|
||||
// Assert
|
||||
|
||||
verify(mView, never()).setTranslationX(anyFloat());
|
||||
verify(mView, never()).setTranslationY(anyFloat());
|
||||
assertFalse(ret);
|
||||
assertEquals(STATE_IDLE, uut.getCurrentState());
|
||||
|
||||
verify(mStateListener, never()).onOverScrollStateChange(eq(uut), anyInt(), anyInt());
|
||||
verify(mUpdateListener, never()).onOverScrollUpdate(eq(uut), anyInt(), anyFloat());
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: Make this work using a decent animation shadows / newer Robolectric
|
||||
* @throws Exception
|
||||
*/
|
||||
@Ignore
|
||||
@Test
|
||||
public void onTouchUpAction_eventWhenLeftOverscrolling_smoothScrollBackToRightEnd() throws Exception {
|
||||
|
||||
// Arrange
|
||||
|
||||
// Bring UUT to a left-overscroll state
|
||||
MotionEvent moveEvent = createShortLeftMoveEvent();
|
||||
|
||||
when(mViewAdapter.isInAbsoluteStart()).thenReturn(false);
|
||||
when(mViewAdapter.isInAbsoluteEnd()).thenReturn(true);
|
||||
|
||||
HorizontalOverScrollBounceEffectDecorator uut = getUUT();
|
||||
uut.onTouch(mView, moveEvent);
|
||||
reset(mView);
|
||||
|
||||
// Make the view as though it's been moved by the move event
|
||||
float viewX = moveEvent.getX();
|
||||
when(mView.getTranslationX()).thenReturn(viewX);
|
||||
|
||||
MotionEvent upEvent = createDefaultUpActionEvent();
|
||||
|
||||
// Act
|
||||
|
||||
boolean ret = uut.onTouch(mView, upEvent);
|
||||
|
||||
// Assert
|
||||
|
||||
assertTrue(ret);
|
||||
|
||||
verify(mView, atLeastOnce()).setTranslationX(anyFloat());
|
||||
}
|
||||
|
||||
protected MotionEvent createShortRightMoveEvent() {
|
||||
MotionEvent event = mock(MotionEvent.class);
|
||||
when(event.getAction()).thenReturn(MotionEvent.ACTION_MOVE);
|
||||
when(event.getX()).thenReturn(100f);
|
||||
when(event.getY()).thenReturn(200f);
|
||||
when(event.getX(0)).thenReturn(100f);
|
||||
when(event.getY(0)).thenReturn(200f);
|
||||
when(event.getHistorySize()).thenReturn(1);
|
||||
when(event.getHistoricalX(eq(0))).thenReturn(80f);
|
||||
when(event.getHistoricalY(eq(0))).thenReturn(190f);
|
||||
when(event.getHistoricalX(eq(0), eq(0))).thenReturn(80f);
|
||||
when(event.getHistoricalY(eq(0), eq(0))).thenReturn(190f);
|
||||
return event;
|
||||
}
|
||||
|
||||
protected MotionEvent createLongRightMoveEvent() {
|
||||
MotionEvent event = mock(MotionEvent.class);
|
||||
when(event.getAction()).thenReturn(MotionEvent.ACTION_MOVE);
|
||||
when(event.getX()).thenReturn(150f);
|
||||
when(event.getY()).thenReturn(250f);
|
||||
when(event.getX(0)).thenReturn(150f);
|
||||
when(event.getY(0)).thenReturn(250f);
|
||||
when(event.getHistorySize()).thenReturn(1);
|
||||
when(event.getHistoricalX(eq(0))).thenReturn(100f);
|
||||
when(event.getHistoricalY(eq(0))).thenReturn(200f);
|
||||
when(event.getHistoricalX(eq(0), eq(0))).thenReturn(100f);
|
||||
when(event.getHistoricalY(eq(0), eq(0))).thenReturn(200f);
|
||||
return event;
|
||||
}
|
||||
|
||||
protected MotionEvent createShortLeftMoveEvent() {
|
||||
MotionEvent event = mock(MotionEvent.class);
|
||||
when(event.getAction()).thenReturn(MotionEvent.ACTION_MOVE);
|
||||
when(event.getX()).thenReturn(100f);
|
||||
when(event.getY()).thenReturn(200f);
|
||||
when(event.getX(0)).thenReturn(100f);
|
||||
when(event.getY(0)).thenReturn(200f);
|
||||
when(event.getHistorySize()).thenReturn(1);
|
||||
when(event.getHistoricalX(eq(0))).thenReturn(120f);
|
||||
when(event.getHistoricalY(eq(0))).thenReturn(220f);
|
||||
when(event.getHistoricalX(eq(0), eq(0))).thenReturn(120f);
|
||||
when(event.getHistoricalY(eq(0), eq(0))).thenReturn(220f);
|
||||
return event;
|
||||
}
|
||||
|
||||
protected MotionEvent createLongLeftMoveEvent() {
|
||||
MotionEvent event = mock(MotionEvent.class);
|
||||
when(event.getAction()).thenReturn(MotionEvent.ACTION_MOVE);
|
||||
when(event.getX()).thenReturn(50f);
|
||||
when(event.getY()).thenReturn(150f);
|
||||
when(event.getX(0)).thenReturn(50f);
|
||||
when(event.getY(0)).thenReturn(150f);
|
||||
when(event.getHistorySize()).thenReturn(1);
|
||||
when(event.getHistoricalX(eq(0))).thenReturn(100f);
|
||||
when(event.getHistoricalY(eq(0))).thenReturn(200f);
|
||||
when(event.getHistoricalX(eq(0), eq(0))).thenReturn(100f);
|
||||
when(event.getHistoricalY(eq(0), eq(0))).thenReturn(200f);
|
||||
return event;
|
||||
}
|
||||
|
||||
protected MotionEvent createDefaultUpActionEvent() {
|
||||
MotionEvent event = mock(MotionEvent.class);
|
||||
when(event.getAction()).thenReturn(MotionEvent.ACTION_UP);
|
||||
return event;
|
||||
}
|
||||
|
||||
protected HorizontalOverScrollBounceEffectDecorator getUUT() {
|
||||
HorizontalOverScrollBounceEffectDecorator uut = new HorizontalOverScrollBounceEffectDecorator(mViewAdapter);
|
||||
uut.setOverScrollStateListener(mStateListener);
|
||||
uut.setOverScrollUpdateListener(mUpdateListener);
|
||||
return uut;
|
||||
}
|
||||
|
||||
protected HorizontalOverScrollBounceEffectDecorator getUUT(float touchDragRatioFwd, float touchDragRatioBck) {
|
||||
HorizontalOverScrollBounceEffectDecorator uut = new HorizontalOverScrollBounceEffectDecorator(mViewAdapter, touchDragRatioFwd, touchDragRatioBck, DEFAULT_DECELERATE_FACTOR);
|
||||
uut.setOverScrollStateListener(mStateListener);
|
||||
uut.setOverScrollUpdateListener(mUpdateListener);
|
||||
return uut;
|
||||
}
|
||||
}
|
|
@ -1,600 +0,0 @@
|
|||
package me.everything.android.ui.overscroll;
|
||||
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.annotation.Config;
|
||||
|
||||
import me.everything.android.ui.overscroll.adapters.IOverScrollDecoratorAdapter;
|
||||
|
||||
import static me.everything.android.ui.overscroll.IOverScrollState.*;
|
||||
import static me.everything.android.ui.overscroll.VerticalOverScrollBounceEffectDecorator.DEFAULT_DECELERATE_FACTOR;
|
||||
import static me.everything.android.ui.overscroll.VerticalOverScrollBounceEffectDecorator.DEFAULT_TOUCH_DRAG_MOVE_RATIO_FWD;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.Matchers.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
/**
|
||||
* @author amitd
|
||||
*/
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@Config(manifest = Config.NONE)
|
||||
public class VerticalOverScrollBounceEffectDecoratorTest {
|
||||
|
||||
View mView;
|
||||
IOverScrollDecoratorAdapter mViewAdapter;
|
||||
IOverScrollStateListener mStateListener;
|
||||
IOverScrollUpdateListener mUpdateListener;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
mView = mock(View.class);
|
||||
mViewAdapter = mock(IOverScrollDecoratorAdapter.class);
|
||||
when(mViewAdapter.getView()).thenReturn(mView);
|
||||
|
||||
mStateListener = mock(IOverScrollStateListener.class);
|
||||
mUpdateListener = mock(IOverScrollUpdateListener.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void detach_decoratorIsAttached_detachFromView() throws Exception {
|
||||
|
||||
// Arrange
|
||||
|
||||
HorizontalOverScrollBounceEffectDecorator uut = new HorizontalOverScrollBounceEffectDecorator(mViewAdapter);
|
||||
|
||||
// Act
|
||||
|
||||
uut.detach();
|
||||
|
||||
// Assert
|
||||
|
||||
verify(mView).setOnTouchListener(eq((View.OnTouchListener) null));
|
||||
verify(mView).setOverScrollMode(View.OVER_SCROLL_ALWAYS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void detach_overScrollInEffect_detachFromView() throws Exception {
|
||||
|
||||
when(mViewAdapter.isInAbsoluteStart()).thenReturn(true);
|
||||
when(mViewAdapter.isInAbsoluteEnd()).thenReturn(false);
|
||||
|
||||
VerticalOverScrollBounceEffectDecorator uut = getUUT();
|
||||
uut.onTouch(mView, createShortDownwardsMoveEvent());
|
||||
|
||||
// Act
|
||||
|
||||
uut.detach();
|
||||
|
||||
// Assert
|
||||
|
||||
verify(mView).setOnTouchListener(eq((View.OnTouchListener) null));
|
||||
verify(mView).setOverScrollMode(View.OVER_SCROLL_ALWAYS);
|
||||
}
|
||||
|
||||
/*
|
||||
* Move-action event
|
||||
*/
|
||||
|
||||
@Test
|
||||
public void onTouchMoveAction_notInViewEnds_ignoreTouchEvent() throws Exception {
|
||||
|
||||
// Arrange
|
||||
|
||||
MotionEvent event = createShortDownwardsMoveEvent();
|
||||
|
||||
when(mViewAdapter.isInAbsoluteStart()).thenReturn(false);
|
||||
when(mViewAdapter.isInAbsoluteEnd()).thenReturn(false);
|
||||
|
||||
VerticalOverScrollBounceEffectDecorator uut = getUUT();
|
||||
|
||||
// Act
|
||||
|
||||
boolean ret = uut.onTouch(mView, event);
|
||||
|
||||
// Assert
|
||||
|
||||
verify(mView, never()).setTranslationX(anyFloat());
|
||||
verify(mView, never()).setTranslationY(anyFloat());
|
||||
assertFalse(ret);
|
||||
assertEquals(STATE_IDLE, uut.getCurrentState());
|
||||
|
||||
verify(mStateListener, never()).onOverScrollStateChange(eq(uut),anyInt(), anyInt());
|
||||
verify(mUpdateListener, never()).onOverScrollUpdate(eq(uut), anyInt(), anyFloat());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onTouchMoveAction_dragDownInUpperEnd_overscrollDownwards() throws Exception {
|
||||
|
||||
// Arrange
|
||||
|
||||
MotionEvent event = createShortDownwardsMoveEvent();
|
||||
|
||||
when(mViewAdapter.isInAbsoluteStart()).thenReturn(true);
|
||||
when(mViewAdapter.isInAbsoluteEnd()).thenReturn(false);
|
||||
|
||||
VerticalOverScrollBounceEffectDecorator uut = getUUT();
|
||||
|
||||
// Act
|
||||
|
||||
boolean ret = uut.onTouch(mView, event);
|
||||
|
||||
// Assert
|
||||
|
||||
float expectedTransY = (event.getY() - event.getHistoricalY(0)) / DEFAULT_TOUCH_DRAG_MOVE_RATIO_FWD;
|
||||
verify(mView).setTranslationY(expectedTransY);
|
||||
verify(mView, never()).setTranslationX(anyFloat());
|
||||
assertTrue(ret);
|
||||
assertEquals(STATE_DRAG_START_SIDE, uut.getCurrentState());
|
||||
|
||||
verify(mStateListener).onOverScrollStateChange(eq(uut), eq(STATE_IDLE), eq(STATE_DRAG_START_SIDE));
|
||||
verify(mUpdateListener).onOverScrollUpdate(eq(uut), eq(STATE_DRAG_START_SIDE), eq(expectedTransY));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onTouchMoveAction_dragUpInBottomEnd_overscrollUpwards() throws Exception {
|
||||
|
||||
// Arrange
|
||||
|
||||
MotionEvent event = createShortUpwardsMoveEvent();
|
||||
|
||||
when(mViewAdapter.isInAbsoluteStart()).thenReturn(false);
|
||||
when(mViewAdapter.isInAbsoluteEnd()).thenReturn(true);
|
||||
|
||||
VerticalOverScrollBounceEffectDecorator uut = getUUT();
|
||||
|
||||
// Act
|
||||
|
||||
boolean ret = uut.onTouch(mView, event);
|
||||
|
||||
// Assert
|
||||
|
||||
float expectedTransY = (event.getY() - event.getHistoricalY(0)) / DEFAULT_TOUCH_DRAG_MOVE_RATIO_FWD;
|
||||
verify(mView).setTranslationY(expectedTransY);
|
||||
verify(mView, never()).setTranslationX(anyFloat());
|
||||
assertTrue(ret);
|
||||
assertEquals(STATE_DRAG_END_SIDE, uut.getCurrentState());
|
||||
|
||||
verify(mStateListener).onOverScrollStateChange(eq(uut), eq(STATE_IDLE), eq(STATE_DRAG_END_SIDE));
|
||||
verify(mUpdateListener).onOverScrollUpdate(eq(uut), eq(STATE_DRAG_END_SIDE), eq(expectedTransY));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onTouchMoveAction_dragUpInUpperEnd_ignoreTouchEvent() throws Exception {
|
||||
|
||||
// Arrange
|
||||
|
||||
MotionEvent event = createShortUpwardsMoveEvent();
|
||||
|
||||
when(mViewAdapter.isInAbsoluteStart()).thenReturn(true);
|
||||
when(mViewAdapter.isInAbsoluteEnd()).thenReturn(false);
|
||||
|
||||
VerticalOverScrollBounceEffectDecorator uut = getUUT();
|
||||
|
||||
// Act
|
||||
|
||||
boolean ret = uut.onTouch(mView, event);
|
||||
|
||||
// Assert
|
||||
|
||||
verify(mView, never()).setTranslationX(anyFloat());
|
||||
verify(mView, never()).setTranslationY(anyFloat());
|
||||
assertFalse(ret);
|
||||
assertEquals(STATE_IDLE, uut.getCurrentState());
|
||||
|
||||
verify(mStateListener, never()).onOverScrollStateChange(eq(uut), anyInt(), anyInt());
|
||||
verify(mUpdateListener, never()).onOverScrollUpdate(eq(uut), anyInt(), anyFloat());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onTouchMoveAction_dragDownInBottomEnd_ignoreTouchEvent() throws Exception {
|
||||
|
||||
// Arrange
|
||||
|
||||
MotionEvent event = createShortDownwardsMoveEvent();
|
||||
|
||||
when(mViewAdapter.isInAbsoluteStart()).thenReturn(false);
|
||||
when(mViewAdapter.isInAbsoluteEnd()).thenReturn(true);
|
||||
|
||||
VerticalOverScrollBounceEffectDecorator uut = getUUT();
|
||||
|
||||
// Act
|
||||
|
||||
boolean ret = uut.onTouch(mView, event);
|
||||
|
||||
// Assert
|
||||
|
||||
verify(mView, never()).setTranslationX(anyFloat());
|
||||
verify(mView, never()).setTranslationY(anyFloat());
|
||||
assertFalse(ret);
|
||||
assertEquals(STATE_IDLE, uut.getCurrentState());
|
||||
|
||||
verify(mStateListener, never()).onOverScrollStateChange(eq(uut), anyInt(), anyInt());
|
||||
verify(mUpdateListener, never()).onOverScrollUpdate(eq(uut), anyInt(), anyFloat());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onTouchMoveAction_2ndDownDragInUpperEnd_overscrollDownwardsFurther() throws Exception {
|
||||
|
||||
// Arrange
|
||||
|
||||
// Bring UUT to a downwards-overscroll state
|
||||
MotionEvent event1 = createShortDownwardsMoveEvent();
|
||||
|
||||
when(mViewAdapter.isInAbsoluteStart()).thenReturn(true);
|
||||
when(mViewAdapter.isInAbsoluteEnd()).thenReturn(false);
|
||||
|
||||
VerticalOverScrollBounceEffectDecorator uut = getUUT();
|
||||
uut.onTouch(mView, event1);
|
||||
reset(mView);
|
||||
|
||||
// Create 2nd downwards-drag event
|
||||
MotionEvent event2 = createLongDownwardsMoveEvent();
|
||||
|
||||
// Act
|
||||
|
||||
final boolean ret = uut.onTouch(mView, event2);
|
||||
|
||||
// Assert
|
||||
|
||||
final float expectedTransY1 = (event1.getY() - event1.getHistoricalY(0)) / DEFAULT_TOUCH_DRAG_MOVE_RATIO_FWD;
|
||||
final float expectedTransY2 = (event2.getY() - event2.getHistoricalY(0)) / DEFAULT_TOUCH_DRAG_MOVE_RATIO_FWD;
|
||||
verify(mView).setTranslationY(expectedTransY2);
|
||||
verify(mView, never()).setTranslationX(anyFloat());
|
||||
assertTrue(ret);
|
||||
assertEquals(STATE_DRAG_START_SIDE, uut.getCurrentState());
|
||||
|
||||
// State-change listener called only once?
|
||||
verify(mStateListener).onOverScrollStateChange(eq(uut), eq(STATE_IDLE), eq(STATE_DRAG_START_SIDE));
|
||||
verify(mStateListener).onOverScrollStateChange(eq(uut), anyInt(), anyInt());
|
||||
// Update-listener called exactly twice?
|
||||
verify(mUpdateListener).onOverScrollUpdate(eq(uut), eq(STATE_DRAG_START_SIDE), eq(expectedTransY1));
|
||||
verify(mUpdateListener).onOverScrollUpdate(eq(uut), eq(STATE_DRAG_START_SIDE), eq(expectedTransY2));
|
||||
verify(mUpdateListener, times(2)).onOverScrollUpdate(eq(uut), anyInt(), anyFloat());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onTouchMoveAction_2ndUpDragInBottomEnd_overscrollUpwardsFurther() throws Exception {
|
||||
|
||||
// Arrange
|
||||
|
||||
// Bring UUT to an upwards-overscroll state
|
||||
MotionEvent event1 = createShortUpwardsMoveEvent();
|
||||
|
||||
when(mViewAdapter.isInAbsoluteStart()).thenReturn(false);
|
||||
when(mViewAdapter.isInAbsoluteEnd()).thenReturn(true);
|
||||
|
||||
VerticalOverScrollBounceEffectDecorator uut = getUUT();
|
||||
uut.onTouch(mView, event1);
|
||||
reset(mView);
|
||||
|
||||
// Create 2nd upward-drag event
|
||||
MotionEvent event2 = createLongUpwardsMoveEvent();
|
||||
|
||||
// Act
|
||||
|
||||
final boolean ret = uut.onTouch(mView, event2);
|
||||
|
||||
// Assert
|
||||
|
||||
final float expectedTransY1 = (event1.getY() - event1.getHistoricalY(0)) / DEFAULT_TOUCH_DRAG_MOVE_RATIO_FWD;
|
||||
final float expectedTransY2 = (event2.getY() - event2.getHistoricalY(0)) / DEFAULT_TOUCH_DRAG_MOVE_RATIO_FWD;
|
||||
verify(mView).setTranslationY(expectedTransY2);
|
||||
verify(mView, never()).setTranslationX(anyFloat());
|
||||
assertTrue(ret);
|
||||
assertEquals(STATE_DRAG_END_SIDE, uut.getCurrentState());
|
||||
|
||||
// State-change listener called only once?
|
||||
verify(mStateListener).onOverScrollStateChange(eq(uut), eq(STATE_IDLE), eq(STATE_DRAG_END_SIDE));
|
||||
verify(mStateListener).onOverScrollStateChange(eq(uut), anyInt(), anyInt());
|
||||
// Update-listener called exactly twice?
|
||||
verify(mUpdateListener).onOverScrollUpdate(eq(uut), eq(STATE_DRAG_END_SIDE), eq(expectedTransY1));
|
||||
verify(mUpdateListener).onOverScrollUpdate(eq(uut), eq(STATE_DRAG_END_SIDE), eq(expectedTransY2));
|
||||
verify(mUpdateListener, times(2)).onOverScrollUpdate(eq(uut), anyInt(), anyFloat());
|
||||
}
|
||||
|
||||
/**
|
||||
* When over-scroll has already started (downwards in this case) and suddenly the user changes
|
||||
* their mind and scrolls a bit in the other direction:
|
||||
* <br/>We expect the <b>touch to still be intercepted</b> in that case, and the <b>overscroll to remain in effect</b>.
|
||||
*/
|
||||
@Test
|
||||
public void onTouchMoveAction_dragUpWhenDownOverscolled_continueOverscrollingUpwards() throws Exception {
|
||||
|
||||
// Arrange
|
||||
|
||||
// In down & up drag tests we use equal ratios to avoid the effect's under-scroll handling
|
||||
final float touchDragRatioFwd = 3f;
|
||||
final float touchDragRatioBck = 3f;
|
||||
|
||||
// Bring UUT to a downwrads-overscroll state
|
||||
when(mViewAdapter.isInAbsoluteStart()).thenReturn(true);
|
||||
when(mViewAdapter.isInAbsoluteEnd()).thenReturn(false);
|
||||
|
||||
VerticalOverScrollBounceEffectDecorator uut = getUUT(touchDragRatioFwd, touchDragRatioBck);
|
||||
MotionEvent eventMoveRight = createLongDownwardsMoveEvent();
|
||||
uut.onTouch(mView, eventMoveRight);
|
||||
reset(mView);
|
||||
float startTransY = (eventMoveRight.getY() - eventMoveRight.getHistoricalY(0)) / touchDragRatioFwd;
|
||||
when(mView.getTranslationY()).thenReturn(startTransY);
|
||||
|
||||
// Create the up-drag event
|
||||
MotionEvent eventMoveUpwards = createShortUpwardsMoveEvent();
|
||||
|
||||
// Act
|
||||
|
||||
boolean ret = uut.onTouch(mView, eventMoveUpwards);
|
||||
|
||||
// Assert
|
||||
|
||||
float expectedTransY = startTransY +
|
||||
(eventMoveUpwards.getY() - eventMoveUpwards.getHistoricalY(0)) / touchDragRatioBck;
|
||||
verify(mView).setTranslationY(expectedTransY);
|
||||
verify(mView, never()).setTranslationX(anyFloat());
|
||||
assertTrue(ret);
|
||||
assertEquals(STATE_DRAG_START_SIDE, uut.getCurrentState());
|
||||
|
||||
// State-change listener called only once?
|
||||
verify(mStateListener).onOverScrollStateChange(eq(uut), eq(STATE_IDLE), eq(STATE_DRAG_START_SIDE));
|
||||
verify(mStateListener).onOverScrollStateChange(eq(uut), anyInt(), anyInt());
|
||||
// Update-listener called exactly twice?
|
||||
verify(mUpdateListener).onOverScrollUpdate(eq(uut), eq(STATE_DRAG_START_SIDE), eq(startTransY));
|
||||
verify(mUpdateListener).onOverScrollUpdate(eq(uut), eq(STATE_DRAG_START_SIDE), eq(expectedTransY));
|
||||
verify(mUpdateListener, times(2)).onOverScrollUpdate(eq(uut), anyInt(), anyFloat());
|
||||
}
|
||||
|
||||
/**
|
||||
* When over-scroll has already started (upwards in this case) and suddenly the user changes
|
||||
* their mind and scrolls a bit in the other direction:
|
||||
* <br/>We expect the <b>touch to still be intercepted</b> in that case, and the <b>overscroll to remain in effect</b>.
|
||||
*/
|
||||
@Test
|
||||
public void onTouchMoveAction_dragDownWhenUpOverscolled_continueOverscrollingDownwards() throws Exception {
|
||||
|
||||
// Arrange
|
||||
|
||||
// In up & down drag tests we use equal ratios to avoid the effect's under-scroll handling
|
||||
final float touchDragRatioFwd = 3f;
|
||||
final float touchDragRatioBck = 3f;
|
||||
|
||||
// Bring UUT to an upwards-overscroll state
|
||||
when(mViewAdapter.isInAbsoluteStart()).thenReturn(false);
|
||||
when(mViewAdapter.isInAbsoluteEnd()).thenReturn(true);
|
||||
|
||||
VerticalOverScrollBounceEffectDecorator uut = getUUT(touchDragRatioFwd, touchDragRatioBck);
|
||||
MotionEvent eventMoveUp = createLongUpwardsMoveEvent();
|
||||
uut.onTouch(mView, eventMoveUp);
|
||||
reset(mView);
|
||||
|
||||
float startTransY = (eventMoveUp.getY() - eventMoveUp.getHistoricalY(0)) / touchDragRatioFwd;
|
||||
when(mView.getTranslationY()).thenReturn(startTransY);
|
||||
|
||||
// Create the down-drag event
|
||||
MotionEvent eventMoveDown = createShortDownwardsMoveEvent();
|
||||
|
||||
// Act
|
||||
|
||||
boolean ret = uut.onTouch(mView, eventMoveDown);
|
||||
|
||||
// Assert
|
||||
|
||||
float expectedTransY = startTransY + (eventMoveDown.getY() - eventMoveDown.getHistoricalY(0)) / touchDragRatioBck;
|
||||
verify(mView).setTranslationY(expectedTransY);
|
||||
verify(mView, never()).setTranslationX(anyFloat());
|
||||
assertTrue(ret);
|
||||
assertEquals(STATE_DRAG_END_SIDE, uut.getCurrentState());
|
||||
|
||||
// State-change listener called only once?
|
||||
verify(mStateListener).onOverScrollStateChange(eq(uut), eq(STATE_IDLE), eq(STATE_DRAG_END_SIDE));
|
||||
verify(mStateListener).onOverScrollStateChange(eq(uut), anyInt(), anyInt());
|
||||
// Update-listener called exactly twice?
|
||||
verify(mUpdateListener).onOverScrollUpdate(eq(uut), eq(STATE_DRAG_END_SIDE), eq(startTransY));
|
||||
verify(mUpdateListener).onOverScrollUpdate(eq(uut), eq(STATE_DRAG_END_SIDE), eq(expectedTransY));
|
||||
verify(mUpdateListener, times(2)).onOverScrollUpdate(eq(uut), anyInt(), anyFloat());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onTouchMoveAction_undragWhenDownOverscrolled_endOverscrolling() throws Exception {
|
||||
|
||||
// Arrange
|
||||
|
||||
// In left & right tests we use equal ratios to avoid the effect's under-scroll handling
|
||||
final float touchDragRatioFwd = 3f;
|
||||
final float touchDragRatioBck = 3f;
|
||||
|
||||
// Bring UUT to a downwards-overscroll state
|
||||
when(mViewAdapter.isInAbsoluteStart()).thenReturn(true);
|
||||
when(mViewAdapter.isInAbsoluteEnd()).thenReturn(false);
|
||||
|
||||
VerticalOverScrollBounceEffectDecorator uut = getUUT(touchDragRatioFwd, touchDragRatioBck);
|
||||
MotionEvent eventMoveDown = createLongDownwardsMoveEvent();
|
||||
uut.onTouch(mView, eventMoveDown);
|
||||
reset(mView);
|
||||
float startTransX = (eventMoveDown.getX() - eventMoveDown.getHistoricalX(0)) / touchDragRatioFwd;
|
||||
when(mView.getTranslationX()).thenReturn(startTransX);
|
||||
|
||||
// Create the (negative) upwards-drag event
|
||||
MotionEvent eventMoveUp = createLongUpwardsMoveEvent();
|
||||
|
||||
// Act
|
||||
|
||||
boolean ret = uut.onTouch(mView, eventMoveUp);
|
||||
|
||||
// Assert
|
||||
|
||||
verify(mView, never()).setTranslationX(anyFloat());
|
||||
verify(mView).setTranslationY(0);
|
||||
assertTrue(ret);
|
||||
assertEquals(STATE_IDLE, uut.getCurrentState());
|
||||
|
||||
// State-change listener invoked to say drag-on and drag-off (idle).
|
||||
verify(mStateListener).onOverScrollStateChange(eq(uut), eq(STATE_IDLE), eq(STATE_DRAG_START_SIDE));
|
||||
verify(mStateListener).onOverScrollStateChange(eq(uut), eq(STATE_DRAG_START_SIDE), eq(STATE_IDLE));
|
||||
verify(mStateListener, times(2)).onOverScrollStateChange(eq(uut), anyInt(), anyInt());
|
||||
// Update-listener called exactly twice?
|
||||
verify(mUpdateListener).onOverScrollUpdate(eq(uut), eq(STATE_DRAG_START_SIDE), eq(startTransX));
|
||||
verify(mUpdateListener).onOverScrollUpdate(eq(uut), eq(STATE_DRAG_START_SIDE), eq(0f));
|
||||
verify(mUpdateListener, times(2)).onOverScrollUpdate(eq(uut), anyInt(), anyFloat());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onTouchMoveAction_undragWhenUpOverscrolled_endOverscrolling() throws Exception {
|
||||
|
||||
// Arrange
|
||||
|
||||
// In left & right tests we use equal ratios to avoid the effect's under-scroll handling
|
||||
final float touchDragRatioFwd = 3f;
|
||||
final float touchDragRatioBck = 3f;
|
||||
|
||||
// Bring UUT to a left-overscroll state
|
||||
when(mViewAdapter.isInAbsoluteStart()).thenReturn(false);
|
||||
when(mViewAdapter.isInAbsoluteEnd()).thenReturn(true);
|
||||
|
||||
VerticalOverScrollBounceEffectDecorator uut = getUUT(touchDragRatioFwd, touchDragRatioBck);
|
||||
MotionEvent eventMoveUp = createLongUpwardsMoveEvent();
|
||||
uut.onTouch(mView, eventMoveUp);
|
||||
reset(mView);
|
||||
float startTransX = (eventMoveUp.getX() - eventMoveUp.getHistoricalX(0)) / touchDragRatioFwd;
|
||||
when(mView.getTranslationX()).thenReturn(startTransX);
|
||||
|
||||
// Create the (negative) downwards-drag event
|
||||
MotionEvent eventMoveDown = createLongDownwardsMoveEvent();
|
||||
|
||||
// Act
|
||||
|
||||
boolean ret = uut.onTouch(mView, eventMoveDown);
|
||||
|
||||
// Assert
|
||||
|
||||
verify(mView, never()).setTranslationX(anyFloat());
|
||||
verify(mView).setTranslationY(0);
|
||||
assertTrue(ret);
|
||||
assertEquals(STATE_IDLE, uut.getCurrentState());
|
||||
|
||||
// State-change listener invoked to say drag-on and drag-off (idle).
|
||||
verify(mStateListener).onOverScrollStateChange(eq(uut), eq(STATE_IDLE), eq(STATE_DRAG_END_SIDE));
|
||||
verify(mStateListener).onOverScrollStateChange(eq(uut), eq(STATE_DRAG_END_SIDE), eq(STATE_IDLE));
|
||||
verify(mStateListener, times(2)).onOverScrollStateChange(eq(uut), anyInt(), anyInt());
|
||||
// Update-listener called exactly twice?
|
||||
verify(mUpdateListener).onOverScrollUpdate(eq(uut), eq(STATE_DRAG_END_SIDE), eq(startTransX));
|
||||
verify(mUpdateListener).onOverScrollUpdate(eq(uut), eq(STATE_DRAG_END_SIDE), eq(0f));
|
||||
verify(mUpdateListener, times(2)).onOverScrollUpdate(eq(uut), anyInt(), anyFloat());
|
||||
}
|
||||
|
||||
/*
|
||||
* Up action event
|
||||
*/
|
||||
|
||||
@Test
|
||||
public void onTouchUpAction_eventWhenNotOverscrolled_ignoreTouchEvent() throws Exception {
|
||||
|
||||
// Arrange
|
||||
|
||||
MotionEvent event = createDefaultUpActionEvent();
|
||||
|
||||
when(mViewAdapter.isInAbsoluteStart()).thenReturn(true);
|
||||
when(mViewAdapter.isInAbsoluteEnd()).thenReturn(true);
|
||||
|
||||
VerticalOverScrollBounceEffectDecorator uut = getUUT();
|
||||
|
||||
// Act
|
||||
|
||||
boolean ret = uut.onTouch(mView, event);
|
||||
|
||||
// Assert
|
||||
|
||||
verify(mView, never()).setTranslationX(anyFloat());
|
||||
verify(mView, never()).setTranslationY(anyFloat());
|
||||
assertFalse(ret);
|
||||
assertEquals(STATE_IDLE, uut.getCurrentState());
|
||||
|
||||
verify(mStateListener, never()).onOverScrollStateChange(eq(uut), anyInt(), anyInt());
|
||||
verify(mUpdateListener, never()).onOverScrollUpdate(eq(uut), anyInt(), anyFloat());
|
||||
}
|
||||
|
||||
protected MotionEvent createShortDownwardsMoveEvent() {
|
||||
MotionEvent event = mock(MotionEvent.class);
|
||||
when(event.getAction()).thenReturn(MotionEvent.ACTION_MOVE);
|
||||
when(event.getX()).thenReturn(200f);
|
||||
when(event.getY()).thenReturn(100f);
|
||||
when(event.getX(0)).thenReturn(200f);
|
||||
when(event.getY(0)).thenReturn(100f);
|
||||
when(event.getHistorySize()).thenReturn(1);
|
||||
when(event.getHistoricalX(eq(0))).thenReturn(190f);
|
||||
when(event.getHistoricalY(eq(0))).thenReturn(80f);
|
||||
when(event.getHistoricalX(eq(0), eq(0))).thenReturn(190f);
|
||||
when(event.getHistoricalY(eq(0), eq(0))).thenReturn(80f);
|
||||
return event;
|
||||
}
|
||||
|
||||
protected MotionEvent createLongDownwardsMoveEvent() {
|
||||
MotionEvent event = mock(MotionEvent.class);
|
||||
when(event.getAction()).thenReturn(MotionEvent.ACTION_MOVE);
|
||||
when(event.getX()).thenReturn(250f);
|
||||
when(event.getY()).thenReturn(150f);
|
||||
when(event.getX(0)).thenReturn(250f);
|
||||
when(event.getY(0)).thenReturn(150f);
|
||||
when(event.getHistorySize()).thenReturn(1);
|
||||
when(event.getHistoricalX(eq(0))).thenReturn(200f);
|
||||
when(event.getHistoricalY(eq(0))).thenReturn(100f);
|
||||
when(event.getHistoricalX(eq(0), eq(0))).thenReturn(200f);
|
||||
when(event.getHistoricalY(eq(0), eq(0))).thenReturn(100f);
|
||||
return event;
|
||||
}
|
||||
|
||||
protected MotionEvent createShortUpwardsMoveEvent() {
|
||||
MotionEvent event = mock(MotionEvent.class);
|
||||
when(event.getAction()).thenReturn(MotionEvent.ACTION_MOVE);
|
||||
when(event.getX()).thenReturn(200f);
|
||||
when(event.getY()).thenReturn(100f);
|
||||
when(event.getX(0)).thenReturn(200f);
|
||||
when(event.getY(0)).thenReturn(100f);
|
||||
when(event.getHistorySize()).thenReturn(1);
|
||||
when(event.getHistoricalX(eq(0))).thenReturn(220f);
|
||||
when(event.getHistoricalY(eq(0))).thenReturn(120f);
|
||||
when(event.getHistoricalX(eq(0), eq(0))).thenReturn(220f);
|
||||
when(event.getHistoricalY(eq(0), eq(0))).thenReturn(120f);
|
||||
return event;
|
||||
}
|
||||
|
||||
protected MotionEvent createLongUpwardsMoveEvent() {
|
||||
MotionEvent event = mock(MotionEvent.class);
|
||||
when(event.getAction()).thenReturn(MotionEvent.ACTION_MOVE);
|
||||
when(event.getX()).thenReturn(200f);
|
||||
when(event.getY()).thenReturn(100f);
|
||||
when(event.getX(0)).thenReturn(200f);
|
||||
when(event.getY(0)).thenReturn(100f);
|
||||
when(event.getHistorySize()).thenReturn(1);
|
||||
when(event.getHistoricalX(eq(0))).thenReturn(250f);
|
||||
when(event.getHistoricalY(eq(0))).thenReturn(150f);
|
||||
when(event.getHistoricalX(eq(0), eq(0))).thenReturn(250f);
|
||||
when(event.getHistoricalY(eq(0), eq(0))).thenReturn(150f);
|
||||
return event;
|
||||
}
|
||||
|
||||
protected MotionEvent createDefaultUpActionEvent() {
|
||||
MotionEvent event = mock(MotionEvent.class);
|
||||
when(event.getAction()).thenReturn(MotionEvent.ACTION_UP);
|
||||
return event;
|
||||
}
|
||||
|
||||
protected VerticalOverScrollBounceEffectDecorator getUUT() {
|
||||
VerticalOverScrollBounceEffectDecorator uut = new VerticalOverScrollBounceEffectDecorator(mViewAdapter);
|
||||
uut.setOverScrollStateListener(mStateListener);
|
||||
uut.setOverScrollUpdateListener(mUpdateListener);
|
||||
return uut;
|
||||
}
|
||||
|
||||
protected VerticalOverScrollBounceEffectDecorator getUUT(float touchDragRatioFwd, float touchDragRatioBck) {
|
||||
VerticalOverScrollBounceEffectDecorator uut = new VerticalOverScrollBounceEffectDecorator(mViewAdapter, touchDragRatioFwd, touchDragRatioBck, DEFAULT_DECELERATE_FACTOR);
|
||||
uut.setOverScrollStateListener(mStateListener);
|
||||
uut.setOverScrollUpdateListener(mUpdateListener);
|
||||
return uut;
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue