NewPipe-app-android/app/src/main/java/com/google/android/material/appbar/FlingBehavior.kt

142 lines
5.3 KiB
Kotlin

package com.google.android.material.appbar
import android.content.Context
import android.graphics.Rect
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.View
import android.widget.OverScroller
import androidx.coordinatorlayout.widget.CoordinatorLayout
import org.schabi.newpipe.R
import java.lang.reflect.Field
import java.util.List
// See https://stackoverflow.com/questions/56849221#57997489
class FlingBehavior(context: Context?, attrs: AttributeSet?) : AppBarLayout.Behavior(context, attrs) {
private val focusScrollRect = Rect()
private var allowScroll = true
private val globalRect = Rect()
private val skipInterceptionOfElements = List.of(
R.id.itemsListPanel, R.id.playbackSeekBar,
R.id.playPauseButton, R.id.playPreviousButton, R.id.playNextButton)
override fun onRequestChildRectangleOnScreen(
coordinatorLayout: CoordinatorLayout, child: AppBarLayout,
rectangle: Rect, immediate: Boolean): Boolean {
focusScrollRect.set(rectangle)
coordinatorLayout.offsetDescendantRectToMyCoords(child, focusScrollRect)
val height = coordinatorLayout.height
if (focusScrollRect.top <= 0 && focusScrollRect.bottom >= height) {
// the child is too big to fit inside ourselves completely, ignore request
return false
}
val dy: Int
dy = if (focusScrollRect.bottom > height) {
focusScrollRect.top
} else if (focusScrollRect.top < 0) {
// scrolling up
-(height - focusScrollRect.bottom)
} else {
// nothing to do
return false
}
val consumed = scroll(coordinatorLayout, child, dy, getMaxDragOffset(child), 0)
return consumed == dy
}
override fun onInterceptTouchEvent(parent: CoordinatorLayout,
child: AppBarLayout,
ev: MotionEvent): Boolean {
for (element in skipInterceptionOfElements) {
val view = child.findViewById<View>(element)
if (view != null) {
val visible = view.getGlobalVisibleRect(globalRect)
if (visible && globalRect.contains(ev.rawX.toInt(), ev.rawY.toInt())) {
allowScroll = false
return false
}
}
}
allowScroll = true
when (ev.actionMasked) {
MotionEvent.ACTION_DOWN -> {
// remove reference to old nested scrolling child
resetNestedScrollingChild()
// Stop fling when your finger touches the screen
stopAppBarLayoutFling()
}
else -> {}
}
return super.onInterceptTouchEvent(parent, child, ev)
}
override fun onStartNestedScroll(parent: CoordinatorLayout,
child: AppBarLayout,
directTargetChild: View,
target: View,
nestedScrollAxes: Int,
type: Int): Boolean {
return allowScroll && super.onStartNestedScroll(
parent, child, directTargetChild, target, nestedScrollAxes, type)
}
override fun onNestedFling(coordinatorLayout: CoordinatorLayout,
child: AppBarLayout,
target: View, velocityX: Float,
velocityY: Float, consumed: Boolean): Boolean {
return allowScroll && super.onNestedFling(
coordinatorLayout, child, target, velocityX, velocityY, consumed)
}
private val scrollerField: OverScroller?
private get() {
try {
val headerBehaviorType: Class<*>? = this.javaClass
.superclass.superclass.superclass
if (headerBehaviorType != null) {
val field = headerBehaviorType.getDeclaredField("scroller")
field.isAccessible = true
return field[this] as OverScroller
}
} catch (e: NoSuchFieldException) {
// ?
} catch (e: IllegalAccessException) {
}
return null
}
private val lastNestedScrollingChildRefField: Field?
private get() {
try {
val headerBehaviorType: Class<*>? = this.javaClass.superclass.superclass
if (headerBehaviorType != null) {
val field = headerBehaviorType.getDeclaredField("lastNestedScrollingChildRef")
field.isAccessible = true
return field
}
} catch (e: NoSuchFieldException) {
// ?
}
return null
}
private fun resetNestedScrollingChild() {
val field = lastNestedScrollingChildRefField
if (field != null) {
try {
val value = field[this]
if (value != null) {
field[this] = null
}
} catch (e: IllegalAccessException) {
// ?
}
}
}
private fun stopAppBarLayoutFling() {
val scroller = scrollerField
scroller?.forceFinished(true)
}
}