blob: c68172eec1c618a85bc9b1b5a97c32bdc85d86b1 [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 Kozynski407ddb22018-10-03 15:04:56 -040011import android.os.Bundle;
Jason Monkcaf37622015-08-18 12:33:50 -040012import android.util.AttributeSet;
13import android.util.Log;
14import android.view.LayoutInflater;
15import android.view.View;
16import android.view.ViewGroup;
Amin Shaikha07a17b2018-02-23 16:02:52 -050017import android.view.animation.Interpolator;
18import android.view.animation.OvershootInterpolator;
19import android.widget.Scroller;
Jason Monkdf36aed2016-07-25 11:21:56 -040020
Fabian Kozynski6eaf3ac2018-10-01 15:59:05 -040021import androidx.viewpager.widget.PagerAdapter;
22import androidx.viewpager.widget.ViewPager;
23
Jason Monkcaf37622015-08-18 12:33:50 -040024import com.android.systemui.R;
25import com.android.systemui.qs.QSPanel.QSTileLayout;
26import com.android.systemui.qs.QSPanel.TileRecord;
27
28import java.util.ArrayList;
Amin Shaikha07a17b2018-02-23 16:02:52 -050029import java.util.Set;
Jason Monkcaf37622015-08-18 12:33:50 -040030
31public class PagedTileLayout extends ViewPager implements QSTileLayout {
32
33 private static final boolean DEBUG = false;
Fabian Kozynski407ddb22018-10-03 15:04:56 -040034 private static final String CURRENT_PAGE = "current_page";
Jason Monkcaf37622015-08-18 12:33:50 -040035
36 private static final String TAG = "PagedTileLayout";
Amin Shaikha07a17b2018-02-23 16:02:52 -050037 private static final int REVEAL_SCROLL_DURATION_MILLIS = 750;
38 private static final float BOUNCE_ANIMATION_TENSION = 1.3f;
39 private static final long BOUNCE_ANIMATION_DURATION = 450L;
40 private static final int TILE_ANIMATION_STAGGER_DELAY = 85;
41 private static final Interpolator SCROLL_CUBIC = (t) -> {
42 t -= 1.0f;
43 return t * t * t + 1.0f;
44 };
45
Amin Shaikh9978c552018-03-22 07:24:41 -040046 private final ArrayList<TileRecord> mTiles = new ArrayList<>();
47 private final ArrayList<TilePage> mPages = new ArrayList<>();
Jason Monkcaf37622015-08-18 12:33:50 -040048
Jason Monkcaf37622015-08-18 12:33:50 -040049 private PageIndicator mPageIndicator;
Rohan Shah3090e792018-04-12 00:01:00 -040050 private float mPageIndicatorPosition;
Jason Monkcaf37622015-08-18 12:33:50 -040051
Jason Monk162011e2016-02-19 08:11:55 -050052 private PageListener mPageListener;
Jason Monkcaf37622015-08-18 12:33:50 -040053
Jason Monke5107a32016-05-31 15:40:58 -040054 private boolean mListening;
Amin Shaikha07a17b2018-02-23 16:02:52 -050055 private Scroller mScroller;
56
57 private AnimatorSet mBounceAnimatorSet;
Amin Shaikh4c9048c2018-04-20 11:27:46 -040058 private float mLastExpansion;
Fabian Kozynski712ae392018-09-12 09:11:16 -040059 private boolean mDistributeTiles = false;
Fabian Kozynski407ddb22018-10-03 15:04:56 -040060 private int mPageToRestore = -1;
61 private int mLayoutOrientation;
62 private int mLayoutDirection;
Jason Monke5107a32016-05-31 15:40:58 -040063
Jason Monkcaf37622015-08-18 12:33:50 -040064 public PagedTileLayout(Context context, AttributeSet attrs) {
65 super(context, attrs);
Amin Shaikha07a17b2018-02-23 16:02:52 -050066 mScroller = new Scroller(context, SCROLL_CUBIC);
Jason Monkcaf37622015-08-18 12:33:50 -040067 setAdapter(mAdapter);
Amin Shaikha07a17b2018-02-23 16:02:52 -050068 setOnPageChangeListener(mOnPageChangeListener);
69 setCurrentItem(0, false);
Fabian Kozynski407ddb22018-10-03 15:04:56 -040070 mLayoutOrientation = getResources().getConfiguration().orientation;
71 mLayoutDirection = getLayoutDirection();
72 }
73
74 public void saveInstanceState(Bundle outState) {
75 outState.putInt(CURRENT_PAGE, getCurrentItem());
76 }
77
78 public void restoreInstanceState(Bundle savedInstanceState) {
79 // There's only 1 page at this point. We want to restore the correct page once the
80 // pages have been inflated
81 mPageToRestore = savedInstanceState.getInt(CURRENT_PAGE, -1);
82 }
83
84 @Override
85 protected void onConfigurationChanged(Configuration newConfig) {
86 super.onConfigurationChanged(newConfig);
87 if (mLayoutOrientation != newConfig.orientation) {
88 mLayoutOrientation = newConfig.orientation;
89 setCurrentItem(0, false);
90 }
Jason Monkcaf37622015-08-18 12:33:50 -040091 }
92
93 @Override
Jason Monk51fb85a2016-03-28 14:06:04 -040094 public void onRtlPropertiesChanged(int layoutDirection) {
95 super.onRtlPropertiesChanged(layoutDirection);
Fabian Kozynski407ddb22018-10-03 15:04:56 -040096 if (mLayoutDirection != layoutDirection) {
97 mLayoutDirection = layoutDirection;
98 setAdapter(mAdapter);
99 setCurrentItem(0, false);
100 }
Jason Monk51fb85a2016-03-28 14:06:04 -0400101 }
102
103 @Override
104 public void setCurrentItem(int item, boolean smoothScroll) {
105 if (isLayoutRtl()) {
106 item = mPages.size() - 1 - item;
107 }
108 super.setCurrentItem(item, smoothScroll);
109 }
110
111 @Override
Jason Monke5107a32016-05-31 15:40:58 -0400112 public void setListening(boolean listening) {
113 if (mListening == listening) return;
114 mListening = listening;
Amin Shaikh9978c552018-03-22 07:24:41 -0400115 updateListening();
116 }
117
118 private void updateListening() {
119 for (TilePage tilePage : mPages) {
120 tilePage.setListening(tilePage.getParent() == null ? false : mListening);
Jason Monke5107a32016-05-31 15:40:58 -0400121 }
122 }
123
Amin Shaikha07a17b2018-02-23 16:02:52 -0500124 @Override
Amin Shaikha07a17b2018-02-23 16:02:52 -0500125 public void computeScroll() {
126 if (!mScroller.isFinished() && mScroller.computeScrollOffset()) {
Amin Shaikh5484d0f2018-07-31 10:44:11 -0400127 fakeDragBy(getScrollX() - mScroller.getCurrX());
Amin Shaikha07a17b2018-02-23 16:02:52 -0500128 // Keep on drawing until the animation has finished.
129 postInvalidateOnAnimation();
130 return;
Amin Shaikh5484d0f2018-07-31 10:44:11 -0400131 } else if (isFakeDragging()) {
132 endFakeDrag();
Amin Shaikha07a17b2018-02-23 16:02:52 -0500133 mBounceAnimatorSet.start();
134 setOffscreenPageLimit(1);
Amin Shaikha07a17b2018-02-23 16:02:52 -0500135 }
136 super.computeScroll();
137 }
138
Jason Monke5107a32016-05-31 15:40:58 -0400139 @Override
Jason Monkc5bdafb2016-02-25 16:24:21 -0500140 public boolean hasOverlappingRendering() {
141 return false;
142 }
143
144 @Override
Jason Monkcaf37622015-08-18 12:33:50 -0400145 protected void onFinishInflate() {
146 super.onFinishInflate();
Jason Monk32508852017-01-18 09:17:13 -0500147 mPages.add((TilePage) LayoutInflater.from(getContext())
Jason Monke4e69302016-01-20 11:27:15 -0500148 .inflate(R.layout.qs_paged_page, this, false));
Jason Monkcaf37622015-08-18 12:33:50 -0400149 }
150
Jason Monk32508852017-01-18 09:17:13 -0500151 public void setPageIndicator(PageIndicator indicator) {
152 mPageIndicator = indicator;
Fabian Kozynski712ae392018-09-12 09:11:16 -0400153 mPageIndicator.setNumPages(mPages.size());
Rohan Shah3090e792018-04-12 00:01:00 -0400154 mPageIndicator.setLocation(mPageIndicatorPosition);
Jason Monk32508852017-01-18 09:17:13 -0500155 }
156
Jason Monkcaf37622015-08-18 12:33:50 -0400157 @Override
158 public int getOffsetTop(TileRecord tile) {
Jason Monkae5bd032016-03-02 14:38:31 -0500159 final ViewGroup parent = (ViewGroup) tile.tileView.getParent();
160 if (parent == null) return 0;
161 return parent.getTop() + getTop();
Jason Monkcaf37622015-08-18 12:33:50 -0400162 }
163
164 @Override
Jason Monkcaf37622015-08-18 12:33:50 -0400165 public void addTile(TileRecord tile) {
166 mTiles.add(tile);
Fabian Kozynski712ae392018-09-12 09:11:16 -0400167 mDistributeTiles = true;
168 requestLayout();
Jason Monkcaf37622015-08-18 12:33:50 -0400169 }
170
171 @Override
172 public void removeTile(TileRecord tile) {
173 if (mTiles.remove(tile)) {
Fabian Kozynski712ae392018-09-12 09:11:16 -0400174 mDistributeTiles = true;
175 requestLayout();
Jason Monkcaf37622015-08-18 12:33:50 -0400176 }
177 }
178
Amin Shaikh0f8ea5432018-03-27 11:09:27 -0400179 @Override
180 public void setExpansion(float expansion) {
Amin Shaikh4c9048c2018-04-20 11:27:46 -0400181 mLastExpansion = expansion;
182 updateSelected();
183 }
184
185 private void updateSelected() {
186 // Start the marquee when fully expanded and stop when fully collapsed. Leave as is for
187 // other expansion ratios since there is no way way to pause the marquee.
188 if (mLastExpansion > 0f && mLastExpansion < 1f) {
189 return;
190 }
191 boolean selected = mLastExpansion == 1f;
Rohan Shah2dbcb572018-05-25 10:51:22 -0700192
193 // Disable accessibility temporarily while we update selected state purely for the
194 // marquee. This will ensure that accessibility doesn't announce the TYPE_VIEW_SELECTED
195 // event on any of the children.
196 setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
Amin Shaikh6f570742018-06-01 17:18:19 -0400197 int currentItem = isLayoutRtl() ? mPages.size() - 1 - getCurrentItem() : getCurrentItem();
Amin Shaikh4c9048c2018-04-20 11:27:46 -0400198 for (int i = 0; i < mPages.size(); i++) {
Amin Shaikh6f570742018-06-01 17:18:19 -0400199 mPages.get(i).setSelected(i == currentItem ? selected : false);
Amin Shaikh0f8ea5432018-03-27 11:09:27 -0400200 }
Rohan Shah2dbcb572018-05-25 10:51:22 -0700201 setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_AUTO);
Amin Shaikh0f8ea5432018-03-27 11:09:27 -0400202 }
203
Jason Monk162011e2016-02-19 08:11:55 -0500204 public void setPageListener(PageListener listener) {
205 mPageListener = listener;
206 }
207
Jason Monkcaf37622015-08-18 12:33:50 -0400208 private void distributeTiles() {
Fabian Kozynski712ae392018-09-12 09:11:16 -0400209 emptyAndInflateOrRemovePages();
210
211 final int tileCount = mPages.get(0).maxTiles();
Jason Monkcaf37622015-08-18 12:33:50 -0400212 if (DEBUG) Log.d(TAG, "Distributing tiles");
Jason Monkcaf37622015-08-18 12:33:50 -0400213 int index = 0;
214 final int NT = mTiles.size();
215 for (int i = 0; i < NT; i++) {
216 TileRecord tile = mTiles.get(i);
Fabian Kozynski712ae392018-09-12 09:11:16 -0400217 if (mPages.get(index).mRecords.size() == tileCount) index++;
218 if (DEBUG) {
219 Log.d(TAG, "Adding " + tile.tile.getClass().getSimpleName() + " to "
220 + index);
Jason Monkcaf37622015-08-18 12:33:50 -0400221 }
Jason Monkcaf37622015-08-18 12:33:50 -0400222 mPages.get(index).addTile(tile);
223 }
Fabian Kozynski712ae392018-09-12 09:11:16 -0400224 }
225
226 private void emptyAndInflateOrRemovePages() {
227 final int nTiles = mTiles.size();
228 int numPages = nTiles / mPages.get(0).maxTiles();
229 // Add one more not full page if needed
230 numPages += (nTiles % mPages.get(0).maxTiles() == 0 ? 0 : 1);
231
232 final int NP = mPages.size();
233 for (int i = 0; i < NP; i++) {
234 mPages.get(i).removeAllViews();
Jason Monkcaf37622015-08-18 12:33:50 -0400235 }
Fabian Kozynski712ae392018-09-12 09:11:16 -0400236 if (NP == numPages) {
237 return;
238 }
239 while (mPages.size() < numPages) {
240 if (DEBUG) Log.d(TAG, "Adding page");
241 mPages.add((TilePage) LayoutInflater.from(getContext())
242 .inflate(R.layout.qs_paged_page, this, false));
243 }
244 while (mPages.size() > numPages) {
245 if (DEBUG) Log.d(TAG, "Removing page");
246 mPages.remove(mPages.size() - 1);
247 }
248 mPageIndicator.setNumPages(mPages.size());
249 setAdapter(mAdapter);
250 mAdapter.notifyDataSetChanged();
Fabian Kozynski407ddb22018-10-03 15:04:56 -0400251 if (mPageToRestore != -1) {
252 setCurrentItem(mPageToRestore, false);
253 mPageToRestore = -1;
254 }
Jason Monkcaf37622015-08-18 12:33:50 -0400255 }
256
257 @Override
Jason Monk9d02a432016-01-20 16:33:46 -0500258 public boolean updateResources() {
Rohan Shah3090e792018-04-12 00:01:00 -0400259 // Update bottom padding, useful for removing extra space once the panel page indicator is
260 // hidden.
Fabian Kozynski6eaf3ac2018-10-01 15:59:05 -0400261 Resources res = getContext().getResources();
262 final int sidePadding = res.getDimensionPixelSize(R.dimen.notification_side_paddings);
263 setPadding(sidePadding, 0, sidePadding,
Rohan Shah3090e792018-04-12 00:01:00 -0400264 getContext().getResources().getDimensionPixelSize(
265 R.dimen.qs_paged_tile_layout_padding_bottom));
Jason Monk9d02a432016-01-20 16:33:46 -0500266 boolean changed = false;
Jason Monkcaf37622015-08-18 12:33:50 -0400267 for (int i = 0; i < mPages.size(); i++) {
Jason Monk9d02a432016-01-20 16:33:46 -0500268 changed |= mPages.get(i).updateResources();
Jason Monkcaf37622015-08-18 12:33:50 -0400269 }
Jason Monk9d02a432016-01-20 16:33:46 -0500270 if (changed) {
Fabian Kozynski712ae392018-09-12 09:11:16 -0400271 mDistributeTiles = true;
272 requestLayout();
Jason Monk9d02a432016-01-20 16:33:46 -0500273 }
274 return changed;
Jason Monkcaf37622015-08-18 12:33:50 -0400275 }
276
277 @Override
278 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Fabian Kozynski712ae392018-09-12 09:11:16 -0400279
280 final int nTiles = mTiles.size();
281 if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.AT_MOST) {
282
283 // Only change the pages if the number of rows or columns (from updateResources) has
284 // changed or the tiles have changed
285 if (mPages.get(0).updateMaxRows(heightMeasureSpec, nTiles) || mDistributeTiles) {
286 mDistributeTiles = false;
287 distributeTiles();
288 }
289
290 final int nRows = mPages.get(0).mRows;
291 for (int i = 0; i < mPages.size(); i++) {
292 TilePage t = mPages.get(i);
293 t.mRows = nRows;
294 }
295 }
296
Jason Monkcaf37622015-08-18 12:33:50 -0400297 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
Fabian Kozynski712ae392018-09-12 09:11:16 -0400298
Jason Monkcaf37622015-08-18 12:33:50 -0400299 // The ViewPager likes to eat all of the space, instead force it to wrap to the max height
300 // of the pages.
301 int maxHeight = 0;
302 final int N = getChildCount();
303 for (int i = 0; i < N; i++) {
304 int height = getChildAt(i).getMeasuredHeight();
305 if (height > maxHeight) {
306 maxHeight = height;
307 }
308 }
Jason Monkf13413e2017-02-15 15:49:32 -0500309 setMeasuredDimension(getMeasuredWidth(), maxHeight + getPaddingBottom());
Jason Monkcaf37622015-08-18 12:33:50 -0400310 }
311
Jason Monk8fb77872016-03-03 16:39:42 -0500312 public int getColumnCount() {
313 if (mPages.size() == 0) return 0;
314 return mPages.get(0).mColumns;
315 }
316
Fabian Kozynski802279f2018-09-07 13:44:54 -0400317 public int getNumVisibleTiles() {
318 if (mPages.size() == 0) return 0;
319 TilePage currentPage = mPages.get(getCurrentItem());
320 return currentPage.mRecords.size();
321 }
322
Amin Shaikha07a17b2018-02-23 16:02:52 -0500323 public void startTileReveal(Set<String> tileSpecs, final Runnable postAnimation) {
Amin Shaikh5484d0f2018-07-31 10:44:11 -0400324 if (tileSpecs.isEmpty() || mPages.size() < 2 || getScrollX() != 0 || !beginFakeDrag()) {
Amin Shaikha07a17b2018-02-23 16:02:52 -0500325 // Do not start the reveal animation unless there are tiles to animate, multiple
326 // TilePages available and the user has not already started dragging.
327 return;
328 }
329
330 final int lastPageNumber = mPages.size() - 1;
331 final TilePage lastPage = mPages.get(lastPageNumber);
332 final ArrayList<Animator> bounceAnims = new ArrayList<>();
333 for (TileRecord tr : lastPage.mRecords) {
334 if (tileSpecs.contains(tr.tile.getTileSpec())) {
335 bounceAnims.add(setupBounceAnimator(tr.tileView, bounceAnims.size()));
336 }
337 }
338
339 if (bounceAnims.isEmpty()) {
340 // All tileSpecs are on the first page. Nothing to do.
341 // TODO: potentially show a bounce animation for first page QS tiles
Amin Shaikh5484d0f2018-07-31 10:44:11 -0400342 endFakeDrag();
Amin Shaikha07a17b2018-02-23 16:02:52 -0500343 return;
344 }
345
346 mBounceAnimatorSet = new AnimatorSet();
347 mBounceAnimatorSet.playTogether(bounceAnims);
348 mBounceAnimatorSet.addListener(new AnimatorListenerAdapter() {
349 @Override
350 public void onAnimationEnd(Animator animation) {
351 mBounceAnimatorSet = null;
352 postAnimation.run();
353 }
354 });
Amin Shaikh5484d0f2018-07-31 10:44:11 -0400355 setOffscreenPageLimit(lastPageNumber); // Ensure the page to reveal has been inflated.
356 int dx = getWidth() * lastPageNumber;
357 mScroller.startScroll(getScrollX(), getScrollY(), isLayoutRtl() ? -dx : dx, 0,
358 REVEAL_SCROLL_DURATION_MILLIS);
Amin Shaikha07a17b2018-02-23 16:02:52 -0500359 postInvalidateOnAnimation();
360 }
361
362 private static Animator setupBounceAnimator(View view, int ordinal) {
363 view.setAlpha(0f);
364 view.setScaleX(0f);
365 view.setScaleY(0f);
366 ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(view,
367 PropertyValuesHolder.ofFloat(View.ALPHA, 1),
368 PropertyValuesHolder.ofFloat(View.SCALE_X, 1),
369 PropertyValuesHolder.ofFloat(View.SCALE_Y, 1));
370 animator.setDuration(BOUNCE_ANIMATION_DURATION);
371 animator.setStartDelay(ordinal * TILE_ANIMATION_STAGGER_DELAY);
372 animator.setInterpolator(new OvershootInterpolator(BOUNCE_ANIMATION_TENSION));
373 return animator;
374 }
375
376 private final ViewPager.OnPageChangeListener mOnPageChangeListener =
377 new ViewPager.SimpleOnPageChangeListener() {
378 @Override
379 public void onPageSelected(int position) {
Amin Shaikh4c9048c2018-04-20 11:27:46 -0400380 updateSelected();
Amin Shaikha07a17b2018-02-23 16:02:52 -0500381 if (mPageIndicator == null) return;
382 if (mPageListener != null) {
383 mPageListener.onPageChanged(isLayoutRtl() ? position == mPages.size() - 1
384 : position == 0);
385 }
386 }
387
388 @Override
389 public void onPageScrolled(int position, float positionOffset,
390 int positionOffsetPixels) {
391 if (mPageIndicator == null) return;
Rohan Shah3090e792018-04-12 00:01:00 -0400392 mPageIndicatorPosition = position + positionOffset;
393 mPageIndicator.setLocation(mPageIndicatorPosition);
Amin Shaikha07a17b2018-02-23 16:02:52 -0500394 if (mPageListener != null) {
395 mPageListener.onPageChanged(positionOffsetPixels == 0 &&
396 (isLayoutRtl() ? position == mPages.size() - 1 : position == 0));
397 }
398 }
399 };
400
Jason Monkcaf37622015-08-18 12:33:50 -0400401 public static class TilePage extends TileLayout {
Fabian Kozynski712ae392018-09-12 09:11:16 -0400402
Jason Monkcaf37622015-08-18 12:33:50 -0400403 public TilePage(Context context, AttributeSet attrs) {
404 super(context, attrs);
Jason Monkbd6dbb02015-09-03 15:46:25 -0400405 }
406
Jason Monkbd6dbb02015-09-03 15:46:25 -0400407 public boolean isFull() {
Fabian Kozynski712ae392018-09-12 09:11:16 -0400408 return mRecords.size() >= mColumns * mRows;
409 }
410
411 public int maxTiles() {
412 return mColumns * mRows;
Jason Monkcaf37622015-08-18 12:33:50 -0400413 }
414 }
415
416 private final PagerAdapter mAdapter = new PagerAdapter() {
Amin Shaikh9978c552018-03-22 07:24:41 -0400417 @Override
Jason Monkcaf37622015-08-18 12:33:50 -0400418 public void destroyItem(ViewGroup container, int position, Object object) {
419 if (DEBUG) Log.d(TAG, "Destantiating " + position);
Jason Monkcaf37622015-08-18 12:33:50 -0400420 container.removeView((View) object);
Amin Shaikh9978c552018-03-22 07:24:41 -0400421 updateListening();
Jason Monkcaf37622015-08-18 12:33:50 -0400422 }
423
Amin Shaikh9978c552018-03-22 07:24:41 -0400424 @Override
Jason Monkcaf37622015-08-18 12:33:50 -0400425 public Object instantiateItem(ViewGroup container, int position) {
426 if (DEBUG) Log.d(TAG, "Instantiating " + position);
Jason Monk51fb85a2016-03-28 14:06:04 -0400427 if (isLayoutRtl()) {
428 position = mPages.size() - 1 - position;
429 }
Jason Monke4e69302016-01-20 11:27:15 -0500430 ViewGroup view = mPages.get(position);
Amin Shaikh53bc0832018-09-17 15:06:18 -0400431 if (view.getParent() != null) {
432 container.removeView(view);
433 }
Jason Monkcaf37622015-08-18 12:33:50 -0400434 container.addView(view);
Amin Shaikh9978c552018-03-22 07:24:41 -0400435 updateListening();
Jason Monkcaf37622015-08-18 12:33:50 -0400436 return view;
437 }
438
439 @Override
440 public int getCount() {
Fabian Kozynski712ae392018-09-12 09:11:16 -0400441 return mPages.size();
Jason Monkcaf37622015-08-18 12:33:50 -0400442 }
443
444 @Override
445 public boolean isViewFromObject(View view, Object object) {
446 return view == object;
447 }
448 };
Jason Monk162011e2016-02-19 08:11:55 -0500449
450 public interface PageListener {
Jason Monk66eaf312016-02-25 12:29:29 -0500451 void onPageChanged(boolean isFirst);
Jason Monk162011e2016-02-19 08:11:55 -0500452 }
Jason Monkcaf37622015-08-18 12:33:50 -0400453}