search: improve ui

Improve the search UI by making it edge-to-edge and adding the
liftOnScroll idiom. It does come with the caveat of walking on
eggshells to get the liftOnScroll code working, but its okay. It
may be improved in the future.
This commit is contained in:
OxygenCobalt 2021-08-29 20:20:25 -06:00
parent e142c17fca
commit a4d2a8d48c
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
6 changed files with 53 additions and 16 deletions

View file

@ -22,6 +22,7 @@ import android.os.Bundle
import android.view.View import android.view.View
import androidx.activity.OnBackPressedCallback import androidx.activity.OnBackPressedCallback
import androidx.annotation.MenuRes import androidx.annotation.MenuRes
import androidx.core.view.updatePadding
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
@ -30,6 +31,7 @@ import androidx.recyclerview.widget.RecyclerView
import org.oxycblt.auxio.databinding.FragmentDetailBinding import org.oxycblt.auxio.databinding.FragmentDetailBinding
import org.oxycblt.auxio.playback.PlaybackViewModel import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.ui.memberBinding import org.oxycblt.auxio.ui.memberBinding
import org.oxycblt.auxio.util.applyEdge
import org.oxycblt.auxio.util.isLandscape import org.oxycblt.auxio.util.isLandscape
/** /**
@ -42,6 +44,10 @@ abstract class DetailFragment : Fragment() {
protected val binding by memberBinding(FragmentDetailBinding::inflate) protected val binding by memberBinding(FragmentDetailBinding::inflate)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
binding.applyEdge { bars ->
binding.detailAppbar.updatePadding(top = bars.top)
}
requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner, callback) requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner, callback)
} }

View file

@ -29,7 +29,6 @@ import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import androidx.viewpager2.adapter.FragmentStateAdapter import androidx.viewpager2.adapter.FragmentStateAdapter
import androidx.viewpager2.widget.ViewPager2 import androidx.viewpager2.widget.ViewPager2
import com.google.android.material.appbar.AppBarLayout
import com.google.android.material.tabs.TabLayoutMediator import com.google.android.material.tabs.TabLayoutMediator
import org.oxycblt.auxio.MainFragmentDirections import org.oxycblt.auxio.MainFragmentDirections
import org.oxycblt.auxio.R import org.oxycblt.auxio.R
@ -44,6 +43,7 @@ import org.oxycblt.auxio.ui.DisplayMode
import org.oxycblt.auxio.util.applyEdge import org.oxycblt.auxio.util.applyEdge
import org.oxycblt.auxio.util.logD import org.oxycblt.auxio.util.logD
import org.oxycblt.auxio.util.logE import org.oxycblt.auxio.util.logE
import org.oxycblt.auxio.util.makeScrollingViewFade
/** /**
* The main "Launching Point" fragment of Auxio, allowing navigation to the detail * The main "Launching Point" fragment of Auxio, allowing navigation to the detail
@ -74,16 +74,7 @@ class HomeFragment : Fragment() {
binding.homeAppbar.updatePadding(top = bars.top) binding.homeAppbar.updatePadding(top = bars.top)
} }
// There is basically no way to prevent the toolbar to draw under the status bar when binding.homeAppbar.makeScrollingViewFade(binding.homeToolbar)
// it collapses, so do the next best thing and fade it out so it doesn't stick out like
// a sore thumb. As a side effect, this looks really cool.
binding.homeAppbar.addOnOffsetChangedListener(
AppBarLayout.OnOffsetChangedListener { _, verticalOffset ->
binding.homeToolbar.apply {
alpha = (height + verticalOffset) / height.toFloat()
}
}
)
binding.homeToolbar.setOnMenuItemClickListener { item -> binding.homeToolbar.setOnMenuItemClickListener { item ->
when (item.itemId) { when (item.itemId) {

View file

@ -27,6 +27,7 @@ import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.ItemTouchHelper
import kotlinx.coroutines.NonDisposableHandle.parent
import org.oxycblt.auxio.R import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.FragmentQueueBinding import org.oxycblt.auxio.databinding.FragmentQueueBinding
import org.oxycblt.auxio.music.BaseModel import org.oxycblt.auxio.music.BaseModel
@ -105,6 +106,7 @@ class QueueFragment : Fragment() {
lastShuffle = isShuffling lastShuffle = isShuffling
binding.queueRecycler.scrollToPosition(0) binding.queueRecycler.scrollToPosition(0)
binding.queueAppbar.isLifted = false // Make sure lifted state changes.
} }
} }

View file

@ -24,6 +24,7 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.inputmethod.InputMethodManager import android.view.inputmethod.InputMethodManager
import androidx.core.view.postDelayed import androidx.core.view.postDelayed
import androidx.core.view.updatePadding
import androidx.core.widget.addTextChangedListener import androidx.core.widget.addTextChangedListener
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
@ -42,9 +43,11 @@ import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.playback.PlaybackViewModel import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.ui.DisplayMode import org.oxycblt.auxio.ui.DisplayMode
import org.oxycblt.auxio.ui.newMenu import org.oxycblt.auxio.ui.newMenu
import org.oxycblt.auxio.util.applyEdge
import org.oxycblt.auxio.util.applySpans import org.oxycblt.auxio.util.applySpans
import org.oxycblt.auxio.util.getSystemServiceSafe import org.oxycblt.auxio.util.getSystemServiceSafe
import org.oxycblt.auxio.util.logD import org.oxycblt.auxio.util.logD
import org.oxycblt.auxio.util.makeScrollingViewFade
/** /**
* A [Fragment] that allows for the searching of the entire music library. * A [Fragment] that allows for the searching of the entire music library.
@ -79,6 +82,12 @@ class SearchFragment : Fragment() {
binding.lifecycleOwner = viewLifecycleOwner binding.lifecycleOwner = viewLifecycleOwner
binding.applyEdge { bars ->
binding.searchAppbar.updatePadding(top = bars.top)
}
binding.searchAppbar.makeScrollingViewFade(binding.searchToolbar)
binding.searchToolbar.apply { binding.searchToolbar.apply {
val itemId = when (searchModel.filterMode) { val itemId = when (searchModel.filterMode) {
DisplayMode.SHOW_SONGS -> R.id.option_filter_songs DisplayMode.SHOW_SONGS -> R.id.option_filter_songs
@ -131,18 +140,18 @@ class SearchFragment : Fragment() {
searchModel.searchResults.observe(viewLifecycleOwner) { results -> searchModel.searchResults.observe(viewLifecycleOwner) { results ->
searchAdapter.submitList(results) { searchAdapter.submitList(results) {
// We've just scrolled back to the top, reset the lifted state
// TODO: Maybe find a better way to keep scroll state when the search
// results didn't actually change.
binding.searchRecycler.scrollToPosition(0) binding.searchRecycler.scrollToPosition(0)
binding.searchAppbar.isLifted = false
} }
if (results.isEmpty()) { if (results.isEmpty()) {
// If the data is empty, then the ability for the toolbar to collapse
// on scroll should be disabled.
binding.searchAppbar.setExpanded(true) binding.searchAppbar.setExpanded(true)
binding.searchRecycler.visibility = View.GONE binding.searchRecycler.visibility = View.GONE
toolbarParams.scrollFlags = AppBarLayout.LayoutParams.SCROLL_FLAG_NO_SCROLL
} else { } else {
binding.searchRecycler.visibility = View.VISIBLE binding.searchRecycler.visibility = View.VISIBLE
toolbarParams.scrollFlags = defaultParams
} }
} }

View file

@ -32,11 +32,13 @@ import androidx.annotation.AttrRes
import androidx.annotation.ColorInt import androidx.annotation.ColorInt
import androidx.annotation.ColorRes import androidx.annotation.ColorRes
import androidx.annotation.DrawableRes import androidx.annotation.DrawableRes
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.view.updatePadding import androidx.core.view.updatePadding
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import androidx.viewbinding.ViewBinding import androidx.viewbinding.ViewBinding
import com.google.android.material.appbar.AppBarLayout
import org.oxycblt.auxio.R import org.oxycblt.auxio.R
/** /**
@ -140,6 +142,33 @@ fun @receiver:AttrRes Int.resolveAttr(context: Context): Int {
return color.resolveColor(context) return color.resolveColor(context)
} }
/**
* Make this [AppBarLayout] fade a scrolling [view] out when it collapses.
* This is mostly because I am unable to figure out how to get a collapsing view not
* to draw under the status bar in edge-to-edge mode.
*/
fun AppBarLayout.makeScrollingViewFade(view: View) {
addOnOffsetChangedListener(
AppBarLayout.OnOffsetChangedListener { _, verticalOffset ->
view.alpha = (view.height + verticalOffset) / view.height.toFloat()
}
)
}
/**
* Force-update this [AppBarLayout]'s lifted state. This is useful when the dataset changes
* and the lifted state must be updated.
*/
fun AppBarLayout.updateLiftedState(recycler: RecyclerView) {
post {
val coordinator = (parent as CoordinatorLayout)
(layoutParams as CoordinatorLayout.LayoutParams).behavior?.onNestedPreScroll(
coordinator, this, recycler, 0, 0, IntArray(2), 0
)
}
}
/** /**
* Apply edge-to-edge tweaks to the root of a [ViewBinding]. * Apply edge-to-edge tweaks to the root of a [ViewBinding].
* @param onApply What to do when the system bar insets are provided * @param onApply What to do when the system bar insets are provided

View file

@ -12,7 +12,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="?attr/colorSurface" android:background="?attr/colorSurface"
android:elevation="@dimen/elevation_normal"> app:liftOnScroll="true">
<androidx.appcompat.widget.Toolbar <androidx.appcompat.widget.Toolbar
android:id="@+id/search_toolbar" android:id="@+id/search_toolbar"