具有多个捕捉点的 CoordinatorLayout

CoordinatorLayout with multiple snapping points

这里我有一个相当复杂的动画,可以使用 CoordinatorLayout 以简单的方式解决(我相信)。它有 3 个状态:

  1. 初始(左侧屏幕)- Header 视图已完全显示(橙色 背景):工具栏,灰色 roundrect(它实际上是一张照片) 加上下面的一些其他视图(TextViews、RatingBar 等)
  2. 向上滚动内容(中间 屏幕)- roundrect 正在放大,其上有一个不断变化的绿色前景 alpha 级别,因此它在滚动时变为绿色(好吧,在这些屏幕上并不明显。绿色背景实际上是一个放大的 roundrect,上面有一个绿色前景,而且是 header 背景变成绿色而不是橙色的原因)
  3. 再次滚动(右侧屏幕)- header 的其余部分应向上滚动

向下滚动内容应该会相应地以相反的方式显示视图。

我有一些使用 CoordinatorLayout 的经验,但我真的不确定我是否理解如何处理 2 个锚点。我了解滚动标志的工作原理以及用于缩放(第 2 页)和更改前景 alpha 的工作原理我需要一个自定义 Behavior 实现,但现在我无法理解如何在一个复杂的环境中处理所有这些。

目前我找到的只有 Saúl Molinero's tutorial and also this tutorial with examples

很抱歉这里的描述不佳,我当然会更新我的问题,当我在这个问题上取得一些成功时会添加源代码,但现在我很乐意得到一些提示或我错过的教程。希望有人在项目中有类似的东西。

这是我的 test repo with the code and here 是我的 layout.xml 文件的 link。

只需设置滚动标志即可获得两个捕捉点,如下所示:

<android.support.design.widget.CollapsingToolbarLayout
    ...stuff...
    app:layout_scrollFlags="scroll|enterAlways|snap">

因此,完全展开是一个停止点,仅显示工具栏是第二个停止点。当进一步滚动视图时,工具栏消失。这就是您希望向上滚动时的工作方式。

现在,当应用栏完全折叠时,向下滚动时应用栏将立即开始显示。这并不奇怪,因为 enterAlways 就是这样做的。如果内容的顶部已经滚出视图,那么在应用栏完全展开之前您将不会再次看到它。所以,如果这是您想要的行为,我们就到此为止。

但是,我认为您想要的是上面概述的退出行为,但具有不同的进入行为。如果按如下方式设置滚动标志,您将获得延迟进入行为:

<android.support.design.widget.CollapsingToolbarLayout
    ...stuff...
    app:layout_scrollFlags="scroll|snap">

这只是删除了 enterAlways 标志。使用这些滚动标志,应用栏将不会重新出现(一旦折叠),直到内容顶部可见并且 "pulls" 应用栏进入视图。

因此,一个解决方案(可能有很多)是编写一个新的行为,该行为将附加到 AppBarLayout 一些将改变滚动的代码应用栏完全折叠后标记,并在再次打开时将其改回。这样您就可以将行为更改为您想要的行为,并且仍然使用 Android 机制来确定视图级别的特定操作。这可以在自定义视图或 activity 中完成 - 无论您可以访问应用栏的滚动状态和滚动标志。它也可以在行为中完成,但这可能不是最好的地方。

哦,正如您所发现的那样,在 API 26.

上捕捉是卡顿的

这里是这个概念的一个实现。为简单起见,实现在 activity:

ScrollingActivity.java

public class ScrollingActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_scrolling);

        final AppBarLayout appBar = (AppBarLayout) findViewById(R.id.app_bar);

        appBar.post(new Runnable() {
            @Override
            public void run() {
                CollapsingToolbarLayout toolbarLayout =
                    (CollapsingToolbarLayout) findViewById(R.id.toolbar_layout);
                setupAppBar(appBar, toolbarLayout);
            }
        });
    }

    private void setupAppBar(AppBarLayout appBar, final CollapsingToolbarLayout toolbarLayout) {
        // Scroll range is positive but offsets are negative. Make signs agree for camparisons.
        final int mScrollRange = -appBar.getTotalScrollRange();

        appBar.addOnOffsetChangedListener(new AppBarLayout.OnOffsetChangedListener() {
            private boolean mAppBarCollapsed = false;

            @Override
            public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {
                if (verticalOffset == mScrollRange) { // App bar just collapsed
                    mAppBarCollapsed = true;
                    AppBarLayout.LayoutParams lp =
                        (AppBarLayout.LayoutParams) toolbarLayout.getLayoutParams();
                    int flags = lp.getScrollFlags()
                        & ~AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS;
                    lp.setScrollFlags(flags);
                    toolbarLayout.setLayoutParams(lp);
                } else if (mAppBarCollapsed) { // App bar is opening back up
                    mAppBarCollapsed = false;
                    AppBarLayout.LayoutParams lp =
                        (AppBarLayout.LayoutParams) toolbarLayout.getLayoutParams();
                    int flags = lp.getScrollFlags()
                        | AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS;
                    lp.setScrollFlags(flags);
                    toolbarLayout.setLayoutParams(lp);
                }
            }
        });
    }
}