playback: make bottom sheet behavior more in-spec

Don't gradually fade out until the very end, reduce the corner radii
at the very end, fix elevation, delift elevation at the very end.

More tweaks are probably needed here to make it look good.
This commit is contained in:
Alexander Capehart 2024-04-19 11:04:24 -06:00
parent fc90d460dc
commit 9990e00a4a
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
7 changed files with 57 additions and 31 deletions

View file

@ -26,13 +26,13 @@ import androidx.activity.OnBackPressedCallback
import androidx.core.view.ViewCompat import androidx.core.view.ViewCompat
import androidx.core.view.isInvisible import androidx.core.view.isInvisible
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.core.view.updatePadding
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import androidx.navigation.findNavController import androidx.navigation.findNavController
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import com.google.android.material.R as MR import com.google.android.material.R as MR
import com.google.android.material.bottomsheet.BackportBottomSheetBehavior import com.google.android.material.bottomsheet.BackportBottomSheetBehavior
import com.google.android.material.shape.MaterialShapeDrawable import com.google.android.material.shape.MaterialShapeDrawable
import com.google.android.material.shape.ShapeAppearanceModel
import com.google.android.material.transition.MaterialFadeThrough import com.google.android.material.transition.MaterialFadeThrough
import com.leinardi.android.speeddial.SpeedDialOverlayLayout import com.leinardi.android.speeddial.SpeedDialOverlayLayout
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
@ -62,7 +62,6 @@ import org.oxycblt.auxio.util.getDimen
import org.oxycblt.auxio.util.lazyReflectedField import org.oxycblt.auxio.util.lazyReflectedField
import org.oxycblt.auxio.util.logD import org.oxycblt.auxio.util.logD
import org.oxycblt.auxio.util.navigateSafe import org.oxycblt.auxio.util.navigateSafe
import org.oxycblt.auxio.util.systemBarInsetsCompat
import org.oxycblt.auxio.util.unlikelyToBeNull import org.oxycblt.auxio.util.unlikelyToBeNull
/** /**
@ -84,6 +83,7 @@ class MainFragment :
private var selectionNavigationListener: DialogAwareNavigationListener? = null private var selectionNavigationListener: DialogAwareNavigationListener? = null
private var lastInsets: WindowInsets? = null private var lastInsets: WindowInsets? = null
private var elevationNormal = 0f private var elevationNormal = 0f
private var normalCornerSize = 0f
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -146,16 +146,22 @@ class MainFragment :
// Emulate the elevated bottom sheet style. // Emulate the elevated bottom sheet style.
background = background =
MaterialShapeDrawable.createWithElevationOverlay(context).apply { MaterialShapeDrawable.createWithElevationOverlay(context).apply {
val cornerSize =
context.resources.getDimension(R.dimen.size_corners_mid_large)
shapeAppearanceModel =
ShapeAppearanceModel.builder()
.setTopLeftCornerSize(cornerSize)
.setTopRightCornerSize(cornerSize)
.build()
fillColor = context.getAttrColorCompat(MR.attr.colorSurfaceContainerHigh) fillColor = context.getAttrColorCompat(MR.attr.colorSurfaceContainerHigh)
} }
// Apply bar insets for the queue's RecyclerView to use.
setOnApplyWindowInsetsListener { v, insets ->
v.updatePadding(top = insets.systemBarInsetsCompat.top)
insets
}
} }
} }
normalCornerSize = playbackSheetBehavior.sheetBackgroundDrawable.topLeftCornerResolvedSize
binding.playbackSheet.elevation = 0f
binding.mainScrim.setOnClickListener { homeModel.setSpeedDialOpen(false) } binding.mainScrim.setOnClickListener { homeModel.setSpeedDialOpen(false) }
binding.sheetScrim.setOnClickListener { homeModel.setSpeedDialOpen(false) } binding.sheetScrim.setOnClickListener { homeModel.setSpeedDialOpen(false) }
@ -241,6 +247,14 @@ class MainFragment :
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 lastStretchRatio = max(playbackRatio - 0.9f, 0f) / 0.1f
binding.mainSheetScrim.alpha = lastStretchRatio
playbackSheetBehavior.sheetBackgroundDrawable.setCornerSize(
normalCornerSize * (1 - lastStretchRatio))
binding.exploreNavHost.isInvisible = playbackRatio == 1f
binding.playbackSheet.translationZ = (1 - lastStretchRatio) * elevationNormal
if (queueSheetBehavior != null) { if (queueSheetBehavior != null) {
// Queue sheet available, the normal transition applies, but it now much be combined // Queue sheet available, the normal transition applies, but it now much be combined
// with another transition where the playback panel disappears and the playback bar // with another transition where the playback panel disappears and the playback bar
@ -263,31 +277,16 @@ class MainFragment :
// No queue sheet, fade normally based on the playback sheet // No queue sheet, fade normally based on the playback sheet
binding.playbackBarFragment.alpha = 1 - halfOutRatio binding.playbackBarFragment.alpha = 1 - halfOutRatio
binding.playbackPanelFragment.alpha = halfInPlaybackRatio binding.playbackPanelFragment.alpha = halfInPlaybackRatio
(binding.queueSheet.background as MaterialShapeDrawable).shapeAppearanceModel =
ShapeAppearanceModel.builder()
.setTopLeftCornerSize(normalCornerSize)
.setTopRightCornerSize(normalCornerSize * (1 - lastStretchRatio))
.build()
} }
// Fade out the content as the playback panel expands.
// TODO: Replace with shadow?
binding.exploreNavHost.apply {
alpha = outPlaybackRatio
// Prevent interactions when the content fully fades out.
isInvisible = alpha == 0f
}
// Reduce playback sheet elevation as it expands. This involves both updating the
// shadow elevation for older versions, and fading out the background drawable
// containing the elevation overlay.
binding.playbackSheet.elevation = elevationNormal * outPlaybackRatio
playbackSheetBehavior.sheetBackgroundDrawable.alpha = (outPlaybackRatio * 255).toInt()
// Fade out the playback bar as the panel expands. // Fade out the playback bar as the panel expands.
binding.playbackBarFragment.apply { binding.playbackBarFragment.apply {
// Prevent interactions when the playback bar fully fades out. // Prevent interactions when the playback bar fully fades out.
isInvisible = alpha == 0f isInvisible = alpha == 0f
// As the playback bar expands, we also want to subtly translate the bar to
// align with the top inset. This results in both a smooth transition from the bar
// to the playback panel's toolbar, but also a correctly positioned playback bar
// for when the queue sheet expands.
lastInsets?.let { translationY = it.systemBarInsetsCompat.top * halfOutRatio }
} }
// Prevent interactions when the playback panel fully fades out. // Prevent interactions when the playback panel fully fades out.

View file

@ -23,12 +23,15 @@ import android.graphics.drawable.LayerDrawable
import android.util.AttributeSet import android.util.AttributeSet
import android.view.MotionEvent import android.view.MotionEvent
import android.view.View import android.view.View
import android.view.WindowInsets
import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.coordinatorlayout.widget.CoordinatorLayout
import com.google.android.material.R as MR import com.google.android.material.R as MR
import com.google.android.material.shape.MaterialShapeDrawable import com.google.android.material.shape.MaterialShapeDrawable
import org.oxycblt.auxio.R import org.oxycblt.auxio.R
import org.oxycblt.auxio.ui.BaseBottomSheetBehavior import org.oxycblt.auxio.ui.BaseBottomSheetBehavior
import org.oxycblt.auxio.util.getAttrColorCompat import org.oxycblt.auxio.util.getAttrColorCompat
import org.oxycblt.auxio.util.replaceSystemBarInsetsCompat
import org.oxycblt.auxio.util.systemBarInsetsCompat
/** /**
* The [BaseBottomSheetBehavior] for the playback bottom sheet. This bottom sheet * The [BaseBottomSheetBehavior] for the playback bottom sheet. This bottom sheet
@ -64,4 +67,14 @@ class PlaybackBottomSheetBehavior<V : View>(context: Context, attributeSet: Attr
fillColor = sheetBackgroundDrawable.fillColor fillColor = sheetBackgroundDrawable.fillColor
}, },
sheetBackgroundDrawable)) sheetBackgroundDrawable))
override fun applyWindowInsets(child: View, insets: WindowInsets): WindowInsets {
super.applyWindowInsets(child, insets)
// Offset our expanded panel by the size of the playback bar, as that is shown when
// we slide up the panel.
val bars = insets.systemBarInsetsCompat
expandedOffset = bars.top
return insets.replaceSystemBarInsetsCompat(
bars.left, bars.top, bars.right, expandedOffset + bars.bottom)
}
} }

View file

@ -87,7 +87,7 @@ class PlaybackPanelFragment :
// --- UI SETUP --- // --- UI SETUP ---
binding.root.setOnApplyWindowInsetsListener { view, insets -> binding.root.setOnApplyWindowInsetsListener { view, insets ->
val bars = insets.systemBarInsetsCompat val bars = insets.systemBarInsetsCompat
view.updatePadding(top = bars.top, bottom = bars.bottom) view.updatePadding(bottom = bars.bottom)
insets insets
} }

View file

@ -73,7 +73,7 @@ class QueueBottomSheetBehavior<V : View>(context: Context, attributeSet: Attribu
// Offset our expanded panel by the size of the playback bar, as that is shown when // Offset our expanded panel by the size of the playback bar, as that is shown when
// we slide up the panel. // we slide up the panel.
val bars = insets.systemBarInsetsCompat val bars = insets.systemBarInsetsCompat
expandedOffset = bars.top + barHeight + barSpacing expandedOffset = barHeight + barSpacing
return insets.replaceSystemBarInsetsCompat( return insets.replaceSystemBarInsetsCompat(
bars.left, bars.top, bars.right, expandedOffset + bars.bottom) bars.left, bars.top, bars.right, expandedOffset + bars.bottom)
} }

View file

@ -19,6 +19,13 @@
<View android:id="@+id/main_scrim" android:layout_height="match_parent" android:layout_width="match_parent"/> <View android:id="@+id/main_scrim" android:layout_height="match_parent" android:layout_width="match_parent"/>
<View
android:id="@+id/main_sheet_scrim"
android:background="?attr/colorSurfaceContainerLow"
android:layout_width="match_parent"
android:alpha="0"
android:layout_height="match_parent" />
<androidx.coordinatorlayout.widget.CoordinatorLayout <androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/playback_sheet" android:id="@+id/playback_sheet"
android:layout_width="match_parent" android:layout_width="match_parent"

View file

@ -23,6 +23,13 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" /> android:layout_height="match_parent" />
<View
android:id="@+id/main_sheet_scrim"
android:background="?attr/colorSurfaceContainerLow"
android:layout_width="match_parent"
android:alpha="0"
android:layout_height="match_parent" />
<androidx.coordinatorlayout.widget.CoordinatorLayout <androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/playback_sheet" android:id="@+id/playback_sheet"
android:layout_width="match_parent" android:layout_width="match_parent"
@ -47,7 +54,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:orientation="vertical" android:orientation="vertical"
style="@style/Widget.Auxio.DisableDropShadows" android:elevation="1dp"
app:layout_behavior="org.oxycblt.auxio.playback.queue.QueueBottomSheetBehavior"> app:layout_behavior="org.oxycblt.auxio.playback.queue.QueueBottomSheetBehavior">
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout

View file

@ -42,7 +42,7 @@
<dimen name="slider_thumb_height">24dp</dimen> <dimen name="slider_thumb_height">24dp</dimen>
<!-- Misc --> <!-- Misc -->
<dimen name="elevation_normal">6dp</dimen> <dimen name="elevation_normal">1dp</dimen>
<dimen name="fast_scroll_popup_min_width">78dp</dimen> <dimen name="fast_scroll_popup_min_width">78dp</dimen>
<dimen name="fast_scroll_popup_min_height">64dp</dimen> <dimen name="fast_scroll_popup_min_height">64dp</dimen>