blob: 892395222d1fbf51408fac381ef0e889ae5c7d28 [file] [log] [blame]
Jason Monkcaf37622015-08-18 12:33:50 -04001package com.android.systemui.qs;
2
Amin Shaikha07a17b2018-02-23 16:02:52 -05003import android.animation.Animator;
4import android.animation.AnimatorListenerAdapter;
5import android.animation.AnimatorSet;
6import android.animation.ObjectAnimator;
7import android.animation.PropertyValuesHolder;
Jason Monkcaf37622015-08-18 12:33:50 -04008import android.content.Context;
Jason Monk6573ef22016-04-06 12:37:18 -04009import android.content.res.Configuration;
10import android.content.res.Resources;
Jason Monk51fb85a2016-03-28 14:06:04 -040011import android.support.v4.view.PagerAdapter;
12import android.support.v4.view.ViewPager;
Jason Monkcaf37622015-08-18 12:33:50 -040013import android.util.AttributeSet;
14import android.util.Log;
15import android.view.LayoutInflater;
Amin Shaikha07a17b2018-02-23 16:02:52 -050016import android.view.MotionEvent;
Jason Monkcaf37622015-08-18 12:33:50 -040017import android.view.View;
18import android.view.ViewGroup;
Amin Shaikha07a17b2018-02-23 16:02:52 -050019import android.view.animation.Interpolator;
20import android.view.animation.OvershootInterpolator;
21import android.widget.Scroller;
Jason Monkdf36aed2016-07-25 11:21:56 -040022
Jason Monkcaf37622015-08-18 12:33:50 -040023import com.android.systemui.R;
24import com.android.systemui.qs.QSPanel.QSTileLayout;
25import com.android.systemui.qs.QSPanel.TileRecord;
26
27import java.util.ArrayList;
Amin Shaikha07a17b2018-02-23 16:02:52 -050028import java.util.Set;
Jason Monkcaf37622015-08-18 12:33:50 -040029
30public class PagedTileLayout extends ViewPager implements QSTileLayout {
31
32 private static final boolean DEBUG = false;
33
34 private static final String TAG = "PagedTileLayout";
Amin Shaikha07a17b2018-02-23 16:02:52 -050035 private static final int REVEAL_SCROLL_DURATION_MILLIS = 750;
36 private static final float BOUNCE_ANIMATION_TENSION = 1.3f;
37 private static final long BOUNCE_ANIMATION_DURATION = 450L;
38 private static final int TILE_ANIMATION_STAGGER_DELAY = 85;
39 private static final Interpolator SCROLL_CUBIC = (t) -> {
40 t -= 1.0f;
41 return t * t * t + 1.0f;
42 };
43
Jason Monkcaf37622015-08-18 12:33:50 -040044
Amin Shaikh9978c552018-03-22 07:24:41 -040045 private final ArrayList<TileRecord> mTiles = new ArrayList<>();
46 private final ArrayList<TilePage> mPages = new ArrayList<>();
Jason Monkcaf37622015-08-18 12:33:50 -040047
Jason Monkcaf37622015-08-18 12:33:50 -040048 private PageIndicator mPageIndicator;
49
50 private int mNumPages;
Jason Monk162011e2016-02-19 08:11:55 -050051 private PageListener mPageListener;
Jason Monkcaf37622015-08-18 12:33:50 -040052
Jason Monke5107a32016-05-31 15:40:58 -040053 private boolean mListening;
Amin Shaikha07a17b2018-02-23 16:02:52 -050054 private Scroller mScroller;
55
56 private AnimatorSet mBounceAnimatorSet;
57 private int mAnimatingToPage = -1;
Jason Monke5107a32016-05-31 15:40:58 -040058
Jason Monkcaf37622015-08-18 12:33:50 -040059 public PagedTileLayout(Context context, AttributeSet attrs) {
60 super(context, attrs);
Amin Shaikha07a17b2018-02-23 16:02:52 -050061 mScroller = new Scroller(context, SCROLL_CUBIC);
Jason Monkcaf37622015-08-18 12:33:50 -040062 setAdapter(mAdapter);
Amin Shaikha07a17b2018-02-23 16:02:52 -050063 setOnPageChangeListener(mOnPageChangeListener);
64 setCurrentItem(0, false);
Jason Monkcaf37622015-08-18 12:33:50 -040065 }
66
67 @Override
Jason Monk51fb85a2016-03-28 14:06:04 -040068 public void onRtlPropertiesChanged(int layoutDirection) {
69 super.onRtlPropertiesChanged(layoutDirection);
70 setAdapter(mAdapter);
71 setCurrentItem(0, false);
72 }
73
74 @Override
75 public void setCurrentItem(int item, boolean smoothScroll) {
76 if (isLayoutRtl()) {
77 item = mPages.size() - 1 - item;
78 }
79 super.setCurrentItem(item, smoothScroll);
80 }
81
82 @Override
Jason Monke5107a32016-05-31 15:40:58 -040083 public void setListening(boolean listening) {
84 if (mListening == listening) return;
85 mListening = listening;
Amin Shaikh9978c552018-03-22 07:24:41 -040086 updateListening();
87 }
88
89 private void updateListening() {
90 for (TilePage tilePage : mPages) {
91 tilePage.setListening(tilePage.getParent() == null ? false : mListening);
Jason Monke5107a32016-05-31 15:40:58 -040092 }
93 }
94
Amin Shaikha07a17b2018-02-23 16:02:52 -050095 @Override
96 public boolean onInterceptTouchEvent(MotionEvent ev) {
97 // Suppress all touch event during reveal animation.
98 if (mAnimatingToPage != -1) {
99 return true;
100 }
101 return super.onInterceptTouchEvent(ev);
102 }
103
104 @Override
105 public boolean onTouchEvent(MotionEvent ev) {
106 // Suppress all touch event during reveal animation.
107 if (mAnimatingToPage != -1) {
108 return true;
109 }
110 return super.onTouchEvent(ev);
111 }
112
113 @Override
114 public void computeScroll() {
115 if (!mScroller.isFinished() && mScroller.computeScrollOffset()) {
116 scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
117 float pageFraction = (float) getScrollX() / getWidth();
118 int position = (int) pageFraction;
119 float positionOffset = pageFraction - position;
120 mOnPageChangeListener.onPageScrolled(position, positionOffset, getScrollX());
121 // Keep on drawing until the animation has finished.
122 postInvalidateOnAnimation();
123 return;
124 }
125 if (mAnimatingToPage != -1) {
126 setCurrentItem(mAnimatingToPage, true);
127 mBounceAnimatorSet.start();
128 setOffscreenPageLimit(1);
129 mAnimatingToPage = -1;
130 }
131 super.computeScroll();
132 }
133
Jason Monke5107a32016-05-31 15:40:58 -0400134 @Override
Jason Monkc5bdafb2016-02-25 16:24:21 -0500135 public boolean hasOverlappingRendering() {
136 return false;
137 }
138
139 @Override
Jason Monkcaf37622015-08-18 12:33:50 -0400140 protected void onFinishInflate() {
141 super.onFinishInflate();
Jason Monk32508852017-01-18 09:17:13 -0500142 mPages.add((TilePage) LayoutInflater.from(getContext())
Jason Monke4e69302016-01-20 11:27:15 -0500143 .inflate(R.layout.qs_paged_page, this, false));
Jason Monkcaf37622015-08-18 12:33:50 -0400144 }
145
Jason Monk32508852017-01-18 09:17:13 -0500146 public void setPageIndicator(PageIndicator indicator) {
147 mPageIndicator = indicator;
148 }
149
Jason Monkcaf37622015-08-18 12:33:50 -0400150 @Override
151 public int getOffsetTop(TileRecord tile) {
Jason Monkae5bd032016-03-02 14:38:31 -0500152 final ViewGroup parent = (ViewGroup) tile.tileView.getParent();
153 if (parent == null) return 0;
154 return parent.getTop() + getTop();
Jason Monkcaf37622015-08-18 12:33:50 -0400155 }
156
157 @Override
Jason Monkcaf37622015-08-18 12:33:50 -0400158 public void addTile(TileRecord tile) {
159 mTiles.add(tile);
Jason Monk9d02a432016-01-20 16:33:46 -0500160 postDistributeTiles();
Jason Monkcaf37622015-08-18 12:33:50 -0400161 }
162
163 @Override
164 public void removeTile(TileRecord tile) {
165 if (mTiles.remove(tile)) {
Jason Monk9d02a432016-01-20 16:33:46 -0500166 postDistributeTiles();
Jason Monkcaf37622015-08-18 12:33:50 -0400167 }
168 }
169
Jason Monk162011e2016-02-19 08:11:55 -0500170 public void setPageListener(PageListener listener) {
171 mPageListener = listener;
172 }
173
Jason Monk9d02a432016-01-20 16:33:46 -0500174 private void postDistributeTiles() {
175 removeCallbacks(mDistribute);
176 post(mDistribute);
177 }
178
Jason Monkcaf37622015-08-18 12:33:50 -0400179 private void distributeTiles() {
180 if (DEBUG) Log.d(TAG, "Distributing tiles");
Jason Monkcaf37622015-08-18 12:33:50 -0400181 final int NP = mPages.size();
182 for (int i = 0; i < NP; i++) {
Jason Monk9d02a432016-01-20 16:33:46 -0500183 mPages.get(i).removeAllViews();
Jason Monkcaf37622015-08-18 12:33:50 -0400184 }
185 int index = 0;
186 final int NT = mTiles.size();
187 for (int i = 0; i < NT; i++) {
188 TileRecord tile = mTiles.get(i);
Jason Monkcaf37622015-08-18 12:33:50 -0400189 if (mPages.get(index).isFull()) {
190 if (++index == mPages.size()) {
Jason Monk8e73ef32016-05-04 11:36:46 -0400191 if (DEBUG) Log.d(TAG, "Adding page for "
192 + tile.tile.getClass().getSimpleName());
Jason Monk32508852017-01-18 09:17:13 -0500193 mPages.add((TilePage) LayoutInflater.from(getContext())
Jason Monkcaf37622015-08-18 12:33:50 -0400194 .inflate(R.layout.qs_paged_page, this, false));
195 }
196 }
197 if (DEBUG) Log.d(TAG, "Adding " + tile.tile.getClass().getSimpleName() + " to "
198 + index);
199 mPages.get(index).addTile(tile);
200 }
201 if (mNumPages != index + 1) {
202 mNumPages = index + 1;
Jason Monk8e73ef32016-05-04 11:36:46 -0400203 while (mPages.size() > mNumPages) {
204 mPages.remove(mPages.size() - 1);
205 }
206 if (DEBUG) Log.d(TAG, "Size: " + mNumPages);
Jason Monkcaf37622015-08-18 12:33:50 -0400207 mPageIndicator.setNumPages(mNumPages);
Jason Monk32508852017-01-18 09:17:13 -0500208 mPageIndicator.setVisibility(mNumPages > 1 ? View.VISIBLE : View.GONE);
Jason Monk8e73ef32016-05-04 11:36:46 -0400209 setAdapter(mAdapter);
Jason Monkcaf37622015-08-18 12:33:50 -0400210 mAdapter.notifyDataSetChanged();
Jason Monk51fb85a2016-03-28 14:06:04 -0400211 setCurrentItem(0, false);
Jason Monkcaf37622015-08-18 12:33:50 -0400212 }
213 }
214
215 @Override
Jason Monk9d02a432016-01-20 16:33:46 -0500216 public boolean updateResources() {
217 boolean changed = false;
Jason Monkcaf37622015-08-18 12:33:50 -0400218 for (int i = 0; i < mPages.size(); i++) {
Jason Monk9d02a432016-01-20 16:33:46 -0500219 changed |= mPages.get(i).updateResources();
Jason Monkcaf37622015-08-18 12:33:50 -0400220 }
Jason Monk9d02a432016-01-20 16:33:46 -0500221 if (changed) {
222 distributeTiles();
223 }
224 return changed;
Jason Monkcaf37622015-08-18 12:33:50 -0400225 }
226
227 @Override
228 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
229 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
230 // The ViewPager likes to eat all of the space, instead force it to wrap to the max height
231 // of the pages.
232 int maxHeight = 0;
233 final int N = getChildCount();
234 for (int i = 0; i < N; i++) {
235 int height = getChildAt(i).getMeasuredHeight();
236 if (height > maxHeight) {
237 maxHeight = height;
238 }
239 }
Jason Monkf13413e2017-02-15 15:49:32 -0500240 setMeasuredDimension(getMeasuredWidth(), maxHeight + getPaddingBottom());
Jason Monkcaf37622015-08-18 12:33:50 -0400241 }
242
Jason Monk9d02a432016-01-20 16:33:46 -0500243 private final Runnable mDistribute = new Runnable() {
244 @Override
245 public void run() {
246 distributeTiles();
247 }
248 };
249
Jason Monk8fb77872016-03-03 16:39:42 -0500250 public int getColumnCount() {
251 if (mPages.size() == 0) return 0;
252 return mPages.get(0).mColumns;
253 }
254
Amin Shaikha07a17b2018-02-23 16:02:52 -0500255 public void startTileReveal(Set<String> tileSpecs, final Runnable postAnimation) {
256 if (tileSpecs.isEmpty() || mPages.size() < 2 || getScrollX() != 0) {
257 // Do not start the reveal animation unless there are tiles to animate, multiple
258 // TilePages available and the user has not already started dragging.
259 return;
260 }
261
262 final int lastPageNumber = mPages.size() - 1;
263 final TilePage lastPage = mPages.get(lastPageNumber);
264 final ArrayList<Animator> bounceAnims = new ArrayList<>();
265 for (TileRecord tr : lastPage.mRecords) {
266 if (tileSpecs.contains(tr.tile.getTileSpec())) {
267 bounceAnims.add(setupBounceAnimator(tr.tileView, bounceAnims.size()));
268 }
269 }
270
271 if (bounceAnims.isEmpty()) {
272 // All tileSpecs are on the first page. Nothing to do.
273 // TODO: potentially show a bounce animation for first page QS tiles
274 return;
275 }
276
277 mBounceAnimatorSet = new AnimatorSet();
278 mBounceAnimatorSet.playTogether(bounceAnims);
279 mBounceAnimatorSet.addListener(new AnimatorListenerAdapter() {
280 @Override
281 public void onAnimationEnd(Animator animation) {
282 mBounceAnimatorSet = null;
283 postAnimation.run();
284 }
285 });
286 mAnimatingToPage = lastPageNumber;
287 setOffscreenPageLimit(mAnimatingToPage); // Ensure the page to reveal has been inflated.
288 mScroller.startScroll(getScrollX(), getScrollY(), getWidth() * mAnimatingToPage, 0,
289 REVEAL_SCROLL_DURATION_MILLIS);
290 postInvalidateOnAnimation();
291 }
292
293 private static Animator setupBounceAnimator(View view, int ordinal) {
294 view.setAlpha(0f);
295 view.setScaleX(0f);
296 view.setScaleY(0f);
297 ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(view,
298 PropertyValuesHolder.ofFloat(View.ALPHA, 1),
299 PropertyValuesHolder.ofFloat(View.SCALE_X, 1),
300 PropertyValuesHolder.ofFloat(View.SCALE_Y, 1));
301 animator.setDuration(BOUNCE_ANIMATION_DURATION);
302 animator.setStartDelay(ordinal * TILE_ANIMATION_STAGGER_DELAY);
303 animator.setInterpolator(new OvershootInterpolator(BOUNCE_ANIMATION_TENSION));
304 return animator;
305 }
306
307 private final ViewPager.OnPageChangeListener mOnPageChangeListener =
308 new ViewPager.SimpleOnPageChangeListener() {
309 @Override
310 public void onPageSelected(int position) {
311 if (mPageIndicator == null) return;
312 if (mPageListener != null) {
313 mPageListener.onPageChanged(isLayoutRtl() ? position == mPages.size() - 1
314 : position == 0);
315 }
316 }
317
318 @Override
319 public void onPageScrolled(int position, float positionOffset,
320 int positionOffsetPixels) {
321 if (mPageIndicator == null) return;
Amin Shaikha07a17b2018-02-23 16:02:52 -0500322 mPageIndicator.setLocation(position + positionOffset);
323 if (mPageListener != null) {
324 mPageListener.onPageChanged(positionOffsetPixels == 0 &&
325 (isLayoutRtl() ? position == mPages.size() - 1 : position == 0));
326 }
327 }
328 };
329
Jason Monkcaf37622015-08-18 12:33:50 -0400330 public static class TilePage extends TileLayout {
Jason Monk94a1bf62015-10-20 08:43:36 -0700331 private int mMaxRows = 3;
Jason Monkcaf37622015-08-18 12:33:50 -0400332 public TilePage(Context context, AttributeSet attrs) {
333 super(context, attrs);
Jason Monkb9c00192015-10-07 11:45:33 -0400334 updateResources();
Jason Monkcaf37622015-08-18 12:33:50 -0400335 }
336
Jason Monk9d02a432016-01-20 16:33:46 -0500337 @Override
338 public boolean updateResources() {
Jason Monk6573ef22016-04-06 12:37:18 -0400339 final int rows = getRows();
340 boolean changed = rows != mMaxRows;
341 if (changed) {
342 mMaxRows = rows;
343 requestLayout();
Jason Monk9d02a432016-01-20 16:33:46 -0500344 }
Jason Monk6573ef22016-04-06 12:37:18 -0400345 return super.updateResources() || changed;
346 }
347
348 private int getRows() {
349 final Resources res = getContext().getResources();
350 if (res.getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
Jason Monk290a4192017-04-21 11:51:17 -0400351 return res.getInteger(R.integer.quick_settings_num_rows_portrait);
Jason Monk6573ef22016-04-06 12:37:18 -0400352 }
353 return Math.max(1, res.getInteger(R.integer.quick_settings_num_rows));
Jason Monk9d02a432016-01-20 16:33:46 -0500354 }
355
Jason Monkbd6dbb02015-09-03 15:46:25 -0400356 public void setMaxRows(int maxRows) {
357 mMaxRows = maxRows;
358 }
359
Jason Monkbd6dbb02015-09-03 15:46:25 -0400360 public boolean isFull() {
Jason Monkcaf37622015-08-18 12:33:50 -0400361 return mRecords.size() >= mColumns * mMaxRows;
362 }
363 }
364
365 private final PagerAdapter mAdapter = new PagerAdapter() {
Amin Shaikh9978c552018-03-22 07:24:41 -0400366 @Override
Jason Monkcaf37622015-08-18 12:33:50 -0400367 public void destroyItem(ViewGroup container, int position, Object object) {
368 if (DEBUG) Log.d(TAG, "Destantiating " + position);
Jason Monkcaf37622015-08-18 12:33:50 -0400369 container.removeView((View) object);
Amin Shaikh9978c552018-03-22 07:24:41 -0400370 updateListening();
Jason Monkcaf37622015-08-18 12:33:50 -0400371 }
372
Amin Shaikh9978c552018-03-22 07:24:41 -0400373 @Override
Jason Monkcaf37622015-08-18 12:33:50 -0400374 public Object instantiateItem(ViewGroup container, int position) {
375 if (DEBUG) Log.d(TAG, "Instantiating " + position);
Jason Monk51fb85a2016-03-28 14:06:04 -0400376 if (isLayoutRtl()) {
377 position = mPages.size() - 1 - position;
378 }
Jason Monke4e69302016-01-20 11:27:15 -0500379 ViewGroup view = mPages.get(position);
Jason Monkcaf37622015-08-18 12:33:50 -0400380 container.addView(view);
Amin Shaikh9978c552018-03-22 07:24:41 -0400381 updateListening();
Jason Monkcaf37622015-08-18 12:33:50 -0400382 return view;
383 }
384
385 @Override
386 public int getCount() {
387 return mNumPages;
388 }
389
390 @Override
391 public boolean isViewFromObject(View view, Object object) {
392 return view == object;
393 }
394 };
Jason Monk162011e2016-02-19 08:11:55 -0500395
396 public interface PageListener {
Jason Monk66eaf312016-02-25 12:29:29 -0500397 void onPageChanged(boolean isFirst);
Jason Monk162011e2016-02-19 08:11:55 -0500398 }
Jason Monkcaf37622015-08-18 12:33:50 -0400399}