Android CollapsingToolbar/ AppBarLayout 在状态栏行为顶部填充后面滚动

Android CollapsingToolbar/ AppBarLayout scroll behind status bar behaviour top padding

我想使用 CoordinatoryLayoutAppBarLayoutCollapsingToolbarLayout 来创建类似于以下来自 Google 日历示例的布局。

我要复制的关键内容:

问题 Google 当用户滚动时,日历似乎会增大滚动容器。我将如何着手做这个或类似的事情来实现我所追求的外观?

我整理了一个我正在尝试构建的示例:

activity_scrolling.xml

<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:animateLayoutChanges="true"
    tools:context="uk.co.exampleapplication.ScrollingActivity">

    <com.google.android.material.appbar.AppBarLayout
        android:id="@+id/app_bar_layout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <com.google.android.material.appbar.CollapsingToolbarLayout
            android:id="@+id/collapsing_toolbar"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:layout_scrollFlags="scroll|exitUntilCollapsed">

            <include
                android:id="@+id/lay_header"
                layout="@layout/layout_header" />

        </com.google.android.material.appbar.CollapsingToolbarLayout>

        <androidx.constraintlayout.widget.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="@drawable/scroll_header_background"
            android:elevation="16dp"
            android:paddingBottom="12dp">

            <TextView
                android:id="@+id/sectionTitleText"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginStart="16dp"
                android:layout_marginTop="32dp"
                android:text="Title"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toTopOf="parent" />

            <Button
                android:id="@+id/filter_button"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="22dp"
                android:layout_marginEnd="16dp"
                android:text="Button"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintTop_toTopOf="parent" />

        </androidx.constraintlayout.widget.ConstraintLayout>

    </com.google.android.material.appbar.AppBarLayout>

    <androidx.core.widget.NestedScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior"
        tools:context=".ScrollingActivity"
        tools:showIn="@layout/activity_scrolling">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_margin="@dimen/text_margin"
            android:text="@string/large_text" />

    </androidx.core.widget.NestedScrollView>

</androidx.coordinatorlayout.widget.CoordinatorLayout>

layout_header.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/header"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    android:paddingHorizontal="16dp"
    android:paddingTop="60dp"
    android:paddingBottom="40dp"
    app:layout_collapseMode="parallax"
    tools:ignore="HardcodedText">

    <TextView
        android:id="@+id/title"
        android:layout_width="match_parent"
        android:layout_height="24dp"
        android:layout_marginTop="8dp"
        android:layout_marginEnd="16dp"
        android:text="Title"
        android:textColor="#FFF"
        android:textSize="20sp"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/subtitle"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Subtitle"
        android:textColor="#FFF"
        android:textSize="16sp"
        app:layout_constraintHorizontal_bias="0.0"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/title" />

</androidx.constraintlayout.widget.ConstraintLayout>

scroll_header_background.xml

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">
    <corners
        android:bottomLeftRadius="0dp"
        android:bottomRightRadius="0dp"
        android:topLeftRadius="20dp"
        android:topRightRadius="20dp" />
    <solid android:color="#FFFFFF" />
    <size
        android:width="64dp"
        android:height="64dp" />
</shape>

我的尝试包含在下面。 header 根据需要在工具栏后面滚动,但我希望在我的视图上方有一些额外的顶部填充(大约顶部插图/状态栏的高度就足够了)。 Google 日历似乎通过让容器随着用户滚动而增长来解决这个问题。

实施一个 AppBarLayout.OnOffsetChangedListener 来调整 ConstraintLayout 上的顶部填充,它包含 TextView按钮。 (将此视图称为 "viewToGrow"。)您还可以在侦听器中执行其他操作,例如在应用栏滚动时更改可绘制对象的角半径。

以下示例调整顶部内边距,为页眉留出更多空间。此填充随着应用栏向上滚动而增加,并随着向下滚动而减少。演示应用程序还在应用栏滚动的最后 15% 期间删除了可绘制对象的角半径。

滚动活动

class ScrollingActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_scrolling)

        val viewToGrow: View = findViewById(R.id.viewToGrow)
        val baseTopPadding = viewToGrow.paddingTop

        // Determine how much top padding has to grow while the app bar scrolls.
        var maxDeltaPadding = 0
        val contentView = findViewById<View>(android.R.id.content)
        ViewCompat.setOnApplyWindowInsetsListener(contentView) { _, insets ->
            maxDeltaPadding = insets.systemWindowInsetTop
            insets
        }

        // Get key metrics for corner radius shrikage.
        var backgroundRadii: FloatArray? = null
        var maxRadius: FloatArray? = null
        val backgroundDrawable = (viewToGrow.background as GradientDrawable?)
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && backgroundDrawable != null) {
            backgroundRadii = backgroundDrawable.cornerRadii
            maxRadius = floatArrayOf(backgroundRadii!![0], backgroundRadii[1])
        }

        // Set up the app bar and the offset change listener.
        val appBar: AppBarLayout = findViewById(R.id.app_bar_layout)
        val appBarTotalScrollRange: Float by lazy {
            appBar.totalScrollRange.toFloat()
        }
        appBar.addOnOffsetChangedListener(AppBarLayout.OnOffsetChangedListener { _, verticalOffset ->
            // Add/remove padding gradually as the appbar scrolls.
            val percentOfScrollRange = (-verticalOffset / appBarTotalScrollRange)
            val deltaPadding = maxDeltaPadding * percentOfScrollRange
            val newTopPadding = baseTopPadding + deltaPadding.toInt()
            if (newTopPadding != viewToGrow.paddingTop) {
                viewToGrow.setPadding(
                    viewToGrow.paddingLeft,
                    newTopPadding,
                    viewToGrow.paddingRight,
                    viewToGrow.paddingBottom
                )
                // Change the drawable radius as the appbar scrolls.
                if (backgroundRadii != null && maxRadius != null) {
                    val radiusShrinkage = if (percentOfScrollRange > (1.0f - CORNER_SHRINK_RANGE)) {
                        (1.0f - percentOfScrollRange) / CORNER_SHRINK_RANGE
                    } else {
                        1.0f
                    }
                    backgroundRadii[0] = maxRadius[0] * radiusShrinkage
                    backgroundRadii[1] = maxRadius[1] * radiusShrinkage
                    backgroundRadii[2] = maxRadius[0] * radiusShrinkage
                    backgroundRadii[3] = maxRadius[1] * radiusShrinkage
                    backgroundDrawable!!.cornerRadii = backgroundRadii
                }
            }
        })
    }

    companion object {
        const val CORNER_SHRINK_RANGE = 0.15f
    }
}