5

Here I've got quite a complex animation that may be resolved (I believe) in a simple way using the CoordinatorLayout. It has 3 states:

  1. Initial (left screen) - Header view is shown fully (orange background): Toolbar, grey roundrect (it's actually a photo there) plus some other views below (TextViews, RatingBar etc)
  2. Scrolling the content up (middle screen) - roundrect is zooming up with a changing green foreground alpha level over it, so it becomes green while scrolling (well, it is not obvious with these screens. Green background is actually a zoomed roundrect with a green foreground over it, and that is the cause the header background becomes green and not orange)
  3. Scrolling once more (right screen) - the rest of the header should be scrolled up

Scrolling down the content should lead to the appearing of the views in a reverse way accordingly.

CoordinatorLayout states

I had some experience working with the CoordinatorLayout, but I'm really not sure I understand how to handle 2 anchor points. I understand how the scroll flags work and that for zooming (p. 2) and for changing the foreground alpha I need a custom Behavior implementation, but for now I cannot understand how shall I handle all of this in a complex.

All I've found so far is Saúl Molinero's tutorial and also this tutorial with examples.

So please sorry for the poor description here, I'll update my question of course and will add the source code when I have some success with this issue, but for now I'd be glad to get some hints maybe or tutorials I've missed. Hope someone had something similar in the projects.

Here's my test repo with the code and here is a link to my layout.xml file.

Willi Mentzel
  • 27,862
  • 20
  • 113
  • 121
UneXp
  • 1,574
  • 2
  • 16
  • 31
  • 1
    Please edit your question title so that it summarizes your problem. "Just another CL challenge" is rather undecsriptive – Tim Sep 14 '17 at 15:43
  • 1
    Is the issue that you want an intermediate snap point represented by the middle image? Scroll up to snap to the first stop (middle image) where the layout will stay indefinitely until scrolled up again to make the header completely disappear? – Cheticamp Sep 14 '17 at 23:19
  • Yes, exactly. All I've done so far is 1st and 2nd states, but when I add snap flag to my CollapsingToolbarLayout, its behavior is strange when you scroll the content, so I'm not sure I've done even this one correct :( – UneXp Sep 15 '17 at 05:51
  • 1
    So, if you change the `scrollFlags` to `scroll|enterAlwaysCollapsed|snap` does that give you what you want except for a snap point (detent) that is the height of the `ToolBar`? – Cheticamp Sep 15 '17 at 10:53
  • Almost. When I scroll the content slowly and then release the touch, the header content scrolls to the 1st or to the second state properly. But if to scroll like a little "punch" - so the strange behavior appears here. So, it's hard to describe, so here's how it looks like: https://drive.google.com/file/d/0B0VqEjRwXBfydHhuU1dGV3R0LWs/view?usp=sharing – UneXp Sep 15 '17 at 14:50
  • 1
    Oh, that is probably this issue: ["How to avoid CollapsingToolbarLayout not being snapped or being “wobbly” when scrolling?"](https://stackoverflow.com/questions/45192654/how-to-avoid-collapsingtoolbarlayout-not-being-snapped-or-being-wobbly-when-sc/45338791#45338791). I thought that this issue was introduce in API 26, but I am seeing it in API 24 as well. – Cheticamp Sep 15 '17 at 15:00
  • @Cheticamp I want to cry... yes, snap works with the 25.4.0 version of the com.android.support:design library. Thanks a lot! Maybe I'll have success with the multiple snapping points also with it – UneXp Sep 15 '17 at 16:26
  • I was mistaken. I was using API 26.0.2 and I haven't seen this behavior in any earlier API, so API 25.4.0 should work. A question: Are you looking for the app bar to enter early or enter only once the content is scrolled to the top? – Cheticamp Sep 15 '17 at 16:33

1 Answers1

5

You can get two snapping points with just setting the scroll flags as follows:

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

So, fully expanded is one stopping point and with just the toolbar visible is the second stopping point. When the view is scrolled further, the toolbar disappears. So this is how you want things to work when scrolling up.

Now when the app bar is fully collapsed, the app bar will start showing immediately when scrolling down. That is not a surprise, since that is what enterAlways does. If the top of the content has been scrolled out of view, then you won't see it again until after the app bar is fully expanded. So, if this is the behavior you want, we'll just stop there.

However, I think that what you want is the exiting behavior outlined above but with a different entry behavior. You will get the late entry behavior if you set the scroll flags as follows:

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

This just deleted the enterAlways flag. With these scroll flags, the app bar will not reappear (once collapsed) until the top of the content is visible and "pulls" the app bar into view.

So, one solution (of what is probably many) is to write a new behavior that will be attached to the AppBarLayout some code that will change the scroll flags once the app bar is fully collapsed and change them back as it opens again. That way you can change the behavior to be what you want and still use the Android machinery to figure out what the specific operations are at the view level. This can be done in a custom view or in the activity - wherever you have access to the scroll state of the app bar and the scrolling flags. It can also be done in a behavior but that is probably not the best place for it.

Oh, and as you have discovered, snapping is janky on API 26.


Here is an implementation of the concept. For simplicity, the implementation is in an activity:

enter image description here

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);
                }
            }
        });
    }
}
Cheticamp
  • 61,413
  • 10
  • 78
  • 131