1 /* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 5 * except in compliance with the License. You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software distributed under the 10 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 11 * KIND, either express or implied. See the License for the specific language governing 12 * permissions and limitations under the License. 13 */ 14 15 package com.android.systemui.qs; 16 17 import static com.android.systemui.qs.dagger.QSFragmentModule.QQS_FOOTER; 18 import static com.android.systemui.qs.dagger.QSFragmentModule.QS_FOOTER; 19 20 import android.animation.TimeInterpolator; 21 import android.animation.ValueAnimator; 22 import android.util.Log; 23 import android.view.View; 24 import android.view.View.OnAttachStateChangeListener; 25 import android.view.View.OnLayoutChangeListener; 26 27 import com.android.systemui.dagger.qualifiers.Main; 28 import com.android.systemui.plugins.qs.QS; 29 import com.android.systemui.plugins.qs.QSTile; 30 import com.android.systemui.plugins.qs.QSTileView; 31 import com.android.systemui.qs.PagedTileLayout.PageListener; 32 import com.android.systemui.qs.QSHost.Callback; 33 import com.android.systemui.qs.QSPanel.QSTileLayout; 34 import com.android.systemui.qs.TouchAnimator.Builder; 35 import com.android.systemui.qs.TouchAnimator.Listener; 36 import com.android.systemui.qs.dagger.QSScope; 37 import com.android.systemui.qs.tileimpl.HeightOverrideable; 38 import com.android.systemui.tuner.TunerService; 39 import com.android.systemui.tuner.TunerService.Tunable; 40 import com.android.wm.shell.animation.Interpolators; 41 42 import java.util.ArrayList; 43 import java.util.Collection; 44 import java.util.List; 45 import java.util.concurrent.Executor; 46 47 import javax.inject.Inject; 48 import javax.inject.Named; 49 50 /** */ 51 @QSScope 52 public class QSAnimator implements Callback, PageListener, Listener, OnLayoutChangeListener, 53 OnAttachStateChangeListener, Tunable { 54 55 private static final String TAG = "QSAnimator"; 56 57 private static final String ALLOW_FANCY_ANIMATION = "sysui_qs_fancy_anim"; 58 private static final String MOVE_FULL_ROWS = "sysui_qs_move_whole_rows"; 59 60 public static final float EXPANDED_TILE_DELAY = .86f; 61 public static final float SHORT_PARALLAX_AMOUNT = 0.1f; 62 private static final long QQS_FADE_IN_DURATION = 200L; 63 // Fade out faster than fade in to finish before QQS hides. 64 private static final long QQS_FADE_OUT_DURATION = 50L; 65 66 /** 67 * List of all views that will be reset when clearing animation state 68 * see {@link #clearAnimationState()} } 69 */ 70 private final ArrayList<View> mAllViews = new ArrayList<>(); 71 /** 72 * List of {@link View}s representing Quick Settings that are being animated from the quick QS 73 * position to the normal QS panel. These views will only show once the animation is complete, 74 * to prevent overlapping of semi transparent views 75 */ 76 private final ArrayList<View> mAnimatedQsViews = new ArrayList<>(); 77 private final QuickQSPanel mQuickQsPanel; 78 private final QSPanelController mQsPanelController; 79 private final QuickQSPanelController mQuickQSPanelController; 80 private final QuickStatusBarHeader mQuickStatusBarHeader; 81 private final QSSecurityFooter mSecurityFooter; 82 private final QS mQs; 83 private final View mQSFooterActions; 84 private final View mQQSFooterActions; 85 86 private PagedTileLayout mPagedLayout; 87 88 private boolean mOnFirstPage = true; 89 private QSExpansionPathInterpolator mQSExpansionPathInterpolator; 90 private TouchAnimator mFirstPageAnimator; 91 private TouchAnimator mFirstPageDelayedAnimator; 92 private TouchAnimator mTranslationXAnimator; 93 private TouchAnimator mTranslationYAnimator; 94 private TouchAnimator mNonfirstPageAnimator; 95 private TouchAnimator mNonfirstPageDelayedAnimator; 96 // This animates fading of SecurityFooter and media divider 97 private TouchAnimator mAllPagesDelayedAnimator; 98 private TouchAnimator mBrightnessAnimator; 99 private TouchAnimator mQQSFooterActionsAnimator; 100 private HeightExpansionAnimator mQQSTileHeightAnimator; 101 private HeightExpansionAnimator mOtherTilesExpandAnimator; 102 103 private boolean mNeedsAnimatorUpdate = false; 104 private boolean mToShowing; 105 private boolean mOnKeyguard; 106 107 private boolean mAllowFancy; 108 private boolean mFullRows; 109 private int mNumQuickTiles; 110 private float mLastPosition; 111 private final QSTileHost mHost; 112 private final Executor mExecutor; 113 private final TunerService mTunerService; 114 private boolean mShowCollapsedOnKeyguard; 115 private boolean mTranslateWhileExpanding; 116 117 @Inject QSAnimator(QS qs, QuickQSPanel quickPanel, QuickStatusBarHeader quickStatusBarHeader, QSPanelController qsPanelController, QuickQSPanelController quickQSPanelController, QSTileHost qsTileHost, QSSecurityFooter securityFooter, @Main Executor executor, TunerService tunerService, QSExpansionPathInterpolator qsExpansionPathInterpolator, @Named(QS_FOOTER) FooterActionsView qsFooterActionsView, @Named(QQS_FOOTER) FooterActionsView qqsFooterActionsView)118 public QSAnimator(QS qs, QuickQSPanel quickPanel, QuickStatusBarHeader quickStatusBarHeader, 119 QSPanelController qsPanelController, 120 QuickQSPanelController quickQSPanelController, QSTileHost qsTileHost, 121 QSSecurityFooter securityFooter, @Main Executor executor, TunerService tunerService, 122 QSExpansionPathInterpolator qsExpansionPathInterpolator, 123 @Named(QS_FOOTER) FooterActionsView qsFooterActionsView, 124 @Named(QQS_FOOTER) FooterActionsView qqsFooterActionsView) { 125 mQs = qs; 126 mQuickQsPanel = quickPanel; 127 mQsPanelController = qsPanelController; 128 mQuickQSPanelController = quickQSPanelController; 129 mQuickStatusBarHeader = quickStatusBarHeader; 130 mQQSFooterActions = qqsFooterActionsView; 131 mQSFooterActions = qsFooterActionsView; 132 mSecurityFooter = securityFooter; 133 mHost = qsTileHost; 134 mExecutor = executor; 135 mTunerService = tunerService; 136 mQSExpansionPathInterpolator = qsExpansionPathInterpolator; 137 mHost.addCallback(this); 138 mQsPanelController.addOnAttachStateChangeListener(this); 139 qs.getView().addOnLayoutChangeListener(this); 140 if (mQsPanelController.isAttachedToWindow()) { 141 onViewAttachedToWindow(null); 142 } 143 QSTileLayout tileLayout = mQsPanelController.getTileLayout(); 144 if (tileLayout instanceof PagedTileLayout) { 145 mPagedLayout = ((PagedTileLayout) tileLayout); 146 } else { 147 Log.w(TAG, "QS Not using page layout"); 148 } 149 mQsPanelController.setPageListener(this); 150 } 151 onRtlChanged()152 public void onRtlChanged() { 153 updateAnimators(); 154 } 155 156 /** 157 * Request an update to the animators. This will update them lazily next time the position 158 * is changed. 159 */ requestAnimatorUpdate()160 public void requestAnimatorUpdate() { 161 mNeedsAnimatorUpdate = true; 162 } 163 setOnKeyguard(boolean onKeyguard)164 public void setOnKeyguard(boolean onKeyguard) { 165 mOnKeyguard = onKeyguard; 166 updateQQSVisibility(); 167 if (mOnKeyguard) { 168 clearAnimationState(); 169 } 170 } 171 172 /** 173 * Sets whether or not the keyguard is currently being shown with a collapsed header. 174 */ setShowCollapsedOnKeyguard(boolean showCollapsedOnKeyguard)175 void setShowCollapsedOnKeyguard(boolean showCollapsedOnKeyguard) { 176 mShowCollapsedOnKeyguard = showCollapsedOnKeyguard; 177 updateQQSVisibility(); 178 setCurrentPosition(); 179 } 180 181 setCurrentPosition()182 private void setCurrentPosition() { 183 setPosition(mLastPosition); 184 } 185 updateQQSVisibility()186 private void updateQQSVisibility() { 187 mQuickQsPanel.setVisibility(mOnKeyguard 188 && !mShowCollapsedOnKeyguard ? View.INVISIBLE : View.VISIBLE); 189 } 190 191 @Override onViewAttachedToWindow(View v)192 public void onViewAttachedToWindow(View v) { 193 mTunerService.addTunable(this, ALLOW_FANCY_ANIMATION, 194 MOVE_FULL_ROWS); 195 } 196 197 @Override onViewDetachedFromWindow(View v)198 public void onViewDetachedFromWindow(View v) { 199 mHost.removeCallback(this); 200 mTunerService.removeTunable(this); 201 } 202 203 @Override onTuningChanged(String key, String newValue)204 public void onTuningChanged(String key, String newValue) { 205 if (ALLOW_FANCY_ANIMATION.equals(key)) { 206 mAllowFancy = TunerService.parseIntegerSwitch(newValue, true); 207 if (!mAllowFancy) { 208 clearAnimationState(); 209 } 210 } else if (MOVE_FULL_ROWS.equals(key)) { 211 mFullRows = TunerService.parseIntegerSwitch(newValue, true); 212 } 213 updateAnimators(); 214 } 215 216 @Override onPageChanged(boolean isFirst)217 public void onPageChanged(boolean isFirst) { 218 if (mOnFirstPage == isFirst) return; 219 if (!isFirst) { 220 clearAnimationState(); 221 } 222 mOnFirstPage = isFirst; 223 } 224 translateContent( View qqsView, View qsView, View commonParent, int xOffset, int yOffset, int[] temp, TouchAnimator.Builder animatorBuilderX, TouchAnimator.Builder animatorBuilderY )225 private void translateContent( 226 View qqsView, 227 View qsView, 228 View commonParent, 229 int xOffset, 230 int yOffset, 231 int[] temp, 232 TouchAnimator.Builder animatorBuilderX, 233 TouchAnimator.Builder animatorBuilderY 234 ) { 235 getRelativePosition(temp, qqsView, commonParent); 236 int qqsPosX = temp[0]; 237 int qqsPosY = temp[1]; 238 getRelativePosition(temp, qsView, commonParent); 239 int qsPosX = temp[0]; 240 int qsPosY = temp[1]; 241 242 int xDiff = qsPosX - qqsPosX - xOffset; 243 animatorBuilderX.addFloat(qqsView, "translationX", 0, xDiff); 244 animatorBuilderX.addFloat(qsView, "translationX", -xDiff, 0); 245 int yDiff = qsPosY - qqsPosY - yOffset; 246 animatorBuilderY.addFloat(qqsView, "translationY", 0, yDiff); 247 animatorBuilderY.addFloat(qsView, "translationY", -yDiff, 0); 248 mAllViews.add(qqsView); 249 mAllViews.add(qsView); 250 } 251 updateAnimators()252 private void updateAnimators() { 253 mNeedsAnimatorUpdate = false; 254 TouchAnimator.Builder firstPageBuilder = new Builder(); 255 TouchAnimator.Builder translationYBuilder = new Builder(); 256 TouchAnimator.Builder translationXBuilder = new Builder(); 257 258 Collection<QSTile> tiles = mHost.getTiles(); 259 int count = 0; 260 int[] loc1 = new int[2]; 261 int[] loc2 = new int[2]; 262 263 clearAnimationState(); 264 mAllViews.clear(); 265 mAnimatedQsViews.clear(); 266 mQQSTileHeightAnimator = null; 267 mOtherTilesExpandAnimator = null; 268 269 mNumQuickTiles = mQuickQsPanel.getNumQuickTiles(); 270 271 QSTileLayout tileLayout = mQsPanelController.getTileLayout(); 272 mAllViews.add((View) tileLayout); 273 int height = mQs.getView() != null ? mQs.getView().getMeasuredHeight() : 0; 274 int heightDiff = height - mQs.getHeader().getBottom() 275 + mQs.getHeader().getPaddingBottom(); 276 if (!mTranslateWhileExpanding) { 277 heightDiff *= SHORT_PARALLAX_AMOUNT; 278 } 279 firstPageBuilder.addFloat(tileLayout, "translationY", heightDiff, 0); 280 281 int qqsTileHeight = 0; 282 283 if (mQsPanelController.areThereTiles()) { 284 for (QSTile tile : tiles) { 285 QSTileView tileView = mQsPanelController.getTileView(tile); 286 if (tileView == null) { 287 Log.e(TAG, "tileView is null " + tile.getTileSpec()); 288 continue; 289 } 290 final View tileIcon = tileView.getIcon().getIconView(); 291 View view = mQs.getView(); 292 293 // This case: less tiles to animate in small displays. 294 if (count < mQuickQSPanelController.getTileLayout().getNumVisibleTiles() 295 && mAllowFancy) { 296 // Quick tiles. 297 QSTileView quickTileView = mQuickQSPanelController.getTileView(tile); 298 if (quickTileView == null) continue; 299 300 getRelativePosition(loc1, quickTileView, view); 301 getRelativePosition(loc2, tileView, view); 302 int yOffset = loc2[1] - loc1[1]; 303 int xOffset = loc2[0] - loc1[0]; 304 305 // Offset the translation animation on the views 306 // (that goes from 0 to getOffsetTranslation) 307 int offsetWithQSBHTranslation = 308 yOffset - mQuickStatusBarHeader.getOffsetTranslation(); 309 translationYBuilder.addFloat(quickTileView, "translationY", 0, 310 offsetWithQSBHTranslation); 311 translationYBuilder.addFloat(tileView, "translationY", 312 -offsetWithQSBHTranslation, 0); 313 314 translationXBuilder.addFloat(quickTileView, "translationX", 0, xOffset); 315 translationXBuilder.addFloat(tileView, "translationX", -xOffset, 0); 316 317 if (mQQSTileHeightAnimator == null) { 318 mQQSTileHeightAnimator = new HeightExpansionAnimator(this, 319 quickTileView.getMeasuredHeight(), tileView.getMeasuredHeight()); 320 qqsTileHeight = quickTileView.getMeasuredHeight(); 321 } 322 323 mQQSTileHeightAnimator.addView(quickTileView); 324 325 // Icons 326 translateContent( 327 quickTileView.getIcon(), 328 tileView.getIcon(), 329 view, 330 xOffset, 331 yOffset, 332 loc1, 333 translationXBuilder, 334 translationYBuilder 335 ); 336 337 // Label containers 338 translateContent( 339 quickTileView.getLabelContainer(), 340 tileView.getLabelContainer(), 341 view, 342 xOffset, 343 yOffset, 344 loc1, 345 translationXBuilder, 346 translationYBuilder 347 ); 348 349 // Secondary icon 350 translateContent( 351 quickTileView.getSecondaryIcon(), 352 tileView.getSecondaryIcon(), 353 view, 354 xOffset, 355 yOffset, 356 loc1, 357 translationXBuilder, 358 translationYBuilder 359 ); 360 361 firstPageBuilder.addFloat(quickTileView.getSecondaryLabel(), "alpha", 0, 1); 362 363 mAnimatedQsViews.add(tileView); 364 mAllViews.add(quickTileView); 365 mAllViews.add(quickTileView.getSecondaryLabel()); 366 } else if (mFullRows && isIconInAnimatedRow(count)) { 367 368 firstPageBuilder.addFloat(tileView, "translationY", -heightDiff, 0); 369 370 mAllViews.add(tileIcon); 371 } else { 372 // Pretend there's a corresponding QQS tile (for the position) that we are 373 // expanding from. 374 SideLabelTileLayout qqsLayout = 375 (SideLabelTileLayout) mQuickQsPanel.getTileLayout(); 376 getRelativePosition(loc1, qqsLayout, view); 377 getRelativePosition(loc2, tileView, view); 378 int diff = loc2[1] - (loc1[1] + qqsLayout.getPhantomTopPosition(count)); 379 translationYBuilder.addFloat(tileView, "translationY", -diff, 0); 380 if (mOtherTilesExpandAnimator == null) { 381 mOtherTilesExpandAnimator = 382 new HeightExpansionAnimator( 383 this, qqsTileHeight, tileView.getMeasuredHeight()); 384 } 385 mOtherTilesExpandAnimator.addView(tileView); 386 tileView.setClipChildren(true); 387 tileView.setClipToPadding(true); 388 firstPageBuilder.addFloat(tileView.getSecondaryLabel(), "alpha", 0, 1); 389 mAllViews.add(tileView.getSecondaryLabel()); 390 } 391 392 mAllViews.add(tileView); 393 count++; 394 } 395 } 396 397 if (mAllowFancy) { 398 animateBrightnessSlider(firstPageBuilder); 399 400 mFirstPageAnimator = firstPageBuilder 401 .setListener(this) 402 .build(); 403 // Fade in the tiles/labels as we reach the final position. 404 Builder builder = new Builder() 405 .addFloat(tileLayout, "alpha", 0, 1); 406 mFirstPageDelayedAnimator = builder.build(); 407 408 if (mQQSFooterActions.getVisibility() != View.GONE) { 409 // only when qqs footer is present (which means split shade mode) it needs to 410 // be animated 411 updateQQSFooterAnimation(); 412 } 413 414 415 // Fade in the security footer and the divider as we reach the final position 416 builder = new Builder().setStartDelay(EXPANDED_TILE_DELAY); 417 builder.addFloat(mSecurityFooter.getView(), "alpha", 0, 1); 418 if (mQsPanelController.shouldUseHorizontalLayout() 419 && mQsPanelController.mMediaHost.hostView != null) { 420 builder.addFloat(mQsPanelController.mMediaHost.hostView, "alpha", 0, 1); 421 } else { 422 // In portrait, media view should always be visible 423 mQsPanelController.mMediaHost.hostView.setAlpha(1.0f); 424 } 425 mAllPagesDelayedAnimator = builder.build(); 426 mAllViews.add(mSecurityFooter.getView()); 427 translationYBuilder.setInterpolator(mQSExpansionPathInterpolator.getYInterpolator()); 428 translationXBuilder.setInterpolator(mQSExpansionPathInterpolator.getXInterpolator()); 429 mTranslationYAnimator = translationYBuilder.build(); 430 mTranslationXAnimator = translationXBuilder.build(); 431 if (mQQSTileHeightAnimator != null) { 432 mQQSTileHeightAnimator.setInterpolator( 433 mQSExpansionPathInterpolator.getYInterpolator()); 434 } 435 if (mOtherTilesExpandAnimator != null) { 436 mOtherTilesExpandAnimator.setInterpolator( 437 mQSExpansionPathInterpolator.getYInterpolator()); 438 } 439 } 440 mNonfirstPageAnimator = new TouchAnimator.Builder() 441 .addFloat(mQuickQsPanel, "alpha", 1, 0) 442 .setListener(mNonFirstPageListener) 443 .setEndDelay(.5f) 444 .build(); 445 mNonfirstPageDelayedAnimator = new TouchAnimator.Builder() 446 .setStartDelay(.14f) 447 .addFloat(tileLayout, "alpha", 0, 1).build(); 448 } 449 animateBrightnessSlider(Builder firstPageBuilder)450 private void animateBrightnessSlider(Builder firstPageBuilder) { 451 View qsBrightness = mQsPanelController.getBrightnessView(); 452 View qqsBrightness = mQuickQSPanelController.getBrightnessView(); 453 if (qqsBrightness != null && qqsBrightness.getVisibility() == View.VISIBLE) { 454 // animating in split shade mode 455 mAnimatedQsViews.add(qsBrightness); 456 mAllViews.add(qqsBrightness); 457 int translationY = getRelativeTranslationY(qsBrightness, qqsBrightness); 458 mBrightnessAnimator = new Builder() 459 // we need to animate qs brightness even if animation will not be visible, 460 // as we might start from sliderScaleY set to 0.3 if device was in collapsed QS 461 // portrait orientation before 462 .addFloat(qsBrightness, "sliderScaleY", 0.3f, 1) 463 .addFloat(qqsBrightness, "translationY", 0, translationY) 464 .build(); 465 } else if (qsBrightness != null) { 466 firstPageBuilder.addFloat(qsBrightness, "translationY", 467 qsBrightness.getMeasuredHeight() * 0.5f, 0); 468 mBrightnessAnimator = new Builder() 469 .addFloat(qsBrightness, "alpha", 0, 1) 470 .addFloat(qsBrightness, "sliderScaleY", 0.3f, 1) 471 .setInterpolator(Interpolators.ALPHA_IN) 472 .setStartDelay(0.3f) 473 .build(); 474 mAllViews.add(qsBrightness); 475 } else { 476 mBrightnessAnimator = null; 477 } 478 } 479 updateQQSFooterAnimation()480 private void updateQQSFooterAnimation() { 481 int translationY = getRelativeTranslationY(mQSFooterActions, mQQSFooterActions); 482 mQQSFooterActionsAnimator = new TouchAnimator.Builder() 483 .addFloat(mQQSFooterActions, "translationY", 0, translationY) 484 .build(); 485 mAnimatedQsViews.add(mQSFooterActions); 486 } 487 getRelativeTranslationY(View view1, View view2)488 private int getRelativeTranslationY(View view1, View view2) { 489 int[] qsPosition = new int[2]; 490 int[] qqsPosition = new int[2]; 491 View commonView = mQs.getView(); 492 getRelativePositionInt(qsPosition, view1, commonView); 493 getRelativePositionInt(qqsPosition, view2, commonView); 494 return (qsPosition[1] - qqsPosition[1]) - mQuickStatusBarHeader.getOffsetTranslation(); 495 } 496 isIconInAnimatedRow(int count)497 private boolean isIconInAnimatedRow(int count) { 498 if (mPagedLayout == null) { 499 return false; 500 } 501 final int columnCount = mPagedLayout.getColumnCount(); 502 return count < ((mNumQuickTiles + columnCount - 1) / columnCount) * columnCount; 503 } 504 getRelativePosition(int[] loc1, View view, View parent)505 private void getRelativePosition(int[] loc1, View view, View parent) { 506 loc1[0] = 0 + view.getWidth() / 2; 507 loc1[1] = 0; 508 getRelativePositionInt(loc1, view, parent); 509 } 510 getRelativePositionInt(int[] loc1, View view, View parent)511 private void getRelativePositionInt(int[] loc1, View view, View parent) { 512 if(view == parent || view == null) return; 513 // Ignore tile pages as they can have some offset we don't want to take into account in 514 // RTL. 515 if (!isAPage(view)) { 516 loc1[0] += view.getLeft(); 517 loc1[1] += view.getTop(); 518 } 519 if (!(view instanceof PagedTileLayout)) { 520 // Remove the scrolling position of all scroll views other than the viewpager 521 loc1[0] -= view.getScrollX(); 522 loc1[1] -= view.getScrollY(); 523 } 524 getRelativePositionInt(loc1, (View) view.getParent(), parent); 525 } 526 527 // Returns true if the view is a possible page in PagedTileLayout isAPage(View view)528 private boolean isAPage(View view) { 529 return view.getClass().equals(SideLabelTileLayout.class); 530 } 531 setPosition(float position)532 public void setPosition(float position) { 533 if (mNeedsAnimatorUpdate) { 534 updateAnimators(); 535 } 536 if (mFirstPageAnimator == null) return; 537 if (mOnKeyguard) { 538 if (mShowCollapsedOnKeyguard) { 539 position = 0; 540 } else { 541 position = 1; 542 } 543 } 544 mLastPosition = position; 545 if (mOnFirstPage && mAllowFancy) { 546 mQuickQsPanel.setAlpha(1); 547 mFirstPageAnimator.setPosition(position); 548 mFirstPageDelayedAnimator.setPosition(position); 549 mTranslationYAnimator.setPosition(position); 550 mTranslationXAnimator.setPosition(position); 551 if (mQQSTileHeightAnimator != null) { 552 mQQSTileHeightAnimator.setPosition(position); 553 } 554 if (mOtherTilesExpandAnimator != null) { 555 mOtherTilesExpandAnimator.setPosition(position); 556 } 557 } else { 558 mNonfirstPageAnimator.setPosition(position); 559 mNonfirstPageDelayedAnimator.setPosition(position); 560 } 561 if (mAllowFancy) { 562 mAllPagesDelayedAnimator.setPosition(position); 563 if (mBrightnessAnimator != null) { 564 mBrightnessAnimator.setPosition(position); 565 } 566 if (mQQSFooterActionsAnimator != null) { 567 mQQSFooterActionsAnimator.setPosition(position); 568 } 569 } 570 } 571 572 @Override onAnimationAtStart()573 public void onAnimationAtStart() { 574 mQuickQsPanel.setVisibility(View.VISIBLE); 575 } 576 577 @Override onAnimationAtEnd()578 public void onAnimationAtEnd() { 579 mQuickQsPanel.setVisibility(View.INVISIBLE); 580 final int N = mAnimatedQsViews.size(); 581 for (int i = 0; i < N; i++) { 582 mAnimatedQsViews.get(i).setVisibility(View.VISIBLE); 583 } 584 } 585 586 @Override onAnimationStarted()587 public void onAnimationStarted() { 588 updateQQSVisibility(); 589 if (mOnFirstPage) { 590 final int N = mAnimatedQsViews.size(); 591 for (int i = 0; i < N; i++) { 592 mAnimatedQsViews.get(i).setVisibility(View.INVISIBLE); 593 } 594 } 595 } 596 clearAnimationState()597 private void clearAnimationState() { 598 final int N = mAllViews.size(); 599 mQuickQsPanel.setAlpha(0); 600 for (int i = 0; i < N; i++) { 601 View v = mAllViews.get(i); 602 v.setAlpha(1); 603 v.setTranslationX(0); 604 v.setTranslationY(0); 605 v.setScaleY(1f); 606 if (v instanceof SideLabelTileLayout) { 607 ((SideLabelTileLayout) v).setClipChildren(false); 608 ((SideLabelTileLayout) v).setClipToPadding(false); 609 } 610 } 611 if (mQQSTileHeightAnimator != null) { 612 mQQSTileHeightAnimator.resetViewsHeights(); 613 } 614 if (mOtherTilesExpandAnimator != null) { 615 mOtherTilesExpandAnimator.resetViewsHeights(); 616 } 617 final int N2 = mAnimatedQsViews.size(); 618 for (int i = 0; i < N2; i++) { 619 mAnimatedQsViews.get(i).setVisibility(View.VISIBLE); 620 } 621 } 622 623 @Override onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom)624 public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, 625 int oldTop, int oldRight, int oldBottom) { 626 mExecutor.execute(mUpdateAnimators); 627 } 628 629 @Override onTilesChanged()630 public void onTilesChanged() { 631 // Give the QS panels a moment to generate their new tiles, then create all new animators 632 // hooked up to the new views. 633 mExecutor.execute(mUpdateAnimators); 634 } 635 636 private final TouchAnimator.Listener mNonFirstPageListener = 637 new TouchAnimator.ListenerAdapter() { 638 @Override 639 public void onAnimationAtEnd() { 640 mQuickQsPanel.setVisibility(View.INVISIBLE); 641 } 642 643 @Override 644 public void onAnimationStarted() { 645 mQuickQsPanel.setVisibility(View.VISIBLE); 646 } 647 }; 648 649 private final Runnable mUpdateAnimators = () -> { 650 updateAnimators(); 651 setCurrentPosition(); 652 }; 653 654 /** 655 * True whe QS will be pulled from the top, false when it will be clipped. 656 */ setTranslateWhileExpanding(boolean shouldTranslate)657 public void setTranslateWhileExpanding(boolean shouldTranslate) { 658 mTranslateWhileExpanding = shouldTranslate; 659 } 660 661 private static class HeightExpansionAnimator { 662 private final List<View> mViews = new ArrayList<>(); 663 private final ValueAnimator mAnimator; 664 private final TouchAnimator.Listener mListener; 665 666 private final ValueAnimator.AnimatorUpdateListener mUpdateListener = 667 new ValueAnimator.AnimatorUpdateListener() { 668 float mLastT = -1; 669 @Override 670 public void onAnimationUpdate(ValueAnimator valueAnimator) { 671 float t = valueAnimator.getAnimatedFraction(); 672 final int viewCount = mViews.size(); 673 int height = (Integer) valueAnimator.getAnimatedValue(); 674 for (int i = 0; i < viewCount; i++) { 675 View v = mViews.get(i); 676 if (v instanceof HeightOverrideable) { 677 ((HeightOverrideable) v).setHeightOverride(height); 678 } else { 679 v.setBottom(v.getTop() + height); 680 } 681 } 682 if (t == 0f) { 683 mListener.onAnimationAtStart(); 684 } else if (t == 1f) { 685 mListener.onAnimationAtEnd(); 686 } else if (mLastT <= 0 || mLastT == 1) { 687 mListener.onAnimationStarted(); 688 } 689 mLastT = t; 690 } 691 }; 692 HeightExpansionAnimator(TouchAnimator.Listener listener, int startHeight, int endHeight)693 HeightExpansionAnimator(TouchAnimator.Listener listener, int startHeight, int endHeight) { 694 mListener = listener; 695 mAnimator = ValueAnimator.ofInt(startHeight, endHeight); 696 mAnimator.setRepeatCount(ValueAnimator.INFINITE); 697 mAnimator.setRepeatMode(ValueAnimator.REVERSE); 698 mAnimator.addUpdateListener(mUpdateListener); 699 } 700 addView(View v)701 void addView(View v) { 702 mViews.add(v); 703 } 704 setInterpolator(TimeInterpolator interpolator)705 void setInterpolator(TimeInterpolator interpolator) { 706 mAnimator.setInterpolator(interpolator); 707 } 708 setPosition(float position)709 void setPosition(float position) { 710 mAnimator.setCurrentFraction(position); 711 } 712 resetViewsHeights()713 void resetViewsHeights() { 714 final int viewsCount = mViews.size(); 715 for (int i = 0; i < viewsCount; i++) { 716 View v = mViews.get(i); 717 if (v instanceof HeightOverrideable) { 718 ((HeightOverrideable) v).resetOverride(); 719 } else { 720 v.setBottom(v.getTop() + v.getMeasuredHeight()); 721 } 722 } 723 } 724 } 725 } 726