Статический нижний колонтитул на нижнем листе Android

Я пытаюсь создать статический нижний колонтитул на нижнем листе Android. Ниже представлена ​​гифка с моей текущей попыткой. Обратите внимание на текст footer внизу листа, когда он полностью развернут. Я хочу, чтобы этот текст нижнего колонтитула всегда отображался независимо от того, в каком состоянии находится лист (например, если лист развернут только наполовину, нижний колонтитул все равно должен отображаться. Поскольку лист разворачивается / сворачивается, нижний колонтитул должен всегда отображаться и т. д.) Как я могу добиться такого поведения? Я видел это: https://github.com/umano/AndroidSlidingUpPanel, но я бы предпочел чтобы получить ванильное решение для Android, не использующее никаких внешних библиотек.

введите описание изображения здесь

Вот мой текущий XML. Я думаю, проблема в том, что нижний колонтитул привязан к нижней части представления, но нижняя часть представления видна только в том случае, если лист полностью развернут. Я хочу посмотреть, смогу ли я отобразить нижний колонтитул независимо от состояния листа:

<?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>

Вот как выглядит мой фрагмент. Это просто пустой фрагмент, который показывает нижний лист: 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()
    }
}

Вот мой макет фрагмента: 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>

person Ryan    schedule 08.10.2019    source источник
comment
Мы сталкиваемся с той же ПРОБЛЕМОЙ, вы нашли какое-нибудь решение?   -  person droidster.me    schedule 08.11.2019
comment
В итоге мы использовали Dialog вместо нижнего листа. На Dialogs действительно легко сохранить статический нижний колонтитул. Хотелось бы узнать, возможно ли это на нижних листах.   -  person Ryan    schedule 09.11.2019


Ответы (2)


Это нужно делать программно, но это действительно проще, чем кажется. Просто отрегулируйте положение представления при скольжении BottomSheet.

  1. Добавьте представление нижнего колонтитула как прямой дочерний элемент вашего BottomSheet
<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>
  1. Добавьте BottomSheetCallback и настройте translationY в 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) {
        }
    })

Это работает гладко, потому что вы просто меняете переводY.

Вы также можете использовать этот метод, чтобы закрепить View в центре BottomSheet:

pinned_center.translationY = (bottomSheetVisibleHeight - pinned_center.height) / 2f

Я сделал образец проекта на GitHub, чтобы воспроизвести оба варианта использования (закрепленные по центру и внизу): https://github.com/dadouf/BottomSheetGravity.

Демо

person David Ferrand    schedule 05.06.2020
comment
Это не работает для прокручиваемого содержимого - если у вас есть recyclerview или scrollview внутри нижнего листа. - person Максим Петлюк; 25.08.2020
comment
У вас есть какое-нибудь решение для прокручиваемого контента? У меня такая же проблема - person geek919; 27.08.2020
comment
для прокручиваемого содержимого я использовал setY() вместо setTranslationY(), и это сработало как шарм. - person amira; 20.10.2020
comment
@ МаксимПетлюк Идея в том, что закрепленные представления должны находиться за пределами вашего адаптера RecyclerView или ScrollView. Например: сделайте нижний лист FrameView, добавьте как контейнер для прокрутки, так и закрепленный вид в качестве дочерних элементов. - person David Ferrand; 21.10.2020
comment
@DavidFerrand есть ли возможность использовать внутри BottomSheetDialogFragment? - person Xan; 16.06.2021
comment
@Xan Я не тестировал, но BottomSheetDialogFragment показывает BottomSheetDialog. Я вижу из doc, что BottomSheetDialog имеет setContentView и getBehavior, чтобы вы могли следовать тому же подходу, что и в моем сообщении: установить настраиваемую ViewGroup, которая включает в себя представление нижнего колонтитула, и анимировать его, используя прослушиватель поведения. - person David Ferrand; 17.06.2021
comment
@DavidFerrand это работает, но мне нужно вручную вызвать onSlide внутри oncreate. я не знаю почему. тебе это не нужно? - person Xan; 17.06.2021

@DavidFerrand на основе 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) }
})
person Xan    schedule 17.06.2021