blob: 90e949aef9340cc25bffba96ad4e2f22636dfa4d [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;
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -080032import android.view.accessibility.AccessibilityEvent;
33import android.view.accessibility.AccessibilityNodeInfo;
Adam Cohena6a4cbc2012-09-26 17:36:40 -070034import android.widget.RemoteViews.OnClickHandler;
Adam Cohen3db40672010-07-19 22:41:57 -070035
Adam Cohen2148d432011-07-28 14:59:54 -070036import java.util.ArrayList;
37import java.util.HashMap;
38
Adam Cohen3db40672010-07-19 22:41:57 -070039/**
40 * Base class for a {@link AdapterView} that will perform animations
41 * when switching between its views.
42 *
43 * @attr ref android.R.styleable#AdapterViewAnimator_inAnimation
44 * @attr ref android.R.styleable#AdapterViewAnimator_outAnimation
45 * @attr ref android.R.styleable#AdapterViewAnimator_animateFirstView
Adam Cohen1b065cd2010-09-28 14:53:47 -070046 * @attr ref android.R.styleable#AdapterViewAnimator_loopViews
Adam Cohen3db40672010-07-19 22:41:57 -070047 */
Adam Cohen44729e32010-07-22 16:00:07 -070048public abstract class AdapterViewAnimator extends AdapterView<Adapter>
Adam Cohena02fdf12010-11-03 13:27:40 -070049 implements RemoteViewsAdapter.RemoteAdapterConnectionCallback, Advanceable {
Adam Cohen3db40672010-07-19 22:41:57 -070050 private static final String TAG = "RemoteViewAnimator";
51
Adam Cohen44729e32010-07-22 16:00:07 -070052 /**
53 * The index of the current child, which appears anywhere from the beginning
54 * to the end of the current set of children, as specified by {@link #mActiveOffset}
55 */
Adam Cohen3db40672010-07-19 22:41:57 -070056 int mWhichChild = 0;
Adam Cohen44729e32010-07-22 16:00:07 -070057
58 /**
Winson Chung16c8d8a2011-01-20 16:19:33 -080059 * The index of the child to restore after the asynchronous connection from the
60 * RemoteViewsAdapter has been.
61 */
62 private int mRestoreWhichChild = -1;
63
64 /**
Adam Cohen44729e32010-07-22 16:00:07 -070065 * Whether or not the first view(s) should be animated in
66 */
Adam Cohen3db40672010-07-19 22:41:57 -070067 boolean mAnimateFirstTime = true;
68
Adam Cohen44729e32010-07-22 16:00:07 -070069 /**
70 * Represents where the in the current window of
71 * views the current <code>mDisplayedChild</code> sits
72 */
73 int mActiveOffset = 0;
74
75 /**
76 * The number of views that the {@link AdapterViewAnimator} keeps as children at any
77 * given time (not counting views that are pending removal, see {@link #mPreviousViews}).
78 */
Adam Cohen96d8d562010-10-24 11:12:18 -070079 int mMaxNumActiveViews = 1;
Adam Cohen44729e32010-07-22 16:00:07 -070080
81 /**
Adam Cohen1b065cd2010-09-28 14:53:47 -070082 * Map of the children of the {@link AdapterViewAnimator}.
Adam Cohen44729e32010-07-22 16:00:07 -070083 */
Adam Cohend38a0ce2011-04-06 13:20:42 -070084 HashMap<Integer, ViewAndMetaData> mViewsMap = new HashMap<Integer, ViewAndMetaData>();
Adam Cohen44729e32010-07-22 16:00:07 -070085
86 /**
87 * List of views pending removal from the {@link AdapterViewAnimator}
88 */
Adam Cohen1b065cd2010-09-28 14:53:47 -070089 ArrayList<Integer> mPreviousViews;
Adam Cohen44729e32010-07-22 16:00:07 -070090
91 /**
92 * The index, relative to the adapter, of the beginning of the window of views
93 */
94 int mCurrentWindowStart = 0;
95
96 /**
97 * The index, relative to the adapter, of the end of the window of views
98 */
99 int mCurrentWindowEnd = -1;
100
101 /**
102 * The same as {@link #mCurrentWindowStart}, except when the we have bounded
103 * {@link #mCurrentWindowStart} to be non-negative
104 */
105 int mCurrentWindowStartUnbounded = 0;
106
107 /**
Adam Cohen44729e32010-07-22 16:00:07 -0700108 * Listens for data changes from the adapter
109 */
Adam Cohen3db40672010-07-19 22:41:57 -0700110 AdapterDataSetObserver mDataSetObserver;
111
Adam Cohen44729e32010-07-22 16:00:07 -0700112 /**
113 * The {@link Adapter} for this {@link AdapterViewAnimator}
114 */
115 Adapter mAdapter;
Adam Cohen3db40672010-07-19 22:41:57 -0700116
Adam Cohen44729e32010-07-22 16:00:07 -0700117 /**
118 * The {@link RemoteViewsAdapter} for this {@link AdapterViewAnimator}
119 */
120 RemoteViewsAdapter mRemoteViewsAdapter;
121
122 /**
Adam Cohen2148d432011-07-28 14:59:54 -0700123 * The remote adapter containing the data to be displayed by this view to be set
124 */
125 boolean mDeferNotifyDataSetChanged = false;
126
127 /**
Adam Cohen44729e32010-07-22 16:00:07 -0700128 * Specifies whether this is the first time the animator is showing views
129 */
130 boolean mFirstTime = true;
131
132 /**
Adam Cohenb04f7ad2010-08-15 13:22:42 -0700133 * Specifies if the animator should wrap from 0 to the end and vice versa
134 * or have hard boundaries at the beginning and end
135 */
Adam Cohen1b065cd2010-09-28 14:53:47 -0700136 boolean mLoopViews = true;
Adam Cohenb04f7ad2010-08-15 13:22:42 -0700137
138 /**
Adam Cohen839f4a52010-08-26 17:36:48 -0700139 * The width and height of some child, used as a size reference in-case our
140 * dimensions are unspecified by the parent.
141 */
142 int mReferenceChildWidth = -1;
143 int mReferenceChildHeight = -1;
144
145 /**
Adam Cohenef521762010-10-04 13:56:11 -0700146 * In and out animations.
Adam Cohen44729e32010-07-22 16:00:07 -0700147 */
Chet Haase2794eb32010-10-12 16:29:28 -0700148 ObjectAnimator mInAnimation;
149 ObjectAnimator mOutAnimation;
Adam Cohenef521762010-10-04 13:56:11 -0700150
Adam Cohena32edd42010-10-26 10:35:01 -0700151 /**
152 * Current touch state.
153 */
154 private int mTouchMode = TOUCH_MODE_NONE;
155
156 /**
157 * Private touch states.
158 */
159 static final int TOUCH_MODE_NONE = 0;
160 static final int TOUCH_MODE_DOWN_IN_CURRENT_VIEW = 1;
161 static final int TOUCH_MODE_HANDLED = 2;
162
163 private Runnable mPendingCheckForTap;
164
Adam Cohenef521762010-10-04 13:56:11 -0700165 private static final int DEFAULT_ANIMATION_DURATION = 200;
166
Adam Cohen3db40672010-07-19 22:41:57 -0700167 public AdapterViewAnimator(Context context) {
Adam Cohen26f072c2011-04-01 16:23:18 -0700168 this(context, null);
Adam Cohen3db40672010-07-19 22:41:57 -0700169 }
170
171 public AdapterViewAnimator(Context context, AttributeSet attrs) {
Adam Cohen26f072c2011-04-01 16:23:18 -0700172 this(context, attrs, 0);
173 }
174
175 public AdapterViewAnimator(Context context, AttributeSet attrs, int defStyleAttr) {
176 super(context, attrs, defStyleAttr);
Adam Cohen3db40672010-07-19 22:41:57 -0700177
Adam Cohen44729e32010-07-22 16:00:07 -0700178 TypedArray a = context.obtainStyledAttributes(attrs,
Adam Cohen26f072c2011-04-01 16:23:18 -0700179 com.android.internal.R.styleable.AdapterViewAnimator, defStyleAttr, 0);
Adam Cohen44729e32010-07-22 16:00:07 -0700180 int resource = a.getResourceId(
Adam Cohen1b065cd2010-09-28 14:53:47 -0700181 com.android.internal.R.styleable.AdapterViewAnimator_inAnimation, 0);
Adam Cohen3db40672010-07-19 22:41:57 -0700182 if (resource > 0) {
183 setInAnimation(context, resource);
Adam Cohenef521762010-10-04 13:56:11 -0700184 } else {
185 setInAnimation(getDefaultInAnimation());
Adam Cohen3db40672010-07-19 22:41:57 -0700186 }
187
Adam Cohen1b065cd2010-09-28 14:53:47 -0700188 resource = a.getResourceId(com.android.internal.R.styleable.AdapterViewAnimator_outAnimation, 0);
Adam Cohen3db40672010-07-19 22:41:57 -0700189 if (resource > 0) {
190 setOutAnimation(context, resource);
Adam Cohenef521762010-10-04 13:56:11 -0700191 } else {
192 setOutAnimation(getDefaultOutAnimation());
Adam Cohen3db40672010-07-19 22:41:57 -0700193 }
194
Adam Cohen44729e32010-07-22 16:00:07 -0700195 boolean flag = a.getBoolean(
Adam Cohen1b065cd2010-09-28 14:53:47 -0700196 com.android.internal.R.styleable.AdapterViewAnimator_animateFirstView, true);
Adam Cohen3db40672010-07-19 22:41:57 -0700197 setAnimateFirstView(flag);
198
Adam Cohen1b065cd2010-09-28 14:53:47 -0700199 mLoopViews = a.getBoolean(
200 com.android.internal.R.styleable.AdapterViewAnimator_loopViews, false);
201
Adam Cohen3db40672010-07-19 22:41:57 -0700202 a.recycle();
203
Romain Guy5b53f912010-08-16 18:24:33 -0700204 initViewAnimator();
Adam Cohen3db40672010-07-19 22:41:57 -0700205 }
206
207 /**
208 * Initialize this {@link AdapterViewAnimator}
209 */
Romain Guy5b53f912010-08-16 18:24:33 -0700210 private void initViewAnimator() {
Adam Cohen1b065cd2010-09-28 14:53:47 -0700211 mPreviousViews = new ArrayList<Integer>();
Adam Cohen44729e32010-07-22 16:00:07 -0700212 }
213
Adam Cohend38a0ce2011-04-06 13:20:42 -0700214 class ViewAndMetaData {
Adam Cohen1b065cd2010-09-28 14:53:47 -0700215 View view;
Adam Cohend38a0ce2011-04-06 13:20:42 -0700216 int relativeIndex;
217 int adapterPosition;
218 long itemId;
219
220 ViewAndMetaData(View view, int relativeIndex, int adapterPosition, long itemId) {
221 this.view = view;
222 this.relativeIndex = relativeIndex;
223 this.adapterPosition = adapterPosition;
224 this.itemId = itemId;
225 }
Adam Cohen1b065cd2010-09-28 14:53:47 -0700226 }
227
Adam Cohen44729e32010-07-22 16:00:07 -0700228 /**
229 * This method is used by subclasses to configure the animator to display the
230 * desired number of views, and specify the offset
231 *
232 * @param numVisibleViews The number of views the animator keeps in the {@link ViewGroup}
Romain Guy5b53f912010-08-16 18:24:33 -0700233 * @param activeOffset This parameter specifies where the current index ({@link #mWhichChild})
Adam Cohen44729e32010-07-22 16:00:07 -0700234 * sits within the window. For example if activeOffset is 1, and numVisibleViews is 3,
Romain Guy5b53f912010-08-16 18:24:33 -0700235 * and {@link #setDisplayedChild(int)} is called with 10, then the effective window will
236 * be the indexes 9, 10, and 11. In the same example, if activeOffset were 0, then the
Adam Cohen44729e32010-07-22 16:00:07 -0700237 * window would instead contain indexes 10, 11 and 12.
Adam Cohenb04f7ad2010-08-15 13:22:42 -0700238 * @param shouldLoop If the animator is show view 0, and setPrevious() is called, do we
239 * we loop back to the end, or do we do nothing
Adam Cohen44729e32010-07-22 16:00:07 -0700240 */
Adam Cohen1b065cd2010-09-28 14:53:47 -0700241 void configureViewAnimator(int numVisibleViews, int activeOffset) {
Adam Cohen44729e32010-07-22 16:00:07 -0700242 if (activeOffset > numVisibleViews - 1) {
243 // Throw an exception here.
244 }
Adam Cohen96d8d562010-10-24 11:12:18 -0700245 mMaxNumActiveViews = numVisibleViews;
Adam Cohen44729e32010-07-22 16:00:07 -0700246 mActiveOffset = activeOffset;
Adam Cohen44729e32010-07-22 16:00:07 -0700247 mPreviousViews.clear();
Adam Cohen1b065cd2010-09-28 14:53:47 -0700248 mViewsMap.clear();
Adam Cohen44729e32010-07-22 16:00:07 -0700249 removeAllViewsInLayout();
250 mCurrentWindowStart = 0;
251 mCurrentWindowEnd = -1;
252 }
253
254 /**
255 * This class should be overridden by subclasses to customize view transitions within
256 * the set of visible views
257 *
258 * @param fromIndex The relative index within the window that the view was in, -1 if it wasn't
259 * in the window
260 * @param toIndex The relative index within the window that the view is going to, -1 if it is
261 * being removed
262 * @param view The view that is being animated
263 */
Adam Cohen78db1aa2011-01-25 12:24:23 -0800264 void transformViewForTransition(int fromIndex, int toIndex, View view, boolean animate) {
Adam Cohen44729e32010-07-22 16:00:07 -0700265 if (fromIndex == -1) {
Adam Cohenef521762010-10-04 13:56:11 -0700266 mInAnimation.setTarget(view);
267 mInAnimation.start();
Adam Cohen44729e32010-07-22 16:00:07 -0700268 } else if (toIndex == -1) {
Adam Cohenef521762010-10-04 13:56:11 -0700269 mOutAnimation.setTarget(view);
270 mOutAnimation.start();
Adam Cohen44729e32010-07-22 16:00:07 -0700271 }
Adam Cohen3db40672010-07-19 22:41:57 -0700272 }
273
Chet Haase2794eb32010-10-12 16:29:28 -0700274 ObjectAnimator getDefaultInAnimation() {
275 ObjectAnimator anim = ObjectAnimator.ofFloat(null, "alpha", 0.0f, 1.0f);
276 anim.setDuration(DEFAULT_ANIMATION_DURATION);
277 return anim;
Adam Cohenef521762010-10-04 13:56:11 -0700278 }
279
Chet Haase2794eb32010-10-12 16:29:28 -0700280 ObjectAnimator getDefaultOutAnimation() {
281 ObjectAnimator anim = ObjectAnimator.ofFloat(null, "alpha", 1.0f, 0.0f);
282 anim.setDuration(DEFAULT_ANIMATION_DURATION);
283 return anim;
Adam Cohenef521762010-10-04 13:56:11 -0700284 }
285
Adam Cohen3db40672010-07-19 22:41:57 -0700286 /**
287 * Sets which child view will be displayed.
288 *
289 * @param whichChild the index of the child view to display
290 */
Adam Cohen0b96a572011-02-10 15:56:16 -0800291 @android.view.RemotableViewMethod
Adam Cohen3db40672010-07-19 22:41:57 -0700292 public void setDisplayedChild(int whichChild) {
Adam Cohen53838d22011-01-26 21:32:33 -0800293 setDisplayedChild(whichChild, true);
294 }
295
296 private void setDisplayedChild(int whichChild, boolean animate) {
Adam Cohen3db40672010-07-19 22:41:57 -0700297 if (mAdapter != null) {
298 mWhichChild = whichChild;
Adam Cohen96d8d562010-10-24 11:12:18 -0700299 if (whichChild >= getWindowSize()) {
300 mWhichChild = mLoopViews ? 0 : getWindowSize() - 1;
Adam Cohen3db40672010-07-19 22:41:57 -0700301 } else if (whichChild < 0) {
Adam Cohen96d8d562010-10-24 11:12:18 -0700302 mWhichChild = mLoopViews ? getWindowSize() - 1 : 0;
Adam Cohen3db40672010-07-19 22:41:57 -0700303 }
304
305 boolean hasFocus = getFocusedChild() != null;
306 // This will clear old focus if we had it
Adam Cohen53838d22011-01-26 21:32:33 -0800307 showOnly(mWhichChild, animate);
Adam Cohen3db40672010-07-19 22:41:57 -0700308 if (hasFocus) {
309 // Try to retake focus if we had it
310 requestFocus(FOCUS_FORWARD);
311 }
312 }
313 }
314
315 /**
Adam Cohen44729e32010-07-22 16:00:07 -0700316 * To be overridden by subclasses. This method applies a view / index specific
317 * transform to the child view.
318 *
319 * @param child
320 * @param relativeIndex
321 */
322 void applyTransformForChildAtIndex(View child, int relativeIndex) {
323 }
324
325 /**
Adam Cohen3db40672010-07-19 22:41:57 -0700326 * Returns the index of the currently displayed child view.
327 */
328 public int getDisplayedChild() {
329 return mWhichChild;
330 }
331
332 /**
333 * Manually shows the next child.
334 */
335 public void showNext() {
336 setDisplayedChild(mWhichChild + 1);
337 }
338
339 /**
340 * Manually shows the previous child.
341 */
342 public void showPrevious() {
343 setDisplayedChild(mWhichChild - 1);
344 }
345
Adam Cohen96d8d562010-10-24 11:12:18 -0700346 int modulo(int pos, int size) {
Adam Cohen30429442010-10-06 10:37:51 -0700347 if (size > 0) {
348 return (size + (pos % size)) % size;
349 } else {
350 return 0;
351 }
Adam Cohen3db40672010-07-19 22:41:57 -0700352 }
353
Adam Cohen44729e32010-07-22 16:00:07 -0700354 /**
355 * Get the view at this index relative to the current window's start
356 *
357 * @param relativeIndex Position relative to the current window's start
358 * @return View at this index, null if the index is outside the bounds
359 */
360 View getViewAtRelativeIndex(int relativeIndex) {
Adam Cohen96d8d562010-10-24 11:12:18 -0700361 if (relativeIndex >= 0 && relativeIndex <= getNumActiveViews() - 1 && mAdapter != null) {
362 int i = modulo(mCurrentWindowStartUnbounded + relativeIndex, getWindowSize());
Adam Cohen6f279622010-10-14 10:37:32 -0700363 if (mViewsMap.get(i) != null) {
364 return mViewsMap.get(i).view;
365 }
Adam Cohen44729e32010-07-22 16:00:07 -0700366 }
367 return null;
368 }
Adam Cohen3db40672010-07-19 22:41:57 -0700369
Adam Cohen96d8d562010-10-24 11:12:18 -0700370 int getNumActiveViews() {
371 if (mAdapter != null) {
Adam Cohenef17dd42011-01-20 17:20:57 -0800372 return Math.min(getCount() + 1, mMaxNumActiveViews);
Adam Cohen96d8d562010-10-24 11:12:18 -0700373 } else {
374 return mMaxNumActiveViews;
375 }
376 }
377
378 int getWindowSize() {
379 if (mAdapter != null) {
Adam Cohenef17dd42011-01-20 17:20:57 -0800380 int adapterCount = getCount();
Adam Cohen96d8d562010-10-24 11:12:18 -0700381 if (adapterCount <= getNumActiveViews() && mLoopViews) {
382 return adapterCount*mMaxNumActiveViews;
383 } else {
384 return adapterCount;
385 }
386 } else {
387 return 0;
388 }
389 }
390
Adam Cohend38a0ce2011-04-06 13:20:42 -0700391 private ViewAndMetaData getMetaDataForChild(View child) {
392 for (ViewAndMetaData vm: mViewsMap.values()) {
393 if (vm.view == child) {
394 return vm;
395 }
396 }
397 return null;
398 }
399
Adam Cohen9b073942010-08-19 16:49:52 -0700400 LayoutParams createOrReuseLayoutParams(View v) {
Romain Guy5b53f912010-08-16 18:24:33 -0700401 final ViewGroup.LayoutParams currentLp = v.getLayoutParams();
Adam Cohen9b073942010-08-19 16:49:52 -0700402 if (currentLp instanceof ViewGroup.LayoutParams) {
Adam Cohenb04f7ad2010-08-15 13:22:42 -0700403 LayoutParams lp = (LayoutParams) currentLp;
Adam Cohenb04f7ad2010-08-15 13:22:42 -0700404 return lp;
Adam Cohen44729e32010-07-22 16:00:07 -0700405 }
Adam Cohen9b073942010-08-19 16:49:52 -0700406 return new ViewGroup.LayoutParams(0, 0);
Adam Cohen44729e32010-07-22 16:00:07 -0700407 }
Adam Cohen3db40672010-07-19 22:41:57 -0700408
Winson Chung6364f2b2010-09-29 11:14:30 -0700409 void refreshChildren() {
Adam Cohena9238c82010-10-25 14:01:29 -0700410 if (mAdapter == null) return;
Adam Cohenbd0136a2010-09-08 14:19:39 -0700411 for (int i = mCurrentWindowStart; i <= mCurrentWindowEnd; i++) {
Adam Cohena9238c82010-10-25 14:01:29 -0700412 int index = modulo(i, getWindowSize());
Adam Cohenbd0136a2010-09-08 14:19:39 -0700413
Adam Cohenef17dd42011-01-20 17:20:57 -0800414 int adapterCount = getCount();
Adam Cohenbd0136a2010-09-08 14:19:39 -0700415 // get the fresh child from the adapter
Adam Cohenef17dd42011-01-20 17:20:57 -0800416 final View updatedChild = mAdapter.getView(modulo(i, adapterCount), null, this);
Adam Cohenbd0136a2010-09-08 14:19:39 -0700417
Svetoslav Ganov42138042012-03-20 11:51:39 -0700418 if (updatedChild.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
419 updatedChild.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
420 }
421
Adam Cohen1b065cd2010-09-28 14:53:47 -0700422 if (mViewsMap.containsKey(index)) {
Adam Cohenef17dd42011-01-20 17:20:57 -0800423 final FrameLayout fl = (FrameLayout) mViewsMap.get(index).view;
Adam Cohenbd0136a2010-09-08 14:19:39 -0700424 // add the new child to the frame, if it exists
425 if (updatedChild != null) {
Adam Cohenef17dd42011-01-20 17:20:57 -0800426 // flush out the old child
427 fl.removeAllViewsInLayout();
Adam Cohenbd0136a2010-09-08 14:19:39 -0700428 fl.addView(updatedChild);
429 }
430 }
431 }
432 }
433
Adam Cohendfcdddd2010-09-10 14:38:40 -0700434 /**
435 * This method can be overridden so that subclasses can provide a custom frame in which their
436 * children can live. For example, StackView adds padding to its childrens' frames so as to
437 * accomodate for the highlight effect.
438 *
439 * @return The FrameLayout into which children can be placed.
440 */
441 FrameLayout getFrameForChild() {
442 return new FrameLayout(mContext);
443 }
444
Adam Cohenef17dd42011-01-20 17:20:57 -0800445 /**
446 * Shows only the specified child. The other displays Views exit the screen,
447 * optionally with the with the {@link #getOutAnimation() out animation} and
448 * the specified child enters the screen, optionally with the
449 * {@link #getInAnimation() in animation}.
450 *
451 * @param childIndex The index of the child to be shown.
452 * @param animate Whether or not to use the in and out animations, defaults
453 * to true.
454 */
455 void showOnly(int childIndex, boolean animate) {
Adam Cohen44729e32010-07-22 16:00:07 -0700456 if (mAdapter == null) return;
Adam Cohenef17dd42011-01-20 17:20:57 -0800457 final int adapterCount = getCount();
Adam Cohen30429442010-10-06 10:37:51 -0700458 if (adapterCount == 0) return;
Adam Cohen3db40672010-07-19 22:41:57 -0700459
Adam Cohen44729e32010-07-22 16:00:07 -0700460 for (int i = 0; i < mPreviousViews.size(); i++) {
Adam Cohen1b065cd2010-09-28 14:53:47 -0700461 View viewToRemove = mViewsMap.get(mPreviousViews.get(i)).view;
462 mViewsMap.remove(mPreviousViews.get(i));
Adam Cohen44729e32010-07-22 16:00:07 -0700463 viewToRemove.clearAnimation();
Adam Cohen3d07af02010-08-18 17:46:23 -0700464 if (viewToRemove instanceof ViewGroup) {
465 ViewGroup vg = (ViewGroup) viewToRemove;
466 vg.removeAllViewsInLayout();
467 }
Adam Cohen44729e32010-07-22 16:00:07 -0700468 // applyTransformForChildAtIndex here just allows for any cleanup
469 // associated with this view that may need to be done by a subclass
470 applyTransformForChildAtIndex(viewToRemove, -1);
Adam Cohen3d07af02010-08-18 17:46:23 -0700471
Adam Cohen44729e32010-07-22 16:00:07 -0700472 removeViewInLayout(viewToRemove);
473 }
474 mPreviousViews.clear();
475 int newWindowStartUnbounded = childIndex - mActiveOffset;
Adam Cohen96d8d562010-10-24 11:12:18 -0700476 int newWindowEndUnbounded = newWindowStartUnbounded + getNumActiveViews() - 1;
Adam Cohen44729e32010-07-22 16:00:07 -0700477 int newWindowStart = Math.max(0, newWindowStartUnbounded);
Adam Cohen1b065cd2010-09-28 14:53:47 -0700478 int newWindowEnd = Math.min(adapterCount - 1, newWindowEndUnbounded);
Adam Cohen3db40672010-07-19 22:41:57 -0700479
Adam Cohen1b065cd2010-09-28 14:53:47 -0700480 if (mLoopViews) {
481 newWindowStart = newWindowStartUnbounded;
482 newWindowEnd = newWindowEndUnbounded;
483 }
Adam Cohen96d8d562010-10-24 11:12:18 -0700484 int rangeStart = modulo(newWindowStart, getWindowSize());
485 int rangeEnd = modulo(newWindowEnd, getWindowSize());
Adam Cohen1b065cd2010-09-28 14:53:47 -0700486
487 boolean wrap = false;
488 if (rangeStart > rangeEnd) {
489 wrap = true;
490 }
491
492 // This section clears out any items that are in our active views list
Adam Cohen44729e32010-07-22 16:00:07 -0700493 // but are outside the effective bounds of our window (this is becomes an issue
494 // at the extremities of the list, eg. where newWindowStartUnbounded < 0 or
Adam Cohenef17dd42011-01-20 17:20:57 -0800495 // newWindowEndUnbounded > adapterCount - 1
Adam Cohen1b065cd2010-09-28 14:53:47 -0700496 for (Integer index : mViewsMap.keySet()) {
497 boolean remove = false;
498 if (!wrap && (index < rangeStart || index > rangeEnd)) {
499 remove = true;
500 } else if (wrap && (index > rangeEnd && index < rangeStart)) {
501 remove = true;
502 }
503
504 if (remove) {
505 View previousView = mViewsMap.get(index).view;
Adam Cohend38a0ce2011-04-06 13:20:42 -0700506 int oldRelativeIndex = mViewsMap.get(index).relativeIndex;
Adam Cohen1b065cd2010-09-28 14:53:47 -0700507
508 mPreviousViews.add(index);
Adam Cohen78db1aa2011-01-25 12:24:23 -0800509 transformViewForTransition(oldRelativeIndex, -1, previousView, animate);
Adam Cohen3db40672010-07-19 22:41:57 -0700510 }
Adam Cohen44729e32010-07-22 16:00:07 -0700511 }
Adam Cohen3db40672010-07-19 22:41:57 -0700512
Adam Cohen44729e32010-07-22 16:00:07 -0700513 // If the window has changed
Adam Cohen96d8d562010-10-24 11:12:18 -0700514 if (!(newWindowStart == mCurrentWindowStart && newWindowEnd == mCurrentWindowEnd &&
515 newWindowStartUnbounded == mCurrentWindowStartUnbounded)) {
Adam Cohen44729e32010-07-22 16:00:07 -0700516 // Run through the indices in the new range
517 for (int i = newWindowStart; i <= newWindowEnd; i++) {
Adam Cohen3db40672010-07-19 22:41:57 -0700518
Adam Cohen96d8d562010-10-24 11:12:18 -0700519 int index = modulo(i, getWindowSize());
Adam Cohen1b065cd2010-09-28 14:53:47 -0700520 int oldRelativeIndex;
521 if (mViewsMap.containsKey(index)) {
Adam Cohend38a0ce2011-04-06 13:20:42 -0700522 oldRelativeIndex = mViewsMap.get(index).relativeIndex;
Adam Cohen1b065cd2010-09-28 14:53:47 -0700523 } else {
524 oldRelativeIndex = -1;
525 }
Adam Cohen44729e32010-07-22 16:00:07 -0700526 int newRelativeIndex = i - newWindowStartUnbounded;
Adam Cohen44729e32010-07-22 16:00:07 -0700527
528 // If this item is in the current window, great, we just need to apply
529 // the transform for it's new relative position in the window, and animate
530 // between it's current and new relative positions
Adam Cohen1b065cd2010-09-28 14:53:47 -0700531 boolean inOldRange = mViewsMap.containsKey(index) && !mPreviousViews.contains(index);
532
533 if (inOldRange) {
534 View view = mViewsMap.get(index).view;
Adam Cohend38a0ce2011-04-06 13:20:42 -0700535 mViewsMap.get(index).relativeIndex = newRelativeIndex;
Adam Cohen44729e32010-07-22 16:00:07 -0700536 applyTransformForChildAtIndex(view, newRelativeIndex);
Adam Cohen78db1aa2011-01-25 12:24:23 -0800537 transformViewForTransition(oldRelativeIndex, newRelativeIndex, view, animate);
Adam Cohen44729e32010-07-22 16:00:07 -0700538
Adam Cohen1b065cd2010-09-28 14:53:47 -0700539 // Otherwise this view is new to the window
Adam Cohen44729e32010-07-22 16:00:07 -0700540 } else {
Adam Cohen1b065cd2010-09-28 14:53:47 -0700541 // Get the new view from the adapter, add it and apply any transform / animation
Adam Cohend38a0ce2011-04-06 13:20:42 -0700542 final int adapterPosition = modulo(i, adapterCount);
543 View newView = mAdapter.getView(adapterPosition, null, this);
544 long itemId = mAdapter.getItemId(adapterPosition);
Adam Cohenbd0136a2010-09-08 14:19:39 -0700545
546 // We wrap the new view in a FrameLayout so as to respect the contract
547 // with the adapter, that is, that we don't modify this view directly
Adam Cohendfcdddd2010-09-10 14:38:40 -0700548 FrameLayout fl = getFrameForChild();
Adam Cohenbd0136a2010-09-08 14:19:39 -0700549
550 // If the view from the adapter is null, we still keep an empty frame in place
Adam Cohen44729e32010-07-22 16:00:07 -0700551 if (newView != null) {
Adam Cohenbd0136a2010-09-08 14:19:39 -0700552 fl.addView(newView);
Adam Cohen44729e32010-07-22 16:00:07 -0700553 }
Adam Cohend38a0ce2011-04-06 13:20:42 -0700554 mViewsMap.put(index, new ViewAndMetaData(fl, newRelativeIndex,
555 adapterPosition, itemId));
Adam Cohenbd0136a2010-09-08 14:19:39 -0700556 addChild(fl);
557 applyTransformForChildAtIndex(fl, newRelativeIndex);
Adam Cohen78db1aa2011-01-25 12:24:23 -0800558 transformViewForTransition(-1, newRelativeIndex, fl, animate);
Adam Cohen44729e32010-07-22 16:00:07 -0700559 }
Adam Cohen1b065cd2010-09-28 14:53:47 -0700560 mViewsMap.get(index).view.bringToFront();
Adam Cohen3db40672010-07-19 22:41:57 -0700561 }
Adam Cohen44729e32010-07-22 16:00:07 -0700562 mCurrentWindowStart = newWindowStart;
563 mCurrentWindowEnd = newWindowEnd;
564 mCurrentWindowStartUnbounded = newWindowStartUnbounded;
Adam Cohenb9673922012-01-05 13:58:47 -0800565 if (mRemoteViewsAdapter != null) {
Adam Cohend0c735f2012-01-23 19:12:22 -0800566 int adapterStart = modulo(mCurrentWindowStart, adapterCount);
567 int adapterEnd = modulo(mCurrentWindowEnd, adapterCount);
568 mRemoteViewsAdapter.setVisibleRangeHint(adapterStart, adapterEnd);
Adam Cohenb9673922012-01-05 13:58:47 -0800569 }
Adam Cohen44729e32010-07-22 16:00:07 -0700570 }
Adam Cohenef17dd42011-01-20 17:20:57 -0800571 requestLayout();
572 invalidate();
Adam Cohen3db40672010-07-19 22:41:57 -0700573 }
574
Adam Cohen839f4a52010-08-26 17:36:48 -0700575 private void addChild(View child) {
576 addViewInLayout(child, -1, createOrReuseLayoutParams(child));
577
578 // This code is used to obtain a reference width and height of a child in case we need
579 // to decide our own size. TODO: Do we want to update the size of the child that we're
580 // using for reference size? If so, when?
581 if (mReferenceChildWidth == -1 || mReferenceChildHeight == -1) {
582 int measureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
583 child.measure(measureSpec, measureSpec);
584 mReferenceChildWidth = child.getMeasuredWidth();
585 mReferenceChildHeight = child.getMeasuredHeight();
586 }
587 }
588
Adam Cohena32edd42010-10-26 10:35:01 -0700589 void showTapFeedback(View v) {
590 v.setPressed(true);
591 }
592
593 void hideTapFeedback(View v) {
594 v.setPressed(false);
595 }
596
597 void cancelHandleClick() {
598 View v = getCurrentView();
599 if (v != null) {
600 hideTapFeedback(v);
601 }
602 mTouchMode = TOUCH_MODE_NONE;
603 }
604
605 final class CheckForTap implements Runnable {
606 public void run() {
607 if (mTouchMode == TOUCH_MODE_DOWN_IN_CURRENT_VIEW) {
608 View v = getCurrentView();
609 showTapFeedback(v);
610 }
611 }
612 }
613
614 @Override
615 public boolean onTouchEvent(MotionEvent ev) {
616 int action = ev.getAction();
617 boolean handled = false;
618 switch (action) {
619 case MotionEvent.ACTION_DOWN: {
620 View v = getCurrentView();
621 if (v != null) {
622 if (isTransformedTouchPointInView(ev.getX(), ev.getY(), v, null)) {
623 if (mPendingCheckForTap == null) {
624 mPendingCheckForTap = new CheckForTap();
625 }
626 mTouchMode = TOUCH_MODE_DOWN_IN_CURRENT_VIEW;
627 postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
628 }
629 }
630 break;
631 }
632 case MotionEvent.ACTION_MOVE: break;
633 case MotionEvent.ACTION_POINTER_UP: break;
634 case MotionEvent.ACTION_UP: {
635 if (mTouchMode == TOUCH_MODE_DOWN_IN_CURRENT_VIEW) {
636 final View v = getCurrentView();
Adam Cohend38a0ce2011-04-06 13:20:42 -0700637 final ViewAndMetaData viewData = getMetaDataForChild(v);
Adam Cohena32edd42010-10-26 10:35:01 -0700638 if (v != null) {
639 if (isTransformedTouchPointInView(ev.getX(), ev.getY(), v, null)) {
640 final Handler handler = getHandler();
641 if (handler != null) {
642 handler.removeCallbacks(mPendingCheckForTap);
643 }
644 showTapFeedback(v);
645 postDelayed(new Runnable() {
646 public void run() {
647 hideTapFeedback(v);
648 post(new Runnable() {
649 public void run() {
Adam Cohend38a0ce2011-04-06 13:20:42 -0700650 if (viewData != null) {
651 performItemClick(v, viewData.adapterPosition,
652 viewData.itemId);
653 } else {
654 performItemClick(v, 0, 0);
655 }
Adam Cohena32edd42010-10-26 10:35:01 -0700656 }
657 });
658 }
659 }, ViewConfiguration.getPressedStateDuration());
660 handled = true;
661 }
662 }
663 }
664 mTouchMode = TOUCH_MODE_NONE;
665 break;
666 }
667 case MotionEvent.ACTION_CANCEL: {
668 View v = getCurrentView();
669 if (v != null) {
670 hideTapFeedback(v);
671 }
672 mTouchMode = TOUCH_MODE_NONE;
673 }
674 }
675 return handled;
676 }
677
Adam Cohen839f4a52010-08-26 17:36:48 -0700678 private void measureChildren() {
679 final int count = getChildCount();
Dianne Hackborn189ee182010-12-02 21:48:53 -0800680 final int childWidth = getMeasuredWidth() - mPaddingLeft - mPaddingRight;
681 final int childHeight = getMeasuredHeight() - mPaddingTop - mPaddingBottom;
Adam Cohen839f4a52010-08-26 17:36:48 -0700682
683 for (int i = 0; i < count; i++) {
684 final View child = getChildAt(i);
685 child.measure(MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.EXACTLY),
686 MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY));
687 }
688 }
689
690 @Override
691 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
692 int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
693 int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
694 final int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
695 final int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
696
697 boolean haveChildRefSize = (mReferenceChildWidth != -1 && mReferenceChildHeight != -1);
698
699 // We need to deal with the case where our parent hasn't told us how
700 // big we should be. In this case we try to use the desired size of the first
701 // child added.
702 if (heightSpecMode == MeasureSpec.UNSPECIFIED) {
703 heightSpecSize = haveChildRefSize ? mReferenceChildHeight + mPaddingTop +
704 mPaddingBottom : 0;
705 } else if (heightSpecMode == MeasureSpec.AT_MOST) {
Dianne Hackborn189ee182010-12-02 21:48:53 -0800706 if (haveChildRefSize) {
707 int height = mReferenceChildHeight + mPaddingTop + mPaddingBottom;
708 if (height > heightSpecSize) {
709 heightSpecSize |= MEASURED_STATE_TOO_SMALL;
710 } else {
711 heightSpecSize = height;
712 }
713 }
Adam Cohen839f4a52010-08-26 17:36:48 -0700714 }
715
716 if (widthSpecMode == MeasureSpec.UNSPECIFIED) {
717 widthSpecSize = haveChildRefSize ? mReferenceChildWidth + mPaddingLeft +
718 mPaddingRight : 0;
719 } else if (heightSpecMode == MeasureSpec.AT_MOST) {
Dianne Hackborn189ee182010-12-02 21:48:53 -0800720 if (haveChildRefSize) {
721 int width = mReferenceChildWidth + mPaddingLeft + mPaddingRight;
722 if (width > widthSpecSize) {
723 widthSpecSize |= MEASURED_STATE_TOO_SMALL;
724 } else {
725 widthSpecSize = width;
726 }
727 }
Adam Cohen839f4a52010-08-26 17:36:48 -0700728 }
729
730 setMeasuredDimension(widthSpecSize, heightSpecSize);
731 measureChildren();
732 }
733
Adam Cohenef17dd42011-01-20 17:20:57 -0800734 void checkForAndHandleDataChanged() {
Adam Cohen3db40672010-07-19 22:41:57 -0700735 boolean dataChanged = mDataChanged;
736 if (dataChanged) {
Adam Cohenef17dd42011-01-20 17:20:57 -0800737 post(new Runnable() {
738 public void run() {
739 handleDataChanged();
740 // if the data changes, mWhichChild might be out of the bounds of the adapter
741 // in this case, we reset mWhichChild to the beginning
742 if (mWhichChild >= getWindowSize()) {
743 mWhichChild = 0;
Adam Cohen3db40672010-07-19 22:41:57 -0700744
Adam Cohen78db1aa2011-01-25 12:24:23 -0800745 showOnly(mWhichChild, false);
Adam Cohene86ff4d2011-01-21 17:46:11 -0800746 } else if (mOldItemCount != getCount()) {
Adam Cohen78db1aa2011-01-25 12:24:23 -0800747 showOnly(mWhichChild, false);
Adam Cohenef17dd42011-01-20 17:20:57 -0800748 }
749 refreshChildren();
750 requestLayout();
751 }
752 });
Adam Cohen3db40672010-07-19 22:41:57 -0700753 }
Adam Cohenef17dd42011-01-20 17:20:57 -0800754 mDataChanged = false;
755 }
756
757 @Override
758 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
759 checkForAndHandleDataChanged();
Adam Cohen3db40672010-07-19 22:41:57 -0700760
761 final int childCount = getChildCount();
762 for (int i = 0; i < childCount; i++) {
763 final View child = getChildAt(i);
764
765 int childRight = mPaddingLeft + child.getMeasuredWidth();
766 int childBottom = mPaddingTop + child.getMeasuredHeight();
767
Adam Cohen839f4a52010-08-26 17:36:48 -0700768 child.layout(mPaddingLeft, mPaddingTop, childRight, childBottom);
Adam Cohen3db40672010-07-19 22:41:57 -0700769 }
Adam Cohen3db40672010-07-19 22:41:57 -0700770 }
771
Adam Cohenb04f7ad2010-08-15 13:22:42 -0700772 static class SavedState extends BaseSavedState {
773 int whichChild;
774
775 /**
776 * Constructor called from {@link AdapterViewAnimator#onSaveInstanceState()}
777 */
778 SavedState(Parcelable superState, int whichChild) {
779 super(superState);
780 this.whichChild = whichChild;
781 }
782
783 /**
784 * Constructor called from {@link #CREATOR}
785 */
786 private SavedState(Parcel in) {
787 super(in);
Winson Chung3ec9a452010-09-23 16:40:28 -0700788 this.whichChild = in.readInt();
Adam Cohenb04f7ad2010-08-15 13:22:42 -0700789 }
790
791 @Override
792 public void writeToParcel(Parcel out, int flags) {
793 super.writeToParcel(out, flags);
Winson Chung3ec9a452010-09-23 16:40:28 -0700794 out.writeInt(this.whichChild);
Adam Cohenb04f7ad2010-08-15 13:22:42 -0700795 }
796
797 @Override
798 public String toString() {
Winson Chung3ec9a452010-09-23 16:40:28 -0700799 return "AdapterViewAnimator.SavedState{ whichChild = " + this.whichChild + " }";
Adam Cohenb04f7ad2010-08-15 13:22:42 -0700800 }
801
802 public static final Parcelable.Creator<SavedState> CREATOR
803 = new Parcelable.Creator<SavedState>() {
804 public SavedState createFromParcel(Parcel in) {
805 return new SavedState(in);
806 }
807
808 public SavedState[] newArray(int size) {
809 return new SavedState[size];
810 }
811 };
812 }
813
814 @Override
815 public Parcelable onSaveInstanceState() {
816 Parcelable superState = super.onSaveInstanceState();
Adam Cohen335c3b62012-07-24 17:18:16 -0700817 if (mRemoteViewsAdapter != null) {
818 mRemoteViewsAdapter.saveRemoteViewsCache();
819 }
Adam Cohenb04f7ad2010-08-15 13:22:42 -0700820 return new SavedState(superState, mWhichChild);
821 }
822
823 @Override
824 public void onRestoreInstanceState(Parcelable state) {
825 SavedState ss = (SavedState) state;
826 super.onRestoreInstanceState(ss.getSuperState());
827
828 // Here we set mWhichChild in addition to setDisplayedChild
829 // We do the former in case mAdapter is null, and hence setDisplayedChild won't
830 // set mWhichChild
831 mWhichChild = ss.whichChild;
Adam Cohen69d66e02011-01-12 14:39:13 -0800832
Winson Chung16c8d8a2011-01-20 16:19:33 -0800833 // When using RemoteAdapters, the async connection process can lead to
834 // onRestoreInstanceState to be called before setAdapter(), so we need to save the previous
835 // values to restore the list position after we connect, and can skip setting the displayed
836 // child until then.
837 if (mRemoteViewsAdapter != null && mAdapter == null) {
838 mRestoreWhichChild = mWhichChild;
839 } else {
Adam Cohen53838d22011-01-26 21:32:33 -0800840 setDisplayedChild(mWhichChild, false);
Winson Chung16c8d8a2011-01-20 16:19:33 -0800841 }
Adam Cohenb04f7ad2010-08-15 13:22:42 -0700842 }
843
Adam Cohen3db40672010-07-19 22:41:57 -0700844 /**
Adam Cohen3db40672010-07-19 22:41:57 -0700845 * Returns the View corresponding to the currently displayed child.
846 *
847 * @return The View currently displayed.
848 *
849 * @see #getDisplayedChild()
850 */
851 public View getCurrentView() {
Adam Cohen44729e32010-07-22 16:00:07 -0700852 return getViewAtRelativeIndex(mActiveOffset);
Adam Cohen3db40672010-07-19 22:41:57 -0700853 }
854
855 /**
856 * Returns the current animation used to animate a View that enters the screen.
857 *
858 * @return An Animation or null if none is set.
859 *
Chet Haase2794eb32010-10-12 16:29:28 -0700860 * @see #setInAnimation(android.animation.ObjectAnimator)
Adam Cohen3db40672010-07-19 22:41:57 -0700861 * @see #setInAnimation(android.content.Context, int)
862 */
Chet Haase2794eb32010-10-12 16:29:28 -0700863 public ObjectAnimator getInAnimation() {
Adam Cohen3db40672010-07-19 22:41:57 -0700864 return mInAnimation;
865 }
866
867 /**
868 * Specifies the animation used to animate a View that enters the screen.
869 *
870 * @param inAnimation The animation started when a View enters the screen.
871 *
872 * @see #getInAnimation()
873 * @see #setInAnimation(android.content.Context, int)
874 */
Chet Haase2794eb32010-10-12 16:29:28 -0700875 public void setInAnimation(ObjectAnimator inAnimation) {
Adam Cohen3db40672010-07-19 22:41:57 -0700876 mInAnimation = inAnimation;
877 }
878
879 /**
880 * Returns the current animation used to animate a View that exits the screen.
881 *
882 * @return An Animation or null if none is set.
883 *
Chet Haase2794eb32010-10-12 16:29:28 -0700884 * @see #setOutAnimation(android.animation.ObjectAnimator)
Adam Cohen3db40672010-07-19 22:41:57 -0700885 * @see #setOutAnimation(android.content.Context, int)
886 */
Chet Haase2794eb32010-10-12 16:29:28 -0700887 public ObjectAnimator getOutAnimation() {
Adam Cohen3db40672010-07-19 22:41:57 -0700888 return mOutAnimation;
889 }
890
891 /**
892 * Specifies the animation used to animate a View that exit the screen.
893 *
894 * @param outAnimation The animation started when a View exit the screen.
895 *
896 * @see #getOutAnimation()
897 * @see #setOutAnimation(android.content.Context, int)
898 */
Chet Haase2794eb32010-10-12 16:29:28 -0700899 public void setOutAnimation(ObjectAnimator outAnimation) {
Adam Cohen3db40672010-07-19 22:41:57 -0700900 mOutAnimation = outAnimation;
901 }
902
903 /**
904 * Specifies the animation used to animate a View that enters the screen.
905 *
906 * @param context The application's environment.
907 * @param resourceID The resource id of the animation.
908 *
909 * @see #getInAnimation()
Chet Haase2794eb32010-10-12 16:29:28 -0700910 * @see #setInAnimation(android.animation.ObjectAnimator)
Adam Cohen3db40672010-07-19 22:41:57 -0700911 */
912 public void setInAnimation(Context context, int resourceID) {
Chet Haase2794eb32010-10-12 16:29:28 -0700913 setInAnimation((ObjectAnimator) AnimatorInflater.loadAnimator(context, resourceID));
Adam Cohen3db40672010-07-19 22:41:57 -0700914 }
915
916 /**
917 * Specifies the animation used to animate a View that exit the screen.
918 *
919 * @param context The application's environment.
920 * @param resourceID The resource id of the animation.
921 *
922 * @see #getOutAnimation()
Chet Haase2794eb32010-10-12 16:29:28 -0700923 * @see #setOutAnimation(android.animation.ObjectAnimator)
Adam Cohen3db40672010-07-19 22:41:57 -0700924 */
925 public void setOutAnimation(Context context, int resourceID) {
Chet Haase2794eb32010-10-12 16:29:28 -0700926 setOutAnimation((ObjectAnimator) AnimatorInflater.loadAnimator(context, resourceID));
Adam Cohen3db40672010-07-19 22:41:57 -0700927 }
928
929 /**
930 * Indicates whether the current View should be animated the first time
931 * the ViewAnimation is displayed.
932 *
933 * @param animate True to animate the current View the first time it is displayed,
934 * false otherwise.
935 */
936 public void setAnimateFirstView(boolean animate) {
937 mAnimateFirstTime = animate;
938 }
939
940 @Override
941 public int getBaseline() {
942 return (getCurrentView() != null) ? getCurrentView().getBaseline() : super.getBaseline();
943 }
944
945 @Override
946 public Adapter getAdapter() {
947 return mAdapter;
948 }
949
950 @Override
951 public void setAdapter(Adapter adapter) {
Adam Cohen83228342010-08-10 16:47:30 -0700952 if (mAdapter != null && mDataSetObserver != null) {
953 mAdapter.unregisterDataSetObserver(mDataSetObserver);
954 }
955
Adam Cohen3db40672010-07-19 22:41:57 -0700956 mAdapter = adapter;
Adam Cohen1480fdd2010-08-25 17:24:53 -0700957 checkFocus();
Adam Cohen3db40672010-07-19 22:41:57 -0700958
959 if (mAdapter != null) {
Adam Cohen3db40672010-07-19 22:41:57 -0700960 mDataSetObserver = new AdapterDataSetObserver();
961 mAdapter.registerDataSetObserver(mDataSetObserver);
Adam Cohenef17dd42011-01-20 17:20:57 -0800962 mItemCount = mAdapter.getCount();
Adam Cohen3db40672010-07-19 22:41:57 -0700963 }
Adam Cohen44729e32010-07-22 16:00:07 -0700964 setFocusable(true);
Adam Cohen78db1aa2011-01-25 12:24:23 -0800965 mWhichChild = 0;
966 showOnly(mWhichChild, false);
Adam Cohen3db40672010-07-19 22:41:57 -0700967 }
968
969 /**
Adam Cohen44729e32010-07-22 16:00:07 -0700970 * Sets up this AdapterViewAnimator to use a remote views adapter which connects to a
971 * RemoteViewsService through the specified intent.
972 *
973 * @param intent the intent used to identify the RemoteViewsService for the adapter to
974 * connect to.
Adam Cohen3db40672010-07-19 22:41:57 -0700975 */
976 @android.view.RemotableViewMethod
977 public void setRemoteViewsAdapter(Intent intent) {
Winson Chung9b3a2cf2010-09-16 14:45:32 -0700978 // Ensure that we don't already have a RemoteViewsAdapter that is bound to an existing
979 // service handling the specified intent.
Winson Chung3ec9a452010-09-23 16:40:28 -0700980 if (mRemoteViewsAdapter != null) {
981 Intent.FilterComparison fcNew = new Intent.FilterComparison(intent);
982 Intent.FilterComparison fcOld = new Intent.FilterComparison(
983 mRemoteViewsAdapter.getRemoteViewsServiceIntent());
984 if (fcNew.equals(fcOld)) {
985 return;
986 }
Winson Chung9b3a2cf2010-09-16 14:45:32 -0700987 }
Adam Cohen2148d432011-07-28 14:59:54 -0700988 mDeferNotifyDataSetChanged = false;
Winson Chung9b3a2cf2010-09-16 14:45:32 -0700989 // Otherwise, create a new RemoteViewsAdapter for binding
Adam Cohen3db40672010-07-19 22:41:57 -0700990 mRemoteViewsAdapter = new RemoteViewsAdapter(getContext(), intent, this);
Adam Cohen335c3b62012-07-24 17:18:16 -0700991 if (mRemoteViewsAdapter.isDataReady()) {
992 setAdapter(mRemoteViewsAdapter);
993 }
Adam Cohen3db40672010-07-19 22:41:57 -0700994 }
995
Adam Cohena6a4cbc2012-09-26 17:36:40 -0700996 /**
997 * Sets up the onClickHandler to be used by the RemoteViewsAdapter when inflating RemoteViews
998 *
999 * @param handler The OnClickHandler to use when inflating RemoteViews.
1000 *
1001 * @hide
1002 */
1003 public void setRemoteViewsOnClickHandler(OnClickHandler handler) {
1004 // Ensure that we don't already have a RemoteViewsAdapter that is bound to an existing
1005 // service handling the specified intent.
1006 if (mRemoteViewsAdapter != null) {
1007 mRemoteViewsAdapter.setRemoteViewsOnClickHandler(handler);
1008 }
1009 }
1010
Adam Cohen3db40672010-07-19 22:41:57 -07001011 @Override
1012 public void setSelection(int position) {
1013 setDisplayedChild(position);
1014 }
1015
1016 @Override
1017 public View getSelectedView() {
Adam Cohen44729e32010-07-22 16:00:07 -07001018 return getViewAtRelativeIndex(mActiveOffset);
Adam Cohen3db40672010-07-19 22:41:57 -07001019 }
1020
1021 /**
Adam Cohen2148d432011-07-28 14:59:54 -07001022 * This defers a notifyDataSetChanged on the pending RemoteViewsAdapter if it has not
1023 * connected yet.
1024 */
1025 public void deferNotifyDataSetChanged() {
1026 mDeferNotifyDataSetChanged = true;
1027 }
1028
1029 /**
Adam Cohen3db40672010-07-19 22:41:57 -07001030 * Called back when the adapter connects to the RemoteViewsService.
1031 */
Winson Chung16c8d8a2011-01-20 16:19:33 -08001032 public boolean onRemoteAdapterConnected() {
Adam Cohen3db40672010-07-19 22:41:57 -07001033 if (mRemoteViewsAdapter != mAdapter) {
1034 setAdapter(mRemoteViewsAdapter);
Winson Chung16c8d8a2011-01-20 16:19:33 -08001035
Adam Cohen2148d432011-07-28 14:59:54 -07001036 if (mDeferNotifyDataSetChanged) {
1037 mRemoteViewsAdapter.notifyDataSetChanged();
1038 mDeferNotifyDataSetChanged = false;
1039 }
1040
Winson Chung16c8d8a2011-01-20 16:19:33 -08001041 // Restore the previous position (see onRestoreInstanceState)
1042 if (mRestoreWhichChild > -1) {
Adam Cohen53838d22011-01-26 21:32:33 -08001043 setDisplayedChild(mRestoreWhichChild, false);
Winson Chung16c8d8a2011-01-20 16:19:33 -08001044 mRestoreWhichChild = -1;
1045 }
1046 return false;
Adam Cohenfb603862010-12-17 12:03:17 -08001047 } else if (mRemoteViewsAdapter != null) {
1048 mRemoteViewsAdapter.superNotifyDataSetChanged();
Winson Chung16c8d8a2011-01-20 16:19:33 -08001049 return true;
Adam Cohen3db40672010-07-19 22:41:57 -07001050 }
Winson Chung16c8d8a2011-01-20 16:19:33 -08001051 return false;
Adam Cohen3db40672010-07-19 22:41:57 -07001052 }
1053
1054 /**
1055 * Called back when the adapter disconnects from the RemoteViewsService.
1056 */
1057 public void onRemoteAdapterDisconnected() {
Adam Cohenfb603862010-12-17 12:03:17 -08001058 // If the remote adapter disconnects, we keep it around
1059 // since the currently displayed items are still cached.
1060 // Further, we want the service to eventually reconnect
1061 // when necessary, as triggered by this view requesting
1062 // items from the Adapter.
Adam Cohen3db40672010-07-19 22:41:57 -07001063 }
Adam Cohena02fdf12010-11-03 13:27:40 -07001064
Adam Cohen0e2de6d2011-01-19 17:16:34 -08001065 /**
1066 * Called by an {@link android.appwidget.AppWidgetHost} in order to advance the current view when
1067 * it is being used within an app widget.
1068 */
Adam Cohena02fdf12010-11-03 13:27:40 -07001069 public void advance() {
1070 showNext();
1071 }
1072
Adam Cohen0e2de6d2011-01-19 17:16:34 -08001073 /**
1074 * Called by an {@link android.appwidget.AppWidgetHost} to indicate that it will be
1075 * automatically advancing the views of this {@link AdapterViewAnimator} by calling
1076 * {@link AdapterViewAnimator#advance()} at some point in the future. This allows subclasses to
1077 * perform any required setup, for example, to stop automatically advancing their children.
1078 */
1079 public void fyiWillBeAdvancedByHostKThx() {
Adam Cohena02fdf12010-11-03 13:27:40 -07001080 }
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -08001081
1082 @Override
1083 public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
1084 super.onInitializeAccessibilityEvent(event);
1085 event.setClassName(AdapterViewAnimator.class.getName());
1086 }
1087
1088 @Override
1089 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
1090 super.onInitializeAccessibilityNodeInfo(info);
1091 info.setClassName(AdapterViewAnimator.class.getName());
1092 }
Adam Cohen3db40672010-07-19 22:41:57 -07001093}