blob: c7735274e4cd0f676a83f5196e39e248bfc5bd3d [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 Cohen44729e32010-07-22 16:00:07 -070019import java.util.ArrayList;
Adam Cohen1b065cd2010-09-28 14:53:47 -070020import java.util.HashMap;
Adam Cohen44729e32010-07-22 16:00:07 -070021
Adam Cohenef521762010-10-04 13:56:11 -070022import android.animation.AnimatorInflater;
Chet Haasea18a86b2010-09-07 13:20:00 -070023import android.animation.ObjectAnimator;
Adam Cohen3db40672010-07-19 22:41:57 -070024import android.content.Context;
25import android.content.Intent;
26import android.content.res.TypedArray;
27import android.os.Handler;
28import android.os.Looper;
Adam Cohenb04f7ad2010-08-15 13:22:42 -070029import android.os.Parcel;
30import android.os.Parcelable;
Adam Cohen3db40672010-07-19 22:41:57 -070031import android.util.AttributeSet;
Adam Cohena32edd42010-10-26 10:35:01 -070032import android.view.MotionEvent;
Adam Cohen3db40672010-07-19 22:41:57 -070033import android.view.View;
Adam Cohena32edd42010-10-26 10:35:01 -070034import android.view.ViewConfiguration;
Adam Cohen44729e32010-07-22 16:00:07 -070035import android.view.ViewGroup;
Adam Cohen3db40672010-07-19 22:41:57 -070036
37/**
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 /**
121 * Specifies whether this is the first time the animator is showing views
122 */
123 boolean mFirstTime = true;
124
125 /**
Adam Cohenb04f7ad2010-08-15 13:22:42 -0700126 * Specifies if the animator should wrap from 0 to the end and vice versa
127 * or have hard boundaries at the beginning and end
128 */
Adam Cohen1b065cd2010-09-28 14:53:47 -0700129 boolean mLoopViews = true;
Adam Cohenb04f7ad2010-08-15 13:22:42 -0700130
131 /**
Adam Cohen839f4a52010-08-26 17:36:48 -0700132 * The width and height of some child, used as a size reference in-case our
133 * dimensions are unspecified by the parent.
134 */
135 int mReferenceChildWidth = -1;
136 int mReferenceChildHeight = -1;
137
138 /**
Adam Cohenef521762010-10-04 13:56:11 -0700139 * In and out animations.
Adam Cohen44729e32010-07-22 16:00:07 -0700140 */
Chet Haase2794eb32010-10-12 16:29:28 -0700141 ObjectAnimator mInAnimation;
142 ObjectAnimator mOutAnimation;
Adam Cohenef521762010-10-04 13:56:11 -0700143
Adam Cohena32edd42010-10-26 10:35:01 -0700144 /**
145 * Current touch state.
146 */
147 private int mTouchMode = TOUCH_MODE_NONE;
148
149 /**
150 * Private touch states.
151 */
152 static final int TOUCH_MODE_NONE = 0;
153 static final int TOUCH_MODE_DOWN_IN_CURRENT_VIEW = 1;
154 static final int TOUCH_MODE_HANDLED = 2;
155
156 private Runnable mPendingCheckForTap;
157
Adam Cohenef521762010-10-04 13:56:11 -0700158 private static final int DEFAULT_ANIMATION_DURATION = 200;
159
Adam Cohen3db40672010-07-19 22:41:57 -0700160 public AdapterViewAnimator(Context context) {
Adam Cohen26f072c2011-04-01 16:23:18 -0700161 this(context, null);
Adam Cohen3db40672010-07-19 22:41:57 -0700162 }
163
164 public AdapterViewAnimator(Context context, AttributeSet attrs) {
Adam Cohen26f072c2011-04-01 16:23:18 -0700165 this(context, attrs, 0);
166 }
167
168 public AdapterViewAnimator(Context context, AttributeSet attrs, int defStyleAttr) {
169 super(context, attrs, defStyleAttr);
Adam Cohen3db40672010-07-19 22:41:57 -0700170
Adam Cohen44729e32010-07-22 16:00:07 -0700171 TypedArray a = context.obtainStyledAttributes(attrs,
Adam Cohen26f072c2011-04-01 16:23:18 -0700172 com.android.internal.R.styleable.AdapterViewAnimator, defStyleAttr, 0);
Adam Cohen44729e32010-07-22 16:00:07 -0700173 int resource = a.getResourceId(
Adam Cohen1b065cd2010-09-28 14:53:47 -0700174 com.android.internal.R.styleable.AdapterViewAnimator_inAnimation, 0);
Adam Cohen3db40672010-07-19 22:41:57 -0700175 if (resource > 0) {
176 setInAnimation(context, resource);
Adam Cohenef521762010-10-04 13:56:11 -0700177 } else {
178 setInAnimation(getDefaultInAnimation());
Adam Cohen3db40672010-07-19 22:41:57 -0700179 }
180
Adam Cohen1b065cd2010-09-28 14:53:47 -0700181 resource = a.getResourceId(com.android.internal.R.styleable.AdapterViewAnimator_outAnimation, 0);
Adam Cohen3db40672010-07-19 22:41:57 -0700182 if (resource > 0) {
183 setOutAnimation(context, resource);
Adam Cohenef521762010-10-04 13:56:11 -0700184 } else {
185 setOutAnimation(getDefaultOutAnimation());
Adam Cohen3db40672010-07-19 22:41:57 -0700186 }
187
Adam Cohen44729e32010-07-22 16:00:07 -0700188 boolean flag = a.getBoolean(
Adam Cohen1b065cd2010-09-28 14:53:47 -0700189 com.android.internal.R.styleable.AdapterViewAnimator_animateFirstView, true);
Adam Cohen3db40672010-07-19 22:41:57 -0700190 setAnimateFirstView(flag);
191
Adam Cohen1b065cd2010-09-28 14:53:47 -0700192 mLoopViews = a.getBoolean(
193 com.android.internal.R.styleable.AdapterViewAnimator_loopViews, false);
194
Adam Cohen3db40672010-07-19 22:41:57 -0700195 a.recycle();
196
Romain Guy5b53f912010-08-16 18:24:33 -0700197 initViewAnimator();
Adam Cohen3db40672010-07-19 22:41:57 -0700198 }
199
200 /**
201 * Initialize this {@link AdapterViewAnimator}
202 */
Romain Guy5b53f912010-08-16 18:24:33 -0700203 private void initViewAnimator() {
Adam Cohen1b065cd2010-09-28 14:53:47 -0700204 mPreviousViews = new ArrayList<Integer>();
Adam Cohen44729e32010-07-22 16:00:07 -0700205 }
206
Adam Cohend38a0ce2011-04-06 13:20:42 -0700207 class ViewAndMetaData {
Adam Cohen1b065cd2010-09-28 14:53:47 -0700208 View view;
Adam Cohend38a0ce2011-04-06 13:20:42 -0700209 int relativeIndex;
210 int adapterPosition;
211 long itemId;
212
213 ViewAndMetaData(View view, int relativeIndex, int adapterPosition, long itemId) {
214 this.view = view;
215 this.relativeIndex = relativeIndex;
216 this.adapterPosition = adapterPosition;
217 this.itemId = itemId;
218 }
Adam Cohen1b065cd2010-09-28 14:53:47 -0700219 }
220
Adam Cohen44729e32010-07-22 16:00:07 -0700221 /**
222 * This method is used by subclasses to configure the animator to display the
223 * desired number of views, and specify the offset
224 *
225 * @param numVisibleViews The number of views the animator keeps in the {@link ViewGroup}
Romain Guy5b53f912010-08-16 18:24:33 -0700226 * @param activeOffset This parameter specifies where the current index ({@link #mWhichChild})
Adam Cohen44729e32010-07-22 16:00:07 -0700227 * sits within the window. For example if activeOffset is 1, and numVisibleViews is 3,
Romain Guy5b53f912010-08-16 18:24:33 -0700228 * and {@link #setDisplayedChild(int)} is called with 10, then the effective window will
229 * be the indexes 9, 10, and 11. In the same example, if activeOffset were 0, then the
Adam Cohen44729e32010-07-22 16:00:07 -0700230 * window would instead contain indexes 10, 11 and 12.
Adam Cohenb04f7ad2010-08-15 13:22:42 -0700231 * @param shouldLoop If the animator is show view 0, and setPrevious() is called, do we
232 * we loop back to the end, or do we do nothing
Adam Cohen44729e32010-07-22 16:00:07 -0700233 */
Adam Cohen1b065cd2010-09-28 14:53:47 -0700234 void configureViewAnimator(int numVisibleViews, int activeOffset) {
Adam Cohen44729e32010-07-22 16:00:07 -0700235 if (activeOffset > numVisibleViews - 1) {
236 // Throw an exception here.
237 }
Adam Cohen96d8d562010-10-24 11:12:18 -0700238 mMaxNumActiveViews = numVisibleViews;
Adam Cohen44729e32010-07-22 16:00:07 -0700239 mActiveOffset = activeOffset;
Adam Cohen44729e32010-07-22 16:00:07 -0700240 mPreviousViews.clear();
Adam Cohen1b065cd2010-09-28 14:53:47 -0700241 mViewsMap.clear();
Adam Cohen44729e32010-07-22 16:00:07 -0700242 removeAllViewsInLayout();
243 mCurrentWindowStart = 0;
244 mCurrentWindowEnd = -1;
245 }
246
247 /**
248 * This class should be overridden by subclasses to customize view transitions within
249 * the set of visible views
250 *
251 * @param fromIndex The relative index within the window that the view was in, -1 if it wasn't
252 * in the window
253 * @param toIndex The relative index within the window that the view is going to, -1 if it is
254 * being removed
255 * @param view The view that is being animated
256 */
Adam Cohen78db1aa2011-01-25 12:24:23 -0800257 void transformViewForTransition(int fromIndex, int toIndex, View view, boolean animate) {
Adam Cohen44729e32010-07-22 16:00:07 -0700258 if (fromIndex == -1) {
Adam Cohenef521762010-10-04 13:56:11 -0700259 mInAnimation.setTarget(view);
260 mInAnimation.start();
Adam Cohen44729e32010-07-22 16:00:07 -0700261 } else if (toIndex == -1) {
Adam Cohenef521762010-10-04 13:56:11 -0700262 mOutAnimation.setTarget(view);
263 mOutAnimation.start();
Adam Cohen44729e32010-07-22 16:00:07 -0700264 }
Adam Cohen3db40672010-07-19 22:41:57 -0700265 }
266
Chet Haase2794eb32010-10-12 16:29:28 -0700267 ObjectAnimator getDefaultInAnimation() {
268 ObjectAnimator anim = ObjectAnimator.ofFloat(null, "alpha", 0.0f, 1.0f);
269 anim.setDuration(DEFAULT_ANIMATION_DURATION);
270 return anim;
Adam Cohenef521762010-10-04 13:56:11 -0700271 }
272
Chet Haase2794eb32010-10-12 16:29:28 -0700273 ObjectAnimator getDefaultOutAnimation() {
274 ObjectAnimator anim = ObjectAnimator.ofFloat(null, "alpha", 1.0f, 0.0f);
275 anim.setDuration(DEFAULT_ANIMATION_DURATION);
276 return anim;
Adam Cohenef521762010-10-04 13:56:11 -0700277 }
278
Adam Cohen3db40672010-07-19 22:41:57 -0700279 /**
280 * Sets which child view will be displayed.
281 *
282 * @param whichChild the index of the child view to display
283 */
Adam Cohen0b96a572011-02-10 15:56:16 -0800284 @android.view.RemotableViewMethod
Adam Cohen3db40672010-07-19 22:41:57 -0700285 public void setDisplayedChild(int whichChild) {
Adam Cohen53838d22011-01-26 21:32:33 -0800286 setDisplayedChild(whichChild, true);
287 }
288
289 private void setDisplayedChild(int whichChild, boolean animate) {
Adam Cohen3db40672010-07-19 22:41:57 -0700290 if (mAdapter != null) {
291 mWhichChild = whichChild;
Adam Cohen96d8d562010-10-24 11:12:18 -0700292 if (whichChild >= getWindowSize()) {
293 mWhichChild = mLoopViews ? 0 : getWindowSize() - 1;
Adam Cohen3db40672010-07-19 22:41:57 -0700294 } else if (whichChild < 0) {
Adam Cohen96d8d562010-10-24 11:12:18 -0700295 mWhichChild = mLoopViews ? getWindowSize() - 1 : 0;
Adam Cohen3db40672010-07-19 22:41:57 -0700296 }
297
298 boolean hasFocus = getFocusedChild() != null;
299 // This will clear old focus if we had it
Adam Cohen53838d22011-01-26 21:32:33 -0800300 showOnly(mWhichChild, animate);
Adam Cohen3db40672010-07-19 22:41:57 -0700301 if (hasFocus) {
302 // Try to retake focus if we had it
303 requestFocus(FOCUS_FORWARD);
304 }
305 }
306 }
307
308 /**
Adam Cohen44729e32010-07-22 16:00:07 -0700309 * To be overridden by subclasses. This method applies a view / index specific
310 * transform to the child view.
311 *
312 * @param child
313 * @param relativeIndex
314 */
315 void applyTransformForChildAtIndex(View child, int relativeIndex) {
316 }
317
318 /**
Adam Cohen3db40672010-07-19 22:41:57 -0700319 * Returns the index of the currently displayed child view.
320 */
321 public int getDisplayedChild() {
322 return mWhichChild;
323 }
324
325 /**
326 * Manually shows the next child.
327 */
328 public void showNext() {
329 setDisplayedChild(mWhichChild + 1);
330 }
331
332 /**
333 * Manually shows the previous child.
334 */
335 public void showPrevious() {
336 setDisplayedChild(mWhichChild - 1);
337 }
338
Adam Cohen96d8d562010-10-24 11:12:18 -0700339 int modulo(int pos, int size) {
Adam Cohen30429442010-10-06 10:37:51 -0700340 if (size > 0) {
341 return (size + (pos % size)) % size;
342 } else {
343 return 0;
344 }
Adam Cohen3db40672010-07-19 22:41:57 -0700345 }
346
Adam Cohen44729e32010-07-22 16:00:07 -0700347 /**
348 * Get the view at this index relative to the current window's start
349 *
350 * @param relativeIndex Position relative to the current window's start
351 * @return View at this index, null if the index is outside the bounds
352 */
353 View getViewAtRelativeIndex(int relativeIndex) {
Adam Cohen96d8d562010-10-24 11:12:18 -0700354 if (relativeIndex >= 0 && relativeIndex <= getNumActiveViews() - 1 && mAdapter != null) {
355 int i = modulo(mCurrentWindowStartUnbounded + relativeIndex, getWindowSize());
Adam Cohen6f279622010-10-14 10:37:32 -0700356 if (mViewsMap.get(i) != null) {
357 return mViewsMap.get(i).view;
358 }
Adam Cohen44729e32010-07-22 16:00:07 -0700359 }
360 return null;
361 }
Adam Cohen3db40672010-07-19 22:41:57 -0700362
Adam Cohen96d8d562010-10-24 11:12:18 -0700363 int getNumActiveViews() {
364 if (mAdapter != null) {
Adam Cohenef17dd42011-01-20 17:20:57 -0800365 return Math.min(getCount() + 1, mMaxNumActiveViews);
Adam Cohen96d8d562010-10-24 11:12:18 -0700366 } else {
367 return mMaxNumActiveViews;
368 }
369 }
370
371 int getWindowSize() {
372 if (mAdapter != null) {
Adam Cohenef17dd42011-01-20 17:20:57 -0800373 int adapterCount = getCount();
Adam Cohen96d8d562010-10-24 11:12:18 -0700374 if (adapterCount <= getNumActiveViews() && mLoopViews) {
375 return adapterCount*mMaxNumActiveViews;
376 } else {
377 return adapterCount;
378 }
379 } else {
380 return 0;
381 }
382 }
383
Adam Cohend38a0ce2011-04-06 13:20:42 -0700384 private ViewAndMetaData getMetaDataForChild(View child) {
385 for (ViewAndMetaData vm: mViewsMap.values()) {
386 if (vm.view == child) {
387 return vm;
388 }
389 }
390 return null;
391 }
392
Adam Cohen9b073942010-08-19 16:49:52 -0700393 LayoutParams createOrReuseLayoutParams(View v) {
Romain Guy5b53f912010-08-16 18:24:33 -0700394 final ViewGroup.LayoutParams currentLp = v.getLayoutParams();
Adam Cohen9b073942010-08-19 16:49:52 -0700395 if (currentLp instanceof ViewGroup.LayoutParams) {
Adam Cohenb04f7ad2010-08-15 13:22:42 -0700396 LayoutParams lp = (LayoutParams) currentLp;
Adam Cohenb04f7ad2010-08-15 13:22:42 -0700397 return lp;
Adam Cohen44729e32010-07-22 16:00:07 -0700398 }
Adam Cohen9b073942010-08-19 16:49:52 -0700399 return new ViewGroup.LayoutParams(0, 0);
Adam Cohen44729e32010-07-22 16:00:07 -0700400 }
Adam Cohen3db40672010-07-19 22:41:57 -0700401
Winson Chung6364f2b2010-09-29 11:14:30 -0700402 void refreshChildren() {
Adam Cohena9238c82010-10-25 14:01:29 -0700403 if (mAdapter == null) return;
Adam Cohenbd0136a2010-09-08 14:19:39 -0700404 for (int i = mCurrentWindowStart; i <= mCurrentWindowEnd; i++) {
Adam Cohena9238c82010-10-25 14:01:29 -0700405 int index = modulo(i, getWindowSize());
Adam Cohenbd0136a2010-09-08 14:19:39 -0700406
Adam Cohenef17dd42011-01-20 17:20:57 -0800407 int adapterCount = getCount();
Adam Cohenbd0136a2010-09-08 14:19:39 -0700408 // get the fresh child from the adapter
Adam Cohenef17dd42011-01-20 17:20:57 -0800409 final View updatedChild = mAdapter.getView(modulo(i, adapterCount), null, this);
Adam Cohenbd0136a2010-09-08 14:19:39 -0700410
Adam Cohen1b065cd2010-09-28 14:53:47 -0700411 if (mViewsMap.containsKey(index)) {
Adam Cohenef17dd42011-01-20 17:20:57 -0800412 final FrameLayout fl = (FrameLayout) mViewsMap.get(index).view;
Adam Cohenbd0136a2010-09-08 14:19:39 -0700413 // add the new child to the frame, if it exists
414 if (updatedChild != null) {
Adam Cohenef17dd42011-01-20 17:20:57 -0800415 // flush out the old child
416 fl.removeAllViewsInLayout();
Adam Cohenbd0136a2010-09-08 14:19:39 -0700417 fl.addView(updatedChild);
418 }
419 }
420 }
421 }
422
Adam Cohendfcdddd2010-09-10 14:38:40 -0700423 /**
424 * This method can be overridden so that subclasses can provide a custom frame in which their
425 * children can live. For example, StackView adds padding to its childrens' frames so as to
426 * accomodate for the highlight effect.
427 *
428 * @return The FrameLayout into which children can be placed.
429 */
430 FrameLayout getFrameForChild() {
431 return new FrameLayout(mContext);
432 }
433
Adam Cohenef17dd42011-01-20 17:20:57 -0800434 /**
435 * Shows only the specified child. The other displays Views exit the screen,
436 * optionally with the with the {@link #getOutAnimation() out animation} and
437 * the specified child enters the screen, optionally with the
438 * {@link #getInAnimation() in animation}.
439 *
440 * @param childIndex The index of the child to be shown.
441 * @param animate Whether or not to use the in and out animations, defaults
442 * to true.
443 */
444 void showOnly(int childIndex, boolean animate) {
Adam Cohen44729e32010-07-22 16:00:07 -0700445 if (mAdapter == null) return;
Adam Cohenef17dd42011-01-20 17:20:57 -0800446 final int adapterCount = getCount();
Adam Cohen30429442010-10-06 10:37:51 -0700447 if (adapterCount == 0) return;
Adam Cohen3db40672010-07-19 22:41:57 -0700448
Adam Cohen44729e32010-07-22 16:00:07 -0700449 for (int i = 0; i < mPreviousViews.size(); i++) {
Adam Cohen1b065cd2010-09-28 14:53:47 -0700450 View viewToRemove = mViewsMap.get(mPreviousViews.get(i)).view;
451 mViewsMap.remove(mPreviousViews.get(i));
Adam Cohen44729e32010-07-22 16:00:07 -0700452 viewToRemove.clearAnimation();
Adam Cohen3d07af02010-08-18 17:46:23 -0700453 if (viewToRemove instanceof ViewGroup) {
454 ViewGroup vg = (ViewGroup) viewToRemove;
455 vg.removeAllViewsInLayout();
456 }
Adam Cohen44729e32010-07-22 16:00:07 -0700457 // applyTransformForChildAtIndex here just allows for any cleanup
458 // associated with this view that may need to be done by a subclass
459 applyTransformForChildAtIndex(viewToRemove, -1);
Adam Cohen3d07af02010-08-18 17:46:23 -0700460
Adam Cohen44729e32010-07-22 16:00:07 -0700461 removeViewInLayout(viewToRemove);
462 }
463 mPreviousViews.clear();
464 int newWindowStartUnbounded = childIndex - mActiveOffset;
Adam Cohen96d8d562010-10-24 11:12:18 -0700465 int newWindowEndUnbounded = newWindowStartUnbounded + getNumActiveViews() - 1;
Adam Cohen44729e32010-07-22 16:00:07 -0700466 int newWindowStart = Math.max(0, newWindowStartUnbounded);
Adam Cohen1b065cd2010-09-28 14:53:47 -0700467 int newWindowEnd = Math.min(adapterCount - 1, newWindowEndUnbounded);
Adam Cohen3db40672010-07-19 22:41:57 -0700468
Adam Cohen1b065cd2010-09-28 14:53:47 -0700469 if (mLoopViews) {
470 newWindowStart = newWindowStartUnbounded;
471 newWindowEnd = newWindowEndUnbounded;
472 }
Adam Cohen96d8d562010-10-24 11:12:18 -0700473 int rangeStart = modulo(newWindowStart, getWindowSize());
474 int rangeEnd = modulo(newWindowEnd, getWindowSize());
Adam Cohen1b065cd2010-09-28 14:53:47 -0700475
476 boolean wrap = false;
477 if (rangeStart > rangeEnd) {
478 wrap = true;
479 }
480
481 // This section clears out any items that are in our active views list
Adam Cohen44729e32010-07-22 16:00:07 -0700482 // but are outside the effective bounds of our window (this is becomes an issue
483 // at the extremities of the list, eg. where newWindowStartUnbounded < 0 or
Adam Cohenef17dd42011-01-20 17:20:57 -0800484 // newWindowEndUnbounded > adapterCount - 1
Adam Cohen1b065cd2010-09-28 14:53:47 -0700485 for (Integer index : mViewsMap.keySet()) {
486 boolean remove = false;
487 if (!wrap && (index < rangeStart || index > rangeEnd)) {
488 remove = true;
489 } else if (wrap && (index > rangeEnd && index < rangeStart)) {
490 remove = true;
491 }
492
493 if (remove) {
494 View previousView = mViewsMap.get(index).view;
Adam Cohend38a0ce2011-04-06 13:20:42 -0700495 int oldRelativeIndex = mViewsMap.get(index).relativeIndex;
Adam Cohen1b065cd2010-09-28 14:53:47 -0700496
497 mPreviousViews.add(index);
Adam Cohen78db1aa2011-01-25 12:24:23 -0800498 transformViewForTransition(oldRelativeIndex, -1, previousView, animate);
Adam Cohen3db40672010-07-19 22:41:57 -0700499 }
Adam Cohen44729e32010-07-22 16:00:07 -0700500 }
Adam Cohen3db40672010-07-19 22:41:57 -0700501
Adam Cohen44729e32010-07-22 16:00:07 -0700502 // If the window has changed
Adam Cohen96d8d562010-10-24 11:12:18 -0700503 if (!(newWindowStart == mCurrentWindowStart && newWindowEnd == mCurrentWindowEnd &&
504 newWindowStartUnbounded == mCurrentWindowStartUnbounded)) {
Adam Cohen44729e32010-07-22 16:00:07 -0700505 // Run through the indices in the new range
506 for (int i = newWindowStart; i <= newWindowEnd; i++) {
Adam Cohen3db40672010-07-19 22:41:57 -0700507
Adam Cohen96d8d562010-10-24 11:12:18 -0700508 int index = modulo(i, getWindowSize());
Adam Cohen1b065cd2010-09-28 14:53:47 -0700509 int oldRelativeIndex;
510 if (mViewsMap.containsKey(index)) {
Adam Cohend38a0ce2011-04-06 13:20:42 -0700511 oldRelativeIndex = mViewsMap.get(index).relativeIndex;
Adam Cohen1b065cd2010-09-28 14:53:47 -0700512 } else {
513 oldRelativeIndex = -1;
514 }
Adam Cohen44729e32010-07-22 16:00:07 -0700515 int newRelativeIndex = i - newWindowStartUnbounded;
Adam Cohen44729e32010-07-22 16:00:07 -0700516
517 // If this item is in the current window, great, we just need to apply
518 // the transform for it's new relative position in the window, and animate
519 // between it's current and new relative positions
Adam Cohen1b065cd2010-09-28 14:53:47 -0700520 boolean inOldRange = mViewsMap.containsKey(index) && !mPreviousViews.contains(index);
521
522 if (inOldRange) {
523 View view = mViewsMap.get(index).view;
Adam Cohend38a0ce2011-04-06 13:20:42 -0700524 mViewsMap.get(index).relativeIndex = newRelativeIndex;
Adam Cohen44729e32010-07-22 16:00:07 -0700525 applyTransformForChildAtIndex(view, newRelativeIndex);
Adam Cohen78db1aa2011-01-25 12:24:23 -0800526 transformViewForTransition(oldRelativeIndex, newRelativeIndex, view, animate);
Adam Cohen44729e32010-07-22 16:00:07 -0700527
Adam Cohen1b065cd2010-09-28 14:53:47 -0700528 // Otherwise this view is new to the window
Adam Cohen44729e32010-07-22 16:00:07 -0700529 } else {
Adam Cohen1b065cd2010-09-28 14:53:47 -0700530 // Get the new view from the adapter, add it and apply any transform / animation
Adam Cohend38a0ce2011-04-06 13:20:42 -0700531 final int adapterPosition = modulo(i, adapterCount);
532 View newView = mAdapter.getView(adapterPosition, null, this);
533 long itemId = mAdapter.getItemId(adapterPosition);
Adam Cohenbd0136a2010-09-08 14:19:39 -0700534
535 // We wrap the new view in a FrameLayout so as to respect the contract
536 // with the adapter, that is, that we don't modify this view directly
Adam Cohendfcdddd2010-09-10 14:38:40 -0700537 FrameLayout fl = getFrameForChild();
Adam Cohenbd0136a2010-09-08 14:19:39 -0700538
539 // If the view from the adapter is null, we still keep an empty frame in place
Adam Cohen44729e32010-07-22 16:00:07 -0700540 if (newView != null) {
Adam Cohenbd0136a2010-09-08 14:19:39 -0700541 fl.addView(newView);
Adam Cohen44729e32010-07-22 16:00:07 -0700542 }
Adam Cohend38a0ce2011-04-06 13:20:42 -0700543 mViewsMap.put(index, new ViewAndMetaData(fl, newRelativeIndex,
544 adapterPosition, itemId));
Adam Cohenbd0136a2010-09-08 14:19:39 -0700545 addChild(fl);
546 applyTransformForChildAtIndex(fl, newRelativeIndex);
Adam Cohen78db1aa2011-01-25 12:24:23 -0800547 transformViewForTransition(-1, newRelativeIndex, fl, animate);
Adam Cohen44729e32010-07-22 16:00:07 -0700548 }
Adam Cohen1b065cd2010-09-28 14:53:47 -0700549 mViewsMap.get(index).view.bringToFront();
Adam Cohen3db40672010-07-19 22:41:57 -0700550 }
Adam Cohen44729e32010-07-22 16:00:07 -0700551 mCurrentWindowStart = newWindowStart;
552 mCurrentWindowEnd = newWindowEnd;
553 mCurrentWindowStartUnbounded = newWindowStartUnbounded;
554 }
Adam Cohenef17dd42011-01-20 17:20:57 -0800555 requestLayout();
556 invalidate();
Adam Cohen3db40672010-07-19 22:41:57 -0700557 }
558
Adam Cohen839f4a52010-08-26 17:36:48 -0700559 private void addChild(View child) {
560 addViewInLayout(child, -1, createOrReuseLayoutParams(child));
561
562 // This code is used to obtain a reference width and height of a child in case we need
563 // to decide our own size. TODO: Do we want to update the size of the child that we're
564 // using for reference size? If so, when?
565 if (mReferenceChildWidth == -1 || mReferenceChildHeight == -1) {
566 int measureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
567 child.measure(measureSpec, measureSpec);
568 mReferenceChildWidth = child.getMeasuredWidth();
569 mReferenceChildHeight = child.getMeasuredHeight();
570 }
571 }
572
Adam Cohena32edd42010-10-26 10:35:01 -0700573 void showTapFeedback(View v) {
574 v.setPressed(true);
575 }
576
577 void hideTapFeedback(View v) {
578 v.setPressed(false);
579 }
580
581 void cancelHandleClick() {
582 View v = getCurrentView();
583 if (v != null) {
584 hideTapFeedback(v);
585 }
586 mTouchMode = TOUCH_MODE_NONE;
587 }
588
589 final class CheckForTap implements Runnable {
590 public void run() {
591 if (mTouchMode == TOUCH_MODE_DOWN_IN_CURRENT_VIEW) {
592 View v = getCurrentView();
593 showTapFeedback(v);
594 }
595 }
596 }
597
598 @Override
599 public boolean onTouchEvent(MotionEvent ev) {
600 int action = ev.getAction();
601 boolean handled = false;
602 switch (action) {
603 case MotionEvent.ACTION_DOWN: {
604 View v = getCurrentView();
605 if (v != null) {
606 if (isTransformedTouchPointInView(ev.getX(), ev.getY(), v, null)) {
607 if (mPendingCheckForTap == null) {
608 mPendingCheckForTap = new CheckForTap();
609 }
610 mTouchMode = TOUCH_MODE_DOWN_IN_CURRENT_VIEW;
611 postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
612 }
613 }
614 break;
615 }
616 case MotionEvent.ACTION_MOVE: break;
617 case MotionEvent.ACTION_POINTER_UP: break;
618 case MotionEvent.ACTION_UP: {
619 if (mTouchMode == TOUCH_MODE_DOWN_IN_CURRENT_VIEW) {
620 final View v = getCurrentView();
Adam Cohend38a0ce2011-04-06 13:20:42 -0700621 final ViewAndMetaData viewData = getMetaDataForChild(v);
Adam Cohena32edd42010-10-26 10:35:01 -0700622 if (v != null) {
623 if (isTransformedTouchPointInView(ev.getX(), ev.getY(), v, null)) {
624 final Handler handler = getHandler();
625 if (handler != null) {
626 handler.removeCallbacks(mPendingCheckForTap);
627 }
628 showTapFeedback(v);
629 postDelayed(new Runnable() {
630 public void run() {
631 hideTapFeedback(v);
632 post(new Runnable() {
633 public void run() {
Adam Cohend38a0ce2011-04-06 13:20:42 -0700634 if (viewData != null) {
635 performItemClick(v, viewData.adapterPosition,
636 viewData.itemId);
637 } else {
638 performItemClick(v, 0, 0);
639 }
Adam Cohena32edd42010-10-26 10:35:01 -0700640 }
641 });
642 }
643 }, ViewConfiguration.getPressedStateDuration());
644 handled = true;
645 }
646 }
647 }
648 mTouchMode = TOUCH_MODE_NONE;
649 break;
650 }
651 case MotionEvent.ACTION_CANCEL: {
652 View v = getCurrentView();
653 if (v != null) {
654 hideTapFeedback(v);
655 }
656 mTouchMode = TOUCH_MODE_NONE;
657 }
658 }
659 return handled;
660 }
661
Adam Cohen839f4a52010-08-26 17:36:48 -0700662 private void measureChildren() {
663 final int count = getChildCount();
Dianne Hackborn189ee182010-12-02 21:48:53 -0800664 final int childWidth = getMeasuredWidth() - mPaddingLeft - mPaddingRight;
665 final int childHeight = getMeasuredHeight() - mPaddingTop - mPaddingBottom;
Adam Cohen839f4a52010-08-26 17:36:48 -0700666
667 for (int i = 0; i < count; i++) {
668 final View child = getChildAt(i);
669 child.measure(MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.EXACTLY),
670 MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY));
671 }
672 }
673
674 @Override
675 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
676 int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
677 int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
678 final int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
679 final int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
680
681 boolean haveChildRefSize = (mReferenceChildWidth != -1 && mReferenceChildHeight != -1);
682
683 // We need to deal with the case where our parent hasn't told us how
684 // big we should be. In this case we try to use the desired size of the first
685 // child added.
686 if (heightSpecMode == MeasureSpec.UNSPECIFIED) {
687 heightSpecSize = haveChildRefSize ? mReferenceChildHeight + mPaddingTop +
688 mPaddingBottom : 0;
689 } else if (heightSpecMode == MeasureSpec.AT_MOST) {
Dianne Hackborn189ee182010-12-02 21:48:53 -0800690 if (haveChildRefSize) {
691 int height = mReferenceChildHeight + mPaddingTop + mPaddingBottom;
692 if (height > heightSpecSize) {
693 heightSpecSize |= MEASURED_STATE_TOO_SMALL;
694 } else {
695 heightSpecSize = height;
696 }
697 }
Adam Cohen839f4a52010-08-26 17:36:48 -0700698 }
699
700 if (widthSpecMode == MeasureSpec.UNSPECIFIED) {
701 widthSpecSize = haveChildRefSize ? mReferenceChildWidth + mPaddingLeft +
702 mPaddingRight : 0;
703 } else if (heightSpecMode == MeasureSpec.AT_MOST) {
Dianne Hackborn189ee182010-12-02 21:48:53 -0800704 if (haveChildRefSize) {
705 int width = mReferenceChildWidth + mPaddingLeft + mPaddingRight;
706 if (width > widthSpecSize) {
707 widthSpecSize |= MEASURED_STATE_TOO_SMALL;
708 } else {
709 widthSpecSize = width;
710 }
711 }
Adam Cohen839f4a52010-08-26 17:36:48 -0700712 }
713
714 setMeasuredDimension(widthSpecSize, heightSpecSize);
715 measureChildren();
716 }
717
Adam Cohenef17dd42011-01-20 17:20:57 -0800718 void checkForAndHandleDataChanged() {
Adam Cohen3db40672010-07-19 22:41:57 -0700719 boolean dataChanged = mDataChanged;
720 if (dataChanged) {
Adam Cohenef17dd42011-01-20 17:20:57 -0800721 post(new Runnable() {
722 public void run() {
723 handleDataChanged();
724 // if the data changes, mWhichChild might be out of the bounds of the adapter
725 // in this case, we reset mWhichChild to the beginning
726 if (mWhichChild >= getWindowSize()) {
727 mWhichChild = 0;
Adam Cohen3db40672010-07-19 22:41:57 -0700728
Adam Cohen78db1aa2011-01-25 12:24:23 -0800729 showOnly(mWhichChild, false);
Adam Cohene86ff4d2011-01-21 17:46:11 -0800730 } else if (mOldItemCount != getCount()) {
Adam Cohen78db1aa2011-01-25 12:24:23 -0800731 showOnly(mWhichChild, false);
Adam Cohenef17dd42011-01-20 17:20:57 -0800732 }
733 refreshChildren();
734 requestLayout();
735 }
736 });
Adam Cohen3db40672010-07-19 22:41:57 -0700737 }
Adam Cohenef17dd42011-01-20 17:20:57 -0800738 mDataChanged = false;
739 }
740
741 @Override
742 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
743 checkForAndHandleDataChanged();
Adam Cohen3db40672010-07-19 22:41:57 -0700744
745 final int childCount = getChildCount();
746 for (int i = 0; i < childCount; i++) {
747 final View child = getChildAt(i);
748
749 int childRight = mPaddingLeft + child.getMeasuredWidth();
750 int childBottom = mPaddingTop + child.getMeasuredHeight();
751
Adam Cohen839f4a52010-08-26 17:36:48 -0700752 child.layout(mPaddingLeft, mPaddingTop, childRight, childBottom);
Adam Cohen3db40672010-07-19 22:41:57 -0700753 }
Adam Cohen3db40672010-07-19 22:41:57 -0700754 }
755
Adam Cohenb04f7ad2010-08-15 13:22:42 -0700756 static class SavedState extends BaseSavedState {
757 int whichChild;
758
759 /**
760 * Constructor called from {@link AdapterViewAnimator#onSaveInstanceState()}
761 */
762 SavedState(Parcelable superState, int whichChild) {
763 super(superState);
764 this.whichChild = whichChild;
765 }
766
767 /**
768 * Constructor called from {@link #CREATOR}
769 */
770 private SavedState(Parcel in) {
771 super(in);
Winson Chung3ec9a452010-09-23 16:40:28 -0700772 this.whichChild = in.readInt();
Adam Cohenb04f7ad2010-08-15 13:22:42 -0700773 }
774
775 @Override
776 public void writeToParcel(Parcel out, int flags) {
777 super.writeToParcel(out, flags);
Winson Chung3ec9a452010-09-23 16:40:28 -0700778 out.writeInt(this.whichChild);
Adam Cohenb04f7ad2010-08-15 13:22:42 -0700779 }
780
781 @Override
782 public String toString() {
Winson Chung3ec9a452010-09-23 16:40:28 -0700783 return "AdapterViewAnimator.SavedState{ whichChild = " + this.whichChild + " }";
Adam Cohenb04f7ad2010-08-15 13:22:42 -0700784 }
785
786 public static final Parcelable.Creator<SavedState> CREATOR
787 = new Parcelable.Creator<SavedState>() {
788 public SavedState createFromParcel(Parcel in) {
789 return new SavedState(in);
790 }
791
792 public SavedState[] newArray(int size) {
793 return new SavedState[size];
794 }
795 };
796 }
797
798 @Override
799 public Parcelable onSaveInstanceState() {
800 Parcelable superState = super.onSaveInstanceState();
801 return new SavedState(superState, mWhichChild);
802 }
803
804 @Override
805 public void onRestoreInstanceState(Parcelable state) {
806 SavedState ss = (SavedState) state;
807 super.onRestoreInstanceState(ss.getSuperState());
808
809 // Here we set mWhichChild in addition to setDisplayedChild
810 // We do the former in case mAdapter is null, and hence setDisplayedChild won't
811 // set mWhichChild
812 mWhichChild = ss.whichChild;
Adam Cohen69d66e02011-01-12 14:39:13 -0800813
Winson Chung16c8d8a2011-01-20 16:19:33 -0800814 // When using RemoteAdapters, the async connection process can lead to
815 // onRestoreInstanceState to be called before setAdapter(), so we need to save the previous
816 // values to restore the list position after we connect, and can skip setting the displayed
817 // child until then.
818 if (mRemoteViewsAdapter != null && mAdapter == null) {
819 mRestoreWhichChild = mWhichChild;
820 } else {
Adam Cohen53838d22011-01-26 21:32:33 -0800821 setDisplayedChild(mWhichChild, false);
Winson Chung16c8d8a2011-01-20 16:19:33 -0800822 }
Adam Cohenb04f7ad2010-08-15 13:22:42 -0700823 }
824
Adam Cohen3db40672010-07-19 22:41:57 -0700825 /**
Adam Cohen3db40672010-07-19 22:41:57 -0700826 * Returns the View corresponding to the currently displayed child.
827 *
828 * @return The View currently displayed.
829 *
830 * @see #getDisplayedChild()
831 */
832 public View getCurrentView() {
Adam Cohen44729e32010-07-22 16:00:07 -0700833 return getViewAtRelativeIndex(mActiveOffset);
Adam Cohen3db40672010-07-19 22:41:57 -0700834 }
835
836 /**
837 * Returns the current animation used to animate a View that enters the screen.
838 *
839 * @return An Animation or null if none is set.
840 *
Chet Haase2794eb32010-10-12 16:29:28 -0700841 * @see #setInAnimation(android.animation.ObjectAnimator)
Adam Cohen3db40672010-07-19 22:41:57 -0700842 * @see #setInAnimation(android.content.Context, int)
843 */
Chet Haase2794eb32010-10-12 16:29:28 -0700844 public ObjectAnimator getInAnimation() {
Adam Cohen3db40672010-07-19 22:41:57 -0700845 return mInAnimation;
846 }
847
848 /**
849 * Specifies the animation used to animate a View that enters the screen.
850 *
851 * @param inAnimation The animation started when a View enters the screen.
852 *
853 * @see #getInAnimation()
854 * @see #setInAnimation(android.content.Context, int)
855 */
Chet Haase2794eb32010-10-12 16:29:28 -0700856 public void setInAnimation(ObjectAnimator inAnimation) {
Adam Cohen3db40672010-07-19 22:41:57 -0700857 mInAnimation = inAnimation;
858 }
859
860 /**
861 * Returns the current animation used to animate a View that exits the screen.
862 *
863 * @return An Animation or null if none is set.
864 *
Chet Haase2794eb32010-10-12 16:29:28 -0700865 * @see #setOutAnimation(android.animation.ObjectAnimator)
Adam Cohen3db40672010-07-19 22:41:57 -0700866 * @see #setOutAnimation(android.content.Context, int)
867 */
Chet Haase2794eb32010-10-12 16:29:28 -0700868 public ObjectAnimator getOutAnimation() {
Adam Cohen3db40672010-07-19 22:41:57 -0700869 return mOutAnimation;
870 }
871
872 /**
873 * Specifies the animation used to animate a View that exit the screen.
874 *
875 * @param outAnimation The animation started when a View exit the screen.
876 *
877 * @see #getOutAnimation()
878 * @see #setOutAnimation(android.content.Context, int)
879 */
Chet Haase2794eb32010-10-12 16:29:28 -0700880 public void setOutAnimation(ObjectAnimator outAnimation) {
Adam Cohen3db40672010-07-19 22:41:57 -0700881 mOutAnimation = outAnimation;
882 }
883
884 /**
885 * Specifies the animation used to animate a View that enters the screen.
886 *
887 * @param context The application's environment.
888 * @param resourceID The resource id of the animation.
889 *
890 * @see #getInAnimation()
Chet Haase2794eb32010-10-12 16:29:28 -0700891 * @see #setInAnimation(android.animation.ObjectAnimator)
Adam Cohen3db40672010-07-19 22:41:57 -0700892 */
893 public void setInAnimation(Context context, int resourceID) {
Chet Haase2794eb32010-10-12 16:29:28 -0700894 setInAnimation((ObjectAnimator) AnimatorInflater.loadAnimator(context, resourceID));
Adam Cohen3db40672010-07-19 22:41:57 -0700895 }
896
897 /**
898 * Specifies the animation used to animate a View that exit the screen.
899 *
900 * @param context The application's environment.
901 * @param resourceID The resource id of the animation.
902 *
903 * @see #getOutAnimation()
Chet Haase2794eb32010-10-12 16:29:28 -0700904 * @see #setOutAnimation(android.animation.ObjectAnimator)
Adam Cohen3db40672010-07-19 22:41:57 -0700905 */
906 public void setOutAnimation(Context context, int resourceID) {
Chet Haase2794eb32010-10-12 16:29:28 -0700907 setOutAnimation((ObjectAnimator) AnimatorInflater.loadAnimator(context, resourceID));
Adam Cohen3db40672010-07-19 22:41:57 -0700908 }
909
910 /**
911 * Indicates whether the current View should be animated the first time
912 * the ViewAnimation is displayed.
913 *
914 * @param animate True to animate the current View the first time it is displayed,
915 * false otherwise.
916 */
917 public void setAnimateFirstView(boolean animate) {
918 mAnimateFirstTime = animate;
919 }
920
921 @Override
922 public int getBaseline() {
923 return (getCurrentView() != null) ? getCurrentView().getBaseline() : super.getBaseline();
924 }
925
926 @Override
927 public Adapter getAdapter() {
928 return mAdapter;
929 }
930
931 @Override
932 public void setAdapter(Adapter adapter) {
Adam Cohen83228342010-08-10 16:47:30 -0700933 if (mAdapter != null && mDataSetObserver != null) {
934 mAdapter.unregisterDataSetObserver(mDataSetObserver);
935 }
936
Adam Cohen3db40672010-07-19 22:41:57 -0700937 mAdapter = adapter;
Adam Cohen1480fdd2010-08-25 17:24:53 -0700938 checkFocus();
Adam Cohen3db40672010-07-19 22:41:57 -0700939
940 if (mAdapter != null) {
Adam Cohen3db40672010-07-19 22:41:57 -0700941 mDataSetObserver = new AdapterDataSetObserver();
942 mAdapter.registerDataSetObserver(mDataSetObserver);
Adam Cohenef17dd42011-01-20 17:20:57 -0800943 mItemCount = mAdapter.getCount();
Adam Cohen3db40672010-07-19 22:41:57 -0700944 }
Adam Cohen44729e32010-07-22 16:00:07 -0700945 setFocusable(true);
Adam Cohen78db1aa2011-01-25 12:24:23 -0800946 mWhichChild = 0;
947 showOnly(mWhichChild, false);
Adam Cohen3db40672010-07-19 22:41:57 -0700948 }
949
950 /**
Adam Cohen44729e32010-07-22 16:00:07 -0700951 * Sets up this AdapterViewAnimator to use a remote views adapter which connects to a
952 * RemoteViewsService through the specified intent.
953 *
954 * @param intent the intent used to identify the RemoteViewsService for the adapter to
955 * connect to.
Adam Cohen3db40672010-07-19 22:41:57 -0700956 */
957 @android.view.RemotableViewMethod
958 public void setRemoteViewsAdapter(Intent intent) {
Winson Chung9b3a2cf2010-09-16 14:45:32 -0700959 // Ensure that we don't already have a RemoteViewsAdapter that is bound to an existing
960 // service handling the specified intent.
Winson Chung3ec9a452010-09-23 16:40:28 -0700961 if (mRemoteViewsAdapter != null) {
962 Intent.FilterComparison fcNew = new Intent.FilterComparison(intent);
963 Intent.FilterComparison fcOld = new Intent.FilterComparison(
964 mRemoteViewsAdapter.getRemoteViewsServiceIntent());
965 if (fcNew.equals(fcOld)) {
966 return;
967 }
Winson Chung9b3a2cf2010-09-16 14:45:32 -0700968 }
969
970 // Otherwise, create a new RemoteViewsAdapter for binding
Adam Cohen3db40672010-07-19 22:41:57 -0700971 mRemoteViewsAdapter = new RemoteViewsAdapter(getContext(), intent, this);
972 }
973
974 @Override
975 public void setSelection(int position) {
976 setDisplayedChild(position);
977 }
978
979 @Override
980 public View getSelectedView() {
Adam Cohen44729e32010-07-22 16:00:07 -0700981 return getViewAtRelativeIndex(mActiveOffset);
Adam Cohen3db40672010-07-19 22:41:57 -0700982 }
983
984 /**
985 * Called back when the adapter connects to the RemoteViewsService.
986 */
Winson Chung16c8d8a2011-01-20 16:19:33 -0800987 public boolean onRemoteAdapterConnected() {
Adam Cohen3db40672010-07-19 22:41:57 -0700988 if (mRemoteViewsAdapter != mAdapter) {
989 setAdapter(mRemoteViewsAdapter);
Winson Chung16c8d8a2011-01-20 16:19:33 -0800990
991 // Restore the previous position (see onRestoreInstanceState)
992 if (mRestoreWhichChild > -1) {
Adam Cohen53838d22011-01-26 21:32:33 -0800993 setDisplayedChild(mRestoreWhichChild, false);
Winson Chung16c8d8a2011-01-20 16:19:33 -0800994 mRestoreWhichChild = -1;
995 }
996 return false;
Adam Cohenfb603862010-12-17 12:03:17 -0800997 } else if (mRemoteViewsAdapter != null) {
998 mRemoteViewsAdapter.superNotifyDataSetChanged();
Winson Chung16c8d8a2011-01-20 16:19:33 -0800999 return true;
Adam Cohen3db40672010-07-19 22:41:57 -07001000 }
Winson Chung16c8d8a2011-01-20 16:19:33 -08001001 return false;
Adam Cohen3db40672010-07-19 22:41:57 -07001002 }
1003
1004 /**
1005 * Called back when the adapter disconnects from the RemoteViewsService.
1006 */
1007 public void onRemoteAdapterDisconnected() {
Adam Cohenfb603862010-12-17 12:03:17 -08001008 // If the remote adapter disconnects, we keep it around
1009 // since the currently displayed items are still cached.
1010 // Further, we want the service to eventually reconnect
1011 // when necessary, as triggered by this view requesting
1012 // items from the Adapter.
Adam Cohen3db40672010-07-19 22:41:57 -07001013 }
Adam Cohena02fdf12010-11-03 13:27:40 -07001014
Adam Cohen0e2de6d2011-01-19 17:16:34 -08001015 /**
1016 * Called by an {@link android.appwidget.AppWidgetHost} in order to advance the current view when
1017 * it is being used within an app widget.
1018 */
Adam Cohena02fdf12010-11-03 13:27:40 -07001019 public void advance() {
1020 showNext();
1021 }
1022
Adam Cohen0e2de6d2011-01-19 17:16:34 -08001023 /**
1024 * Called by an {@link android.appwidget.AppWidgetHost} to indicate that it will be
1025 * automatically advancing the views of this {@link AdapterViewAnimator} by calling
1026 * {@link AdapterViewAnimator#advance()} at some point in the future. This allows subclasses to
1027 * perform any required setup, for example, to stop automatically advancing their children.
1028 */
1029 public void fyiWillBeAdvancedByHostKThx() {
Adam Cohena02fdf12010-11-03 13:27:40 -07001030 }
Adam Cohen3db40672010-07-19 22:41:57 -07001031}