blob: aaff9ac47ebf41debd6606ba8138104cb693d165 [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;
Fabian Kozynski407ddb22018-10-03 15:04:56 -04009import android.content.res.Configuration;
Fabian Kozynski6eaf3ac2018-10-01 15:59:05 -040010import android.content.res.Resources;
Fabian Kozynski52bd8c22018-10-08 12:52:51 -040011import android.graphics.Rect;
Fabian Kozynski407ddb22018-10-03 15:04:56 -040012import android.os.Bundle;
Jason Monkcaf37622015-08-18 12:33:50 -040013import android.util.AttributeSet;
14import android.util.Log;
15import android.view.LayoutInflater;
16import android.view.View;
17import android.view.ViewGroup;
Amin Shaikha07a17b2018-02-23 16:02:52 -050018import android.view.animation.Interpolator;
19import android.view.animation.OvershootInterpolator;
20import android.widget.Scroller;
Jason Monkdf36aed2016-07-25 11:21:56 -040021
Fabian Kozynski6eaf3ac2018-10-01 15:59:05 -040022import androidx.viewpager.widget.PagerAdapter;
23import androidx.viewpager.widget.ViewPager;
24
Fabian Kozynski2ff6df92020-04-24 12:00:49 -040025import com.android.internal.logging.UiEventLogger;
Jason Monkcaf37622015-08-18 12:33:50 -040026import com.android.systemui.R;
Fabian Kozynski2ff6df92020-04-24 12:00:49 -040027import com.android.systemui.plugins.qs.QSTile;
Jason Monkcaf37622015-08-18 12:33:50 -040028import com.android.systemui.qs.QSPanel.QSTileLayout;
29import com.android.systemui.qs.QSPanel.TileRecord;
30
31import java.util.ArrayList;
Amin Shaikha07a17b2018-02-23 16:02:52 -050032import java.util.Set;
Jason Monkcaf37622015-08-18 12:33:50 -040033
34public class PagedTileLayout extends ViewPager implements QSTileLayout {
35
36 private static final boolean DEBUG = false;
Fabian Kozynski407ddb22018-10-03 15:04:56 -040037 private static final String CURRENT_PAGE = "current_page";
Jason Monkcaf37622015-08-18 12:33:50 -040038
39 private static final String TAG = "PagedTileLayout";
Amin Shaikha07a17b2018-02-23 16:02:52 -050040 private static final int REVEAL_SCROLL_DURATION_MILLIS = 750;
41 private static final float BOUNCE_ANIMATION_TENSION = 1.3f;
42 private static final long BOUNCE_ANIMATION_DURATION = 450L;
43 private static final int TILE_ANIMATION_STAGGER_DELAY = 85;
44 private static final Interpolator SCROLL_CUBIC = (t) -> {
45 t -= 1.0f;
46 return t * t * t + 1.0f;
47 };
48
Amin Shaikh9978c552018-03-22 07:24:41 -040049 private final ArrayList<TileRecord> mTiles = new ArrayList<>();
50 private final ArrayList<TilePage> mPages = new ArrayList<>();
Jason Monkcaf37622015-08-18 12:33:50 -040051
Jason Monkcaf37622015-08-18 12:33:50 -040052 private PageIndicator mPageIndicator;
Rohan Shah3090e792018-04-12 00:01:00 -040053 private float mPageIndicatorPosition;
Jason Monkcaf37622015-08-18 12:33:50 -040054
Jason Monk162011e2016-02-19 08:11:55 -050055 private PageListener mPageListener;
Jason Monkcaf37622015-08-18 12:33:50 -040056
Jason Monke5107a32016-05-31 15:40:58 -040057 private boolean mListening;
Amin Shaikha07a17b2018-02-23 16:02:52 -050058 private Scroller mScroller;
59
60 private AnimatorSet mBounceAnimatorSet;
Amin Shaikh4c9048c2018-04-20 11:27:46 -040061 private float mLastExpansion;
Fabian Kozynski712ae392018-09-12 09:11:16 -040062 private boolean mDistributeTiles = false;
Fabian Kozynski407ddb22018-10-03 15:04:56 -040063 private int mPageToRestore = -1;
64 private int mLayoutOrientation;
65 private int mLayoutDirection;
Fabian Kozynski52bd8c22018-10-08 12:52:51 -040066 private int mHorizontalClipBound;
Fabian Kozynski87bdf0d2019-04-01 09:52:57 -040067 private final Rect mClippingRect;
Fabian Kozynski2ff6df92020-04-24 12:00:49 -040068 private final UiEventLogger mUiEventLogger = QSEvents.INSTANCE.getQsUiEventsLogger();
Jason Monke5107a32016-05-31 15:40:58 -040069
Jason Monkcaf37622015-08-18 12:33:50 -040070 public PagedTileLayout(Context context, AttributeSet attrs) {
71 super(context, attrs);
Amin Shaikha07a17b2018-02-23 16:02:52 -050072 mScroller = new Scroller(context, SCROLL_CUBIC);
Jason Monkcaf37622015-08-18 12:33:50 -040073 setAdapter(mAdapter);
Amin Shaikha07a17b2018-02-23 16:02:52 -050074 setOnPageChangeListener(mOnPageChangeListener);
75 setCurrentItem(0, false);
Fabian Kozynski407ddb22018-10-03 15:04:56 -040076 mLayoutOrientation = getResources().getConfiguration().orientation;
77 mLayoutDirection = getLayoutDirection();
Fabian Kozynski87bdf0d2019-04-01 09:52:57 -040078 mClippingRect = new Rect();
Fabian Kozynski407ddb22018-10-03 15:04:56 -040079 }
Fabian Kozynski2ff6df92020-04-24 12:00:49 -040080 private int mLastMaxHeight = -1;
Fabian Kozynski407ddb22018-10-03 15:04:56 -040081
82 public void saveInstanceState(Bundle outState) {
83 outState.putInt(CURRENT_PAGE, getCurrentItem());
84 }
85
86 public void restoreInstanceState(Bundle savedInstanceState) {
87 // There's only 1 page at this point. We want to restore the correct page once the
88 // pages have been inflated
89 mPageToRestore = savedInstanceState.getInt(CURRENT_PAGE, -1);
90 }
91
92 @Override
93 protected void onConfigurationChanged(Configuration newConfig) {
94 super.onConfigurationChanged(newConfig);
95 if (mLayoutOrientation != newConfig.orientation) {
96 mLayoutOrientation = newConfig.orientation;
97 setCurrentItem(0, false);
Fabian Kozynskid9e8aa32019-04-12 10:09:48 -040098 mPageToRestore = 0;
Fabian Kozynski407ddb22018-10-03 15:04:56 -040099 }
Jason Monkcaf37622015-08-18 12:33:50 -0400100 }
101
102 @Override
Jason Monk51fb85a2016-03-28 14:06:04 -0400103 public void onRtlPropertiesChanged(int layoutDirection) {
104 super.onRtlPropertiesChanged(layoutDirection);
Fabian Kozynski407ddb22018-10-03 15:04:56 -0400105 if (mLayoutDirection != layoutDirection) {
106 mLayoutDirection = layoutDirection;
107 setAdapter(mAdapter);
108 setCurrentItem(0, false);
Fabian Kozynskid9e8aa32019-04-12 10:09:48 -0400109 mPageToRestore = 0;
Fabian Kozynski407ddb22018-10-03 15:04:56 -0400110 }
Jason Monk51fb85a2016-03-28 14:06:04 -0400111 }
112
113 @Override
114 public void setCurrentItem(int item, boolean smoothScroll) {
115 if (isLayoutRtl()) {
116 item = mPages.size() - 1 - item;
117 }
118 super.setCurrentItem(item, smoothScroll);
119 }
120
Fabian Kozynskid9e8aa32019-04-12 10:09:48 -0400121 /**
122 * Obtains the current page number respecting RTL
123 */
124 private int getCurrentPageNumber() {
125 int page = getCurrentItem();
126 if (mLayoutDirection == LAYOUT_DIRECTION_RTL) {
127 page = mPages.size() - 1 - page;
128 }
129 return page;
130 }
131
Fabian Kozynski2ff6df92020-04-24 12:00:49 -0400132 // This will dump to the ui log all the tiles that are visible in this page
133 private void logVisibleTiles(TilePage page) {
134 for (int i = 0; i < page.mRecords.size(); i++) {
135 QSTile t = page.mRecords.get(i).tile;
136 mUiEventLogger.logWithInstanceId(QSEvent.QS_TILE_VISIBLE, 0, t.getMetricsSpec(),
137 t.getInstanceId());
138 }
139 }
140
Jason Monk51fb85a2016-03-28 14:06:04 -0400141 @Override
Jason Monke5107a32016-05-31 15:40:58 -0400142 public void setListening(boolean listening) {
143 if (mListening == listening) return;
144 mListening = listening;
Amin Shaikh9978c552018-03-22 07:24:41 -0400145 updateListening();
146 }
147
148 private void updateListening() {
149 for (TilePage tilePage : mPages) {
150 tilePage.setListening(tilePage.getParent() == null ? false : mListening);
Jason Monke5107a32016-05-31 15:40:58 -0400151 }
152 }
153
Amin Shaikha07a17b2018-02-23 16:02:52 -0500154 @Override
Fabian Kozynski545f2502020-04-13 11:37:10 -0400155 public void fakeDragBy(float xOffset) {
156 try {
157 super.fakeDragBy(xOffset);
158 // Keep on drawing until the animation has finished.
159 postInvalidateOnAnimation();
160 } catch (NullPointerException e) {
161 Log.e(TAG, "FakeDragBy called before begin", e);
162 // If we were trying to fake drag, it means we just added a new tile to the last
163 // page, so animate there.
164 final int lastPageNumber = mPages.size() - 1;
165 post(() -> {
166 setCurrentItem(lastPageNumber, true);
167 if (mBounceAnimatorSet != null) {
168 mBounceAnimatorSet.start();
169 }
170 setOffscreenPageLimit(1);
171 });
172 }
173 }
174
175 @Override
Amin Shaikha07a17b2018-02-23 16:02:52 -0500176 public void computeScroll() {
177 if (!mScroller.isFinished() && mScroller.computeScrollOffset()) {
Fabian Kozynski9073ef02020-03-16 11:50:12 -0400178 if (!isFakeDragging()) {
179 beginFakeDrag();
180 }
Amin Shaikh5484d0f2018-07-31 10:44:11 -0400181 fakeDragBy(getScrollX() - mScroller.getCurrX());
Amin Shaikh5484d0f2018-07-31 10:44:11 -0400182 } else if (isFakeDragging()) {
183 endFakeDrag();
Amin Shaikha07a17b2018-02-23 16:02:52 -0500184 mBounceAnimatorSet.start();
185 setOffscreenPageLimit(1);
Amin Shaikha07a17b2018-02-23 16:02:52 -0500186 }
187 super.computeScroll();
188 }
189
Jason Monke5107a32016-05-31 15:40:58 -0400190 @Override
Jason Monkc5bdafb2016-02-25 16:24:21 -0500191 public boolean hasOverlappingRendering() {
192 return false;
193 }
194
195 @Override
Jason Monkcaf37622015-08-18 12:33:50 -0400196 protected void onFinishInflate() {
197 super.onFinishInflate();
Jason Monk32508852017-01-18 09:17:13 -0500198 mPages.add((TilePage) LayoutInflater.from(getContext())
Jason Monke4e69302016-01-20 11:27:15 -0500199 .inflate(R.layout.qs_paged_page, this, false));
Fabian Kozynskifae42a82018-10-04 11:18:42 -0400200 mAdapter.notifyDataSetChanged();
Jason Monkcaf37622015-08-18 12:33:50 -0400201 }
202
Jason Monk32508852017-01-18 09:17:13 -0500203 public void setPageIndicator(PageIndicator indicator) {
204 mPageIndicator = indicator;
Fabian Kozynski712ae392018-09-12 09:11:16 -0400205 mPageIndicator.setNumPages(mPages.size());
Rohan Shah3090e792018-04-12 00:01:00 -0400206 mPageIndicator.setLocation(mPageIndicatorPosition);
Jason Monk32508852017-01-18 09:17:13 -0500207 }
208
Jason Monkcaf37622015-08-18 12:33:50 -0400209 @Override
210 public int getOffsetTop(TileRecord tile) {
Jason Monkae5bd032016-03-02 14:38:31 -0500211 final ViewGroup parent = (ViewGroup) tile.tileView.getParent();
212 if (parent == null) return 0;
213 return parent.getTop() + getTop();
Jason Monkcaf37622015-08-18 12:33:50 -0400214 }
215
216 @Override
Jason Monkcaf37622015-08-18 12:33:50 -0400217 public void addTile(TileRecord tile) {
218 mTiles.add(tile);
Fabian Kozynski712ae392018-09-12 09:11:16 -0400219 mDistributeTiles = true;
220 requestLayout();
Jason Monkcaf37622015-08-18 12:33:50 -0400221 }
222
223 @Override
224 public void removeTile(TileRecord tile) {
225 if (mTiles.remove(tile)) {
Fabian Kozynski712ae392018-09-12 09:11:16 -0400226 mDistributeTiles = true;
227 requestLayout();
Jason Monkcaf37622015-08-18 12:33:50 -0400228 }
229 }
230
Amin Shaikh0f8ea5432018-03-27 11:09:27 -0400231 @Override
232 public void setExpansion(float expansion) {
Amin Shaikh4c9048c2018-04-20 11:27:46 -0400233 mLastExpansion = expansion;
234 updateSelected();
235 }
236
237 private void updateSelected() {
238 // Start the marquee when fully expanded and stop when fully collapsed. Leave as is for
239 // other expansion ratios since there is no way way to pause the marquee.
240 if (mLastExpansion > 0f && mLastExpansion < 1f) {
241 return;
242 }
243 boolean selected = mLastExpansion == 1f;
Rohan Shah2dbcb572018-05-25 10:51:22 -0700244
245 // Disable accessibility temporarily while we update selected state purely for the
246 // marquee. This will ensure that accessibility doesn't announce the TYPE_VIEW_SELECTED
247 // event on any of the children.
248 setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
Fabian Kozynskid9e8aa32019-04-12 10:09:48 -0400249 int currentItem = getCurrentPageNumber();
Amin Shaikh4c9048c2018-04-20 11:27:46 -0400250 for (int i = 0; i < mPages.size(); i++) {
Fabian Kozynski2ff6df92020-04-24 12:00:49 -0400251 TilePage page = mPages.get(i);
252 page.setSelected(i == currentItem ? selected : false);
253 if (page.isSelected()) {
254 logVisibleTiles(page);
255 }
Amin Shaikh0f8ea5432018-03-27 11:09:27 -0400256 }
Rohan Shah2dbcb572018-05-25 10:51:22 -0700257 setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_AUTO);
Amin Shaikh0f8ea5432018-03-27 11:09:27 -0400258 }
259
Jason Monk162011e2016-02-19 08:11:55 -0500260 public void setPageListener(PageListener listener) {
261 mPageListener = listener;
262 }
263
Jason Monkcaf37622015-08-18 12:33:50 -0400264 private void distributeTiles() {
Fabian Kozynski712ae392018-09-12 09:11:16 -0400265 emptyAndInflateOrRemovePages();
266
267 final int tileCount = mPages.get(0).maxTiles();
Jason Monkcaf37622015-08-18 12:33:50 -0400268 if (DEBUG) Log.d(TAG, "Distributing tiles");
Jason Monkcaf37622015-08-18 12:33:50 -0400269 int index = 0;
270 final int NT = mTiles.size();
271 for (int i = 0; i < NT; i++) {
272 TileRecord tile = mTiles.get(i);
Fabian Kozynski712ae392018-09-12 09:11:16 -0400273 if (mPages.get(index).mRecords.size() == tileCount) index++;
274 if (DEBUG) {
275 Log.d(TAG, "Adding " + tile.tile.getClass().getSimpleName() + " to "
276 + index);
Jason Monkcaf37622015-08-18 12:33:50 -0400277 }
Jason Monkcaf37622015-08-18 12:33:50 -0400278 mPages.get(index).addTile(tile);
279 }
Fabian Kozynski712ae392018-09-12 09:11:16 -0400280 }
281
282 private void emptyAndInflateOrRemovePages() {
283 final int nTiles = mTiles.size();
Fabian Kozynski6ff5c182019-06-20 14:57:47 -0400284 // We should always have at least one page, even if it's empty.
285 int numPages = Math.max(nTiles / mPages.get(0).maxTiles(), 1);
286
Fabian Kozynski712ae392018-09-12 09:11:16 -0400287 // Add one more not full page if needed
Fabian Kozynski492be7c2019-07-31 14:41:18 -0400288 if (nTiles > numPages * mPages.get(0).maxTiles()) {
289 numPages++;
290 }
Fabian Kozynski712ae392018-09-12 09:11:16 -0400291
292 final int NP = mPages.size();
293 for (int i = 0; i < NP; i++) {
294 mPages.get(i).removeAllViews();
Jason Monkcaf37622015-08-18 12:33:50 -0400295 }
Fabian Kozynski712ae392018-09-12 09:11:16 -0400296 if (NP == numPages) {
297 return;
298 }
299 while (mPages.size() < numPages) {
300 if (DEBUG) Log.d(TAG, "Adding page");
301 mPages.add((TilePage) LayoutInflater.from(getContext())
302 .inflate(R.layout.qs_paged_page, this, false));
303 }
304 while (mPages.size() > numPages) {
305 if (DEBUG) Log.d(TAG, "Removing page");
306 mPages.remove(mPages.size() - 1);
307 }
308 mPageIndicator.setNumPages(mPages.size());
309 setAdapter(mAdapter);
310 mAdapter.notifyDataSetChanged();
Fabian Kozynski407ddb22018-10-03 15:04:56 -0400311 if (mPageToRestore != -1) {
312 setCurrentItem(mPageToRestore, false);
313 mPageToRestore = -1;
314 }
Jason Monkcaf37622015-08-18 12:33:50 -0400315 }
316
317 @Override
Jason Monk9d02a432016-01-20 16:33:46 -0500318 public boolean updateResources() {
Rohan Shah3090e792018-04-12 00:01:00 -0400319 // Update bottom padding, useful for removing extra space once the panel page indicator is
320 // hidden.
Fabian Kozynski6eaf3ac2018-10-01 15:59:05 -0400321 Resources res = getContext().getResources();
Fabian Kozynski52bd8c22018-10-08 12:52:51 -0400322 mHorizontalClipBound = res.getDimensionPixelSize(R.dimen.notification_side_paddings);
323 setPadding(0, 0, 0,
Rohan Shah3090e792018-04-12 00:01:00 -0400324 getContext().getResources().getDimensionPixelSize(
325 R.dimen.qs_paged_tile_layout_padding_bottom));
Jason Monk9d02a432016-01-20 16:33:46 -0500326 boolean changed = false;
Jason Monkcaf37622015-08-18 12:33:50 -0400327 for (int i = 0; i < mPages.size(); i++) {
Jason Monk9d02a432016-01-20 16:33:46 -0500328 changed |= mPages.get(i).updateResources();
Jason Monkcaf37622015-08-18 12:33:50 -0400329 }
Jason Monk9d02a432016-01-20 16:33:46 -0500330 if (changed) {
Fabian Kozynski712ae392018-09-12 09:11:16 -0400331 mDistributeTiles = true;
332 requestLayout();
Jason Monk9d02a432016-01-20 16:33:46 -0500333 }
334 return changed;
Jason Monkcaf37622015-08-18 12:33:50 -0400335 }
336
337 @Override
Fabian Kozynski52bd8c22018-10-08 12:52:51 -0400338 protected void onLayout(boolean changed, int l, int t, int r, int b) {
339 super.onLayout(changed, l, t, r, b);
Fabian Kozynski87bdf0d2019-04-01 09:52:57 -0400340 mClippingRect.set(mHorizontalClipBound, 0, (r - l) - mHorizontalClipBound, b - t);
341 setClipBounds(mClippingRect);
Fabian Kozynski52bd8c22018-10-08 12:52:51 -0400342 }
343
344 @Override
Jason Monkcaf37622015-08-18 12:33:50 -0400345 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Fabian Kozynski712ae392018-09-12 09:11:16 -0400346
347 final int nTiles = mTiles.size();
Fabian Kozynski72cd84a92019-04-15 14:09:43 -0400348 // If we have no reason to recalculate the number of rows, skip this step. In particular,
349 // if the height passed by its parent is the same as the last time, we try not to remeasure.
350 if (mDistributeTiles || mLastMaxHeight != MeasureSpec.getSize(heightMeasureSpec)) {
Fabian Kozynski712ae392018-09-12 09:11:16 -0400351
Fabian Kozynski72cd84a92019-04-15 14:09:43 -0400352 mLastMaxHeight = MeasureSpec.getSize(heightMeasureSpec);
Fabian Kozynski712ae392018-09-12 09:11:16 -0400353 // Only change the pages if the number of rows or columns (from updateResources) has
354 // changed or the tiles have changed
355 if (mPages.get(0).updateMaxRows(heightMeasureSpec, nTiles) || mDistributeTiles) {
356 mDistributeTiles = false;
357 distributeTiles();
358 }
359
360 final int nRows = mPages.get(0).mRows;
361 for (int i = 0; i < mPages.size(); i++) {
362 TilePage t = mPages.get(i);
363 t.mRows = nRows;
364 }
365 }
366
Jason Monkcaf37622015-08-18 12:33:50 -0400367 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
Fabian Kozynski712ae392018-09-12 09:11:16 -0400368
Jason Monkcaf37622015-08-18 12:33:50 -0400369 // The ViewPager likes to eat all of the space, instead force it to wrap to the max height
370 // of the pages.
371 int maxHeight = 0;
372 final int N = getChildCount();
373 for (int i = 0; i < N; i++) {
374 int height = getChildAt(i).getMeasuredHeight();
375 if (height > maxHeight) {
376 maxHeight = height;
377 }
378 }
Jason Monkf13413e2017-02-15 15:49:32 -0500379 setMeasuredDimension(getMeasuredWidth(), maxHeight + getPaddingBottom());
Jason Monkcaf37622015-08-18 12:33:50 -0400380 }
381
Jason Monk8fb77872016-03-03 16:39:42 -0500382 public int getColumnCount() {
383 if (mPages.size() == 0) return 0;
384 return mPages.get(0).mColumns;
385 }
386
Fabian Kozynski802279f2018-09-07 13:44:54 -0400387 public int getNumVisibleTiles() {
388 if (mPages.size() == 0) return 0;
Fabian Kozynskid9e8aa32019-04-12 10:09:48 -0400389 TilePage currentPage = mPages.get(getCurrentPageNumber());
Fabian Kozynski802279f2018-09-07 13:44:54 -0400390 return currentPage.mRecords.size();
391 }
392
Amin Shaikha07a17b2018-02-23 16:02:52 -0500393 public void startTileReveal(Set<String> tileSpecs, final Runnable postAnimation) {
Amin Shaikh5484d0f2018-07-31 10:44:11 -0400394 if (tileSpecs.isEmpty() || mPages.size() < 2 || getScrollX() != 0 || !beginFakeDrag()) {
Amin Shaikha07a17b2018-02-23 16:02:52 -0500395 // Do not start the reveal animation unless there are tiles to animate, multiple
396 // TilePages available and the user has not already started dragging.
397 return;
398 }
399
400 final int lastPageNumber = mPages.size() - 1;
401 final TilePage lastPage = mPages.get(lastPageNumber);
402 final ArrayList<Animator> bounceAnims = new ArrayList<>();
403 for (TileRecord tr : lastPage.mRecords) {
404 if (tileSpecs.contains(tr.tile.getTileSpec())) {
405 bounceAnims.add(setupBounceAnimator(tr.tileView, bounceAnims.size()));
406 }
407 }
408
409 if (bounceAnims.isEmpty()) {
410 // All tileSpecs are on the first page. Nothing to do.
411 // TODO: potentially show a bounce animation for first page QS tiles
Amin Shaikh5484d0f2018-07-31 10:44:11 -0400412 endFakeDrag();
Amin Shaikha07a17b2018-02-23 16:02:52 -0500413 return;
414 }
415
416 mBounceAnimatorSet = new AnimatorSet();
417 mBounceAnimatorSet.playTogether(bounceAnims);
418 mBounceAnimatorSet.addListener(new AnimatorListenerAdapter() {
419 @Override
420 public void onAnimationEnd(Animator animation) {
421 mBounceAnimatorSet = null;
422 postAnimation.run();
423 }
424 });
Amin Shaikh5484d0f2018-07-31 10:44:11 -0400425 setOffscreenPageLimit(lastPageNumber); // Ensure the page to reveal has been inflated.
426 int dx = getWidth() * lastPageNumber;
427 mScroller.startScroll(getScrollX(), getScrollY(), isLayoutRtl() ? -dx : dx, 0,
428 REVEAL_SCROLL_DURATION_MILLIS);
Amin Shaikha07a17b2018-02-23 16:02:52 -0500429 postInvalidateOnAnimation();
430 }
431
432 private static Animator setupBounceAnimator(View view, int ordinal) {
433 view.setAlpha(0f);
434 view.setScaleX(0f);
435 view.setScaleY(0f);
436 ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(view,
437 PropertyValuesHolder.ofFloat(View.ALPHA, 1),
438 PropertyValuesHolder.ofFloat(View.SCALE_X, 1),
439 PropertyValuesHolder.ofFloat(View.SCALE_Y, 1));
440 animator.setDuration(BOUNCE_ANIMATION_DURATION);
441 animator.setStartDelay(ordinal * TILE_ANIMATION_STAGGER_DELAY);
442 animator.setInterpolator(new OvershootInterpolator(BOUNCE_ANIMATION_TENSION));
443 return animator;
444 }
445
446 private final ViewPager.OnPageChangeListener mOnPageChangeListener =
447 new ViewPager.SimpleOnPageChangeListener() {
448 @Override
449 public void onPageSelected(int position) {
Amin Shaikh4c9048c2018-04-20 11:27:46 -0400450 updateSelected();
Amin Shaikha07a17b2018-02-23 16:02:52 -0500451 if (mPageIndicator == null) return;
452 if (mPageListener != null) {
453 mPageListener.onPageChanged(isLayoutRtl() ? position == mPages.size() - 1
454 : position == 0);
455 }
Fabian Kozynski2ff6df92020-04-24 12:00:49 -0400456
Amin Shaikha07a17b2018-02-23 16:02:52 -0500457 }
458
459 @Override
460 public void onPageScrolled(int position, float positionOffset,
461 int positionOffsetPixels) {
462 if (mPageIndicator == null) return;
Rohan Shah3090e792018-04-12 00:01:00 -0400463 mPageIndicatorPosition = position + positionOffset;
464 mPageIndicator.setLocation(mPageIndicatorPosition);
Amin Shaikha07a17b2018-02-23 16:02:52 -0500465 if (mPageListener != null) {
466 mPageListener.onPageChanged(positionOffsetPixels == 0 &&
467 (isLayoutRtl() ? position == mPages.size() - 1 : position == 0));
468 }
469 }
470 };
471
Jason Monkcaf37622015-08-18 12:33:50 -0400472 public static class TilePage extends TileLayout {
Fabian Kozynski712ae392018-09-12 09:11:16 -0400473
Jason Monkcaf37622015-08-18 12:33:50 -0400474 public TilePage(Context context, AttributeSet attrs) {
475 super(context, attrs);
Jason Monkbd6dbb02015-09-03 15:46:25 -0400476 }
477
Jason Monkbd6dbb02015-09-03 15:46:25 -0400478 public boolean isFull() {
Fabian Kozynski6ff5c182019-06-20 14:57:47 -0400479 return mRecords.size() >= maxTiles();
Fabian Kozynski712ae392018-09-12 09:11:16 -0400480 }
481
482 public int maxTiles() {
Fabian Kozynski6ff5c182019-06-20 14:57:47 -0400483 // Each page should be able to hold at least one tile. If there's not enough room to
484 // show even 1 or there are no tiles, it probably means we are in the middle of setting
485 // up.
486 return Math.max(mColumns * mRows, 1);
Jason Monkcaf37622015-08-18 12:33:50 -0400487 }
Fabian Kozynski52bd8c22018-10-08 12:52:51 -0400488
489 @Override
490 public boolean updateResources() {
491 final int sidePadding = getContext().getResources().getDimensionPixelSize(
492 R.dimen.notification_side_paddings);
493 setPadding(sidePadding, 0, sidePadding, 0);
494 return super.updateResources();
495 }
Jason Monkcaf37622015-08-18 12:33:50 -0400496 }
497
498 private final PagerAdapter mAdapter = new PagerAdapter() {
Amin Shaikh9978c552018-03-22 07:24:41 -0400499 @Override
Jason Monkcaf37622015-08-18 12:33:50 -0400500 public void destroyItem(ViewGroup container, int position, Object object) {
501 if (DEBUG) Log.d(TAG, "Destantiating " + position);
Jason Monkcaf37622015-08-18 12:33:50 -0400502 container.removeView((View) object);
Amin Shaikh9978c552018-03-22 07:24:41 -0400503 updateListening();
Jason Monkcaf37622015-08-18 12:33:50 -0400504 }
505
Amin Shaikh9978c552018-03-22 07:24:41 -0400506 @Override
Jason Monkcaf37622015-08-18 12:33:50 -0400507 public Object instantiateItem(ViewGroup container, int position) {
508 if (DEBUG) Log.d(TAG, "Instantiating " + position);
Jason Monk51fb85a2016-03-28 14:06:04 -0400509 if (isLayoutRtl()) {
510 position = mPages.size() - 1 - position;
511 }
Jason Monke4e69302016-01-20 11:27:15 -0500512 ViewGroup view = mPages.get(position);
Amin Shaikh53bc0832018-09-17 15:06:18 -0400513 if (view.getParent() != null) {
514 container.removeView(view);
515 }
Jason Monkcaf37622015-08-18 12:33:50 -0400516 container.addView(view);
Amin Shaikh9978c552018-03-22 07:24:41 -0400517 updateListening();
Jason Monkcaf37622015-08-18 12:33:50 -0400518 return view;
519 }
520
521 @Override
522 public int getCount() {
Fabian Kozynski712ae392018-09-12 09:11:16 -0400523 return mPages.size();
Jason Monkcaf37622015-08-18 12:33:50 -0400524 }
525
526 @Override
527 public boolean isViewFromObject(View view, Object object) {
528 return view == object;
529 }
530 };
Jason Monk162011e2016-02-19 08:11:55 -0500531
532 public interface PageListener {
Jason Monk66eaf312016-02-25 12:29:29 -0500533 void onPageChanged(boolean isFirst);
Jason Monk162011e2016-02-19 08:11:55 -0500534 }
Jason Monkcaf37622015-08-18 12:33:50 -0400535}