I am trying to make a static footer on a bottomsheet on Android. Below is a GIF of my current attempt. Notice the footer
text at the bottom of the sheet when it is fully expanded. I want this footer text to always show regardless of what state the sheet is in. (e.g. if the sheet is only half expanded, the footer should still show. As the sheet is being expanded/collapsed, the footer should still always show etc.) How can I accomplish this behavior? I have had a look at this: https://github.com/umano/AndroidSlidingUpPanel but I would prefer to get a vanilla android solution that does not involve any external libraries.
Here is my current XML. I think the problem is the footer is anchored to the bottom of the view, but the bottom of the view is only visible if the sheet is fully expanded. I want to see if I can get the footer to show regardless of the state of the sheet:
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout
xmlns:airbnb="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:focusableInTouchMode="true"
android:orientation="vertical"
android:id="@+id/linear_layout_container">
<TextView
android:id="@+id/header_text"
android:layout_width="match_parent"
android:text="Header"
android:layout_height="36dp"
android:layout_alignParentTop="true"
/>
<com.airbnb.epoxy.EpoxyRecyclerView
android:id="@+id/bottom_sheet_recycler_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
airbnb:layoutManager="LinearLayoutManager"
android:layout_above="@+id/footer_text"
android:layout_below="@+id/header_text"
/>
<TextView
android:id="@+id/footer_text"
android:layout_width="match_parent"
android:text="Footer"
android:layout_height="36dp"
android:layout_alignParentBottom="true"
airbnb:layout_anchorGravity="bottom"
/>
</RelativeLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
Here is what my fragment looks like. It is just a blank fragment that shows a bottom sheet:
ContainerFragment.kt
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.LinearLayoutManager
import com.airbnb.epoxy.EpoxyRecyclerView
import com.google.android.material.bottomsheet.BottomSheetDialog
class ContainerFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.container_fragment_layout, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
view.alpha = 0f
val bottomSheet = BottomSheetDialog(requireContext())
val bottomSheetView = LayoutInflater.from(context).inflate(R.layout.test_bottom_sheet_layout, null)
bottomSheet.setContentView(bottomSheetView)
bottomSheet.show()
val epoxyRecyclerView : EpoxyRecyclerView = bottomSheetView.findViewById(R.id.bottom_sheet_recycler_view)
epoxyRecyclerView.layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
epoxyRecyclerView.withModels {
(0..100).forEach {
basicRow {
id(it)
title("This is the entry at index $it.")
}
}
}
}
companion object {
fun newInstance() = ContainerFragment()
}
}
Here is my fragment layout:
container_fragment_layout.xml
:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
</LinearLayout>
You need to do it programmatically but it's really simpler than it looks. Simply adjust the position of the View as the BottomSheet slides.
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:id="@+id/bottom_sheet"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#EECCCCCC"
app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior">
<TextView
android:id="@+id/pinned_bottom"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#500000FF"
android:padding="16dp"
android:text="Bottom" />
</FrameLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
BottomSheetCallback
and adjust the translationY
in onSlide()BottomSheetBehavior.from(bottom_sheet).addBottomSheetCallback(
object : BottomSheetBehavior.BottomSheetCallback() {
override fun onSlide(bottomSheet: View, slideOffset: Float) {
val bottomSheetVisibleHeight = bottomSheet.height - bottomSheet.top
pinned_bottom.translationY =
(bottomSheetVisibleHeight - pinned_bottom.height).toFloat()
}
override fun onStateChanged(bottomSheet: View, newState: Int) {
}
})
It runs smoothly because you're simply changing the translationY.
You can also use this technique to pin a View in the center of the BottomSheet:
pinned_center.translationY = (bottomSheetVisibleHeight - pinned_center.height) / 2f
I've made a sample project on GitHub to reproduce both use cases (pinned to center and bottom): https://github.com/dadouf/BottomSheetGravity
@DavidFerrand based using insead BottomSheetDialogFragment
val behavior: BottomSheetBehavior<*> = (dialog as BottomSheetDialog).behavior
behavior.addBottomSheetCallback(object : BottomSheetBehavior.BottomSheetCallback() {
override fun onStateChanged(bottomSheet: View, newState: Int) {}
override fun onSlide(bottomSheet: View, slideOffset: Float) {
binding.bottom.y = ((bottomSheet.parent as View).height - bottomSheet.top - binding.bottom.height).toFloat()
}
}.apply {
binding.root.post { onSlide(binding.root.parent as View, 0f) }
})
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With