Set status bar foreground when app bar is hidden
This commit is contained in:
@@ -0,0 +1,151 @@
|
|||||||
|
package com.google.android.material.appbar
|
||||||
|
|
||||||
|
import android.animation.AnimatorSet
|
||||||
|
import android.animation.ValueAnimator
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.Context
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.annotation.FloatRange
|
||||||
|
import com.google.android.material.animation.AnimationUtils
|
||||||
|
import com.google.android.material.appbar.AppBarLayout.OnOffsetChangedListener
|
||||||
|
import com.google.android.material.shape.MaterialShapeDrawable
|
||||||
|
import org.koitharu.kotatsu.R
|
||||||
|
import com.google.android.material.R as materialR
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [AppBarLayout] with our own lift state handler and custom title alpha.
|
||||||
|
*
|
||||||
|
* Inside this package to access some package-private methods.
|
||||||
|
*/
|
||||||
|
class KotatsuAppBarLayout @JvmOverloads constructor(
|
||||||
|
context: Context,
|
||||||
|
attrs: AttributeSet? = null
|
||||||
|
) : AppBarLayout(context, attrs) {
|
||||||
|
|
||||||
|
private var lifted = true
|
||||||
|
|
||||||
|
private val toolbar by lazy { findViewById<MaterialToolbar>(R.id.toolbar) }
|
||||||
|
|
||||||
|
@FloatRange(from = 0.0, to = 1.0)
|
||||||
|
var titleTextAlpha = 1F
|
||||||
|
set(value) {
|
||||||
|
field = value
|
||||||
|
titleTextView?.alpha = field
|
||||||
|
}
|
||||||
|
|
||||||
|
private var titleTextView: TextView? = null
|
||||||
|
set(value) {
|
||||||
|
field = value
|
||||||
|
field?.alpha = titleTextAlpha
|
||||||
|
}
|
||||||
|
|
||||||
|
private var animatorSet: AnimatorSet? = null
|
||||||
|
|
||||||
|
private var statusBarForegroundAnimator: ValueAnimator? = null
|
||||||
|
private val offsetListener = OnOffsetChangedListener { appBarLayout, verticalOffset ->
|
||||||
|
// Show status bar foreground when offset
|
||||||
|
val foreground = (appBarLayout?.statusBarForeground as? MaterialShapeDrawable) ?: return@OnOffsetChangedListener
|
||||||
|
val start = foreground.alpha
|
||||||
|
val end = if (verticalOffset != 0) 255 else 0
|
||||||
|
|
||||||
|
statusBarForegroundAnimator?.cancel()
|
||||||
|
if (animatorSet?.isRunning == true) {
|
||||||
|
foreground.alpha = end
|
||||||
|
return@OnOffsetChangedListener
|
||||||
|
}
|
||||||
|
if (start != end) {
|
||||||
|
statusBarForegroundAnimator = ValueAnimator.ofInt(start, end).apply {
|
||||||
|
duration = resources.getInteger(materialR.integer.app_bar_elevation_anim_duration).toLong()
|
||||||
|
interpolator = AnimationUtils.LINEAR_INTERPOLATOR
|
||||||
|
addUpdateListener {
|
||||||
|
foreground.alpha = it.animatedValue as Int
|
||||||
|
}
|
||||||
|
start()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var isTransparentWhenNotLifted = false
|
||||||
|
set(value) {
|
||||||
|
if (field != value) {
|
||||||
|
field = value
|
||||||
|
updateStates()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun isLiftOnScroll(): Boolean = false
|
||||||
|
|
||||||
|
override fun isLifted(): Boolean = lifted
|
||||||
|
|
||||||
|
override fun setLifted(lifted: Boolean): Boolean {
|
||||||
|
return if (this.lifted != lifted) {
|
||||||
|
this.lifted = lifted
|
||||||
|
updateStates()
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setLiftedState(lifted: Boolean, force: Boolean): Boolean = false
|
||||||
|
|
||||||
|
override fun onAttachedToWindow() {
|
||||||
|
super.onAttachedToWindow()
|
||||||
|
addOnOffsetChangedListener(offsetListener)
|
||||||
|
toolbar.background.alpha = 0 // Use app bar background
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDetachedFromWindow() {
|
||||||
|
super.onDetachedFromWindow()
|
||||||
|
removeOnOffsetChangedListener(offsetListener)
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("Recycle")
|
||||||
|
private fun updateStates() {
|
||||||
|
val animators = mutableListOf<ValueAnimator>()
|
||||||
|
|
||||||
|
val fromElevation = elevation
|
||||||
|
val toElevation = if (lifted) {
|
||||||
|
resources.getDimension(materialR.dimen.design_appbar_elevation)
|
||||||
|
} else {
|
||||||
|
0F
|
||||||
|
}
|
||||||
|
if (fromElevation != toElevation) {
|
||||||
|
ValueAnimator.ofFloat(fromElevation, toElevation).apply {
|
||||||
|
addUpdateListener {
|
||||||
|
elevation = it.animatedValue as Float
|
||||||
|
(statusBarForeground as? MaterialShapeDrawable)?.elevation = it.animatedValue as Float
|
||||||
|
}
|
||||||
|
animators.add(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val transparent = if (lifted) false else isTransparentWhenNotLifted
|
||||||
|
val fromAlpha = (background as? MaterialShapeDrawable)?.alpha ?: background.alpha
|
||||||
|
val toAlpha = if (transparent) 0 else 255
|
||||||
|
if (fromAlpha != toAlpha) {
|
||||||
|
ValueAnimator.ofInt(fromAlpha, toAlpha).apply {
|
||||||
|
addUpdateListener {
|
||||||
|
val value = it.animatedValue as Int
|
||||||
|
background.alpha = value
|
||||||
|
}
|
||||||
|
animators.add(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (animators.isNotEmpty()) {
|
||||||
|
animatorSet?.cancel()
|
||||||
|
animatorSet = AnimatorSet().apply {
|
||||||
|
duration = resources.getInteger(materialR.integer.app_bar_elevation_anim_duration).toLong()
|
||||||
|
interpolator = AnimationUtils.LINEAR_INTERPOLATOR
|
||||||
|
playTogether(*animators.toTypedArray())
|
||||||
|
start()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
statusBarForeground = MaterialShapeDrawable.createWithElevationOverlay(context)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
package org.koitharu.kotatsu.base.ui.widgets
|
||||||
|
|
||||||
|
import android.animation.ValueAnimator
|
||||||
|
import android.content.Context
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.view.animation.DecelerateInterpolator
|
||||||
|
import androidx.appcompat.widget.Toolbar
|
||||||
|
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||||
|
import androidx.core.view.ViewCompat
|
||||||
|
import com.google.android.material.appbar.AppBarLayout
|
||||||
|
import com.google.android.material.bottomnavigation.BottomNavigationView
|
||||||
|
import org.koitharu.kotatsu.utils.ext.animatorDurationScale
|
||||||
|
import org.koitharu.kotatsu.utils.ext.findChild
|
||||||
|
import kotlin.math.roundToLong
|
||||||
|
|
||||||
|
class HideBottomNavigationOnScrollBehavior @JvmOverloads constructor(
|
||||||
|
context: Context? = null,
|
||||||
|
attrs: AttributeSet? = null,
|
||||||
|
) : CoordinatorLayout.Behavior<BottomNavigationView>(context, attrs) {
|
||||||
|
|
||||||
|
@ViewCompat.NestedScrollType
|
||||||
|
private var lastStartedType: Int = 0
|
||||||
|
|
||||||
|
private var offsetAnimator: ValueAnimator? = null
|
||||||
|
|
||||||
|
private var dyRatio = 1F
|
||||||
|
|
||||||
|
override fun layoutDependsOn(parent: CoordinatorLayout, child: BottomNavigationView, dependency: View): Boolean {
|
||||||
|
return dependency is AppBarLayout
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDependentViewChanged(
|
||||||
|
parent: CoordinatorLayout,
|
||||||
|
child: BottomNavigationView,
|
||||||
|
dependency: View,
|
||||||
|
): Boolean {
|
||||||
|
val toolbarSize = (dependency as ViewGroup).findChild<Toolbar>()?.height ?: 0
|
||||||
|
dyRatio = if (toolbarSize > 0) {
|
||||||
|
child.height.toFloat() / toolbarSize
|
||||||
|
} else {
|
||||||
|
1F
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStartNestedScroll(
|
||||||
|
coordinatorLayout: CoordinatorLayout,
|
||||||
|
child: BottomNavigationView,
|
||||||
|
directTargetChild: View,
|
||||||
|
target: View,
|
||||||
|
axes: Int,
|
||||||
|
type: Int,
|
||||||
|
): Boolean {
|
||||||
|
if (axes != ViewCompat.SCROLL_AXIS_VERTICAL) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
lastStartedType = type
|
||||||
|
offsetAnimator?.cancel()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onNestedPreScroll(
|
||||||
|
coordinatorLayout: CoordinatorLayout,
|
||||||
|
child: BottomNavigationView,
|
||||||
|
target: View,
|
||||||
|
dx: Int,
|
||||||
|
dy: Int,
|
||||||
|
consumed: IntArray,
|
||||||
|
type: Int,
|
||||||
|
) {
|
||||||
|
super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed, type)
|
||||||
|
child.translationY = (child.translationY + (dy * dyRatio)).coerceIn(0F, child.height.toFloat())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStopNestedScroll(
|
||||||
|
coordinatorLayout: CoordinatorLayout,
|
||||||
|
child: BottomNavigationView,
|
||||||
|
target: View,
|
||||||
|
type: Int,
|
||||||
|
) {
|
||||||
|
if (lastStartedType == ViewCompat.TYPE_TOUCH || type == ViewCompat.TYPE_NON_TOUCH) {
|
||||||
|
animateBottomNavigationVisibility(child, child.translationY < child.height / 2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun animateBottomNavigationVisibility(child: BottomNavigationView, isVisible: Boolean) {
|
||||||
|
offsetAnimator?.cancel()
|
||||||
|
offsetAnimator = ValueAnimator().apply {
|
||||||
|
interpolator = DecelerateInterpolator()
|
||||||
|
duration = (150 * child.context.animatorDurationScale).roundToLong()
|
||||||
|
addUpdateListener {
|
||||||
|
child.translationY = it.animatedValue as Float
|
||||||
|
}
|
||||||
|
}
|
||||||
|
offsetAnimator?.setFloatValues(
|
||||||
|
child.translationY,
|
||||||
|
if (isVisible) 0F else child.height.toFloat(),
|
||||||
|
)
|
||||||
|
offsetAnimator?.start()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,156 @@
|
|||||||
|
package org.koitharu.kotatsu.base.ui.widgets
|
||||||
|
|
||||||
|
import android.animation.Animator
|
||||||
|
import android.animation.AnimatorListenerAdapter
|
||||||
|
import android.animation.TimeInterpolator
|
||||||
|
import android.content.Context
|
||||||
|
import android.os.Parcel
|
||||||
|
import android.os.Parcelable
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import android.view.ViewPropertyAnimator
|
||||||
|
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||||
|
import androidx.core.view.doOnLayout
|
||||||
|
import androidx.core.view.updateLayoutParams
|
||||||
|
import androidx.customview.view.AbsSavedState
|
||||||
|
import androidx.interpolator.view.animation.FastOutLinearInInterpolator
|
||||||
|
import androidx.interpolator.view.animation.LinearOutSlowInInterpolator
|
||||||
|
import androidx.lifecycle.findViewTreeLifecycleOwner
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import com.google.android.material.bottomnavigation.BottomNavigationView
|
||||||
|
import org.koitharu.kotatsu.utils.ext.applySystemAnimatorScale
|
||||||
|
import com.google.android.material.R as materialR
|
||||||
|
|
||||||
|
class KotatsuBottomNavigationView @JvmOverloads constructor(
|
||||||
|
context: Context,
|
||||||
|
attrs: AttributeSet? = null,
|
||||||
|
defStyleAttr: Int = materialR.attr.bottomNavigationStyle,
|
||||||
|
defStyleRes: Int = materialR.style.Widget_Design_BottomNavigationView,
|
||||||
|
) : BottomNavigationView(context, attrs, defStyleAttr, defStyleRes) {
|
||||||
|
|
||||||
|
private var currentAnimator: ViewPropertyAnimator? = null
|
||||||
|
|
||||||
|
private var currentState = STATE_UP
|
||||||
|
|
||||||
|
init {
|
||||||
|
// Hide on scroll
|
||||||
|
doOnLayout {
|
||||||
|
findViewTreeLifecycleOwner()?.lifecycleScope?.let {
|
||||||
|
updateLayoutParams<CoordinatorLayout.LayoutParams> {
|
||||||
|
behavior = HideBottomNavigationOnScrollBehavior()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSaveInstanceState(): Parcelable {
|
||||||
|
val superState = super.onSaveInstanceState()
|
||||||
|
return SavedState(superState).also {
|
||||||
|
it.currentState = currentState
|
||||||
|
it.translationY = translationY
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onRestoreInstanceState(state: Parcelable?) {
|
||||||
|
if (state is SavedState) {
|
||||||
|
super.onRestoreInstanceState(state.superState)
|
||||||
|
super.setTranslationY(state.translationY)
|
||||||
|
currentState = state.currentState
|
||||||
|
} else {
|
||||||
|
super.onRestoreInstanceState(state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setTranslationY(translationY: Float) {
|
||||||
|
// Disallow translation change when state down
|
||||||
|
if (currentState == STATE_DOWN) return
|
||||||
|
super.setTranslationY(translationY)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows this view up.
|
||||||
|
*/
|
||||||
|
fun slideUp() = post {
|
||||||
|
currentAnimator?.cancel()
|
||||||
|
clearAnimation()
|
||||||
|
|
||||||
|
currentState = STATE_UP
|
||||||
|
animateTranslation(
|
||||||
|
0F,
|
||||||
|
SLIDE_UP_ANIMATION_DURATION,
|
||||||
|
LinearOutSlowInInterpolator(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hides this view down. [setTranslationY] won't work until [slideUp] is called.
|
||||||
|
*/
|
||||||
|
fun slideDown() = post {
|
||||||
|
currentAnimator?.cancel()
|
||||||
|
clearAnimation()
|
||||||
|
|
||||||
|
currentState = STATE_DOWN
|
||||||
|
animateTranslation(
|
||||||
|
height.toFloat(),
|
||||||
|
SLIDE_DOWN_ANIMATION_DURATION,
|
||||||
|
FastOutLinearInInterpolator(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun animateTranslation(targetY: Float, duration: Long, interpolator: TimeInterpolator) {
|
||||||
|
currentAnimator = animate()
|
||||||
|
.translationY(targetY)
|
||||||
|
.setInterpolator(interpolator)
|
||||||
|
.setDuration(duration)
|
||||||
|
.applySystemAnimatorScale(context)
|
||||||
|
.setListener(object : AnimatorListenerAdapter() {
|
||||||
|
override fun onAnimationEnd(animation: Animator?) {
|
||||||
|
currentAnimator = null
|
||||||
|
postInvalidate()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class SavedState : AbsSavedState {
|
||||||
|
var currentState = STATE_UP
|
||||||
|
var translationY = 0F
|
||||||
|
|
||||||
|
constructor(superState: Parcelable) : super(superState)
|
||||||
|
|
||||||
|
constructor(source: Parcel, loader: ClassLoader?) : super(source, loader) {
|
||||||
|
currentState = source.readInt()
|
||||||
|
translationY = source.readFloat()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun writeToParcel(out: Parcel, flags: Int) {
|
||||||
|
super.writeToParcel(out, flags)
|
||||||
|
out.writeInt(currentState)
|
||||||
|
out.writeFloat(translationY)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
@JvmField
|
||||||
|
val CREATOR: Parcelable.ClassLoaderCreator<SavedState> = object : Parcelable.ClassLoaderCreator<SavedState> {
|
||||||
|
override fun createFromParcel(source: Parcel, loader: ClassLoader): SavedState {
|
||||||
|
return SavedState(source, loader)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun createFromParcel(source: Parcel): SavedState {
|
||||||
|
return SavedState(source, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun newArray(size: Int): Array<SavedState> {
|
||||||
|
return newArray(size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val STATE_DOWN = 1
|
||||||
|
private const val STATE_UP = 2
|
||||||
|
|
||||||
|
private const val SLIDE_UP_ANIMATION_DURATION = 225L
|
||||||
|
private const val SLIDE_DOWN_ANIMATION_DURATION = 175L
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -81,22 +81,11 @@ class FavouritesContainerFragment :
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onWindowInsetsChanged(insets: Insets) {
|
override fun onWindowInsetsChanged(insets: Insets) {
|
||||||
val headerHeight = (activity as? AppBarOwner)?.appBar?.measureHeight() ?: insets.top
|
|
||||||
binding.root.updatePadding(
|
|
||||||
top = headerHeight - insets.top
|
|
||||||
)
|
|
||||||
binding.pager.updatePadding(
|
|
||||||
// 8 dp is needed so that the top of the list is not attached to tabs (visible when ActionMode is active)
|
|
||||||
top = -headerHeight + resources.resolveDp(8)
|
|
||||||
)
|
|
||||||
binding.tabs.apply {
|
binding.tabs.apply {
|
||||||
updatePadding(
|
updatePadding(
|
||||||
left = insets.left,
|
left = insets.left,
|
||||||
right = insets.right
|
right = insets.right
|
||||||
)
|
)
|
||||||
updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
|
||||||
topMargin = insets.top
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -185,10 +185,6 @@ abstract class MangaListFragment :
|
|||||||
right = insets.right,
|
right = insets.right,
|
||||||
)
|
)
|
||||||
if (activity is MainActivity) {
|
if (activity is MainActivity) {
|
||||||
binding.recyclerView.updatePadding(
|
|
||||||
top = headerHeight,
|
|
||||||
bottom = insets.bottom,
|
|
||||||
)
|
|
||||||
binding.swipeRefreshLayout.setProgressViewOffset(
|
binding.swipeRefreshLayout.setProgressViewOffset(
|
||||||
true,
|
true,
|
||||||
headerHeight + resources.resolveDp(-72),
|
headerHeight + resources.resolveDp(-72),
|
||||||
|
|||||||
@@ -76,12 +76,9 @@ class FeedFragment :
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onWindowInsetsChanged(insets: Insets) {
|
override fun onWindowInsetsChanged(insets: Insets) {
|
||||||
val headerHeight = (activity as? AppBarOwner)?.appBar?.measureHeight() ?: insets.top
|
|
||||||
binding.recyclerView.updatePadding(
|
binding.recyclerView.updatePadding(
|
||||||
top = headerHeight + paddingVertical,
|
|
||||||
left = insets.left + paddingHorizontal,
|
left = insets.left + paddingHorizontal,
|
||||||
right = insets.right + paddingHorizontal,
|
right = insets.right + paddingHorizontal,
|
||||||
bottom = insets.bottom + paddingVertical,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,14 +9,19 @@ import android.net.Network
|
|||||||
import android.net.NetworkRequest
|
import android.net.NetworkRequest
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
|
import android.provider.Settings
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.view.ViewPropertyAnimator
|
||||||
import android.view.Window
|
import android.view.Window
|
||||||
|
import android.view.animation.Animation
|
||||||
import androidx.activity.result.ActivityResultLauncher
|
import androidx.activity.result.ActivityResultLauncher
|
||||||
|
import androidx.constraintlayout.motion.widget.MotionScene
|
||||||
import androidx.core.app.ActivityOptionsCompat
|
import androidx.core.app.ActivityOptionsCompat
|
||||||
|
import androidx.core.view.children
|
||||||
import androidx.lifecycle.Lifecycle
|
import androidx.lifecycle.Lifecycle
|
||||||
import androidx.lifecycle.coroutineScope
|
import androidx.lifecycle.coroutineScope
|
||||||
import androidx.work.CoroutineWorker
|
import androidx.work.CoroutineWorker
|
||||||
import com.google.android.material.elevation.ElevationOverlayProvider
|
import com.google.android.material.elevation.ElevationOverlayProvider
|
||||||
import kotlin.coroutines.resume
|
|
||||||
import kotlinx.coroutines.channels.awaitClose
|
import kotlinx.coroutines.channels.awaitClose
|
||||||
import kotlinx.coroutines.channels.trySendBlocking
|
import kotlinx.coroutines.channels.trySendBlocking
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
@@ -112,3 +117,14 @@ fun Window.setNavigationBarTransparentCompat(context: Context, elevation: Float
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val Context.animatorDurationScale: Float
|
||||||
|
get() = Settings.Global.getFloat(this.contentResolver, Settings.Global.ANIMATOR_DURATION_SCALE, 1f)
|
||||||
|
|
||||||
|
fun ViewPropertyAnimator.applySystemAnimatorScale(context: Context): ViewPropertyAnimator = apply {
|
||||||
|
this.duration = (this.duration * context.animatorDurationScale).toLong()
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <reified T> ViewGroup.findChild(): T? {
|
||||||
|
return children.find { it is T } as? T
|
||||||
|
}
|
||||||
@@ -15,7 +15,8 @@
|
|||||||
<androidx.viewpager2.widget.ViewPager2
|
<androidx.viewpager2.widget.ViewPager2
|
||||||
android:id="@+id/pager"
|
android:id="@+id/pager"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent" />
|
android:layout_height="match_parent"
|
||||||
|
android:paddingVertical="8dp"/>
|
||||||
|
|
||||||
<ViewStub
|
<ViewStub
|
||||||
android:id="@+id/stub_empty_state"
|
android:id="@+id/stub_empty_state"
|
||||||
|
|||||||
Reference in New Issue
Block a user