blob: 5174c1a34fcd114c556006acc65966e4d94de761 [file] [log] [blame]
Adam Cohen3db40672010-07-19 22:41:57 -07001/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.widget;
18
Adam Cohenef521762010-10-04 13:56:11 -070019import android.animation.AnimatorInflater;
Chet Haasea18a86b2010-09-07 13:20:00 -070020import android.animation.ObjectAnimator;
Adam Cohen3db40672010-07-19 22:41:57 -070021import android.content.Context;
22import android.content.Intent;
23import android.content.res.TypedArray;
24import android.os.Handler;
Adam Cohenb04f7ad2010-08-15 13:22:42 -070025import android.os.Parcel;
26import android.os.Parcelable;
Adam Cohen3db40672010-07-19 22:41:57 -070027import android.util.AttributeSet;
Adam Cohena32edd42010-10-26 10:35:01 -070028import android.view.MotionEvent;
Adam Cohen3db40672010-07-19 22:41:57 -070029import android.view.View;
Adam Cohena32edd42010-10-26 10:35:01 -070030import android.view.ViewConfiguration;
Adam Cohen44729e32010-07-22 16:00:07 -070031import android.view.ViewGroup;
Adam Cohena6a4cbc2012-09-26 17:36:40 -070032import android.widget.RemoteViews.OnClickHandler;
Adam Cohen3db40672010-07-19 22:41:57 -070033
Adam Cohen2148d432011-07-28 14:59:54 -070034import java.util.ArrayList;
35import java.util.HashMap;
36
Adam Cohen3db40672010-07-19 22:41:57 -070037/**
38 * Base class for a {@link AdapterView} that will perform animations
39 * when switching between its views.
40 *
41 * @attr ref android.R.styleable#AdapterViewAnimator_inAnimation
42 * @attr ref android.R.styleable#AdapterViewAnimator_outAnimation
43 * @attr ref android.R.styleable#AdapterViewAnimator_animateFirstView
Adam Cohen1b065cd2010-09-28 14:53:47 -070044 * @attr ref android.R.styleable#AdapterViewAnimator_loopViews
Adam Cohen3db40672010-07-19 22:41:57 -070045 */
Adam Cohen44729e32010-07-22 16:00:07 -070046public abstract class AdapterViewAnimator extends AdapterView<Adapter>
Adam Cohena02fdf12010-11-03 13:27:40 -070047 implements RemoteViewsAdapter.RemoteAdapterConnectionCallback, Advanceable {
Adam Cohen3db40672010-07-19 22:41:57 -070048 private static final String TAG = "RemoteViewAnimator";
49
Adam Cohen44729e32010-07-22 16:00:07 -070050 /**
51 * The index of the current child, which appears anywhere from the beginning
52 * to the end of the current set of children, as specified by {@link #mActiveOffset}
53 */
Adam Cohen3db40672010-07-19 22:41:57 -070054 int mWhichChild = 0;
Adam Cohen44729e32010-07-22 16:00:07 -070055
56 /**
Winson Chung16c8d8a2011-01-20 16:19:33 -080057 * The index of the child to restore after the asynchronous connection from the
58 * RemoteViewsAdapter has been.
59 */
60 private int mRestoreWhichChild = -1;
61
62 /**
Adam Cohen44729e32010-07-22 16:00:07 -070063 * Whether or not the first view(s) should be animated in
64 */
Adam Cohen3db40672010-07-19 22:41:57 -070065 boolean mAnimateFirstTime = true;
66
Adam Cohen44729e32010-07-22 16:00:07 -070067 /**
68 * Represents where the in the current window of
69 * views the current <code>mDisplayedChild</code> sits
70 */
71 int mActiveOffset = 0;
72
73 /**
74 * The number of views that the {@link AdapterViewAnimator} keeps as children at any
75 * given time (not counting views that are pending removal, see {@link #mPreviousViews}).
76 */
Adam Cohen96d8d562010-10-24 11:12:18 -070077 int mMaxNumActiveViews = 1;
Adam Cohen44729e32010-07-22 16:00:07 -070078
79 /**
Adam Cohen1b065cd2010-09-28 14:53:47 -070080 * Map of the children of the {@link AdapterViewAnimator}.
Adam Cohen44729e32010-07-22 16:00:07 -070081 */
Adam Cohend38a0ce2011-04-06 13:20:42 -070082 HashMap<Integer, ViewAndMetaData> mViewsMap = new HashMap<Integer, ViewAndMetaData>();
Adam Cohen44729e32010-07-22 16:00:07 -070083
84 /**
85 * List of views pending removal from the {@link AdapterViewAnimator}
86 */
Adam Cohen1b065cd2010-09-28 14:53:47 -070087 ArrayList<Integer> mPreviousViews;
Adam Cohen44729e32010-07-22 16:00:07 -070088
89 /**
90 * The index, relative to the adapter, of the beginning of the window of views
91 */
92 int mCurrentWindowStart = 0;
93
94 /**
95 * The index, relative to the adapter, of the end of the window of views
96 */
97 int mCurrentWindowEnd = -1;
98
99 /**
100 * The same as {@link #mCurrentWindowStart}, except when the we have bounded
101 * {@link #mCurrentWindowStart} to be non-negative
102 */
103 int mCurrentWindowStartUnbounded = 0;
104
105 /**
Adam Cohen44729e32010-07-22 16:00:07 -0700106 * Listens for data changes from the adapter
107 */
Adam Cohen3db40672010-07-19 22:41:57 -0700108 AdapterDataSetObserver mDataSetObserver;
109
Adam Cohen44729e32010-07-22 16:00:07 -0700110 /**
111 * The {@link Adapter} for this {@link AdapterViewAnimator}
112 */
113 Adapter mAdapter;
Adam Cohen3db40672010-07-19 22:41:57 -0700114
Adam Cohen44729e32010-07-22 16:00:07 -0700115 /**
116 * The {@link RemoteViewsAdapter} for this {@link AdapterViewAnimator}
117 */
118 RemoteViewsAdapter mRemoteViewsAdapter;
119
120 /**
Adam Cohen2148d432011-07-28 14:59:54 -0700121 * The remote adapter containing the data to be displayed by this view to be set
122 */
123 boolean mDeferNotifyDataSetChanged = false;
124
125 /**
Adam Cohen44729e32010-07-22 16:00:07 -0700126 * Specifies whether this is the first time the animator is showing views
127 */
128 boolean mFirstTime = true;
129
130 /**
Adam Cohenb04f7ad2010-08-15 13:22:42 -0700131 * Specifies if the animator should wrap from 0 to the end and vice versa
132 * or have hard boundaries at the beginning and end
133 */
Adam Cohen1b065cd2010-09-28 14:53:47 -0700134 boolean mLoopViews = true;
Adam Cohenb04f7ad2010-08-15 13:22:42 -0700135
136 /**
Adam Cohen839f4a52010-08-26 17:36:48 -0700137 * The width and height of some child, used as a size reference in-case our
138 * dimensions are unspecified by the parent.
139 */
140 int mReferenceChildWidth = -1;
141 int mReferenceChildHeight = -1;
142
143 /**
Adam Cohenef521762010-10-04 13:56:11 -0700144 * In and out animations.
Adam Cohen44729e32010-07-22 16:00:07 -0700145 */
Chet Haase2794eb32010-10-12 16:29:28 -0700146 ObjectAnimator mInAnimation;
147 ObjectAnimator mOutAnimation;
Adam Cohenef521762010-10-04 13:56:11 -0700148
Adam Cohena32edd42010-10-26 10:35:01 -0700149 /**
150 * Current touch state.
151 */
152 private int mTouchMode = TOUCH_MODE_NONE;
153
154 /**
155 * Private touch states.
156 */
157 static final int TOUCH_MODE_NONE = 0;
158 static final int TOUCH_MODE_DOWN_IN_CURRENT_VIEW = 1;
159 static final int TOUCH_MODE_HANDLED = 2;
160
161 private Runnable mPendingCheckForTap;
162
Adam Cohenef521762010-10-04 13:56:11 -0700163 private static final int DEFAULT_ANIMATION_DURATION = 200;
164
Adam Cohen3db40672010-07-19 22:41:57 -0700165 public AdapterViewAnimator(Context context) {
Adam Cohen26f072c2011-04-01 16:23:18 -0700166 this(context, null);
Adam Cohen3db40672010-07-19 22:41:57 -0700167 }
168
169 public AdapterViewAnimator(Context context, AttributeSet attrs) {
Adam Cohen26f072c2011-04-01 16:23:18 -0700170 this(context, attrs, 0);
171 }
172
173 public AdapterViewAnimator(Context context, AttributeSet attrs, int defStyleAttr) {
Alan Viverette617feb92013-09-09 18:09:13 -0700174 this(context, attrs, defStyleAttr, 0);
175 }
Adam Cohen3db40672010-07-19 22:41:57 -0700176
Alan Viverette617feb92013-09-09 18:09:13 -0700177 public AdapterViewAnimator(
178 Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
179 super(context, attrs, defStyleAttr, defStyleRes);
180
181 final TypedArray a = context.obtainStyledAttributes(attrs,
182 com.android.internal.R.styleable.AdapterViewAnimator, defStyleAttr, defStyleRes);
Aurimas Liutikasab324cf2019-02-07 16:46:38 -0800183 saveAttributeDataForStyleable(context, com.android.internal.R.styleable.AdapterViewAnimator,
184 attrs, a, defStyleAttr, defStyleRes);
185
Adam Cohen44729e32010-07-22 16:00:07 -0700186 int resource = a.getResourceId(
Adam Cohen1b065cd2010-09-28 14:53:47 -0700187 com.android.internal.R.styleable.AdapterViewAnimator_inAnimation, 0);
Adam Cohen3db40672010-07-19 22:41:57 -0700188 if (resource > 0) {
189 setInAnimation(context, resource);
Adam Cohenef521762010-10-04 13:56:11 -0700190 } else {
191 setInAnimation(getDefaultInAnimation());
Adam Cohen3db40672010-07-19 22:41:57 -0700192 }
193
Adam Cohen1b065cd2010-09-28 14:53:47 -0700194 resource = a.getResourceId(com.android.internal.R.styleable.AdapterViewAnimator_outAnimation, 0);
Adam Cohen3db40672010-07-19 22:41:57 -0700195 if (resource > 0) {
196 setOutAnimation(context, resource);
Adam Cohenef521762010-10-04 13:56:11 -0700197 } else {
198 setOutAnimation(getDefaultOutAnimation());
Adam Cohen3db40672010-07-19 22:41:57 -0700199 }
200
Adam Cohen44729e32010-07-22 16:00:07 -0700201 boolean flag = a.getBoolean(
Adam Cohen1b065cd2010-09-28 14:53:47 -0700202 com.android.internal.R.styleable.AdapterViewAnimator_animateFirstView, true);
Adam Cohen3db40672010-07-19 22:41:57 -0700203 setAnimateFirstView(flag);
204
Adam Cohen1b065cd2010-09-28 14:53:47 -0700205 mLoopViews = a.getBoolean(
206 com.android.internal.R.styleable.AdapterViewAnimator_loopViews, false);
207
Adam Cohen3db40672010-07-19 22:41:57 -0700208 a.recycle();
209
Romain Guy5b53f912010-08-16 18:24:33 -0700210 initViewAnimator();
Adam Cohen3db40672010-07-19 22:41:57 -0700211 }
212
213 /**
214 * Initialize this {@link AdapterViewAnimator}
215 */
Romain Guy5b53f912010-08-16 18:24:33 -0700216 private void initViewAnimator() {
Adam Cohen1b065cd2010-09-28 14:53:47 -0700217 mPreviousViews = new ArrayList<Integer>();
Adam Cohen44729e32010-07-22 16:00:07 -0700218 }
219
Adam Cohend38a0ce2011-04-06 13:20:42 -0700220 class ViewAndMetaData {
Adam Cohen1b065cd2010-09-28 14:53:47 -0700221 View view;
Adam Cohend38a0ce2011-04-06 13:20:42 -0700222 int relativeIndex;
223 int adapterPosition;
224 long itemId;
225
226 ViewAndMetaData(View view, int relativeIndex, int adapterPosition, long itemId) {
227 this.view = view;
228 this.relativeIndex = relativeIndex;
229 this.adapterPosition = adapterPosition;
230 this.itemId = itemId;
231 }
Adam Cohen1b065cd2010-09-28 14:53:47 -0700232 }
233
Adam Cohen44729e32010-07-22 16:00:07 -0700234 /**
235 * This method is used by subclasses to configure the animator to display the
236 * desired number of views, and specify the offset
237 *
238 * @param numVisibleViews The number of views the animator keeps in the {@link ViewGroup}
Romain Guy5b53f912010-08-16 18:24:33 -0700239 * @param activeOffset This parameter specifies where the current index ({@link #mWhichChild})
Adam Cohen44729e32010-07-22 16:00:07 -0700240 * sits within the window. For example if activeOffset is 1, and numVisibleViews is 3,
Romain Guy5b53f912010-08-16 18:24:33 -0700241 * and {@link #setDisplayedChild(int)} is called with 10, then the effective window will
242 * be the indexes 9, 10, and 11. In the same example, if activeOffset were 0, then the
Adam Cohen44729e32010-07-22 16:00:07 -0700243 * window would instead contain indexes 10, 11 and 12.
Adam Cohenb04f7ad2010-08-15 13:22:42 -0700244 * @param shouldLoop If the animator is show view 0, and setPrevious() is called, do we
245 * we loop back to the end, or do we do nothing
Adam Cohen44729e32010-07-22 16:00:07 -0700246 */
Adam Cohen1b065cd2010-09-28 14:53:47 -0700247 void configureViewAnimator(int numVisibleViews, int activeOffset) {
Adam Cohen44729e32010-07-22 16:00:07 -0700248 if (activeOffset > numVisibleViews - 1) {
249 // Throw an exception here.
250 }
Adam Cohen96d8d562010-10-24 11:12:18 -0700251 mMaxNumActiveViews = numVisibleViews;
Adam Cohen44729e32010-07-22 16:00:07 -0700252 mActiveOffset = activeOffset;
Adam Cohen44729e32010-07-22 16:00:07 -0700253 mPreviousViews.clear();
Adam Cohen1b065cd2010-09-28 14:53:47 -0700254 mViewsMap.clear();
Adam Cohen44729e32010-07-22 16:00:07 -0700255 removeAllViewsInLayout();
256 mCurrentWindowStart = 0;
257 mCurrentWindowEnd = -1;
258 }
259
260 /**
261 * This class should be overridden by subclasses to customize view transitions within
262 * the set of visible views
263 *
264 * @param fromIndex The relative index within the window that the view was in, -1 if it wasn't
265 * in the window
266 * @param toIndex The relative index within the window that the view is going to, -1 if it is
267 * being removed
268 * @param view The view that is being animated
269 */
Adam Cohen78db1aa2011-01-25 12:24:23 -0800270 void transformViewForTransition(int fromIndex, int toIndex, View view, boolean animate) {
Adam Cohen44729e32010-07-22 16:00:07 -0700271 if (fromIndex == -1) {
Adam Cohenef521762010-10-04 13:56:11 -0700272 mInAnimation.setTarget(view);
273 mInAnimation.start();
Adam Cohen44729e32010-07-22 16:00:07 -0700274 } else if (toIndex == -1) {
Adam Cohenef521762010-10-04 13:56:11 -0700275 mOutAnimation.setTarget(view);
276 mOutAnimation.start();
Adam Cohen44729e32010-07-22 16:00:07 -0700277 }
Adam Cohen3db40672010-07-19 22:41:57 -0700278 }
279
Chet Haase2794eb32010-10-12 16:29:28 -0700280 ObjectAnimator getDefaultInAnimation() {
281 ObjectAnimator anim = ObjectAnimator.ofFloat(null, "alpha", 0.0f, 1.0f);
282 anim.setDuration(DEFAULT_ANIMATION_DURATION);
283 return anim;
Adam Cohenef521762010-10-04 13:56:11 -0700284 }
285
Chet Haase2794eb32010-10-12 16:29:28 -0700286 ObjectAnimator getDefaultOutAnimation() {
287 ObjectAnimator anim = ObjectAnimator.ofFloat(null, "alpha", 1.0f, 0.0f);
288 anim.setDuration(DEFAULT_ANIMATION_DURATION);
289 return anim;
Adam Cohenef521762010-10-04 13:56:11 -0700290 }
291
Adam Cohen3db40672010-07-19 22:41:57 -0700292 /**
293 * Sets which child view will be displayed.
294 *
295 * @param whichChild the index of the child view to display
296 */
Adam Cohen0b96a572011-02-10 15:56:16 -0800297 @android.view.RemotableViewMethod
Adam Cohen3db40672010-07-19 22:41:57 -0700298 public void setDisplayedChild(int whichChild) {
Adam Cohen53838d22011-01-26 21:32:33 -0800299 setDisplayedChild(whichChild, true);
300 }
301
302 private void setDisplayedChild(int whichChild, boolean animate) {
Adam Cohen3db40672010-07-19 22:41:57 -0700303 if (mAdapter != null) {
304 mWhichChild = whichChild;
Adam Cohen96d8d562010-10-24 11:12:18 -0700305 if (whichChild >= getWindowSize()) {
306 mWhichChild = mLoopViews ? 0 : getWindowSize() - 1;
Adam Cohen3db40672010-07-19 22:41:57 -0700307 } else if (whichChild < 0) {
Adam Cohen96d8d562010-10-24 11:12:18 -0700308 mWhichChild = mLoopViews ? getWindowSize() - 1 : 0;
Adam Cohen3db40672010-07-19 22:41:57 -0700309 }
310
311 boolean hasFocus = getFocusedChild() != null;
312 // This will clear old focus if we had it
Adam Cohen53838d22011-01-26 21:32:33 -0800313 showOnly(mWhichChild, animate);
Adam Cohen3db40672010-07-19 22:41:57 -0700314 if (hasFocus) {
315 // Try to retake focus if we had it
316 requestFocus(FOCUS_FORWARD);
317 }
318 }
319 }
320
321 /**
Adam Cohen44729e32010-07-22 16:00:07 -0700322 * To be overridden by subclasses. This method applies a view / index specific
323 * transform to the child view.
324 *
325 * @param child
326 * @param relativeIndex
327 */
328 void applyTransformForChildAtIndex(View child, int relativeIndex) {
329 }
330
331 /**
Adam Cohen3db40672010-07-19 22:41:57 -0700332 * Returns the index of the currently displayed child view.
333 */
334 public int getDisplayedChild() {
335 return mWhichChild;
336 }
337
338 /**
339 * Manually shows the next child.
340 */
341 public void showNext() {
342 setDisplayedChild(mWhichChild + 1);
343 }
344
345 /**
346 * Manually shows the previous child.
347 */
348 public void showPrevious() {
349 setDisplayedChild(mWhichChild - 1);
350 }
351
Adam Cohen96d8d562010-10-24 11:12:18 -0700352 int modulo(int pos, int size) {
Adam Cohen30429442010-10-06 10:37:51 -0700353 if (size > 0) {
354 return (size + (pos % size)) % size;
355 } else {
356 return 0;
357 }
Adam Cohen3db40672010-07-19 22:41:57 -0700358 }
359
Adam Cohen44729e32010-07-22 16:00:07 -0700360 /**
361 * Get the view at this index relative to the current window's start
362 *
363 * @param relativeIndex Position relative to the current window's start
364 * @return View at this index, null if the index is outside the bounds
365 */
366 View getViewAtRelativeIndex(int relativeIndex) {
Adam Cohen96d8d562010-10-24 11:12:18 -0700367 if (relativeIndex >= 0 && relativeIndex <= getNumActiveViews() - 1 && mAdapter != null) {
368 int i = modulo(mCurrentWindowStartUnbounded + relativeIndex, getWindowSize());
Adam Cohen6f279622010-10-14 10:37:32 -0700369 if (mViewsMap.get(i) != null) {
370 return mViewsMap.get(i).view;
371 }
Adam Cohen44729e32010-07-22 16:00:07 -0700372 }
373 return null;
374 }
Adam Cohen3db40672010-07-19 22:41:57 -0700375
Adam Cohen96d8d562010-10-24 11:12:18 -0700376 int getNumActiveViews() {
377 if (mAdapter != null) {
Adam Cohenef17dd42011-01-20 17:20:57 -0800378 return Math.min(getCount() + 1, mMaxNumActiveViews);
Adam Cohen96d8d562010-10-24 11:12:18 -0700379 } else {
380 return mMaxNumActiveViews;
381 }
382 }
383
384 int getWindowSize() {
385 if (mAdapter != null) {
Adam Cohenef17dd42011-01-20 17:20:57 -0800386 int adapterCount = getCount();
Adam Cohen96d8d562010-10-24 11:12:18 -0700387 if (adapterCount <= getNumActiveViews() && mLoopViews) {
388 return adapterCount*mMaxNumActiveViews;
389 } else {
390 return adapterCount;
391 }
392 } else {
393 return 0;
394 }
395 }
396
Adam Cohend38a0ce2011-04-06 13:20:42 -0700397 private ViewAndMetaData getMetaDataForChild(View child) {
398 for (ViewAndMetaData vm: mViewsMap.values()) {
399 if (vm.view == child) {
400 return vm;
401 }
402 }
403 return null;
404 }
405
Adam Cohen9b073942010-08-19 16:49:52 -0700406 LayoutParams createOrReuseLayoutParams(View v) {
Andreas Gampe3f9ef972015-03-15 16:31:41 -0700407 final LayoutParams currentLp = v.getLayoutParams();
408 if (currentLp != null) {
409 return currentLp;
Adam Cohen44729e32010-07-22 16:00:07 -0700410 }
Andreas Gampe3f9ef972015-03-15 16:31:41 -0700411 return new LayoutParams(0, 0);
Adam Cohen44729e32010-07-22 16:00:07 -0700412 }
Adam Cohen3db40672010-07-19 22:41:57 -0700413
Winson Chung6364f2b2010-09-29 11:14:30 -0700414 void refreshChildren() {
Adam Cohena9238c82010-10-25 14:01:29 -0700415 if (mAdapter == null) return;
Adam Cohenbd0136a2010-09-08 14:19:39 -0700416 for (int i = mCurrentWindowStart; i <= mCurrentWindowEnd; i++) {
Adam Cohena9238c82010-10-25 14:01:29 -0700417 int index = modulo(i, getWindowSize());
Adam Cohenbd0136a2010-09-08 14:19:39 -0700418
Adam Cohenef17dd42011-01-20 17:20:57 -0800419 int adapterCount = getCount();
Adam Cohenbd0136a2010-09-08 14:19:39 -0700420 // get the fresh child from the adapter
Adam Cohenef17dd42011-01-20 17:20:57 -0800421 final View updatedChild = mAdapter.getView(modulo(i, adapterCount), null, this);
Adam Cohenbd0136a2010-09-08 14:19:39 -0700422
Svetoslav Ganov42138042012-03-20 11:51:39 -0700423 if (updatedChild.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
424 updatedChild.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
425 }
426
Adam Cohen1b065cd2010-09-28 14:53:47 -0700427 if (mViewsMap.containsKey(index)) {
Adam Cohenef17dd42011-01-20 17:20:57 -0800428 final FrameLayout fl = (FrameLayout) mViewsMap.get(index).view;
Adam Cohenbd0136a2010-09-08 14:19:39 -0700429 // add the new child to the frame, if it exists
430 if (updatedChild != null) {
Adam Cohenef17dd42011-01-20 17:20:57 -0800431 // flush out the old child
432 fl.removeAllViewsInLayout();
Adam Cohenbd0136a2010-09-08 14:19:39 -0700433 fl.addView(updatedChild);
434 }
435 }
436 }
437 }
438
Adam Cohendfcdddd2010-09-10 14:38:40 -0700439 /**
440 * This method can be overridden so that subclasses can provide a custom frame in which their
441 * children can live. For example, StackView adds padding to its childrens' frames so as to
442 * accomodate for the highlight effect.
443 *
444 * @return The FrameLayout into which children can be placed.
445 */
446 FrameLayout getFrameForChild() {
447 return new FrameLayout(mContext);
448 }
449
Adam Cohenef17dd42011-01-20 17:20:57 -0800450 /**
451 * Shows only the specified child. The other displays Views exit the screen,
452 * optionally with the with the {@link #getOutAnimation() out animation} and
453 * the specified child enters the screen, optionally with the
454 * {@link #getInAnimation() in animation}.
455 *
456 * @param childIndex The index of the child to be shown.
457 * @param animate Whether or not to use the in and out animations, defaults
458 * to true.
459 */
460 void showOnly(int childIndex, boolean animate) {
Adam Cohen44729e32010-07-22 16:00:07 -0700461 if (mAdapter == null) return;
Adam Cohenef17dd42011-01-20 17:20:57 -0800462 final int adapterCount = getCount();
Adam Cohen30429442010-10-06 10:37:51 -0700463 if (adapterCount == 0) return;
Adam Cohen3db40672010-07-19 22:41:57 -0700464
Adam Cohen44729e32010-07-22 16:00:07 -0700465 for (int i = 0; i < mPreviousViews.size(); i++) {
Adam Cohen1b065cd2010-09-28 14:53:47 -0700466 View viewToRemove = mViewsMap.get(mPreviousViews.get(i)).view;
467 mViewsMap.remove(mPreviousViews.get(i));
Adam Cohen44729e32010-07-22 16:00:07 -0700468 viewToRemove.clearAnimation();
Adam Cohen3d07af02010-08-18 17:46:23 -0700469 if (viewToRemove instanceof ViewGroup) {
470 ViewGroup vg = (ViewGroup) viewToRemove;
471 vg.removeAllViewsInLayout();
472 }
Adam Cohen44729e32010-07-22 16:00:07 -0700473 // applyTransformForChildAtIndex here just allows for any cleanup
474 // associated with this view that may need to be done by a subclass
475 applyTransformForChildAtIndex(viewToRemove, -1);
Adam Cohen3d07af02010-08-18 17:46:23 -0700476
Adam Cohen44729e32010-07-22 16:00:07 -0700477 removeViewInLayout(viewToRemove);
478 }
479 mPreviousViews.clear();
480 int newWindowStartUnbounded = childIndex - mActiveOffset;
Adam Cohen96d8d562010-10-24 11:12:18 -0700481 int newWindowEndUnbounded = newWindowStartUnbounded + getNumActiveViews() - 1;
Adam Cohen44729e32010-07-22 16:00:07 -0700482 int newWindowStart = Math.max(0, newWindowStartUnbounded);
Adam Cohen1b065cd2010-09-28 14:53:47 -0700483 int newWindowEnd = Math.min(adapterCount - 1, newWindowEndUnbounded);
Adam Cohen3db40672010-07-19 22:41:57 -0700484
Adam Cohen1b065cd2010-09-28 14:53:47 -0700485 if (mLoopViews) {
486 newWindowStart = newWindowStartUnbounded;
487 newWindowEnd = newWindowEndUnbounded;
488 }
Adam Cohen96d8d562010-10-24 11:12:18 -0700489 int rangeStart = modulo(newWindowStart, getWindowSize());
490 int rangeEnd = modulo(newWindowEnd, getWindowSize());
Adam Cohen1b065cd2010-09-28 14:53:47 -0700491
492 boolean wrap = false;
493 if (rangeStart > rangeEnd) {
494 wrap = true;
495 }
496
497 // This section clears out any items that are in our active views list
Adam Cohen44729e32010-07-22 16:00:07 -0700498 // but are outside the effective bounds of our window (this is becomes an issue
499 // at the extremities of the list, eg. where newWindowStartUnbounded < 0 or
Adam Cohenef17dd42011-01-20 17:20:57 -0800500 // newWindowEndUnbounded > adapterCount - 1
Adam Cohen1b065cd2010-09-28 14:53:47 -0700501 for (Integer index : mViewsMap.keySet()) {
502 boolean remove = false;
503 if (!wrap && (index < rangeStart || index > rangeEnd)) {
504 remove = true;
505 } else if (wrap && (index > rangeEnd && index < rangeStart)) {
506 remove = true;
507 }
508
509 if (remove) {
510 View previousView = mViewsMap.get(index).view;
Adam Cohend38a0ce2011-04-06 13:20:42 -0700511 int oldRelativeIndex = mViewsMap.get(index).relativeIndex;
Adam Cohen1b065cd2010-09-28 14:53:47 -0700512
513 mPreviousViews.add(index);
Adam Cohen78db1aa2011-01-25 12:24:23 -0800514 transformViewForTransition(oldRelativeIndex, -1, previousView, animate);
Adam Cohen3db40672010-07-19 22:41:57 -0700515 }
Adam Cohen44729e32010-07-22 16:00:07 -0700516 }
Adam Cohen3db40672010-07-19 22:41:57 -0700517
Adam Cohen44729e32010-07-22 16:00:07 -0700518 // If the window has changed
Adam Cohen96d8d562010-10-24 11:12:18 -0700519 if (!(newWindowStart == mCurrentWindowStart && newWindowEnd == mCurrentWindowEnd &&
520 newWindowStartUnbounded == mCurrentWindowStartUnbounded)) {
Adam Cohen44729e32010-07-22 16:00:07 -0700521 // Run through the indices in the new range
522 for (int i = newWindowStart; i <= newWindowEnd; i++) {
Adam Cohen3db40672010-07-19 22:41:57 -0700523
Adam Cohen96d8d562010-10-24 11:12:18 -0700524 int index = modulo(i, getWindowSize());
Adam Cohen1b065cd2010-09-28 14:53:47 -0700525 int oldRelativeIndex;
526 if (mViewsMap.containsKey(index)) {
Adam Cohend38a0ce2011-04-06 13:20:42 -0700527 oldRelativeIndex = mViewsMap.get(index).relativeIndex;
Adam Cohen1b065cd2010-09-28 14:53:47 -0700528 } else {
529 oldRelativeIndex = -1;
530 }
Adam Cohen44729e32010-07-22 16:00:07 -0700531 int newRelativeIndex = i - newWindowStartUnbounded;
Adam Cohen44729e32010-07-22 16:00:07 -0700532
533 // If this item is in the current window, great, we just need to apply
534 // the transform for it's new relative position in the window, and animate
535 // between it's current and new relative positions
Adam Cohen1b065cd2010-09-28 14:53:47 -0700536 boolean inOldRange = mViewsMap.containsKey(index) && !mPreviousViews.contains(index);
537
538 if (inOldRange) {
539 View view = mViewsMap.get(index).view;
Adam Cohend38a0ce2011-04-06 13:20:42 -0700540 mViewsMap.get(index).relativeIndex = newRelativeIndex;
Adam Cohen44729e32010-07-22 16:00:07 -0700541 applyTransformForChildAtIndex(view, newRelativeIndex);
Adam Cohen78db1aa2011-01-25 12:24:23 -0800542 transformViewForTransition(oldRelativeIndex, newRelativeIndex, view, animate);
Adam Cohen44729e32010-07-22 16:00:07 -0700543
Adam Cohen1b065cd2010-09-28 14:53:47 -0700544 // Otherwise this view is new to the window
Adam Cohen44729e32010-07-22 16:00:07 -0700545 } else {
Adam Cohen1b065cd2010-09-28 14:53:47 -0700546 // Get the new view from the adapter, add it and apply any transform / animation
Adam Cohend38a0ce2011-04-06 13:20:42 -0700547 final int adapterPosition = modulo(i, adapterCount);
548 View newView = mAdapter.getView(adapterPosition, null, this);
549 long itemId = mAdapter.getItemId(adapterPosition);
Adam Cohenbd0136a2010-09-08 14:19:39 -0700550
551 // We wrap the new view in a FrameLayout so as to respect the contract
552 // with the adapter, that is, that we don't modify this view directly
Adam Cohendfcdddd2010-09-10 14:38:40 -0700553 FrameLayout fl = getFrameForChild();
Adam Cohenbd0136a2010-09-08 14:19:39 -0700554
555 // If the view from the adapter is null, we still keep an empty frame in place
Adam Cohen44729e32010-07-22 16:00:07 -0700556 if (newView != null) {
Adam Cohenbd0136a2010-09-08 14:19:39 -0700557 fl.addView(newView);
Adam Cohen44729e32010-07-22 16:00:07 -0700558 }
Adam Cohend38a0ce2011-04-06 13:20:42 -0700559 mViewsMap.put(index, new ViewAndMetaData(fl, newRelativeIndex,
560 adapterPosition, itemId));
Adam Cohenbd0136a2010-09-08 14:19:39 -0700561 addChild(fl);
562 applyTransformForChildAtIndex(fl, newRelativeIndex);
Adam Cohen78db1aa2011-01-25 12:24:23 -0800563 transformViewForTransition(-1, newRelativeIndex, fl, animate);
Adam Cohen44729e32010-07-22 16:00:07 -0700564 }
Adam Cohen1b065cd2010-09-28 14:53:47 -0700565 mViewsMap.get(index).view.bringToFront();
Adam Cohen3db40672010-07-19 22:41:57 -0700566 }
Adam Cohen44729e32010-07-22 16:00:07 -0700567 mCurrentWindowStart = newWindowStart;
568 mCurrentWindowEnd = newWindowEnd;
569 mCurrentWindowStartUnbounded = newWindowStartUnbounded;
Adam Cohenb9673922012-01-05 13:58:47 -0800570 if (mRemoteViewsAdapter != null) {
Adam Cohend0c735f2012-01-23 19:12:22 -0800571 int adapterStart = modulo(mCurrentWindowStart, adapterCount);
572 int adapterEnd = modulo(mCurrentWindowEnd, adapterCount);
573 mRemoteViewsAdapter.setVisibleRangeHint(adapterStart, adapterEnd);
Adam Cohenb9673922012-01-05 13:58:47 -0800574 }
Adam Cohen44729e32010-07-22 16:00:07 -0700575 }
Adam Cohenef17dd42011-01-20 17:20:57 -0800576 requestLayout();
577 invalidate();
Adam Cohen3db40672010-07-19 22:41:57 -0700578 }
579
Adam Cohen839f4a52010-08-26 17:36:48 -0700580 private void addChild(View child) {
581 addViewInLayout(child, -1, createOrReuseLayoutParams(child));
582
583 // This code is used to obtain a reference width and height of a child in case we need
584 // to decide our own size. TODO: Do we want to update the size of the child that we're
585 // using for reference size? If so, when?
586 if (mReferenceChildWidth == -1 || mReferenceChildHeight == -1) {
587 int measureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
588 child.measure(measureSpec, measureSpec);
589 mReferenceChildWidth = child.getMeasuredWidth();
590 mReferenceChildHeight = child.getMeasuredHeight();
591 }
592 }
593
Adam Cohena32edd42010-10-26 10:35:01 -0700594 void showTapFeedback(View v) {
595 v.setPressed(true);
596 }
597
598 void hideTapFeedback(View v) {
599 v.setPressed(false);
600 }
601
602 void cancelHandleClick() {
603 View v = getCurrentView();
604 if (v != null) {
605 hideTapFeedback(v);
606 }
607 mTouchMode = TOUCH_MODE_NONE;
608 }
609
610 final class CheckForTap implements Runnable {
611 public void run() {
612 if (mTouchMode == TOUCH_MODE_DOWN_IN_CURRENT_VIEW) {
613 View v = getCurrentView();
614 showTapFeedback(v);
615 }
616 }
617 }
618
619 @Override
620 public boolean onTouchEvent(MotionEvent ev) {
621 int action = ev.getAction();
622 boolean handled = false;
623 switch (action) {
624 case MotionEvent.ACTION_DOWN: {
625 View v = getCurrentView();
626 if (v != null) {
627 if (isTransformedTouchPointInView(ev.getX(), ev.getY(), v, null)) {
628 if (mPendingCheckForTap == null) {
629 mPendingCheckForTap = new CheckForTap();
630 }
631 mTouchMode = TOUCH_MODE_DOWN_IN_CURRENT_VIEW;
632 postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
633 }
634 }
635 break;
636 }
637 case MotionEvent.ACTION_MOVE: break;
638 case MotionEvent.ACTION_POINTER_UP: break;
639 case MotionEvent.ACTION_UP: {
640 if (mTouchMode == TOUCH_MODE_DOWN_IN_CURRENT_VIEW) {
641 final View v = getCurrentView();
Adam Cohend38a0ce2011-04-06 13:20:42 -0700642 final ViewAndMetaData viewData = getMetaDataForChild(v);
Adam Cohena32edd42010-10-26 10:35:01 -0700643 if (v != null) {
644 if (isTransformedTouchPointInView(ev.getX(), ev.getY(), v, null)) {
645 final Handler handler = getHandler();
646 if (handler != null) {
647 handler.removeCallbacks(mPendingCheckForTap);
648 }
649 showTapFeedback(v);
650 postDelayed(new Runnable() {
651 public void run() {
652 hideTapFeedback(v);
653 post(new Runnable() {
654 public void run() {
Adam Cohend38a0ce2011-04-06 13:20:42 -0700655 if (viewData != null) {
656 performItemClick(v, viewData.adapterPosition,
657 viewData.itemId);
658 } else {
659 performItemClick(v, 0, 0);
660 }
Adam Cohena32edd42010-10-26 10:35:01 -0700661 }
662 });
663 }
664 }, ViewConfiguration.getPressedStateDuration());
665 handled = true;
666 }
667 }
668 }
669 mTouchMode = TOUCH_MODE_NONE;
670 break;
671 }
672 case MotionEvent.ACTION_CANCEL: {
673 View v = getCurrentView();
674 if (v != null) {
675 hideTapFeedback(v);
676 }
677 mTouchMode = TOUCH_MODE_NONE;
678 }
679 }
680 return handled;
681 }
682
Adam Cohen839f4a52010-08-26 17:36:48 -0700683 private void measureChildren() {
684 final int count = getChildCount();
Dianne Hackborn189ee182010-12-02 21:48:53 -0800685 final int childWidth = getMeasuredWidth() - mPaddingLeft - mPaddingRight;
686 final int childHeight = getMeasuredHeight() - mPaddingTop - mPaddingBottom;
Adam Cohen839f4a52010-08-26 17:36:48 -0700687
688 for (int i = 0; i < count; i++) {
689 final View child = getChildAt(i);
690 child.measure(MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.EXACTLY),
691 MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY));
692 }
693 }
694
695 @Override
696 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
697 int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
698 int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
699 final int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
700 final int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
701
702 boolean haveChildRefSize = (mReferenceChildWidth != -1 && mReferenceChildHeight != -1);
703
704 // We need to deal with the case where our parent hasn't told us how
705 // big we should be. In this case we try to use the desired size of the first
706 // child added.
707 if (heightSpecMode == MeasureSpec.UNSPECIFIED) {
708 heightSpecSize = haveChildRefSize ? mReferenceChildHeight + mPaddingTop +
709 mPaddingBottom : 0;
710 } else if (heightSpecMode == MeasureSpec.AT_MOST) {
Dianne Hackborn189ee182010-12-02 21:48:53 -0800711 if (haveChildRefSize) {
712 int height = mReferenceChildHeight + mPaddingTop + mPaddingBottom;
713 if (height > heightSpecSize) {
714 heightSpecSize |= MEASURED_STATE_TOO_SMALL;
715 } else {
716 heightSpecSize = height;
717 }
718 }
Adam Cohen839f4a52010-08-26 17:36:48 -0700719 }
720
721 if (widthSpecMode == MeasureSpec.UNSPECIFIED) {
722 widthSpecSize = haveChildRefSize ? mReferenceChildWidth + mPaddingLeft +
723 mPaddingRight : 0;
724 } else if (heightSpecMode == MeasureSpec.AT_MOST) {
Dianne Hackborn189ee182010-12-02 21:48:53 -0800725 if (haveChildRefSize) {
726 int width = mReferenceChildWidth + mPaddingLeft + mPaddingRight;
727 if (width > widthSpecSize) {
728 widthSpecSize |= MEASURED_STATE_TOO_SMALL;
729 } else {
730 widthSpecSize = width;
731 }
732 }
Adam Cohen839f4a52010-08-26 17:36:48 -0700733 }
734
735 setMeasuredDimension(widthSpecSize, heightSpecSize);
736 measureChildren();
737 }
738
Adam Cohenef17dd42011-01-20 17:20:57 -0800739 void checkForAndHandleDataChanged() {
Adam Cohen3db40672010-07-19 22:41:57 -0700740 boolean dataChanged = mDataChanged;
741 if (dataChanged) {
Adam Cohenef17dd42011-01-20 17:20:57 -0800742 post(new Runnable() {
743 public void run() {
744 handleDataChanged();
745 // if the data changes, mWhichChild might be out of the bounds of the adapter
746 // in this case, we reset mWhichChild to the beginning
747 if (mWhichChild >= getWindowSize()) {
748 mWhichChild = 0;
Adam Cohen3db40672010-07-19 22:41:57 -0700749
Adam Cohen78db1aa2011-01-25 12:24:23 -0800750 showOnly(mWhichChild, false);
Adam Cohene86ff4d2011-01-21 17:46:11 -0800751 } else if (mOldItemCount != getCount()) {
Adam Cohen78db1aa2011-01-25 12:24:23 -0800752 showOnly(mWhichChild, false);
Adam Cohenef17dd42011-01-20 17:20:57 -0800753 }
754 refreshChildren();
755 requestLayout();
756 }
757 });
Adam Cohen3db40672010-07-19 22:41:57 -0700758 }
Adam Cohenef17dd42011-01-20 17:20:57 -0800759 mDataChanged = false;
760 }
761
762 @Override
763 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
764 checkForAndHandleDataChanged();
Adam Cohen3db40672010-07-19 22:41:57 -0700765
766 final int childCount = getChildCount();
767 for (int i = 0; i < childCount; i++) {
768 final View child = getChildAt(i);
769
770 int childRight = mPaddingLeft + child.getMeasuredWidth();
771 int childBottom = mPaddingTop + child.getMeasuredHeight();
772
Adam Cohen839f4a52010-08-26 17:36:48 -0700773 child.layout(mPaddingLeft, mPaddingTop, childRight, childBottom);
Adam Cohen3db40672010-07-19 22:41:57 -0700774 }
Adam Cohen3db40672010-07-19 22:41:57 -0700775 }
776
Adam Cohenb04f7ad2010-08-15 13:22:42 -0700777 static class SavedState extends BaseSavedState {
778 int whichChild;
779
780 /**
781 * Constructor called from {@link AdapterViewAnimator#onSaveInstanceState()}
782 */
783 SavedState(Parcelable superState, int whichChild) {
784 super(superState);
785 this.whichChild = whichChild;
786 }
787
788 /**
789 * Constructor called from {@link #CREATOR}
790 */
791 private SavedState(Parcel in) {
792 super(in);
Winson Chung3ec9a452010-09-23 16:40:28 -0700793 this.whichChild = in.readInt();
Adam Cohenb04f7ad2010-08-15 13:22:42 -0700794 }
795
796 @Override
797 public void writeToParcel(Parcel out, int flags) {
798 super.writeToParcel(out, flags);
Winson Chung3ec9a452010-09-23 16:40:28 -0700799 out.writeInt(this.whichChild);
Adam Cohenb04f7ad2010-08-15 13:22:42 -0700800 }
801
802 @Override
803 public String toString() {
Winson Chung3ec9a452010-09-23 16:40:28 -0700804 return "AdapterViewAnimator.SavedState{ whichChild = " + this.whichChild + " }";
Adam Cohenb04f7ad2010-08-15 13:22:42 -0700805 }
806
807 public static final Parcelable.Creator<SavedState> CREATOR
808 = new Parcelable.Creator<SavedState>() {
809 public SavedState createFromParcel(Parcel in) {
810 return new SavedState(in);
811 }
812
813 public SavedState[] newArray(int size) {
814 return new SavedState[size];
815 }
816 };
817 }
818
819 @Override
820 public Parcelable onSaveInstanceState() {
821 Parcelable superState = super.onSaveInstanceState();
Adam Cohen335c3b62012-07-24 17:18:16 -0700822 if (mRemoteViewsAdapter != null) {
823 mRemoteViewsAdapter.saveRemoteViewsCache();
824 }
Adam Cohenb04f7ad2010-08-15 13:22:42 -0700825 return new SavedState(superState, mWhichChild);
826 }
827
828 @Override
829 public void onRestoreInstanceState(Parcelable state) {
830 SavedState ss = (SavedState) state;
831 super.onRestoreInstanceState(ss.getSuperState());
832
833 // Here we set mWhichChild in addition to setDisplayedChild
834 // We do the former in case mAdapter is null, and hence setDisplayedChild won't
835 // set mWhichChild
836 mWhichChild = ss.whichChild;
Adam Cohen69d66e02011-01-12 14:39:13 -0800837
Winson Chung16c8d8a2011-01-20 16:19:33 -0800838 // When using RemoteAdapters, the async connection process can lead to
839 // onRestoreInstanceState to be called before setAdapter(), so we need to save the previous
840 // values to restore the list position after we connect, and can skip setting the displayed
841 // child until then.
842 if (mRemoteViewsAdapter != null && mAdapter == null) {
843 mRestoreWhichChild = mWhichChild;
844 } else {
Adam Cohen53838d22011-01-26 21:32:33 -0800845 setDisplayedChild(mWhichChild, false);
Winson Chung16c8d8a2011-01-20 16:19:33 -0800846 }
Adam Cohenb04f7ad2010-08-15 13:22:42 -0700847 }
848
Adam Cohen3db40672010-07-19 22:41:57 -0700849 /**
Adam Cohen3db40672010-07-19 22:41:57 -0700850 * Returns the View corresponding to the currently displayed child.
851 *
852 * @return The View currently displayed.
853 *
854 * @see #getDisplayedChild()
855 */
856 public View getCurrentView() {
Adam Cohen44729e32010-07-22 16:00:07 -0700857 return getViewAtRelativeIndex(mActiveOffset);
Adam Cohen3db40672010-07-19 22:41:57 -0700858 }
859
860 /**
861 * Returns the current animation used to animate a View that enters the screen.
862 *
863 * @return An Animation or null if none is set.
864 *
Chet Haase2794eb32010-10-12 16:29:28 -0700865 * @see #setInAnimation(android.animation.ObjectAnimator)
Adam Cohen3db40672010-07-19 22:41:57 -0700866 * @see #setInAnimation(android.content.Context, int)
867 */
Chet Haase2794eb32010-10-12 16:29:28 -0700868 public ObjectAnimator getInAnimation() {
Adam Cohen3db40672010-07-19 22:41:57 -0700869 return mInAnimation;
870 }
871
872 /**
873 * Specifies the animation used to animate a View that enters the screen.
874 *
875 * @param inAnimation The animation started when a View enters the screen.
876 *
877 * @see #getInAnimation()
878 * @see #setInAnimation(android.content.Context, int)
879 */
Chet Haase2794eb32010-10-12 16:29:28 -0700880 public void setInAnimation(ObjectAnimator inAnimation) {
Adam Cohen3db40672010-07-19 22:41:57 -0700881 mInAnimation = inAnimation;
882 }
883
884 /**
885 * Returns the current animation used to animate a View that exits the screen.
886 *
887 * @return An Animation or null if none is set.
888 *
Chet Haase2794eb32010-10-12 16:29:28 -0700889 * @see #setOutAnimation(android.animation.ObjectAnimator)
Adam Cohen3db40672010-07-19 22:41:57 -0700890 * @see #setOutAnimation(android.content.Context, int)
891 */
Chet Haase2794eb32010-10-12 16:29:28 -0700892 public ObjectAnimator getOutAnimation() {
Adam Cohen3db40672010-07-19 22:41:57 -0700893 return mOutAnimation;
894 }
895
896 /**
897 * Specifies the animation used to animate a View that exit the screen.
898 *
899 * @param outAnimation The animation started when a View exit the screen.
900 *
901 * @see #getOutAnimation()
902 * @see #setOutAnimation(android.content.Context, int)
903 */
Chet Haase2794eb32010-10-12 16:29:28 -0700904 public void setOutAnimation(ObjectAnimator outAnimation) {
Adam Cohen3db40672010-07-19 22:41:57 -0700905 mOutAnimation = outAnimation;
906 }
907
908 /**
909 * Specifies the animation used to animate a View that enters the screen.
910 *
911 * @param context The application's environment.
912 * @param resourceID The resource id of the animation.
913 *
914 * @see #getInAnimation()
Chet Haase2794eb32010-10-12 16:29:28 -0700915 * @see #setInAnimation(android.animation.ObjectAnimator)
Adam Cohen3db40672010-07-19 22:41:57 -0700916 */
917 public void setInAnimation(Context context, int resourceID) {
Chet Haase2794eb32010-10-12 16:29:28 -0700918 setInAnimation((ObjectAnimator) AnimatorInflater.loadAnimator(context, resourceID));
Adam Cohen3db40672010-07-19 22:41:57 -0700919 }
920
921 /**
922 * Specifies the animation used to animate a View that exit the screen.
923 *
924 * @param context The application's environment.
925 * @param resourceID The resource id of the animation.
926 *
927 * @see #getOutAnimation()
Chet Haase2794eb32010-10-12 16:29:28 -0700928 * @see #setOutAnimation(android.animation.ObjectAnimator)
Adam Cohen3db40672010-07-19 22:41:57 -0700929 */
930 public void setOutAnimation(Context context, int resourceID) {
Chet Haase2794eb32010-10-12 16:29:28 -0700931 setOutAnimation((ObjectAnimator) AnimatorInflater.loadAnimator(context, resourceID));
Adam Cohen3db40672010-07-19 22:41:57 -0700932 }
933
934 /**
935 * Indicates whether the current View should be animated the first time
936 * the ViewAnimation is displayed.
937 *
938 * @param animate True to animate the current View the first time it is displayed,
939 * false otherwise.
940 */
941 public void setAnimateFirstView(boolean animate) {
942 mAnimateFirstTime = animate;
943 }
944
945 @Override
946 public int getBaseline() {
947 return (getCurrentView() != null) ? getCurrentView().getBaseline() : super.getBaseline();
948 }
949
950 @Override
951 public Adapter getAdapter() {
952 return mAdapter;
953 }
954
955 @Override
956 public void setAdapter(Adapter adapter) {
Adam Cohen83228342010-08-10 16:47:30 -0700957 if (mAdapter != null && mDataSetObserver != null) {
958 mAdapter.unregisterDataSetObserver(mDataSetObserver);
959 }
960
Adam Cohen3db40672010-07-19 22:41:57 -0700961 mAdapter = adapter;
Adam Cohen1480fdd2010-08-25 17:24:53 -0700962 checkFocus();
Adam Cohen3db40672010-07-19 22:41:57 -0700963
964 if (mAdapter != null) {
Adam Cohen3db40672010-07-19 22:41:57 -0700965 mDataSetObserver = new AdapterDataSetObserver();
966 mAdapter.registerDataSetObserver(mDataSetObserver);
Adam Cohenef17dd42011-01-20 17:20:57 -0800967 mItemCount = mAdapter.getCount();
Adam Cohen3db40672010-07-19 22:41:57 -0700968 }
Adam Cohen44729e32010-07-22 16:00:07 -0700969 setFocusable(true);
Adam Cohen78db1aa2011-01-25 12:24:23 -0800970 mWhichChild = 0;
971 showOnly(mWhichChild, false);
Adam Cohen3db40672010-07-19 22:41:57 -0700972 }
973
974 /**
Adam Cohen44729e32010-07-22 16:00:07 -0700975 * Sets up this AdapterViewAnimator to use a remote views adapter which connects to a
976 * RemoteViewsService through the specified intent.
977 *
978 * @param intent the intent used to identify the RemoteViewsService for the adapter to
979 * connect to.
Adam Cohen3db40672010-07-19 22:41:57 -0700980 */
Sunny Goyal5c022632016-02-17 16:30:41 -0800981 @android.view.RemotableViewMethod(asyncImpl="setRemoteViewsAdapterAsync")
Adam Cohen3db40672010-07-19 22:41:57 -0700982 public void setRemoteViewsAdapter(Intent intent) {
Sunny Goyal5c022632016-02-17 16:30:41 -0800983 setRemoteViewsAdapter(intent, false);
984 }
985
986 /** @hide **/
987 public Runnable setRemoteViewsAdapterAsync(final Intent intent) {
988 return new RemoteViewsAdapter.AsyncRemoteAdapterAction(this, intent);
989 }
990
991 /** @hide **/
992 @Override
993 public void setRemoteViewsAdapter(Intent intent, boolean isAsync) {
Winson Chung9b3a2cf2010-09-16 14:45:32 -0700994 // Ensure that we don't already have a RemoteViewsAdapter that is bound to an existing
995 // service handling the specified intent.
Winson Chung3ec9a452010-09-23 16:40:28 -0700996 if (mRemoteViewsAdapter != null) {
997 Intent.FilterComparison fcNew = new Intent.FilterComparison(intent);
998 Intent.FilterComparison fcOld = new Intent.FilterComparison(
999 mRemoteViewsAdapter.getRemoteViewsServiceIntent());
1000 if (fcNew.equals(fcOld)) {
1001 return;
1002 }
Winson Chung9b3a2cf2010-09-16 14:45:32 -07001003 }
Adam Cohen2148d432011-07-28 14:59:54 -07001004 mDeferNotifyDataSetChanged = false;
Winson Chung9b3a2cf2010-09-16 14:45:32 -07001005 // Otherwise, create a new RemoteViewsAdapter for binding
Sunny Goyal5c022632016-02-17 16:30:41 -08001006 mRemoteViewsAdapter = new RemoteViewsAdapter(getContext(), intent, this, isAsync);
Adam Cohen335c3b62012-07-24 17:18:16 -07001007 if (mRemoteViewsAdapter.isDataReady()) {
1008 setAdapter(mRemoteViewsAdapter);
1009 }
Adam Cohen3db40672010-07-19 22:41:57 -07001010 }
1011
Adam Cohena6a4cbc2012-09-26 17:36:40 -07001012 /**
1013 * Sets up the onClickHandler to be used by the RemoteViewsAdapter when inflating RemoteViews
1014 *
1015 * @param handler The OnClickHandler to use when inflating RemoteViews.
1016 *
1017 * @hide
1018 */
1019 public void setRemoteViewsOnClickHandler(OnClickHandler handler) {
1020 // Ensure that we don't already have a RemoteViewsAdapter that is bound to an existing
1021 // service handling the specified intent.
1022 if (mRemoteViewsAdapter != null) {
1023 mRemoteViewsAdapter.setRemoteViewsOnClickHandler(handler);
1024 }
1025 }
1026
Adam Cohen3db40672010-07-19 22:41:57 -07001027 @Override
1028 public void setSelection(int position) {
1029 setDisplayedChild(position);
1030 }
1031
1032 @Override
1033 public View getSelectedView() {
Adam Cohen44729e32010-07-22 16:00:07 -07001034 return getViewAtRelativeIndex(mActiveOffset);
Adam Cohen3db40672010-07-19 22:41:57 -07001035 }
1036
1037 /**
Adam Cohen2148d432011-07-28 14:59:54 -07001038 * This defers a notifyDataSetChanged on the pending RemoteViewsAdapter if it has not
1039 * connected yet.
1040 */
1041 public void deferNotifyDataSetChanged() {
1042 mDeferNotifyDataSetChanged = true;
1043 }
1044
1045 /**
Adam Cohen3db40672010-07-19 22:41:57 -07001046 * Called back when the adapter connects to the RemoteViewsService.
1047 */
Winson Chung16c8d8a2011-01-20 16:19:33 -08001048 public boolean onRemoteAdapterConnected() {
Adam Cohen3db40672010-07-19 22:41:57 -07001049 if (mRemoteViewsAdapter != mAdapter) {
1050 setAdapter(mRemoteViewsAdapter);
Winson Chung16c8d8a2011-01-20 16:19:33 -08001051
Adam Cohen2148d432011-07-28 14:59:54 -07001052 if (mDeferNotifyDataSetChanged) {
1053 mRemoteViewsAdapter.notifyDataSetChanged();
1054 mDeferNotifyDataSetChanged = false;
1055 }
1056
Winson Chung16c8d8a2011-01-20 16:19:33 -08001057 // Restore the previous position (see onRestoreInstanceState)
1058 if (mRestoreWhichChild > -1) {
Adam Cohen53838d22011-01-26 21:32:33 -08001059 setDisplayedChild(mRestoreWhichChild, false);
Winson Chung16c8d8a2011-01-20 16:19:33 -08001060 mRestoreWhichChild = -1;
1061 }
1062 return false;
Adam Cohenfb603862010-12-17 12:03:17 -08001063 } else if (mRemoteViewsAdapter != null) {
1064 mRemoteViewsAdapter.superNotifyDataSetChanged();
Winson Chung16c8d8a2011-01-20 16:19:33 -08001065 return true;
Adam Cohen3db40672010-07-19 22:41:57 -07001066 }
Winson Chung16c8d8a2011-01-20 16:19:33 -08001067 return false;
Adam Cohen3db40672010-07-19 22:41:57 -07001068 }
1069
1070 /**
1071 * Called back when the adapter disconnects from the RemoteViewsService.
1072 */
1073 public void onRemoteAdapterDisconnected() {
Adam Cohenfb603862010-12-17 12:03:17 -08001074 // If the remote adapter disconnects, we keep it around
1075 // since the currently displayed items are still cached.
1076 // Further, we want the service to eventually reconnect
1077 // when necessary, as triggered by this view requesting
1078 // items from the Adapter.
Adam Cohen3db40672010-07-19 22:41:57 -07001079 }
Adam Cohena02fdf12010-11-03 13:27:40 -07001080
Adam Cohen0e2de6d2011-01-19 17:16:34 -08001081 /**
1082 * Called by an {@link android.appwidget.AppWidgetHost} in order to advance the current view when
1083 * it is being used within an app widget.
1084 */
Adam Cohena02fdf12010-11-03 13:27:40 -07001085 public void advance() {
1086 showNext();
1087 }
1088
Adam Cohen0e2de6d2011-01-19 17:16:34 -08001089 /**
1090 * Called by an {@link android.appwidget.AppWidgetHost} to indicate that it will be
1091 * automatically advancing the views of this {@link AdapterViewAnimator} by calling
1092 * {@link AdapterViewAnimator#advance()} at some point in the future. This allows subclasses to
1093 * perform any required setup, for example, to stop automatically advancing their children.
1094 */
1095 public void fyiWillBeAdvancedByHostKThx() {
Adam Cohena02fdf12010-11-03 13:27:40 -07001096 }
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -08001097
1098 @Override
Dianne Hackborna7bb6fb2015-02-03 18:13:40 -08001099 public CharSequence getAccessibilityClassName() {
1100 return AdapterViewAnimator.class.getName();
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -08001101 }
Adam Cohen3db40672010-07-19 22:41:57 -07001102}