blob: 00273bc34552e4d9f7d57841001f707f45b1f194 [file] [log] [blame]
package com.android.systemui.media
import android.content.Context
import android.os.SystemClock
import android.util.AttributeSet
import android.view.InputDevice
import android.view.MotionEvent
import android.view.ViewGroup
import android.widget.HorizontalScrollView
import com.android.systemui.Gefingerpoken
import com.android.wm.shell.animation.physicsAnimator
/**
* A ScrollView used in Media that doesn't limit itself to the childs bounds. This is useful
* when only measuring children but not the parent, when trying to apply a new scroll position
*/
class MediaScrollView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
)
: HorizontalScrollView(context, attrs, defStyleAttr) {
lateinit var contentContainer: ViewGroup
private set
var touchListener: Gefingerpoken? = null
/**
* The target value of the translation X animation. Only valid if the physicsAnimator is running
*/
var animationTargetX = 0.0f
/**
* Get the current content translation. This is usually the normal translationX of the content,
* but when animating, it might differ
*/
fun getContentTranslation() = if (contentContainer.physicsAnimator.isRunning()) {
animationTargetX
} else {
contentContainer.translationX
}
/**
* Convert between the absolute (left-to-right) and relative (start-to-end) scrollX of the media
* carousel. The player indices are always relative (start-to-end) and the scrollView.scrollX
* is always absolute. This function is its own inverse.
*/
private fun transformScrollX(scrollX: Int): Int = if (isLayoutRtl) {
contentContainer.width - width - scrollX
} else {
scrollX
}
/**
* Get the layoutDirection-relative (start-to-end) scroll X position of the carousel.
*/
var relativeScrollX: Int
get() = transformScrollX(scrollX)
set(value) {
scrollX = transformScrollX(value)
}
/**
* Allow all scrolls to go through, use base implementation
*/
override fun scrollTo(x: Int, y: Int) {
if (mScrollX != x || mScrollY != y) {
val oldX: Int = mScrollX
val oldY: Int = mScrollY
mScrollX = x
mScrollY = y
invalidateParentCaches()
onScrollChanged(mScrollX, mScrollY, oldX, oldY)
if (!awakenScrollBars()) {
postInvalidateOnAnimation()
}
}
}
override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {
var intercept = false
touchListener?.let {
intercept = it.onInterceptTouchEvent(ev)
}
return super.onInterceptTouchEvent(ev) || intercept
}
override fun onTouchEvent(ev: MotionEvent?): Boolean {
var touch = false
touchListener?.let {
touch = it.onTouchEvent(ev)
}
return super.onTouchEvent(ev) || touch
}
override fun onFinishInflate() {
super.onFinishInflate()
contentContainer = getChildAt(0) as ViewGroup
}
override fun overScrollBy(
deltaX: Int,
deltaY: Int,
scrollX: Int,
scrollY: Int,
scrollRangeX: Int,
scrollRangeY: Int,
maxOverScrollX: Int,
maxOverScrollY: Int,
isTouchEvent: Boolean
): Boolean {
if (getContentTranslation() != 0.0f) {
// When we're dismissing we ignore all the scrolling
return false
}
return super.overScrollBy(deltaX, deltaY, scrollX, scrollY, scrollRangeX,
scrollRangeY, maxOverScrollX, maxOverScrollY, isTouchEvent)
}
/**
* Cancel the current touch event going on.
*/
fun cancelCurrentScroll() {
val now = SystemClock.uptimeMillis()
val event = MotionEvent.obtain(now, now,
MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0)
event.source = InputDevice.SOURCE_TOUCHSCREEN
super.onTouchEvent(event)
event.recycle()
}
}