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 android.animation.TimeInterpolator; 18 import android.animation.ValueAnimator; 19 import android.util.Log; 20 import android.view.View; 21 import android.view.View.OnAttachStateChangeListener; 22 import android.view.View.OnLayoutChangeListener; 23 24 import com.android.systemui.dagger.qualifiers.Main; 25 import com.android.systemui.plugins.qs.QS; 26 import com.android.systemui.plugins.qs.QSTile; 27 import com.android.systemui.plugins.qs.QSTileView; 28 import com.android.systemui.qs.PagedTileLayout.PageListener; 29 import com.android.systemui.qs.QSHost.Callback; 30 import com.android.systemui.qs.QSPanel.QSTileLayout; 31 import com.android.systemui.qs.TouchAnimator.Builder; 32 import com.android.systemui.qs.TouchAnimator.Listener; 33 import com.android.systemui.qs.dagger.QSScope; 34 import com.android.systemui.qs.tileimpl.HeightOverrideable; 35 import com.android.systemui.statusbar.CrossFadeHelper; 36 import com.android.systemui.tuner.TunerService; 37 import com.android.systemui.tuner.TunerService.Tunable; 38 import com.android.wm.shell.animation.Interpolators; 39 40 import java.util.ArrayList; 41 import java.util.Collection; 42 import java.util.List; 43 import java.util.concurrent.Executor; 44 45 import javax.inject.Inject; 46 47 /** */ 48 @QSScope 49 public class QSAnimator implements Callback, PageListener, Listener, OnLayoutChangeListener, 50 OnAttachStateChangeListener, Tunable { 51 52 private static final String TAG = "QSAnimator"; 53 54 private static final String ALLOW_FANCY_ANIMATION = "sysui_qs_fancy_anim"; 55 private static final String MOVE_FULL_ROWS = "sysui_qs_move_whole_rows"; 56 57 public static final float EXPANDED_TILE_DELAY = .86f; 58 public static final float SHORT_PARALLAX_AMOUNT = 0.1f; 59 private static final long QQS_FADE_IN_DURATION = 200L; 60 // Fade out faster than fade in to finish before QQS hides. 61 private static final long QQS_FADE_OUT_DURATION = 50L; 62 63 64 private final ArrayList<View> mAllViews = new ArrayList<>(); 65 /** 66 * List of {@link View}s representing Quick Settings that are being animated from the quick QS 67 * position to the normal QS panel. These views will only show once the animation is complete, 68 * to prevent overlapping of semi transparent views 69 */ 70 private final ArrayList<View> mQuickQsViews = new ArrayList<>(); 71 private final QuickQSPanel mQuickQsPanel; 72 private final QSPanelController mQsPanelController; 73 private final QuickQSPanelController mQuickQSPanelController; 74 private final QuickStatusBarHeader mQuickStatusBarHeader; 75 private final QSSecurityFooter mSecurityFooter; 76 private final QS mQs; 77 78 private PagedTileLayout mPagedLayout; 79 80 private boolean mOnFirstPage = true; 81 private QSExpansionPathInterpolator mQSExpansionPathInterpolator; 82 private TouchAnimator mFirstPageAnimator; 83 private TouchAnimator mFirstPageDelayedAnimator; 84 private TouchAnimator mTranslationXAnimator; 85 private TouchAnimator mTranslationYAnimator; 86 private TouchAnimator mNonfirstPageAnimator; 87 private TouchAnimator mNonfirstPageDelayedAnimator; 88 // This animates fading of SecurityFooter and media divider 89 private TouchAnimator mAllPagesDelayedAnimator; 90 private TouchAnimator mBrightnessAnimator; 91 private HeightExpansionAnimator mQQSTileHeightAnimator; 92 private HeightExpansionAnimator mOtherTilesExpandAnimator; 93 94 private boolean mNeedsAnimatorUpdate = false; 95 private boolean mToShowing; 96 private boolean mOnKeyguard; 97 98 private boolean mAllowFancy; 99 private boolean mFullRows; 100 private int mNumQuickTiles; 101 private float mLastPosition; 102 private final QSTileHost mHost; 103 private final Executor mExecutor; 104 private final TunerService mTunerService; 105 private boolean mShowCollapsedOnKeyguard; 106 private boolean mTranslateWhileExpanding; 107 108 @Inject QSAnimator(QS qs, QuickQSPanel quickPanel, QuickStatusBarHeader quickStatusBarHeader, QSPanelController qsPanelController, QuickQSPanelController quickQSPanelController, QSTileHost qsTileHost, QSSecurityFooter securityFooter, @Main Executor executor, TunerService tunerService, QSExpansionPathInterpolator qsExpansionPathInterpolator)109 public QSAnimator(QS qs, QuickQSPanel quickPanel, QuickStatusBarHeader quickStatusBarHeader, 110 QSPanelController qsPanelController, 111 QuickQSPanelController quickQSPanelController, QSTileHost qsTileHost, 112 QSSecurityFooter securityFooter, @Main Executor executor, TunerService tunerService, 113 QSExpansionPathInterpolator qsExpansionPathInterpolator) { 114 mQs = qs; 115 mQuickQsPanel = quickPanel; 116 mQsPanelController = qsPanelController; 117 mQuickQSPanelController = quickQSPanelController; 118 mQuickStatusBarHeader = quickStatusBarHeader; 119 mSecurityFooter = securityFooter; 120 mHost = qsTileHost; 121 mExecutor = executor; 122 mTunerService = tunerService; 123 mQSExpansionPathInterpolator = qsExpansionPathInterpolator; 124 mHost.addCallback(this); 125 mQsPanelController.addOnAttachStateChangeListener(this); 126 qs.getView().addOnLayoutChangeListener(this); 127 if (mQsPanelController.isAttachedToWindow()) { 128 onViewAttachedToWindow(null); 129 } 130 QSTileLayout tileLayout = mQsPanelController.getTileLayout(); 131 if (tileLayout instanceof PagedTileLayout) { 132 mPagedLayout = ((PagedTileLayout) tileLayout); 133 } else { 134 Log.w(TAG, "QS Not using page layout"); 135 } 136 mQsPanelController.setPageListener(this); 137 } 138 onRtlChanged()139 public void onRtlChanged() { 140 updateAnimators(); 141 } 142 143 /** 144 * Request an update to the animators. This will update them lazily next time the position 145 * is changed. 146 */ requestAnimatorUpdate()147 public void requestAnimatorUpdate() { 148 mNeedsAnimatorUpdate = true; 149 } 150 setOnKeyguard(boolean onKeyguard)151 public void setOnKeyguard(boolean onKeyguard) { 152 mOnKeyguard = onKeyguard; 153 updateQQSVisibility(); 154 if (mOnKeyguard) { 155 clearAnimationState(); 156 } 157 } 158 startAlphaAnimation(boolean show)159 void startAlphaAnimation(boolean show) { 160 if (show == mToShowing) { 161 return; 162 } 163 mToShowing = show; 164 if (show) { 165 CrossFadeHelper.fadeIn(mQs.getView(), QQS_FADE_IN_DURATION, 0 /* delay */); 166 } else { 167 CrossFadeHelper.fadeOut(mQs.getView(), QQS_FADE_OUT_DURATION, 0 /* delay */, 168 null /* endRunnable */); 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 mQuickQsViews.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.getHeight(), tileView.getHeight()); 320 qqsTileHeight = quickTileView.getHeight(); 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 mQuickQsViews.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.getHeight()); 384 } 385 mOtherTilesExpandAnimator.addView(tileView); 386 tileView.setClipChildren(true); 387 tileView.setClipToPadding(true); 388 firstPageBuilder.addFloat(tileView.getSecondaryLabel(), "alpha", 0, 1); 389 } 390 391 mAllViews.add(tileView); 392 count++; 393 } 394 } 395 396 if (mAllowFancy) { 397 // Make brightness appear static position and alpha in through second half. 398 View brightness = mQsPanelController.getBrightnessView(); 399 if (brightness != null) { 400 firstPageBuilder.addFloat(brightness, "translationY", 401 brightness.getMeasuredHeight() * 0.5f, 0); 402 mBrightnessAnimator = new TouchAnimator.Builder() 403 .addFloat(brightness, "alpha", 0, 1) 404 .addFloat(brightness, "sliderScaleY", 0.3f, 1) 405 .setInterpolator(Interpolators.ALPHA_IN) 406 .setStartDelay(0.3f) 407 .build(); 408 mAllViews.add(brightness); 409 } else { 410 mBrightnessAnimator = null; 411 } 412 mFirstPageAnimator = firstPageBuilder 413 .setListener(this) 414 .build(); 415 // Fade in the tiles/labels as we reach the final position. 416 Builder builder = new Builder() 417 .addFloat(tileLayout, "alpha", 0, 1); 418 mFirstPageDelayedAnimator = builder.build(); 419 420 // Fade in the security footer and the divider as we reach the final position 421 builder = new Builder().setStartDelay(EXPANDED_TILE_DELAY); 422 builder.addFloat(mSecurityFooter.getView(), "alpha", 0, 1); 423 if (mQsPanelController.shouldUseHorizontalLayout() 424 && mQsPanelController.mMediaHost.hostView != null) { 425 builder.addFloat(mQsPanelController.mMediaHost.hostView, "alpha", 0, 1); 426 } else { 427 // In portrait, media view should always be visible 428 mQsPanelController.mMediaHost.hostView.setAlpha(1.0f); 429 } 430 mAllPagesDelayedAnimator = builder.build(); 431 mAllViews.add(mSecurityFooter.getView()); 432 translationYBuilder.setInterpolator(mQSExpansionPathInterpolator.getYInterpolator()); 433 translationXBuilder.setInterpolator(mQSExpansionPathInterpolator.getXInterpolator()); 434 mTranslationYAnimator = translationYBuilder.build(); 435 mTranslationXAnimator = translationXBuilder.build(); 436 if (mQQSTileHeightAnimator != null) { 437 mQQSTileHeightAnimator.setInterpolator( 438 mQSExpansionPathInterpolator.getYInterpolator()); 439 } 440 if (mOtherTilesExpandAnimator != null) { 441 mOtherTilesExpandAnimator.setInterpolator( 442 mQSExpansionPathInterpolator.getYInterpolator()); 443 } 444 } 445 mNonfirstPageAnimator = new TouchAnimator.Builder() 446 .addFloat(mQuickQsPanel, "alpha", 1, 0) 447 .setListener(mNonFirstPageListener) 448 .setEndDelay(.5f) 449 .build(); 450 mNonfirstPageDelayedAnimator = new TouchAnimator.Builder() 451 .setStartDelay(.14f) 452 .addFloat(tileLayout, "alpha", 0, 1).build(); 453 } 454 isIconInAnimatedRow(int count)455 private boolean isIconInAnimatedRow(int count) { 456 if (mPagedLayout == null) { 457 return false; 458 } 459 final int columnCount = mPagedLayout.getColumnCount(); 460 return count < ((mNumQuickTiles + columnCount - 1) / columnCount) * columnCount; 461 } 462 getRelativePosition(int[] loc1, View view, View parent)463 private void getRelativePosition(int[] loc1, View view, View parent) { 464 loc1[0] = 0 + view.getWidth() / 2; 465 loc1[1] = 0; 466 getRelativePositionInt(loc1, view, parent); 467 } 468 getRelativePositionInt(int[] loc1, View view, View parent)469 private void getRelativePositionInt(int[] loc1, View view, View parent) { 470 if(view == parent || view == null) return; 471 // Ignore tile pages as they can have some offset we don't want to take into account in 472 // RTL. 473 if (!isAPage(view)) { 474 loc1[0] += view.getLeft(); 475 loc1[1] += view.getTop(); 476 } 477 if (!(view instanceof PagedTileLayout)) { 478 // Remove the scrolling position of all scroll views other than the viewpager 479 loc1[0] -= view.getScrollX(); 480 loc1[1] -= view.getScrollY(); 481 } 482 getRelativePositionInt(loc1, (View) view.getParent(), parent); 483 } 484 485 // Returns true if the view is a possible page in PagedTileLayout isAPage(View view)486 private boolean isAPage(View view) { 487 return view.getClass().equals(SideLabelTileLayout.class); 488 } 489 setPosition(float position)490 public void setPosition(float position) { 491 if (mNeedsAnimatorUpdate) { 492 updateAnimators(); 493 } 494 if (mFirstPageAnimator == null) return; 495 if (mOnKeyguard) { 496 if (mShowCollapsedOnKeyguard) { 497 position = 0; 498 } else { 499 position = 1; 500 } 501 } 502 mLastPosition = position; 503 if (mOnFirstPage && mAllowFancy) { 504 mQuickQsPanel.setAlpha(1); 505 mFirstPageAnimator.setPosition(position); 506 mFirstPageDelayedAnimator.setPosition(position); 507 mTranslationYAnimator.setPosition(position); 508 mTranslationXAnimator.setPosition(position); 509 if (mQQSTileHeightAnimator != null) { 510 mQQSTileHeightAnimator.setPosition(position); 511 } 512 if (mOtherTilesExpandAnimator != null) { 513 mOtherTilesExpandAnimator.setPosition(position); 514 } 515 } else { 516 mNonfirstPageAnimator.setPosition(position); 517 mNonfirstPageDelayedAnimator.setPosition(position); 518 } 519 if (mAllowFancy) { 520 mAllPagesDelayedAnimator.setPosition(position); 521 if (mBrightnessAnimator != null) { 522 mBrightnessAnimator.setPosition(position); 523 } 524 } 525 } 526 527 @Override onAnimationAtStart()528 public void onAnimationAtStart() { 529 mQuickQsPanel.setVisibility(View.VISIBLE); 530 } 531 532 @Override onAnimationAtEnd()533 public void onAnimationAtEnd() { 534 mQuickQsPanel.setVisibility(View.INVISIBLE); 535 final int N = mQuickQsViews.size(); 536 for (int i = 0; i < N; i++) { 537 mQuickQsViews.get(i).setVisibility(View.VISIBLE); 538 } 539 } 540 541 @Override onAnimationStarted()542 public void onAnimationStarted() { 543 updateQQSVisibility(); 544 if (mOnFirstPage) { 545 final int N = mQuickQsViews.size(); 546 for (int i = 0; i < N; i++) { 547 mQuickQsViews.get(i).setVisibility(View.INVISIBLE); 548 } 549 } 550 } 551 clearAnimationState()552 private void clearAnimationState() { 553 final int N = mAllViews.size(); 554 mQuickQsPanel.setAlpha(0); 555 for (int i = 0; i < N; i++) { 556 View v = mAllViews.get(i); 557 v.setAlpha(1); 558 v.setTranslationX(0); 559 v.setTranslationY(0); 560 v.setScaleY(1f); 561 if (v instanceof SideLabelTileLayout) { 562 ((SideLabelTileLayout) v).setClipChildren(false); 563 ((SideLabelTileLayout) v).setClipToPadding(false); 564 } 565 } 566 if (mQQSTileHeightAnimator != null) { 567 mQQSTileHeightAnimator.resetViewsHeights(); 568 } 569 if (mOtherTilesExpandAnimator != null) { 570 mOtherTilesExpandAnimator.resetViewsHeights(); 571 } 572 final int N2 = mQuickQsViews.size(); 573 for (int i = 0; i < N2; i++) { 574 mQuickQsViews.get(i).setVisibility(View.VISIBLE); 575 } 576 } 577 578 @Override onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom)579 public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, 580 int oldTop, int oldRight, int oldBottom) { 581 mExecutor.execute(mUpdateAnimators); 582 } 583 584 @Override onTilesChanged()585 public void onTilesChanged() { 586 // Give the QS panels a moment to generate their new tiles, then create all new animators 587 // hooked up to the new views. 588 mExecutor.execute(mUpdateAnimators); 589 } 590 591 private final TouchAnimator.Listener mNonFirstPageListener = 592 new TouchAnimator.ListenerAdapter() { 593 @Override 594 public void onAnimationAtEnd() { 595 mQuickQsPanel.setVisibility(View.INVISIBLE); 596 } 597 598 @Override 599 public void onAnimationStarted() { 600 mQuickQsPanel.setVisibility(View.VISIBLE); 601 } 602 }; 603 604 private final Runnable mUpdateAnimators = () -> { 605 updateAnimators(); 606 setCurrentPosition(); 607 }; 608 609 /** 610 * True whe QS will be pulled from the top, false when it will be clipped. 611 */ setTranslateWhileExpanding(boolean shouldTranslate)612 public void setTranslateWhileExpanding(boolean shouldTranslate) { 613 mTranslateWhileExpanding = shouldTranslate; 614 } 615 616 static class HeightExpansionAnimator { 617 private final List<View> mViews = new ArrayList<>(); 618 private final ValueAnimator mAnimator; 619 private final TouchAnimator.Listener mListener; 620 621 private final ValueAnimator.AnimatorUpdateListener mUpdateListener = 622 new ValueAnimator.AnimatorUpdateListener() { 623 float mLastT = -1; 624 @Override 625 public void onAnimationUpdate(ValueAnimator valueAnimator) { 626 float t = valueAnimator.getAnimatedFraction(); 627 final int viewCount = mViews.size(); 628 int height = (Integer) valueAnimator.getAnimatedValue(); 629 for (int i = 0; i < viewCount; i++) { 630 View v = mViews.get(i); 631 v.setBottom(v.getTop() + height); 632 if (v instanceof HeightOverrideable) { 633 ((HeightOverrideable) v).setHeightOverride(height); 634 } 635 } 636 if (t == 0f) { 637 mListener.onAnimationAtStart(); 638 } else if (t == 1f) { 639 mListener.onAnimationAtEnd(); 640 } else if (mLastT <= 0 || mLastT == 1) { 641 mListener.onAnimationStarted(); 642 } 643 mLastT = t; 644 } 645 }; 646 HeightExpansionAnimator(TouchAnimator.Listener listener, int startHeight, int endHeight)647 HeightExpansionAnimator(TouchAnimator.Listener listener, int startHeight, int endHeight) { 648 mListener = listener; 649 mAnimator = ValueAnimator.ofInt(startHeight, endHeight); 650 mAnimator.setRepeatCount(ValueAnimator.INFINITE); 651 mAnimator.setRepeatMode(ValueAnimator.REVERSE); 652 mAnimator.addUpdateListener(mUpdateListener); 653 } 654 addView(View v)655 void addView(View v) { 656 mViews.add(v); 657 } 658 setInterpolator(TimeInterpolator interpolator)659 void setInterpolator(TimeInterpolator interpolator) { 660 mAnimator.setInterpolator(interpolator); 661 } 662 setPosition(float position)663 void setPosition(float position) { 664 mAnimator.setCurrentFraction(position); 665 } 666 resetViewsHeights()667 void resetViewsHeights() { 668 final int viewsCount = mViews.size(); 669 for (int i = 0; i < viewsCount; i++) { 670 View v = mViews.get(i); 671 v.setBottom(v.getTop() + v.getMeasuredHeight()); 672 if (v instanceof HeightOverrideable) { 673 ((HeightOverrideable) v).resetOverride(); 674 } 675 } 676 } 677 } 678 } 679