ui: optimize bottom sheets

Desperately try to minimize the amount of layouts my bottom sheet code
is producing.

It still relayouts twice in one pass. I hate android.
This commit is contained in:
OxygenCobalt 2022-07-30 16:06:17 -06:00
parent 54be8dc2dc
commit c3d8509069
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
9 changed files with 74 additions and 68 deletions

View file

@ -13,6 +13,7 @@ at the cost of longer loading times
- Queue can now be swiped up [#92] - Queue can now be swiped up [#92]
- Playing song is now shown in queue [#92] - Playing song is now shown in queue [#92]
- Added ability to play songs from queue [#92] - Added ability to play songs from queue [#92]
- Added ability to see previous songs in queue
- Added Last Added sorting - Added Last Added sorting
- Search now takes sort tags and file names in account [#184] - Search now takes sort tags and file names in account [#184]
- Added option to clear playback state in settings - Added option to clear playback state in settings

View file

@ -11,8 +11,8 @@ android {
defaultConfig { defaultConfig {
applicationId namespace applicationId namespace
versionName "2.5.0" versionName "2.6.0-beta"
versionCode 18 versionCode 19
// API 33 is still busted, waiting until the XML element issue is fixed // API 33 is still busted, waiting until the XML element issue is fixed
// noinspection OldTargetApi // noinspection OldTargetApi

View file

@ -131,19 +131,19 @@ class MainFragment :
val playbackRatio = max(playbackSheetBehavior.calculateSlideOffset(), 0f) val playbackRatio = max(playbackSheetBehavior.calculateSlideOffset(), 0f)
val queueRatio = max(queueSheetBehavior.calculateSlideOffset(), 0f) val queueRatio = max(queueSheetBehavior.calculateSlideOffset(), 0f)
val outRatio = 1 - playbackRatio val outPlaybackRatio = 1 - playbackRatio
val halfOutRatio = min(playbackRatio * 2, 1f) val halfOutRatio = min(playbackRatio * 2, 1f)
val halfInPlaybackRatio = max(playbackRatio - 0.5f, 0f) * 2 val halfInPlaybackRatio = max(playbackRatio - 0.5f, 0f) * 2
val halfOutQueueRatio = min(queueRatio * 2, 1f) val halfOutQueueRatio = min(queueRatio * 2, 1f)
val halfInQueueRatio = max(queueRatio - 0.5f, 0f) * 2 val halfInQueueRatio = max(queueRatio - 0.5f, 0f) * 2
binding.exploreNavHost.apply { binding.exploreNavHost.apply {
alpha = outRatio alpha = outPlaybackRatio
isInvisible = alpha == 0f isInvisible = alpha == 0f
} }
binding.playbackSheet.translationZ = 3f * outRatio binding.playbackSheet.translationZ = 3f * outPlaybackRatio
playbackSheetBehavior.sheetBackgroundDrawable.alpha = (outRatio * 255).toInt() playbackSheetBehavior.sheetBackgroundDrawable.alpha = (outPlaybackRatio * 255).toInt()
binding.playbackBarFragment.apply { binding.playbackBarFragment.apply {
alpha = max(1 - halfOutRatio, halfInQueueRatio) alpha = max(1 - halfOutRatio, halfInQueueRatio)
@ -156,7 +156,10 @@ class MainFragment :
isInvisible = alpha == 0f isInvisible = alpha == 0f
} }
binding.queueFragment.alpha = queueRatio binding.queueFragment.apply {
alpha = queueRatio
isInvisible = alpha == 0f
}
playbackSheetBehavior.isDraggable = playbackSheetBehavior.isDraggable =
playbackSheetBehavior.state != BottomSheetBehavior.STATE_HIDDEN && playbackSheetBehavior.state != BottomSheetBehavior.STATE_HIDDEN &&

View file

@ -59,9 +59,8 @@ class PlaybackBarFragment : ViewBindingFragment<FragmentPlaybackBarBinding>() {
// Load the track color in manually as it's unclear whether the track actually supports // Load the track color in manually as it's unclear whether the track actually supports
// using a ColorStateList in the resources // using a ColorStateList in the resources
binding.playbackProgressBar.apply { binding.playbackProgressBar.trackColor =
trackColor = requireContext().getColorStateListSafe(R.color.sel_track).defaultColor requireContext().getColorStateListSafe(R.color.sel_track).defaultColor
}
binding.playbackPlayPause.setOnClickListener { playbackModel.invertPlaying() } binding.playbackPlayPause.setOnClickListener { playbackModel.invertPlaying() }

View file

@ -22,10 +22,6 @@ import android.view.animation.AccelerateDecelerateInterpolator
import androidx.core.view.isInvisible import androidx.core.view.isInvisible
import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min
import kotlin.math.sign
import org.oxycblt.auxio.R import org.oxycblt.auxio.R
import org.oxycblt.auxio.util.getDimenSafe import org.oxycblt.auxio.util.getDimenSafe
import org.oxycblt.auxio.util.logD import org.oxycblt.auxio.util.logD
@ -53,27 +49,6 @@ class QueueDragCallback(private val playbackModel: QueueViewModel) : ItemTouchHe
} }
} }
override fun interpolateOutOfBoundsScroll(
recyclerView: RecyclerView,
viewSize: Int,
viewSizeOutOfBounds: Int,
totalSize: Int,
msSinceStartScroll: Long
): Int {
// Fix to make QueueFragment scroll slower when an item is scrolled out of bounds.
// Adapted from NewPipe: https://github.com/TeamNewPipe/NewPipe
val standardSpeed =
super.interpolateOutOfBoundsScroll(
recyclerView, viewSize, viewSizeOutOfBounds, totalSize, msSinceStartScroll)
val clampedAbsVelocity =
max(
MINIMUM_INITIAL_DRAG_VELOCITY,
min(abs(standardSpeed), MAXIMUM_INITIAL_DRAG_VELOCITY))
return clampedAbsVelocity * sign(viewSizeOutOfBounds.toDouble()).toInt()
}
override fun onChildDraw( override fun onChildDraw(
c: Canvas, c: Canvas,
recyclerView: RecyclerView, recyclerView: RecyclerView,

View file

@ -83,9 +83,11 @@ class QueueFragment : ViewBindingFragment<FragmentQueueBinding>(), QueueItemList
.findLastCompletelyVisibleItemPosition() .findLastCompletelyVisibleItemPosition()
if (instructions.scrollTo !in indices) { if (instructions.scrollTo !in indices) {
requireBinding().queueRecycler.scrollToPosition(instructions.scrollTo) binding.queueRecycler.scrollToPosition(instructions.scrollTo)
} }
} }
queueModel.finishInstructions()
} else { } else {
queueAdapter.data.submitList(queue) queueAdapter.data.submitList(queue)
} }

View file

@ -18,6 +18,7 @@
package org.oxycblt.auxio.playback.queue package org.oxycblt.auxio.playback.queue
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import kotlin.math.min
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import org.oxycblt.auxio.music.MusicParent import org.oxycblt.auxio.music.MusicParent
@ -80,7 +81,7 @@ class QueueViewModel : ViewModel(), PlaybackStateManager.Callback {
} }
override fun onIndexMoved(index: Int) { override fun onIndexMoved(index: Int) {
instructions = QueueInstructions(false, index + 1) instructions = QueueInstructions(false, min(index + 1, playbackManager.queue.lastIndex))
_queue.value = generateQueue(index, playbackManager.queue) _queue.value = generateQueue(index, playbackManager.queue)
} }
@ -90,12 +91,12 @@ class QueueViewModel : ViewModel(), PlaybackStateManager.Callback {
} }
override fun onQueueReworked(index: Int, queue: List<Song>) { override fun onQueueReworked(index: Int, queue: List<Song>) {
instructions = QueueInstructions(true, index + 1) instructions = QueueInstructions(true, min(index + 1, playbackManager.queue.lastIndex))
_queue.value = generateQueue(index, queue) _queue.value = generateQueue(index, queue)
} }
override fun onNewPlayback(index: Int, queue: List<Song>, parent: MusicParent?) { override fun onNewPlayback(index: Int, queue: List<Song>, parent: MusicParent?) {
instructions = QueueInstructions(true, index + 1) instructions = QueueInstructions(true, min(index + 1, playbackManager.queue.lastIndex))
_queue.value = generateQueue(index, queue) _queue.value = generateQueue(index, queue)
} }

