1/*
2 * Copyright (C) 2016 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.support.design.widget;
18
19import static org.junit.Assert.assertEquals;
20
21import android.os.Build;
22import android.os.SystemClock;
23import android.support.design.test.R;
24import android.support.test.InstrumentationRegistry;
25import android.support.test.filters.SdkSuppress;
26import android.test.suitebuilder.annotation.MediumTest;
27import android.widget.ImageView;
28
29import org.junit.Test;
30
31@MediumTest
32public class AppBarWithCollapsingToolbarTest extends AppBarLayoutBaseTest {
33    @Test
34    public void testPinnedToolbar() {
35        configureContent(R.layout.design_appbar_toolbar_collapse_pin,
36                R.string.design_appbar_collapsing_toolbar_pin);
37
38        CollapsingToolbarLayout.LayoutParams toolbarLp =
39                (CollapsingToolbarLayout.LayoutParams) mToolbar.getLayoutParams();
40        assertEquals(CollapsingToolbarLayout.LayoutParams.COLLAPSE_MODE_PIN,
41                toolbarLp.getCollapseMode());
42
43        final int[] appbarOnScreenXY = new int[2];
44        final int[] coordinatorLayoutOnScreenXY = new int[2];
45        mAppBar.getLocationOnScreen(appbarOnScreenXY);
46        mCoordinatorLayout.getLocationOnScreen(coordinatorLayoutOnScreenXY);
47
48        final int originalAppbarTop = appbarOnScreenXY[1];
49        final int originalAppbarBottom = appbarOnScreenXY[1] + mAppBar.getHeight();
50        final int centerX = appbarOnScreenXY[0] + mAppBar.getWidth() / 2;
51
52        final int toolbarHeight = mToolbar.getHeight();
53        final int appbarHeight = mAppBar.getHeight();
54        final int longSwipeAmount = 3 * appbarHeight / 2;
55        final int reallyLongSwipeAmount = 2 * appbarHeight;
56        final int shortSwipeAmount = toolbarHeight;
57
58        assertAppBarElevation(0f);
59
60        // Perform a swipe-up gesture across the horizontal center of the screen.
61        performVerticalSwipeUpGesture(
62                R.id.coordinator_layout,
63                centerX,
64                originalAppbarBottom + longSwipeAmount / 2,
65                longSwipeAmount);
66
67        mAppBar.getLocationOnScreen(appbarOnScreenXY);
68        // At this point the app bar should be visually snapped below the system status bar.
69        // Allow for off-by-a-pixel margin of error.
70        assertEquals(originalAppbarTop + toolbarHeight + mAppBar.getTopInset(),
71                appbarOnScreenXY[1] + appbarHeight, 1);
72
73        // Perform another swipe-up gesture
74        performVerticalSwipeUpGesture(
75                R.id.coordinator_layout,
76                centerX,
77                appbarOnScreenXY[1] + appbarHeight + 5,
78                shortSwipeAmount);
79
80        mAppBar.getLocationOnScreen(appbarOnScreenXY);
81        // At this point the app bar should still be visually snapped below the system status bar
82        // as it is in the pinned mode. Allow for off-by-a-pixel margin of error.
83        assertEquals(originalAppbarTop + toolbarHeight + mAppBar.getTopInset(),
84                appbarOnScreenXY[1] + appbarHeight, 1);
85        assertAppBarElevation(mDefaultElevationValue);
86
87        // Perform a short swipe-down gesture across the horizontal center of the screen.
88        // Note that the swipe down is a bit longer than the swipe up to check that the app bar
89        // is not starting to expand too early.
90        performVerticalSwipeDownGesture(
91                R.id.coordinator_layout,
92                centerX,
93                originalAppbarBottom - shortSwipeAmount,
94                3 * shortSwipeAmount / 2);
95
96        mAppBar.getLocationOnScreen(appbarOnScreenXY);
97        // At this point the app bar should still be visually snapped below the system status bar
98        // as it is in the pinned mode and we haven't fully swiped down the content below the
99        // app bar. Allow for off-by-a-pixel margin of error.
100        assertEquals(originalAppbarTop + toolbarHeight + mAppBar.getTopInset(),
101                appbarOnScreenXY[1] + appbarHeight, 1);
102        assertAppBarElevation(mDefaultElevationValue);
103
104        // Perform another swipe-down gesture across the horizontal center of the screen.
105        performVerticalSwipeDownGesture(
106                R.id.coordinator_layout,
107                centerX,
108                originalAppbarBottom,
109                reallyLongSwipeAmount);
110
111        mAppBar.getLocationOnScreen(appbarOnScreenXY);
112        // At this point the app bar should be in its original position.
113        // Allow for off-by-a-pixel margin of error.
114        assertEquals(originalAppbarTop, appbarOnScreenXY[1], 1);
115        assertEquals(originalAppbarBottom, appbarOnScreenXY[1] + appbarHeight, 1);
116        assertAppBarElevation(0f);
117
118        // Perform yet another swipe-down gesture across the horizontal center of the screen.
119        performVerticalSwipeDownGesture(
120                R.id.coordinator_layout,
121                centerX,
122                originalAppbarBottom,
123                longSwipeAmount);
124
125        mAppBar.getLocationOnScreen(appbarOnScreenXY);
126        // At this point the app bar should still be in its original position.
127        // Allow for off-by-a-pixel margin of error.
128        assertEquals(originalAppbarTop, appbarOnScreenXY[1], 1);
129        assertEquals(originalAppbarBottom, appbarOnScreenXY[1] + appbarHeight, 1);
130        assertAppBarElevation(0f);
131    }
132
133    @Test
134    public void testScrollingToolbar() {
135        configureContent(R.layout.design_appbar_toolbar_collapse_scroll,
136                R.string.design_appbar_collapsing_toolbar_scroll);
137
138        CollapsingToolbarLayout.LayoutParams toolbarLp =
139                (CollapsingToolbarLayout.LayoutParams) mToolbar.getLayoutParams();
140        assertEquals(CollapsingToolbarLayout.LayoutParams.COLLAPSE_MODE_PIN,
141                toolbarLp.getCollapseMode());
142
143        final int[] appbarOnScreenXY = new int[2];
144        final int[] coordinatorLayoutOnScreenXY = new int[2];
145        mAppBar.getLocationOnScreen(appbarOnScreenXY);
146        mCoordinatorLayout.getLocationOnScreen(coordinatorLayoutOnScreenXY);
147
148        final int topInset = mAppBar.getTopInset();
149
150        final int originalAppbarTop = appbarOnScreenXY[1];
151        final int originalAppbarBottom = appbarOnScreenXY[1] + mAppBar.getHeight();
152        final int centerX = appbarOnScreenXY[0] + mAppBar.getWidth() / 2;
153
154        final int toolbarHeight = mToolbar.getHeight();
155        final int appbarHeight = mAppBar.getHeight();
156        final int longSwipeAmount = 3 * appbarHeight / 2;
157        final int reallyLongSwipeAmount = 2 * appbarHeight;
158        final int shortSwipeAmount = toolbarHeight;
159
160        assertAppBarElevation(0f);
161
162        // Perform a swipe-up gesture across the horizontal center of the screen, starting from
163        // just below the AppBarLayout
164        performVerticalSwipeUpGesture(
165                R.id.coordinator_layout,
166                centerX,
167                originalAppbarBottom + 20,
168                longSwipeAmount);
169
170        mAppBar.getLocationOnScreen(appbarOnScreenXY);
171        // At this point the app bar should not be visually "present" on the screen, with its bottom
172        // edge aligned with the bottom of system status bar. If we're running on a device which
173        // supports a translucent status bar, we need to take the status bar height into account.
174        // Allow for off-by-a-pixel margin of error.
175        assertEquals(originalAppbarTop, appbarOnScreenXY[1] + appbarHeight - topInset, 1);
176        assertAppBarElevation(0f);
177
178        // Perform another swipe-up gesture
179        performVerticalSwipeUpGesture(
180                R.id.coordinator_layout,
181                centerX,
182                originalAppbarBottom,
183                shortSwipeAmount);
184
185        mAppBar.getLocationOnScreen(appbarOnScreenXY);
186        // At this point the app bar should still be off the screen. Allow for off-by-a-pixel
187        // margin of error.
188        assertEquals(originalAppbarTop, appbarOnScreenXY[1] + appbarHeight - topInset, 1);
189        assertAppBarElevation(0f);
190
191        // Perform a short swipe-down gesture across the horizontal center of the screen.
192        // Note that the swipe down is a bit longer than the swipe up to fully bring down
193        // the scrolled-away toolbar
194        performVerticalSwipeDownGesture(
195                R.id.coordinator_layout,
196                centerX,
197                originalAppbarBottom,
198                3 * shortSwipeAmount / 2);
199
200        mAppBar.getLocationOnScreen(appbarOnScreenXY);
201        // At this point the app bar should be visually snapped below the system status bar as it
202        // in scrolling mode and we've swiped down, but not fully. Allow for off-by-a-pixel
203        // margin of error.
204        assertEquals(originalAppbarTop + toolbarHeight + topInset,
205                appbarOnScreenXY[1] + appbarHeight, 1);
206        assertAppBarElevation(mDefaultElevationValue);
207
208        // Perform another swipe-down gesture across the horizontal center of the screen.
209        performVerticalSwipeDownGesture(
210                R.id.coordinator_layout,
211                centerX,
212                originalAppbarBottom,
213                reallyLongSwipeAmount);
214
215        mAppBar.getLocationOnScreen(appbarOnScreenXY);
216        // At this point the app bar should be in its original position.
217        // Allow for off-by-a-pixel margin of error.
218        assertEquals(originalAppbarTop, appbarOnScreenXY[1]);
219        assertEquals(originalAppbarBottom, appbarOnScreenXY[1] + appbarHeight);
220        assertAppBarElevation(0f);
221
222        // Perform yet another swipe-down gesture across the horizontal center of the screen.
223        performVerticalSwipeDownGesture(
224                R.id.coordinator_layout,
225                centerX,
226                originalAppbarBottom,
227                longSwipeAmount);
228
229        mAppBar.getLocationOnScreen(appbarOnScreenXY);
230        // At this point the app bar should still be in its original position.
231        // Allow for off-by-a-pixel margin of error.
232        assertEquals(originalAppbarTop, appbarOnScreenXY[1], 1);
233        assertEquals(originalAppbarBottom, appbarOnScreenXY[1] + appbarHeight, 1);
234        assertAppBarElevation(0f);
235    }
236
237    @Test
238    public void testPinnedToolbarAndAnchoredFab() throws Throwable {
239        configureContent(R.layout.design_appbar_toolbar_collapse_pin_with_fab,
240                R.string.design_appbar_collapsing_toolbar_pin_fab);
241
242        CollapsingToolbarLayout.LayoutParams toolbarLp =
243                (CollapsingToolbarLayout.LayoutParams) mToolbar.getLayoutParams();
244        assertEquals(CollapsingToolbarLayout.LayoutParams.COLLAPSE_MODE_PIN,
245                toolbarLp.getCollapseMode());
246
247        final FloatingActionButton fab =
248                (FloatingActionButton) mCoordinatorLayout.findViewById(R.id.fab);
249
250        final int[] appbarOnScreenXY = new int[2];
251        final int[] coordinatorLayoutOnScreenXY = new int[2];
252        mAppBar.getLocationOnScreen(appbarOnScreenXY);
253        mCoordinatorLayout.getLocationOnScreen(coordinatorLayoutOnScreenXY);
254
255        final int originalAppbarBottom = appbarOnScreenXY[1] + mAppBar.getHeight();
256        final int centerX = appbarOnScreenXY[0] + mAppBar.getWidth() / 2;
257
258        final int appbarHeight = mAppBar.getHeight();
259        final int longSwipeAmount = 3 * appbarHeight / 2;
260
261        // Perform a swipe-up gesture across the horizontal center of the screen.
262        performVerticalSwipeUpGesture(
263                R.id.coordinator_layout,
264                centerX,
265                originalAppbarBottom + longSwipeAmount / 2,
266                longSwipeAmount);
267
268        // Since we the visibility change listener path is only exposed via direct calls to
269        // FloatingActionButton.show and not the internal path that FAB's behavior is using,
270        // this test needs to be tied to the internal implementation details of running animation
271        // that scales the FAB to 0/0 scales and interpolates its alpha to 0. Since that animation
272        // starts running partway through our swipe gesture and may complete a bit later then
273        // the swipe gesture, sleep for a bit to catch the "final" state of the FAB.
274        SystemClock.sleep(200);
275
276        // At this point the FAB should be scaled to 0/0 and set at alpha 0. Since the relevant
277        // getter methods are only available on v11+, wrap the asserts with build version check.
278        if (Build.VERSION.SDK_INT >= 11) {
279            assertEquals(0.0f, fab.getScaleX(), 0.0f);
280            assertEquals(0.0f, fab.getScaleY(), 0.0f);
281            assertEquals(0.0f, fab.getAlpha(), 0.0f);
282        }
283
284        // Perform a swipe-down gesture across the horizontal center of the screen.
285        performVerticalSwipeDownGesture(
286                R.id.coordinator_layout,
287                centerX,
288                originalAppbarBottom,
289                longSwipeAmount);
290
291        // Same as for swipe-up gesture - sleep for a bit to catch the "final" visible state of
292        // the FAB.
293        SystemClock.sleep(200);
294
295        // At this point the FAB should be scaled back to its original size and be at full opacity.
296        if (Build.VERSION.SDK_INT >= 11) {
297            assertEquals(1.0f, fab.getScaleX(), 0.0f);
298            assertEquals(1.0f, fab.getScaleY(), 0.0f);
299            assertEquals(1.0f, fab.getAlpha(), 0.0f);
300        }
301    }
302
303    @Test
304    public void testPinnedToolbarAndParallaxImage() {
305        configureContent(R.layout.design_appbar_toolbar_collapse_with_image,
306                R.string.design_appbar_collapsing_toolbar_with_image);
307
308        final ImageView parallaxImageView =
309                (ImageView) mCoordinatorLayout.findViewById(R.id.app_bar_image);
310
311        // We have not set any padding on the ImageView, so ensure that none is set via
312        // window insets handling
313        assertEquals(0, parallaxImageView.getPaddingLeft());
314        assertEquals(0, parallaxImageView.getPaddingTop());
315        assertEquals(0, parallaxImageView.getPaddingRight());
316        assertEquals(0, parallaxImageView.getPaddingBottom());
317
318        CollapsingToolbarLayout.LayoutParams parallaxImageViewLp =
319                (CollapsingToolbarLayout.LayoutParams) parallaxImageView.getLayoutParams();
320        assertEquals(CollapsingToolbarLayout.LayoutParams.COLLAPSE_MODE_PARALLAX,
321                parallaxImageViewLp.getCollapseMode());
322
323        final float parallaxMultiplier = parallaxImageViewLp.getParallaxMultiplier();
324
325        final int[] appbarOnScreenXY = new int[2];
326        final int[] parallaxImageOnScreenXY = new int[2];
327        final int appbarHeight = mAppBar.getHeight();
328        final int toolbarHeight = mToolbar.getHeight();
329        final int parallaxImageHeight = parallaxImageView.getHeight();
330
331        mAppBar.getLocationOnScreen(appbarOnScreenXY);
332        parallaxImageView.getLocationOnScreen(parallaxImageOnScreenXY);
333
334        final int originalAppbarTop = appbarOnScreenXY[1];
335        final int originalAppbarBottom = appbarOnScreenXY[1] + mAppBar.getHeight();
336        final int originalParallaxImageTop = parallaxImageOnScreenXY[1];
337        final int centerX = appbarOnScreenXY[0] + mAppBar.getWidth() / 2;
338
339        // Test that at the beginning our image is top-aligned with the app bar
340        assertEquals(appbarOnScreenXY[1], parallaxImageOnScreenXY[1]);
341
342        // Swipe up by the toolbar's height
343        performVerticalSwipeUpGesture(
344                R.id.coordinator_layout,
345                centerX,
346                originalAppbarBottom,
347                toolbarHeight);
348
349        // Test that the top edge of the image (in the screen coordinates) has "moved" by half
350        // the amount that the top edge of the app bar (in the screen coordinates) has.
351        mAppBar.getLocationOnScreen(appbarOnScreenXY);
352        parallaxImageView.getLocationOnScreen(parallaxImageOnScreenXY);
353        assertEquals(parallaxMultiplier * (appbarOnScreenXY[1] - originalAppbarTop),
354                parallaxImageOnScreenXY[1] - originalParallaxImageTop, 1);
355
356        // Swipe up by another toolbar's height
357        performVerticalSwipeUpGesture(
358                R.id.coordinator_layout,
359                centerX,
360                originalAppbarBottom,
361                toolbarHeight);
362
363        // Test that the top edge of the image (in the screen coordinates) has "moved" by half
364        // the amount that the top edge of the app bar (in the screen coordinates) has.
365        mAppBar.getLocationOnScreen(appbarOnScreenXY);
366        parallaxImageView.getLocationOnScreen(parallaxImageOnScreenXY);
367        assertEquals(parallaxMultiplier * (appbarOnScreenXY[1] - originalAppbarTop),
368                parallaxImageOnScreenXY[1] - originalParallaxImageTop, 1);
369
370        // Swipe down by a different value (150% of the toolbar's height) to test parallax going the
371        // other way
372        performVerticalSwipeDownGesture(
373                R.id.coordinator_layout,
374                centerX,
375                originalAppbarBottom,
376                3 * toolbarHeight / 2);
377
378        // Test that the top edge of the image (in the screen coordinates) has "moved" by half
379        // the amount that the top edge of the app bar (in the screen coordinates) has.
380        mAppBar.getLocationOnScreen(appbarOnScreenXY);
381        parallaxImageView.getLocationOnScreen(parallaxImageOnScreenXY);
382        assertEquals(parallaxMultiplier * (appbarOnScreenXY[1] - originalAppbarTop),
383                parallaxImageOnScreenXY[1] - originalParallaxImageTop, 1);
384    }
385
386    @Test
387    public void testAddViewWithDefaultLayoutParams() {
388        configureContent(R.layout.design_appbar_toolbar_collapse_pin,
389                R.string.design_appbar_collapsing_toolbar_pin);
390
391        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
392            @Override
393            public void run() {
394                ImageView view = new ImageView(mCollapsingToolbar.getContext());
395                mCollapsingToolbar.addView(view);
396            }
397        });
398
399    }
400
401    /**
402     * This test only runs on API 11+ since FrameLayout (which CollapsingToolbarLayout
403     * inherits from) has an issue with measuring children with margins when run on earlier
404     * versions of the platform.
405     */
406    @Test
407    @SdkSuppress(minSdkVersion = 11)
408    public void testPinnedToolbarWithMargins() {
409        configureContent(R.layout.design_appbar_toolbar_collapse_pin_margins,
410                R.string.design_appbar_collapsing_toolbar_pin_margins);
411
412        CollapsingToolbarLayout.LayoutParams toolbarLp =
413                (CollapsingToolbarLayout.LayoutParams) mToolbar.getLayoutParams();
414        assertEquals(CollapsingToolbarLayout.LayoutParams.COLLAPSE_MODE_PIN,
415                toolbarLp.getCollapseMode());
416
417        final int[] appbarOnScreenXY = new int[2];
418        final int[] toolbarOnScreenXY = new int[2];
419        mAppBar.getLocationOnScreen(appbarOnScreenXY);
420        mToolbar.getLocationOnScreen(toolbarOnScreenXY);
421
422        final int originalAppbarTop = appbarOnScreenXY[1];
423        final int originalAppbarBottom = originalAppbarTop + mAppBar.getHeight();
424        final int centerX = appbarOnScreenXY[0] + mAppBar.getWidth() / 2;
425
426        final int toolbarHeight = mToolbar.getHeight();
427        final int toolbarVerticalMargins = toolbarLp.topMargin + toolbarLp.bottomMargin;
428        final int appbarHeight = mAppBar.getHeight();
429
430        // Perform a swipe-up gesture across the horizontal center of the screen.
431        int swipeAmount = appbarHeight - toolbarHeight - toolbarVerticalMargins;
432        performVerticalSwipeUpGesture(
433                R.id.coordinator_layout,
434                centerX,
435                originalAppbarBottom + (3 * swipeAmount / 2),
436                swipeAmount);
437
438        mAppBar.getLocationOnScreen(appbarOnScreenXY);
439        mToolbar.getLocationOnScreen(toolbarOnScreenXY);
440        // At this point the toolbar should be visually pinned to the bottom of the appbar layout,
441        // observing it's margins and top inset
442        // The toolbar should still be visually pinned to the bottom of the appbar layout
443        assertEquals(originalAppbarTop + mAppBar.getTopInset(),
444                toolbarOnScreenXY[1] - toolbarLp.topMargin, 1);
445
446        // Swipe up again, this time just 50% of the margin size
447        swipeAmount = toolbarVerticalMargins / 2;
448        performVerticalSwipeUpGesture(
449                R.id.coordinator_layout,
450                centerX,
451                originalAppbarBottom + (3 * swipeAmount / 2),
452                swipeAmount);
453
454        mAppBar.getLocationOnScreen(appbarOnScreenXY);
455        mToolbar.getLocationOnScreen(toolbarOnScreenXY);
456
457        // The toolbar should still be visually pinned to the bottom of the appbar layout
458        assertEquals(appbarOnScreenXY[1] + appbarHeight,
459                toolbarOnScreenXY[1] + toolbarHeight + toolbarLp.bottomMargin, 1);
460    }
461}
462