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 android.os.SystemClock; 20import android.support.annotation.LayoutRes; 21import android.support.annotation.StringRes; 22import android.support.design.test.R; 23import android.support.design.testutils.Cheeses; 24import android.test.suitebuilder.annotation.LargeTest; 25import android.test.suitebuilder.annotation.MediumTest; 26import org.junit.Test; 27 28import static android.support.design.testutils.TestUtilsActions.addTabs; 29import static android.support.test.espresso.Espresso.onView; 30import static android.support.test.espresso.matcher.ViewMatchers.withId; 31import static org.junit.Assert.assertEquals; 32 33@MediumTest 34public class AppBarWithToolbarAndTabsTest extends AppBarLayoutBaseTest { 35 private TabLayout mTabLayout; 36 37 @Override 38 protected void configureContent(@LayoutRes int layoutResId, @StringRes int titleResId) { 39 super.configureContent(layoutResId, titleResId); 40 41 mTabLayout = (TabLayout) mAppBar.findViewById(R.id.tabs); 42 String[] tabTitles = new String[5]; 43 System.arraycopy(Cheeses.sCheeseStrings, 0, tabTitles, 0, 5); 44 onView(withId(R.id.tabs)).perform(addTabs(tabTitles)); 45 } 46 47 @Test 48 public void testScrollingToolbarAndScrollingTabs() { 49 configureContent(R.layout.design_appbar_toolbar_scroll_tabs_scroll, 50 R.string.design_appbar_toolbar_scroll_tabs_scroll); 51 52 final int[] appbarOnScreenXY = new int[2]; 53 final int[] coordinatorLayoutOnScreenXY = new int[2]; 54 mAppBar.getLocationOnScreen(appbarOnScreenXY); 55 mCoordinatorLayout.getLocationOnScreen(coordinatorLayoutOnScreenXY); 56 57 final int originalAppbarTop = appbarOnScreenXY[1]; 58 final int originalAppbarBottom = appbarOnScreenXY[1] + mAppBar.getHeight(); 59 final int centerX = appbarOnScreenXY[0] + mAppBar.getWidth() / 2; 60 61 final int toolbarHeight = mToolbar.getHeight(); 62 final int tabsHeight = mTabLayout.getHeight(); 63 final int appbarHeight = mAppBar.getHeight(); 64 final int longSwipeAmount = 3 * appbarHeight / 2; 65 final int shortSwipeAmount = toolbarHeight; 66 67 // Perform a swipe-up gesture across the horizontal center of the screen, starting from 68 // just below the AppBarLayout 69 performVerticalSwipeUpGesture( 70 R.id.coordinator_layout, 71 centerX, 72 originalAppbarBottom + 20, 73 longSwipeAmount); 74 75 mAppBar.getLocationOnScreen(appbarOnScreenXY); 76 // At this point the app bar should not be visually "present" on the screen, with its bottom 77 // edge aligned with the system status bar. Allow for off-by-a-pixel margin of error. 78 assertEquals(originalAppbarTop, appbarOnScreenXY[1] + appbarHeight, 1); 79 assertAppBarElevation(mDefaultElevationValue); 80 81 // Perform another swipe-up gesture 82 performVerticalSwipeUpGesture( 83 R.id.coordinator_layout, 84 centerX, 85 originalAppbarBottom, 86 shortSwipeAmount); 87 88 mAppBar.getLocationOnScreen(appbarOnScreenXY); 89 // At this point the app bar should still be off the screen. Allow for off-by-a-pixel 90 // margin of error. 91 assertEquals(originalAppbarTop, appbarOnScreenXY[1] + appbarHeight, 1); 92 assertAppBarElevation(mDefaultElevationValue); 93 94 // Perform a long swipe-down gesture across the horizontal center of the screen. 95 // Note that the swipe down is a bit longer than the swipe up to fully bring down 96 // the scrolled-away toolbar and tab layout 97 performVerticalSwipeDownGesture( 98 R.id.coordinator_layout, 99 centerX, 100 originalAppbarBottom, 101 longSwipeAmount * 3 / 2); 102 103 mAppBar.getLocationOnScreen(appbarOnScreenXY); 104 // At this point the app bar should be visually snapped below the system status bar as it 105 // in scrolling mode and we've swiped down. Allow for off-by-a-pixel 106 // margin of error. 107 assertEquals(originalAppbarTop, appbarOnScreenXY[1], 1); 108 assertEquals(originalAppbarBottom, appbarOnScreenXY[1] + appbarHeight, 1); 109 assertAppBarElevation(mDefaultElevationValue); 110 111 // Perform another swipe-down gesture across the horizontal center of the screen. 112 performVerticalSwipeDownGesture( 113 R.id.coordinator_layout, 114 centerX, 115 originalAppbarBottom, 116 longSwipeAmount); 117 118 mAppBar.getLocationOnScreen(appbarOnScreenXY); 119 // At this point the app bar should be in its original position. 120 // Allow for off-by-a-pixel margin of error. 121 assertEquals(originalAppbarTop, appbarOnScreenXY[1], 1); 122 assertEquals(originalAppbarBottom, appbarOnScreenXY[1] + appbarHeight, 1); 123 assertAppBarElevation(mDefaultElevationValue); 124 125 // Perform yet another swipe-down gesture across the horizontal center of the screen. 126 performVerticalSwipeDownGesture( 127 R.id.coordinator_layout, 128 centerX, 129 originalAppbarBottom, 130 longSwipeAmount); 131 132 mAppBar.getLocationOnScreen(appbarOnScreenXY); 133 // At this point the app bar should still be in its original position. 134 // Allow for off-by-a-pixel margin of error. 135 assertEquals(originalAppbarTop, appbarOnScreenXY[1], 1); 136 assertEquals(originalAppbarBottom, appbarOnScreenXY[1] + appbarHeight, 1); 137 assertAppBarElevation(mDefaultElevationValue); 138 } 139 140 @Test 141 public void testScrollingToolbarAndPinnedTabs() { 142 configureContent(R.layout.design_appbar_toolbar_scroll_tabs_pinned, 143 R.string.design_appbar_toolbar_scroll_tabs_pin); 144 145 final int[] appbarOnScreenXY = new int[2]; 146 final int[] coordinatorLayoutOnScreenXY = new int[2]; 147 mAppBar.getLocationOnScreen(appbarOnScreenXY); 148 mCoordinatorLayout.getLocationOnScreen(coordinatorLayoutOnScreenXY); 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 tabsHeight = mTabLayout.getHeight(); 156 final int appbarHeight = mAppBar.getHeight(); 157 final int longSwipeAmount = 3 * appbarHeight / 2; 158 final int shortSwipeAmount = toolbarHeight; 159 160 // Perform a swipe-up gesture across the horizontal center of the screen, starting from 161 // just below the AppBarLayout 162 performVerticalSwipeUpGesture( 163 R.id.coordinator_layout, 164 centerX, 165 originalAppbarBottom + 20, 166 longSwipeAmount); 167 168 mAppBar.getLocationOnScreen(appbarOnScreenXY); 169 // At this point the tab bar should be visually snapped below the system status bar. 170 // Allow for off-by-a-pixel margin of error. 171 assertEquals(originalAppbarTop + tabsHeight, appbarOnScreenXY[1] + appbarHeight, 1); 172 assertAppBarElevation(mDefaultElevationValue); 173 174 // Perform another swipe-up gesture 175 performVerticalSwipeUpGesture( 176 R.id.coordinator_layout, 177 centerX, 178 originalAppbarBottom, 179 shortSwipeAmount); 180 181 mAppBar.getLocationOnScreen(appbarOnScreenXY); 182 // At this point the tab bar should still be visually snapped below the system status bar 183 // as it is in the pinned mode. Allow for off-by-a-pixel margin of error. 184 assertEquals(originalAppbarTop + tabsHeight, appbarOnScreenXY[1] + appbarHeight, 1); 185 assertAppBarElevation(mDefaultElevationValue); 186 187 // Perform a short swipe-down gesture across the horizontal center of the screen. 188 // Note that the swipe down is a bit longer than the swipe up to fully bring down 189 // the scrolled-away toolbar and tab layout 190 performVerticalSwipeDownGesture( 191 R.id.coordinator_layout, 192 centerX, 193 originalAppbarBottom - shortSwipeAmount, 194 3 * shortSwipeAmount / 2); 195 196 mAppBar.getLocationOnScreen(appbarOnScreenXY); 197 // At this point the app bar should be in its original position as it 198 // in scrolling mode and we've swiped down. Allow for off-by-a-pixel 199 // margin of error. 200 assertEquals(originalAppbarTop, appbarOnScreenXY[1], 1); 201 assertEquals(originalAppbarBottom, appbarOnScreenXY[1] + appbarHeight, 1); 202 assertAppBarElevation(mDefaultElevationValue); 203 204 // Perform another swipe-down gesture across the horizontal center of the screen. 205 performVerticalSwipeDownGesture( 206 R.id.coordinator_layout, 207 centerX, 208 originalAppbarBottom, 209 longSwipeAmount); 210 211 mAppBar.getLocationOnScreen(appbarOnScreenXY); 212 // At this point the app bar should be in its original position. 213 // Allow for off-by-a-pixel margin of error. 214 assertEquals(originalAppbarTop, appbarOnScreenXY[1], 1); 215 assertEquals(originalAppbarBottom, appbarOnScreenXY[1] + appbarHeight, 1); 216 assertAppBarElevation(mDefaultElevationValue); 217 218 // Perform yet another swipe-down gesture across the horizontal center of the screen. 219 performVerticalSwipeDownGesture( 220 R.id.coordinator_layout, 221 centerX, 222 originalAppbarBottom, 223 longSwipeAmount); 224 225 mAppBar.getLocationOnScreen(appbarOnScreenXY); 226 // At this point the app bar should still be in its original position. 227 // Allow for off-by-a-pixel margin of error. 228 assertEquals(originalAppbarTop, appbarOnScreenXY[1], 1); 229 assertEquals(originalAppbarBottom, appbarOnScreenXY[1] + appbarHeight, 1); 230 assertAppBarElevation(mDefaultElevationValue); 231 } 232 233 @LargeTest 234 @Test 235 public void testSnappingToolbarAndSnappingTabs() { 236 configureContent(R.layout.design_appbar_toolbar_scroll_tabs_scroll_snap, 237 R.string.design_appbar_toolbar_scroll_tabs_scroll_snap); 238 239 final int[] appbarOnScreenXY = new int[2]; 240 final int[] coordinatorLayoutOnScreenXY = new int[2]; 241 mAppBar.getLocationOnScreen(appbarOnScreenXY); 242 mCoordinatorLayout.getLocationOnScreen(coordinatorLayoutOnScreenXY); 243 244 final int originalAppbarTop = appbarOnScreenXY[1]; 245 final int originalAppbarBottom = appbarOnScreenXY[1] + mAppBar.getHeight(); 246 final int centerX = appbarOnScreenXY[0] + mAppBar.getWidth() / 2; 247 248 final int toolbarHeight = mToolbar.getHeight(); 249 final int tabsHeight = mTabLayout.getHeight(); 250 final int appbarHeight = mAppBar.getHeight(); 251 252 // Since AppBarLayout doesn't expose a way to track snap animations, the three possible 253 // options are 254 // a) track how vertical offsets and invalidation is propagated through the 255 // view hierarchy and wait until there are no more events of that kind 256 // b) run a dummy Espresso action that waits until the main thread is idle 257 // c) sleep for a hardcoded period of time to "wait" until the snap animation is done 258 // In this test method we go with option b) 259 260 // Perform a swipe-up gesture across the horizontal center of the screen. The amount 261 // of swipe is 25% of the toolbar height and we expect the snap behavior to "move" 262 // the app bar back to its original position. 263 performVerticalSwipeUpGesture( 264 R.id.coordinator_layout, 265 centerX, 266 originalAppbarBottom + toolbarHeight, 267 toolbarHeight / 4); 268 269 // Wait for the snap animation to be done 270 waitForSnapAnimationToFinish(); 271 272 mAppBar.getLocationOnScreen(appbarOnScreenXY); 273 // At this point the app bar should be in its original position as it 274 // in snapping mode and we haven't swiped "enough". Allow for off-by-a-pixel 275 // margin of error. 276 assertEquals(originalAppbarTop, appbarOnScreenXY[1], 1); 277 assertEquals(originalAppbarBottom, appbarOnScreenXY[1] + appbarHeight, 1); 278 assertAppBarElevation(mDefaultElevationValue); 279 280 // Perform a slightly longer swipe-up gesture, this time by 75% of the toolbar height. 281 // We expect the snap behavior to move the app bar to snap the tab layout below the 282 // system status bar. 283 performVerticalSwipeUpGesture( 284 R.id.coordinator_layout, 285 centerX, 286 originalAppbarBottom + toolbarHeight, 287 3 * toolbarHeight / 4); 288 289 // Wait for the snap animation to be done 290 waitForSnapAnimationToFinish(); 291 292 mAppBar.getLocationOnScreen(appbarOnScreenXY); 293 // At this point the app bar should "snap" the toolbar away and align the tab layout below 294 // the system status bar. Allow for off-by-a-pixel margin of error. 295 assertEquals(originalAppbarTop + tabsHeight, appbarOnScreenXY[1] + appbarHeight, 1); 296 assertAppBarElevation(mDefaultElevationValue); 297 298 // Perform a short swipe-up gesture, this time by 25% of the tab layout height. We expect 299 // snap behavior to move the app bar back to snap the tab layout below the system status 300 // bar. 301 performVerticalSwipeUpGesture( 302 R.id.coordinator_layout, 303 centerX, 304 originalAppbarBottom + toolbarHeight, 305 tabsHeight / 4); 306 307 // Wait for the snap animation to be done 308 waitForSnapAnimationToFinish(); 309 310 mAppBar.getLocationOnScreen(appbarOnScreenXY); 311 // At this point the app bar should "snap" back to align the tab layout below 312 // the system status bar. Allow for off-by-a-pixel margin of error. 313 assertEquals(originalAppbarTop + tabsHeight, appbarOnScreenXY[1] + appbarHeight, 1); 314 assertAppBarElevation(mDefaultElevationValue); 315 316 // Perform a longer swipe-up gesture, this time by 75% of the tab layout height. We expect 317 // snap behavior to move the app bar fully away from the screen. 318 performVerticalSwipeUpGesture( 319 R.id.coordinator_layout, 320 centerX, 321 originalAppbarBottom + toolbarHeight, 322 3 * tabsHeight / 4); 323 324 // Wait for the snap animation to be done 325 waitForSnapAnimationToFinish(); 326 327 mAppBar.getLocationOnScreen(appbarOnScreenXY); 328 // At this point the app bar should not be visually "present" on the screen, with its bottom 329 // edge aligned with the system status bar. Allow for off-by-a-pixel margin of error. 330 assertEquals(originalAppbarTop, appbarOnScreenXY[1] + appbarHeight, 1); 331 assertAppBarElevation(mDefaultElevationValue); 332 333 // Perform a short swipe-down gesture by 25% of the tab layout height. We expect 334 // snap behavior to move the app bar back fully away from the screen. 335 performVerticalSwipeDownGesture( 336 R.id.coordinator_layout, 337 centerX, 338 originalAppbarBottom + toolbarHeight, 339 tabsHeight / 4); 340 341 // Wait for the snap animation to be done 342 waitForSnapAnimationToFinish(); 343 344 mAppBar.getLocationOnScreen(appbarOnScreenXY); 345 // At this point the app bar should still not be visually "present" on the screen, with 346 // its bottom edge aligned with the system status bar. Allow for off-by-a-pixel margin 347 // of error. 348 assertEquals(originalAppbarTop, appbarOnScreenXY[1] + appbarHeight, 1); 349 assertAppBarElevation(mDefaultElevationValue); 350 351 // Perform a longer swipe-up gesture, this time by 75% of the tab layout height. We expect 352 // snap behavior to move the app bar to snap the tab layout below the system status 353 // bar. 354 performVerticalSwipeDownGesture( 355 R.id.coordinator_layout, 356 centerX, 357 originalAppbarBottom + toolbarHeight, 358 3 * tabsHeight / 4); 359 360 // Wait for the snap animation to be done 361 waitForSnapAnimationToFinish(); 362 363 mAppBar.getLocationOnScreen(appbarOnScreenXY); 364 // At this point the app bar should "snap" the toolbar away and align the tab layout below 365 // the system status bar. Allow for off-by-a-pixel margin of error. 366 assertEquals(originalAppbarTop + tabsHeight, appbarOnScreenXY[1] + appbarHeight, 1); 367 assertAppBarElevation(mDefaultElevationValue); 368 369 // Perform a short swipe-down gesture by 25% of the toolbar height. We expect 370 // snap behavior to align the tab layout below the system status bar 371 performVerticalSwipeDownGesture( 372 R.id.coordinator_layout, 373 centerX, 374 originalAppbarBottom + toolbarHeight, 375 toolbarHeight / 4); 376 377 // Wait for the snap animation to be done 378 waitForSnapAnimationToFinish(); 379 380 mAppBar.getLocationOnScreen(appbarOnScreenXY); 381 // At this point the app bar should still align the tab layout below 382 // the system status bar. Allow for off-by-a-pixel margin of error. 383 assertEquals(originalAppbarTop + tabsHeight, appbarOnScreenXY[1] + appbarHeight, 1); 384 assertAppBarElevation(mDefaultElevationValue); 385 386 // Perform a longer swipe-up gesture, this time by 75% of the toolbar height. We expect 387 // snap behavior to move the app bar back to its original place (fully visible). 388 performVerticalSwipeDownGesture( 389 R.id.coordinator_layout, 390 centerX, 391 originalAppbarBottom + toolbarHeight, 392 3 * tabsHeight / 4); 393 394 // Wait for the snap animation to be done 395 waitForSnapAnimationToFinish(); 396 397 mAppBar.getLocationOnScreen(appbarOnScreenXY); 398 // At this point the app bar should be in its original position. 399 // Allow for off-by-a-pixel margin of error. 400 assertEquals(originalAppbarTop, appbarOnScreenXY[1], 1); 401 assertEquals(originalAppbarBottom, appbarOnScreenXY[1] + appbarHeight, 1); 402 assertAppBarElevation(mDefaultElevationValue); 403 } 404 405 private void waitForSnapAnimationToFinish() { 406 final AppBarLayout.Behavior behavior = (AppBarLayout.Behavior) 407 ((CoordinatorLayout.LayoutParams) mAppBar.getLayoutParams()).getBehavior(); 408 while (behavior.isOffsetAnimatorRunning()) { 409 SystemClock.sleep(16); 410 } 411 } 412} 413