View file

@ -25,6 +25,7 @@ import androidx.coordinatorlayout.widget.CoordinatorLayout
import com.google.android.material.bottomsheet.NeoBottomSheetBehavior import com.google.android.material.bottomsheet.NeoBottomSheetBehavior
import kotlin.math.abs import kotlin.math.abs
import org.oxycblt.auxio.util.coordinatorLayoutBehavior import org.oxycblt.auxio.util.coordinatorLayoutBehavior
import org.oxycblt.auxio.util.logD
import org.oxycblt.auxio.util.replaceSystemBarInsetsCompat import org.oxycblt.auxio.util.replaceSystemBarInsetsCompat
import org.oxycblt.auxio.util.systemBarInsetsCompat import org.oxycblt.auxio.util.systemBarInsetsCompat
@ -33,6 +34,7 @@ class BottomSheetContentBehavior<V : View>(context: Context, attributeSet: Attri
private var lastInsets: WindowInsets? = null private var lastInsets: WindowInsets? = null
private var dep: View? = null private var dep: View? = null
private var setup: Boolean = false private var setup: Boolean = false
private var lastConsumed: Int? = null
override fun onMeasureChild( override fun onMeasureChild(
parent: CoordinatorLayout, parent: CoordinatorLayout,
@ -42,32 +44,37 @@ class BottomSheetContentBehavior<V : View>(context: Context, attributeSet: Attri
parentHeightMeasureSpec: Int, parentHeightMeasureSpec: Int,
heightUsed: Int heightUsed: Int
): Boolean { ): Boolean {
return measureContent(parent, child, dep ?: return false) val dep = dep ?: return false
val behavior = dep.coordinatorLayoutBehavior as NeoBottomSheetBehavior
val consumed = behavior.calculateConsumedByBar()
if (consumed == Int.MIN_VALUE) {
return false
}
measureContent(parent, child, consumed)
return true
} }
override fun onLayoutChild(parent: CoordinatorLayout, child: V, layoutDirection: Int): Boolean { override fun onLayoutChild(parent: CoordinatorLayout, child: V, layoutDirection: Int): Boolean {
super.onLayoutChild(parent, child, layoutDirection) super.onLayoutChild(parent, child, layoutDirection)
child.layout(0, 0, child.measuredWidth, child.measuredHeight) layoutContent(child)
if (!setup) { if (!setup) {
child.setOnApplyWindowInsetsListener { _, insets ->
child.setOnApplyWindowInsetsListener { v, insets ->
lastInsets = insets lastInsets = insets
val dep = dep ?: return@setOnApplyWindowInsetsListener insets val dep = dep ?: return@setOnApplyWindowInsetsListener insets
val bars = insets.systemBarInsetsCompat
val behavior = dep.coordinatorLayoutBehavior as NeoBottomSheetBehavior val behavior = dep.coordinatorLayoutBehavior as NeoBottomSheetBehavior
val consumed = behavior.calculateConsumedByBar()
val offset = behavior.calculateSlideOffset() if (consumed == Int.MIN_VALUE) {
if (behavior.peekHeight < 0 || offset == Float.MIN_VALUE) {
return@setOnApplyWindowInsetsListener insets return@setOnApplyWindowInsetsListener insets
} }
val adjustedBottomInset = val bars = insets.systemBarInsetsCompat
(bars.bottom - behavior.calculateConsumedByBar()).coerceAtLeast(0)
insets.replaceSystemBarInsetsCompat( insets.replaceSystemBarInsetsCompat(
bars.left, bars.top, bars.right, adjustedBottomInset) bars.left, bars.top, bars.right, (bars.bottom - consumed).coerceAtLeast(0))
} }
setup = true setup = true
@ -76,27 +83,28 @@ class BottomSheetContentBehavior<V : View>(context: Context, attributeSet: Attri
return true return true
} }
private fun measureContent(parent: View, child: View, dep: View): Boolean { private fun measureContent(parent: View, child: View, consumed: Int) {
val behavior = dep.coordinatorLayoutBehavior as NeoBottomSheetBehavior
val offset = behavior.calculateSlideOffset()
if (behavior.peekHeight < 0 || offset == Float.MIN_VALUE) {
return false
}
val contentWidthSpec = val contentWidthSpec =
View.MeasureSpec.makeMeasureSpec(parent.measuredWidth, View.MeasureSpec.EXACTLY) View.MeasureSpec.makeMeasureSpec(parent.measuredWidth, View.MeasureSpec.EXACTLY)
val contentHeightSpec = val contentHeightSpec =
View.MeasureSpec.makeMeasureSpec( View.MeasureSpec.makeMeasureSpec(
parent.measuredHeight - behavior.calculateConsumedByBar(), View.MeasureSpec.EXACTLY) parent.measuredHeight - consumed, View.MeasureSpec.EXACTLY)
child.measure(contentWidthSpec, contentHeightSpec) child.measure(contentWidthSpec, contentHeightSpec)
}
return true private fun layoutContent(child: View) {
logD("Measure")
child.layout(0, 0, child.measuredWidth, child.measuredHeight)
} }
private fun NeoBottomSheetBehavior<*>.calculateConsumedByBar(): Int { private fun NeoBottomSheetBehavior<*>.calculateConsumedByBar(): Int {
val offset = calculateSlideOffset() val offset = calculateSlideOffset()
if (offset == Float.MIN_VALUE || peekHeight < 0) {
return Int.MIN_VALUE
}
return if (offset >= 0) { return if (offset >= 0) {
peekHeight peekHeight
} else { } else {
@ -118,13 +126,30 @@ class BottomSheetContentBehavior<V : View>(context: Context, attributeSet: Attri
child: V, child: V,
dependency: View dependency: View
): Boolean { ): Boolean {
logD("Dependent view changed $child")
val behavior = dependency.coordinatorLayoutBehavior as NeoBottomSheetBehavior val behavior = dependency.coordinatorLayoutBehavior as NeoBottomSheetBehavior
if (behavior.calculateSlideOffset() > 0) { val consumed = behavior.calculateConsumedByBar()
if (consumed < Int.MIN_VALUE) {
return false return false
} }
if (consumed != lastConsumed) {
logD("Dependent view changed important $child")
lastConsumed = consumed
val insets = lastInsets
if (insets != null) {
child.dispatchApplyWindowInsets(insets)
}
lastInsets?.let(child::dispatchApplyWindowInsets) lastInsets?.let(child::dispatchApplyWindowInsets)
return measureContent(parent, child, dependency) && measureContent(parent, child, consumed)
onLayoutChild(parent, child, parent.layoutDirection) layoutContent(child)
return true
}
return false
} }
} }

View file

@ -36,11 +36,11 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
init { init {
// Prevent children from being clipped by window insets // Prevent children from being clipped by window insets
clipToPadding = false clipToPadding = false
setHasFixedSize(true)
} }
override fun onAttachedToWindow() { final override fun setHasFixedSize(hasFixedSize: Boolean) {
super.onAttachedToWindow() super.setHasFixedSize(hasFixedSize)
setHasFixedSize(true)
} }
override fun onApplyWindowInsets(insets: WindowInsets): WindowInsets { override fun onApplyWindowInsets(insets: WindowInsets): WindowInsets {