展开 CollapsingToolbarLayout 时折叠内容而不是滚动

Collapse content instead of scrolling when CollapsingToolbarLayout is expanded

我用 Android 的 CollapsingToolbarLayout 创建了一个简单的项目,这是它的结构(问题底部的完整布局源):

CoordinatorLayout
  AppBarLayout
    CollapsingToolbarLayout
      Toolbar
  RelativeLayout (layout_behavior="@string/appbar_scrolling_view_behavior")
    TextView (layout_alignParentTop="true")
    TextView (layout_alignParentBottom="true")

当我 expanding/collapsing 栏时,视图滚动并且底部视图超出屏幕:

我要实现的是collapsing/shrinking(而不是滚动)底部文本始终可见的内容(我的RelativeLayout):

如何在 Android 上完成?

这是我的完整布局:

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true">

    <android.support.design.widget.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="@dimen/app_bar_height"
        android:fitsSystemWindows="true"
        android:theme="@style/AppTheme.AppBarOverlay">

        <android.support.design.widget.CollapsingToolbarLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:fitsSystemWindows="true"
            app:contentScrim="?attr/colorPrimary"
            app:layout_scrollFlags="scroll|exitUntilCollapsed"
            app:toolbarId="@+id/toolbar">

            <android.support.v7.widget.Toolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                app:layout_collapseMode="pin"
                app:popupTheme="@style/AppTheme.PopupOverlay"/>

        </android.support.design.widget.CollapsingToolbarLayout>
    </android.support.design.widget.AppBarLayout>

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior">

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_alignParentTop="true"
            android:text="The text on the top"
            android:textSize="32sp"/>

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_alignParentBottom="true"
            android:text="The text on the bottom"
            android:textSize="32sp"/>
    </RelativeLayout>

 </android.support.design.widget.CoordinatorLayout>

不要将 RelativeLayout 与 layout_behavior="@string/appbar_scrolling_view_behavior" 一起使用,您应该选择 NestedScrollingView 或 RecyclerViw 以使一切按预期工作。

如上所述here

The scrolling content should be placed in a RecyclerView, NestedScrollView, or another view that supports nested scrolling.

考虑到您的布局,我想您需要将 RelativeLayout 放在 NestedScrollingView 中并将 layout_behavior 分配给 NestedScrollingView 并从 RelativeLayout 中删除 layout_behavior。

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true">

    <android.support.design.widget.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="@dimen/app_bar_height"
        android:fitsSystemWindows="true"
        android:theme="@style/AppTheme.AppBarOverlay">

        <android.support.design.widget.CollapsingToolbarLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:fitsSystemWindows="true"
            app:contentScrim="?attr/colorPrimary"
            app:layout_scrollFlags="scroll|exitUntilCollapsed"
            app:toolbarId="@+id/toolbar">

            <android.support.v7.widget.Toolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                app:layout_collapseMode="pin"
                app:popupTheme="@style/AppTheme.PopupOverlay"/>

        </android.support.design.widget.CollapsingToolbarLayout>
    </android.support.design.widget.AppBarLayout>

    <NestedScrollView android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior">
        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height= "match_parent">

            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_alignParentTop="true"
                android:text="The text on the top"
                android:textSize="32sp"/>

            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_alignParentBottom="true"
                android:text="The text on the bottom"
                android:textSize="32sp"/>
        </RelativeLayout>
    </NestedScrollView>

</android.support.design.widget.CoordinatorLayout>

编辑 2017 年 11 月 4 日 4:58PM

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:fitsSystemWindows="true">

    <android.support.design.widget.CoordinatorLayout
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:fitsSystemWindows="true">

        <android.support.design.widget.AppBarLayout
            android:layout_width="match_parent"
            android:layout_height="256dp"
            android:fitsSystemWindows="true"
            android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">

            <android.support.design.widget.CollapsingToolbarLayout
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:fitsSystemWindows="true"
                app:contentScrim="?attr/colorPrimary"
                app:layout_scrollFlags="scroll|exitUntilCollapsed"
                app:toolbarId="@+id/toolbar">

                <android.support.v7.widget.Toolbar
                    android:id="@+id/toolbar"
                    android:layout_width="match_parent"
                    android:layout_height="?attr/actionBarSize"
                    app:layout_collapseMode="pin"
                    app:popupTheme="@style/ThemeOverlay.AppCompat.Light"/>

            </android.support.design.widget.CollapsingToolbarLayout>
        </android.support.design.widget.AppBarLayout>


        <android.support.v4.widget.NestedScrollView android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:layout_behavior="@string/appbar_scrolling_view_behavior">

            <RelativeLayout
                android:layout_width="match_parent"
                android:layout_height="match_parent">

                <TextView
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_alignParentTop="true"
                    android:text="The text on the top"
                    android:textSize="32sp"/>

            </RelativeLayout>

        </android.support.v4.widget.NestedScrollView>

    </android.support.design.widget.CoordinatorLayout>

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="The text on the bottom"
        android:textSize="32sp"/>

</LinearLayout>

现在效果如下图

