2 * Copyright (C) 2016 The Android Open Source Project
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
7 * http://www.apache.org/licenses/LICENSE-2.0
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.
15 package com.android.systemui.qs;
17 import android.graphics.Path;
18 import android.util.Log;
19 import android.view.View;
20 import android.view.View.OnAttachStateChangeListener;
21 import android.view.View.OnLayoutChangeListener;
22 import android.widget.TextView;
24 import com.android.systemui.qs.PagedTileLayout.PageListener;
25 import com.android.systemui.qs.QSPanel.QSTileLayout;
26 import com.android.systemui.qs.QSTile.Host.Callback;
27 import com.android.systemui.qs.TouchAnimator.Builder;
28 import com.android.systemui.qs.TouchAnimator.Listener;
29 import com.android.systemui.statusbar.phone.QSTileHost;
30 import com.android.systemui.tuner.TunerService;
31 import com.android.systemui.tuner.TunerService.Tunable;
33 import java.util.ArrayList;
34 import java.util.Collection;
36 public class QSAnimator implements Callback, PageListener, Listener, OnLayoutChangeListener,
37 OnAttachStateChangeListener, Tunable {
39 private static final String TAG = "QSAnimator";
41 private static final String ALLOW_FANCY_ANIMATION = "sysui_qs_fancy_anim";
42 private static final String MOVE_FULL_ROWS = "sysui_qs_move_whole_rows";
44 public static final float EXPANDED_TILE_DELAY = .7f;
45 private static final float LAST_ROW_EXPANDED_DELAY = .86f;
47 private final ArrayList<View> mAllViews = new ArrayList<>();
48 private final ArrayList<View> mTopFiveQs = new ArrayList<>();
49 private final QuickQSPanel mQuickQsPanel;
50 private final QSPanel mQsPanel;
51 private final QSContainer mQsContainer;
53 private PagedTileLayout mPagedLayout;
55 private boolean mOnFirstPage = true;
56 private TouchAnimator mFirstPageAnimator;
57 private TouchAnimator mFirstPageDelayedAnimator;
58 private TouchAnimator mTranslationXAnimator;
59 private TouchAnimator mTranslationYAnimator;
60 private TouchAnimator mNonfirstPageAnimator;
61 private TouchAnimator mLastRowAnimator;
63 private boolean mOnKeyguard;
65 private boolean mAllowFancy;
66 private boolean mFullRows;
67 private int mNumQuickTiles;
68 private float mLastPosition;
69 private QSTileHost mHost;
71 public QSAnimator(QSContainer container, QuickQSPanel quickPanel, QSPanel panel) {
72 mQsContainer = container;
73 mQuickQsPanel = quickPanel;
75 mQsPanel.addOnAttachStateChangeListener(this);
76 container.addOnLayoutChangeListener(this);
77 QSTileLayout tileLayout = mQsPanel.getTileLayout();
78 if (tileLayout instanceof PagedTileLayout) {
79 mPagedLayout = ((PagedTileLayout) tileLayout);
80 mPagedLayout.setPageListener(this);
82 Log.w(TAG, "QS Not using page layout");
86 public void onRtlChanged() {
90 public void setOnKeyguard(boolean onKeyguard) {
91 mOnKeyguard = onKeyguard;
92 mQuickQsPanel.setVisibility(mOnKeyguard ? View.INVISIBLE : View.VISIBLE);
94 clearAnimationState();
98 public void setHost(QSTileHost qsh) {
100 qsh.addCallback(this);
105 public void onViewAttachedToWindow(View v) {
106 TunerService.get(mQsContainer.getContext()).addTunable(this, ALLOW_FANCY_ANIMATION,
107 MOVE_FULL_ROWS, QuickQSPanel.NUM_QUICK_TILES);
111 public void onViewDetachedFromWindow(View v) {
113 mHost.removeCallback(this);
115 TunerService.get(mQsContainer.getContext()).removeTunable(this);
119 public void onTuningChanged(String key, String newValue) {
120 if (ALLOW_FANCY_ANIMATION.equals(key)) {
121 mAllowFancy = newValue == null || Integer.parseInt(newValue) != 0;
123 clearAnimationState();
125 } else if (MOVE_FULL_ROWS.equals(key)) {
126 mFullRows = newValue == null || Integer.parseInt(newValue) != 0;
127 } else if (QuickQSPanel.NUM_QUICK_TILES.equals(key)) {
128 mNumQuickTiles = mQuickQsPanel.getNumQuickTiles(mQsContainer.getContext());
129 clearAnimationState();
135 public void onPageChanged(boolean isFirst) {
136 if (mOnFirstPage == isFirst) return;
138 clearAnimationState();
140 mOnFirstPage = isFirst;
143 private void updateAnimators() {
144 TouchAnimator.Builder firstPageBuilder = new Builder();
145 TouchAnimator.Builder translationXBuilder = new Builder();
146 TouchAnimator.Builder translationYBuilder = new Builder();
147 TouchAnimator.Builder lastRowBuilder = new Builder();
149 if (mQsPanel.getHost() == null) return;
150 Collection<QSTile<?>> tiles = mQsPanel.getHost().getTiles();
152 int[] loc1 = new int[2];
153 int[] loc2 = new int[2];
158 clearAnimationState();
162 mAllViews.add((View) mQsPanel.getTileLayout());
164 for (QSTile<?> tile : tiles) {
165 QSTileBaseView tileView = mQsPanel.getTileView(tile);
166 final TextView label = ((QSTileView) tileView).getLabel();
167 final View tileIcon = tileView.getIcon().getIconView();
168 if (count < mNumQuickTiles && mAllowFancy) {
170 QSTileBaseView quickTileView = mQuickQsPanel.getTileView(tile);
173 getRelativePosition(loc1, quickTileView.getIcon(), mQsContainer);
174 getRelativePosition(loc2, tileIcon, mQsContainer);
175 final int xDiff = loc2[0] - loc1[0];
176 final int yDiff = loc2[1] - loc1[1];
177 lastXDiff = loc1[0] - lastX;
179 // Move the quick tile right from its location to the new one.
180 translationXBuilder.addFloat(quickTileView, "translationX", 0, xDiff);
181 translationYBuilder.addFloat(quickTileView, "translationY", 0, yDiff);
183 // Counteract the parent translation on the tile. So we have a static base to
184 // animate the label position off from.
185 firstPageBuilder.addFloat(tileView, "translationY", mQsPanel.getHeight(), 0);
187 // Move the real tile's label from the quick tile position to its final
189 translationXBuilder.addFloat(label, "translationX", -xDiff, 0);
190 translationYBuilder.addFloat(label, "translationY", -yDiff, 0);
192 mTopFiveQs.add(tileIcon);
193 mAllViews.add(tileIcon);
194 mAllViews.add(quickTileView);
195 } else if (mFullRows && isIconInAnimatedRow(count)) {
196 // TODO: Refactor some of this, it shares a lot with the above block.
197 // Move the last tile position over by the last difference between quick tiles.
198 // This makes the extra icons seems as if they are coming from positions in the
200 loc1[0] += lastXDiff;
201 getRelativePosition(loc2, tileIcon, mQsContainer);
202 final int xDiff = loc2[0] - loc1[0];
203 final int yDiff = loc2[1] - loc1[1];
205 firstPageBuilder.addFloat(tileView, "translationY", mQsPanel.getHeight(), 0);
206 translationXBuilder.addFloat(tileView, "translationX", -xDiff, 0);
207 translationYBuilder.addFloat(label, "translationY", -yDiff, 0);
208 translationYBuilder.addFloat(tileIcon, "translationY", -yDiff, 0);
210 mAllViews.add(tileIcon);
212 lastRowBuilder.addFloat(tileView, "alpha", 0, 1);
214 mAllViews.add(tileView);
215 mAllViews.add(label);
219 mFirstPageAnimator = firstPageBuilder
222 // Fade in the tiles/labels as we reach the final position.
223 mFirstPageDelayedAnimator = new TouchAnimator.Builder()
224 .setStartDelay(EXPANDED_TILE_DELAY)
225 .addFloat(mQsPanel.getTileLayout(), "alpha", 0, 1).build();
226 mLastRowAnimator = lastRowBuilder
227 .setStartDelay(LAST_ROW_EXPANDED_DELAY)
229 Path path = new Path();
231 path.cubicTo(0, 0, 0, 1, 1, 1);
232 PathInterpolatorBuilder interpolatorBuilder = new PathInterpolatorBuilder(0, 0, 0, 1);
233 translationXBuilder.setInterpolator(interpolatorBuilder.getXInterpolator());
234 translationYBuilder.setInterpolator(interpolatorBuilder.getYInterpolator());
235 mTranslationXAnimator = translationXBuilder.build();
236 mTranslationYAnimator = translationYBuilder.build();
238 mNonfirstPageAnimator = new TouchAnimator.Builder()
239 .addFloat(mQuickQsPanel, "alpha", 1, 0)
240 .setListener(mNonFirstPageListener)
245 private boolean isIconInAnimatedRow(int count) {
246 if (mPagedLayout == null) {
249 final int columnCount = mPagedLayout.getColumnCount();
250 return count < ((mNumQuickTiles + columnCount - 1) / columnCount) * columnCount;
253 private void getRelativePosition(int[] loc1, View view, View parent) {
254 loc1[0] = 0 + view.getWidth() / 2;
256 getRelativePositionInt(loc1, view, parent);
259 private void getRelativePositionInt(int[] loc1, View view, View parent) {
260 if(view == parent || view == null) return;
261 // Ignore tile pages as they can have some offset we don't want to take into account in
263 if (!(view instanceof PagedTileLayout.TilePage)) {
264 loc1[0] += view.getLeft();
265 loc1[1] += view.getTop();
267 getRelativePositionInt(loc1, (View) view.getParent(), parent);
270 public void setPosition(float position) {
271 if (mFirstPageAnimator == null) return;
275 mLastPosition = position;
276 if (mOnFirstPage && mAllowFancy) {
277 mQuickQsPanel.setAlpha(1);
278 mFirstPageAnimator.setPosition(position);
279 mFirstPageDelayedAnimator.setPosition(position);
280 mTranslationXAnimator.setPosition(position);
281 mTranslationYAnimator.setPosition(position);
282 mLastRowAnimator.setPosition(position);
284 mNonfirstPageAnimator.setPosition(position);
289 public void onAnimationAtStart() {
290 mQuickQsPanel.setVisibility(View.VISIBLE);
294 public void onAnimationAtEnd() {
295 mQuickQsPanel.setVisibility(View.INVISIBLE);
296 final int N = mTopFiveQs.size();
297 for (int i = 0; i < N; i++) {
298 mTopFiveQs.get(i).setVisibility(View.VISIBLE);
303 public void onAnimationStarted() {
304 mQuickQsPanel.setVisibility(mOnKeyguard ? View.INVISIBLE : View.VISIBLE);
306 final int N = mTopFiveQs.size();
307 for (int i = 0; i < N; i++) {
308 mTopFiveQs.get(i).setVisibility(View.INVISIBLE);
313 private void clearAnimationState() {
314 final int N = mAllViews.size();
315 mQuickQsPanel.setAlpha(0);
316 for (int i = 0; i < N; i++) {
317 View v = mAllViews.get(i);
319 v.setTranslationX(0);
320 v.setTranslationY(0);
322 final int N2 = mTopFiveQs.size();
323 for (int i = 0; i < N2; i++) {
324 mTopFiveQs.get(i).setVisibility(View.VISIBLE);
329 public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
330 int oldTop, int oldRight, int oldBottom) {
331 mQsPanel.post(mUpdateAnimators);
335 public void onTilesChanged() {
336 // Give the QS panels a moment to generate their new tiles, then create all new animators
337 // hooked up to the new views.
338 mQsPanel.post(mUpdateAnimators);
341 private final TouchAnimator.Listener mNonFirstPageListener =
342 new TouchAnimator.ListenerAdapter() {
344 public void onAnimationStarted() {
345 mQuickQsPanel.setVisibility(View.VISIBLE);
349 private Runnable mUpdateAnimators = new Runnable() {
353 setPosition(mLastPosition);