Redo controls view
Redo the controls view to support a slide enter/exit animation, along with fixes to elevation problems on certain devices.
This commit is contained in:
parent
2b24d6661e
commit
a4801bdf2f
6 changed files with 164 additions and 65 deletions
|
@ -1,5 +1,6 @@
|
||||||
package org.oxycblt.auxio
|
package org.oxycblt.auxio
|
||||||
|
|
||||||
|
import android.animation.LayoutTransition
|
||||||
import android.content.res.ColorStateList
|
import android.content.res.ColorStateList
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
|
@ -9,6 +10,7 @@ import android.view.ViewGroup
|
||||||
import androidx.core.graphics.ColorUtils
|
import androidx.core.graphics.ColorUtils
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.activityViewModels
|
import androidx.fragment.app.activityViewModels
|
||||||
|
import androidx.interpolator.view.animation.FastOutSlowInInterpolator
|
||||||
import androidx.navigation.NavController
|
import androidx.navigation.NavController
|
||||||
import androidx.navigation.NavOptions
|
import androidx.navigation.NavOptions
|
||||||
import androidx.navigation.fragment.NavHostFragment
|
import androidx.navigation.fragment.NavHostFragment
|
||||||
|
@ -69,6 +71,13 @@ class MainFragment : Fragment() {
|
||||||
|
|
||||||
binding.lifecycleOwner = this
|
binding.lifecycleOwner = this
|
||||||
|
|
||||||
|
// Apply custom interpolation effects
|
||||||
|
// The container layout types differ in the land/port layouts so it has to be cast to their parent instead.
|
||||||
|
(binding.controlsContainer as ViewGroup).layoutTransition = LayoutTransition().apply {
|
||||||
|
setInterpolator(LayoutTransition.APPEARING, FastOutSlowInInterpolator())
|
||||||
|
setInterpolator(LayoutTransition.DISAPPEARING, FastOutSlowInInterpolator())
|
||||||
|
}
|
||||||
|
|
||||||
binding.navBar.apply {
|
binding.navBar.apply {
|
||||||
itemIconTintList = navTints
|
itemIconTintList = navTints
|
||||||
itemTextColor = navTints
|
itemTextColor = navTints
|
||||||
|
@ -150,7 +159,9 @@ class MainFragment : Fragment() {
|
||||||
if (song == null) {
|
if (song == null) {
|
||||||
logD("Hiding CompactPlaybackFragment since no song is being played.")
|
logD("Hiding CompactPlaybackFragment since no song is being played.")
|
||||||
|
|
||||||
if (!isLandscape(resources)) {
|
if (isLandscape(resources)) {
|
||||||
|
binding.compactPlayback.visibility = View.INVISIBLE
|
||||||
|
} else {
|
||||||
binding.compactPlayback.visibility = View.GONE
|
binding.compactPlayback.visibility = View.GONE
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -57,10 +57,6 @@ class CompactPlaybackFragment : Fragment() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (playbackModel.song.value == null) {
|
|
||||||
setInvisible(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- VIEWMODEL SETUP ---
|
// --- VIEWMODEL SETUP ---
|
||||||
|
|
||||||
playbackModel.song.observe(viewLifecycleOwner) {
|
playbackModel.song.observe(viewLifecycleOwner) {
|
||||||
|
@ -69,9 +65,6 @@ class CompactPlaybackFragment : Fragment() {
|
||||||
|
|
||||||
binding.song = it
|
binding.song = it
|
||||||
binding.playbackProgress.max = it.seconds.toInt()
|
binding.playbackProgress.max = it.seconds.toInt()
|
||||||
setInvisible(false)
|
|
||||||
} else {
|
|
||||||
setInvisible(true)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -117,20 +110,4 @@ class CompactPlaybackFragment : Fragment() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Set this fragment to be invisible, if needed. Only runs in landscape mode.
|
|
||||||
*/
|
|
||||||
private fun setInvisible(invisible: Boolean) {
|
|
||||||
// Does not run in landscape
|
|
||||||
if (!isLandscape(resources)) return
|
|
||||||
|
|
||||||
val visibility = if (invisible) View.INVISIBLE else View.VISIBLE
|
|
||||||
|
|
||||||
binding.playbackLayout.children.forEach {
|
|
||||||
it.visibility = visibility
|
|
||||||
}
|
|
||||||
|
|
||||||
binding.root.isEnabled = !invisible
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
98
app/src/main/java/org/oxycblt/auxio/ui/SlideLinearLayout.kt
Normal file
98
app/src/main/java/org/oxycblt/auxio/ui/SlideLinearLayout.kt
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
package org.oxycblt.auxio.ui
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.Canvas
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.LinearLayout
|
||||||
|
import org.oxycblt.auxio.logD
|
||||||
|
import org.oxycblt.auxio.logE
|
||||||
|
import java.lang.reflect.Field
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hack layout that fixes an issue where disappearing views would draw over non-disappearing
|
||||||
|
* views when animated with a stock LayoutTransition. Adapted from this StackOverflow answer:
|
||||||
|
* https://stackoverflow.com/a/35087229
|
||||||
|
*/
|
||||||
|
class SlideLinearLayout @JvmOverloads constructor(
|
||||||
|
context: Context,
|
||||||
|
attrs: AttributeSet? = null,
|
||||||
|
defStyleAttr: Int = -1
|
||||||
|
) : LinearLayout(context, attrs, defStyleAttr) {
|
||||||
|
private val disappearingChildrenField: Field? = try {
|
||||||
|
ViewGroup::class.java.getDeclaredField("mDisappearingChildren").also {
|
||||||
|
it.isAccessible = true
|
||||||
|
}
|
||||||
|
} catch (e: NoSuchFieldException) {
|
||||||
|
logE("Could not get mDisappearingChildren.")
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
private var disappearingChildren: List<View>? = null
|
||||||
|
|
||||||
|
private var dumpView: View? = null
|
||||||
|
private var doDrawingTrick: Boolean = false
|
||||||
|
|
||||||
|
init {
|
||||||
|
if (disappearingChildrenField != null) {
|
||||||
|
// Create a junk view and add it, which makes all the magic happen [I think??].
|
||||||
|
dumpView = View(context)
|
||||||
|
addView(dumpView, 0, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun dispatchDraw(canvas: Canvas?) {
|
||||||
|
doDrawingTrick = beforeDispatchDraw()
|
||||||
|
super.dispatchDraw(canvas)
|
||||||
|
|
||||||
|
if (doDrawingTrick) {
|
||||||
|
doDrawingTrick = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun drawChild(canvas: Canvas?, child: View?, drawingTime: Long): Boolean {
|
||||||
|
val children = getDisappearingChildren()
|
||||||
|
|
||||||
|
// I have no idea what this code does.
|
||||||
|
if (doDrawingTrick && children != null) {
|
||||||
|
if (child == dumpView) {
|
||||||
|
var more = false
|
||||||
|
children.forEach {
|
||||||
|
more = more or super.drawChild(canvas, it, drawingTime)
|
||||||
|
}
|
||||||
|
return more
|
||||||
|
} else if (children.contains(child)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.drawChild(canvas, child, drawingTime)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun beforeDispatchDraw(): Boolean {
|
||||||
|
val children = getDisappearingChildren()
|
||||||
|
|
||||||
|
if (children == null || children.isEmpty() || childCount <= 1) { // Junk view included
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
private fun getDisappearingChildren(): List<View>? {
|
||||||
|
if (disappearingChildrenField == null || disappearingChildren != null) {
|
||||||
|
return disappearingChildren
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there is no list of disappearing children yet, attempt to get it.
|
||||||
|
try {
|
||||||
|
disappearingChildren = disappearingChildrenField.get(this) as List<View>?
|
||||||
|
} catch (e: IllegalAccessException) {
|
||||||
|
logD("Could not get list of disappearing children.")
|
||||||
|
}
|
||||||
|
|
||||||
|
return disappearingChildren
|
||||||
|
}
|
||||||
|
}
|
|
@ -17,34 +17,41 @@
|
||||||
android:name="androidx.navigation.fragment.NavHostFragment"
|
android:name="androidx.navigation.fragment.NavHostFragment"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
app:layout_constraintBottom_toTopOf="@+id/compact_playback"
|
app:layout_constraintBottom_toTopOf="@+id/controls_container"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
app:navGraph="@navigation/nav_explore"
|
app:navGraph="@navigation/nav_explore"
|
||||||
tools:layout="@layout/fragment_library" />
|
tools:layout="@layout/fragment_library" />
|
||||||
|
|
||||||
<androidx.fragment.app.FragmentContainerView
|
<LinearLayout
|
||||||
android:id="@+id/compact_playback"
|
android:id="@+id/controls_container"
|
||||||
android:name="org.oxycblt.auxio.playback.CompactPlaybackFragment"
|
android:layout_width="match_parent"
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
android:background="?android:attr/windowBackground"
|
android:background="?android:attr/windowBackground"
|
||||||
android:elevation="@dimen/elevation_high"
|
android:baselineAligned="false"
|
||||||
android:outlineProvider="bounds"
|
android:animateLayoutChanges="true"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
android:elevation="@dimen/elevation_normal"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent">
|
||||||
app:layout_constraintStart_toEndOf="@+id/nav_bar"
|
|
||||||
tools:layout="@layout/fragment_compact_playback" />
|
|
||||||
|
|
||||||
<com.google.android.material.bottomnavigation.BottomNavigationView
|
<com.google.android.material.bottomnavigation.BottomNavigationView
|
||||||
android:id="@+id/nav_bar"
|
android:id="@+id/nav_bar"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="0dp"
|
||||||
android:layout_height="0dp"
|
android:layout_height="match_parent"
|
||||||
android:background="@color/background"
|
android:background="@color/background"
|
||||||
app:labelVisibilityMode="selected"
|
android:elevation="0dp"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:elevation="0dp"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
android:layout_weight="1.5"
|
||||||
app:layout_constraintTop_toTopOf="@+id/compact_playback"
|
app:menu="@menu/menu_nav" />
|
||||||
app:menu="@menu/menu_nav" />
|
|
||||||
|
<androidx.fragment.app.FragmentContainerView
|
||||||
|
android:id="@+id/compact_playback"
|
||||||
|
android:name="org.oxycblt.auxio.playback.CompactPlaybackFragment"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="2"
|
||||||
|
tools:layout="@layout/fragment_compact_playback" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
</layout>
|
</layout>
|
|
@ -17,33 +17,40 @@
|
||||||
android:name="androidx.navigation.fragment.NavHostFragment"
|
android:name="androidx.navigation.fragment.NavHostFragment"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
app:layout_constraintBottom_toTopOf="@+id/compact_playback"
|
app:layout_constraintBottom_toTopOf="@+id/controls_container"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
app:navGraph="@navigation/nav_explore"
|
app:navGraph="@navigation/nav_explore"
|
||||||
tools:layout="@layout/fragment_library" />
|
tools:layout="@layout/fragment_library" />
|
||||||
|
|
||||||
<androidx.fragment.app.FragmentContainerView
|
<org.oxycblt.auxio.ui.SlideLinearLayout
|
||||||
android:id="@+id/compact_playback"
|
android:id="@+id/controls_container"
|
||||||
android:name="org.oxycblt.auxio.playback.CompactPlaybackFragment"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
android:background="?android:attr/windowBackground"
|
android:background="?android:attr/windowBackground"
|
||||||
android:elevation="@dimen/elevation_high"
|
android:animateLayoutChanges="true"
|
||||||
android:outlineProvider="bounds"
|
android:elevation="@dimen/elevation_normal"
|
||||||
app:layout_constraintBottom_toTopOf="@+id/nav_bar"
|
app:layout_constraintBottom_toBottomOf="parent">
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
tools:layout="@layout/fragment_compact_playback" />
|
|
||||||
|
|
||||||
<com.google.android.material.bottomnavigation.BottomNavigationView
|
<androidx.fragment.app.FragmentContainerView
|
||||||
android:id="@+id/nav_bar"
|
android:id="@+id/compact_playback"
|
||||||
android:layout_width="match_parent"
|
android:name="org.oxycblt.auxio.playback.CompactPlaybackFragment"
|
||||||
android:layout_height="wrap_content"
|
android:layout_width="match_parent"
|
||||||
android:background="@color/background"
|
android:layout_height="wrap_content"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toTopOf="@+id/nav_bar"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
tools:layout="@layout/fragment_compact_playback" />
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:menu="@menu/menu_nav" />
|
|
||||||
|
|
||||||
|
<com.google.android.material.bottomnavigation.BottomNavigationView
|
||||||
|
android:id="@+id/nav_bar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="bottom"
|
||||||
|
android:background="@color/background"
|
||||||
|
android:elevation="0dp"
|
||||||
|
app:elevation="0dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:menu="@menu/menu_nav" />
|
||||||
|
|
||||||
|
</org.oxycblt.auxio.ui.SlideLinearLayout>
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
</layout>
|
</layout>
|
|
@ -49,7 +49,6 @@
|
||||||
</com.google.android.material.textfield.TextInputLayout>
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
</com.google.android.material.appbar.AppBarLayout>
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
|
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
android:id="@+id/search_recycler"
|
android:id="@+id/search_recycler"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
|
Loading…
Reference in a new issue