我认为您需要在 Relative Full 代码末尾关闭 Coordinator 布局。

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true">

    <android.support.design.widget.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="@dimen/app_bar_height"
        android:fitsSystemWindows="true"
        android:theme="@style/AppTheme.AppBarOverlay">

        <android.support.design.widget.CollapsingToolbarLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:fitsSystemWindows="true"
            app:contentScrim="?attr/colorPrimary"
            app:layout_scrollFlags="scroll|exitUntilCollapsed"
            app:toolbarId="@+id/toolbar">

            <android.support.v7.widget.Toolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                app:layout_collapseMode="pin"
                app:popupTheme="@style/AppTheme.PopupOverlay"/>

        </android.support.design.widget.CollapsingToolbarLayout>
    </android.support.design.widget.AppBarLayout>

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior">

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_alignParentTop="true"
            android:text="The text on the top"
            android:textSize="32sp"/>

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_alignParentBottom="true"
            android:text="The text on the bottom"
            android:textSize="32sp"/>
    </RelativeLayout>
</android.support.design.widget.CoordinatorLayout>

这里真正的问题是如何在应用栏更改高度时正确调整 RelativeLayout 的大小。在查看如何操作之前,我们需要更改 XML 布局。

来自documentation for AppBarLayout

AppBarLayout also requires a separate scrolling sibling in order to know when to scroll.

由于 RelativeLayout` 不符合 "scrolling sibling," 我们需要将其封装在 "NestedScrollView." 中,这是更新后的 XML 文件:

activity_main.xml

<android.support.design.widget.CoordinatorLayout 
    android:id="@+id/coordinatorLayoutProfile"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    tools:context=".MainActivity">

    <android.support.design.widget.AppBarLayout
        android:id="@+id/appBar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:fitsSystemWindows="true"
        android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">

        <android.support.design.widget.CollapsingToolbarLayout
            android:layout_width="match_parent"
            android:layout_height="220dp"
            android:background="@color/colorPrimary"
            app:layout_scrollFlags="scroll|exitUntilCollapsed">

            <android.support.v7.widget.Toolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="?android:attr/actionBarSize"
                app:layout_collapseMode="pin"
                app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />

        </android.support.design.widget.CollapsingToolbarLayout>

    </android.support.design.widget.AppBarLayout>

    <android.support.v4.widget.NestedScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior">

        <RelativeLayout
            android:id="@+id/relativeLayout"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="@android:color/holo_blue_light">

            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_alignParentTop="true"
                android:text="The text on the top"
                android:textSize="32sp" />

            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_alignParentBottom="true"
                android:text="The text on the bottom"
                android:textSize="32sp" />
        </RelativeLayout>
    </android.support.v4.widget.NestedScrollView>
</android.support.design.widget.CoordinatorLayout>

我可能对 XML 进行了一些其他更改以使其在我的环境中正常工作,但除了插入 NestedScrollView.

之外应该没有实质性更改

RelativeLayout 大小的更改将在代码中设置,并由 AppBarLayout.OnOffsetChangedListener 触发。我们将根据应用栏的垂直偏移计算和更改 RelativeLayout 的大小。

MainActivity.java

public class MainActivity extends AppCompatActivity {
    private RelativeLayout mRelativeLayout;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Toolbar toolbar = findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        mRelativeLayout = findViewById(R.id.relativeLayout);
        final AppBarLayout appBarLayout = findViewById(R.id.appBar);
        final int screenHeight = getResources().getDisplayMetrics().heightPixels;

        appBarLayout.post(new Runnable() {
            @Override
            public void run() {
                adjustRelLayoutHeight(mRelativeLayout, screenHeight - appBarLayout.getBottom());
            }
        });

        appBarLayout.addOnOffsetChangedListener(new AppBarLayout.OnOffsetChangedListener() {
            @Override
            public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {
                adjustRelLayoutHeight(mRelativeLayout, screenHeight - appBarLayout.getBottom());
            }
        });
    }

    private void adjustRelLayoutHeight(RelativeLayout layout, int newHeight) {
        FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) layout.getLayoutParams();
        lp.height = newHeight;
        layout.setLayoutParams(lp);
    }

    private static final String TAG = "MainActivity";
}

结果如下:

更新: 以下是使用 ViewTreeObserver.OnPreDrawListener() 的替代方法,可避免额外抽奖。但是效果是一样的:

MainActivity.java(替代方法)

public class MainActivity extends AppCompatActivity {
    private RelativeLayout mRelativeLayout;
    private int mCoordinatorHeight;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Toolbar toolbar = findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        mRelativeLayout = findViewById(R.id.relativeLayout);
        final AppBarLayout appBarLayout = findViewById(R.id.appBar);

        final CoordinatorLayout coordinatorLayout =
            (CoordinatorLayout) findViewById(R.id.coordinatorLayoutProfile);
        // Wait for just before drawing the view to get its height.
        coordinatorLayout.getViewTreeObserver()
            .addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
                @Override
                public boolean onPreDraw() {
                    // Remove the listener so we don't go into an infinite loop.
                    coordinatorLayout.getViewTreeObserver().removeOnPreDrawListener(this);
                    mCoordinatorHeight = coordinatorLayout.getHeight();
                    adjustRelLayoutHeight(mRelativeLayout, mCoordinatorHeight - appBarLayout.getBottom());
                    return false; // abort drawing
                }
            });

        appBarLayout.addOnOffsetChangedListener(new AppBarLayout.OnOffsetChangedListener() {
            @Override
            public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {
                adjustRelLayoutHeight(mRelativeLayout, mCoordinatorHeight - appBarLayout.getBottom());
            }
        });
    }

    private void adjustRelLayoutHeight(RelativeLayout layout, int newHeight) {
        if (newHeight <= 0) {
            // no-op if height is invalid
            return;
        }
        FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) layout.getLayoutParams();
        if (lp.height != newHeight) {
            lp.height = newHeight;
            layout.setLayoutParams(lp);
        }
    }

    private static final String TAG = "MainActivity";
}