blob: 7d787776886d648e7e3a0cf1f62b49775eccca61 [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;
Adam Cohena32edd42010-10-26 10:35:01 -070027import android.graphics.Rect;
Adam Cohen3db40672010-07-19 22:41:57 -070028import android.os.Handler;
29import android.os.Looper;
Adam Cohenb04f7ad2010-08-15 13:22:42 -070030import android.os.Parcel;
31import android.os.Parcelable;
Adam Cohen3db40672010-07-19 22:41:57 -070032import android.util.AttributeSet;
Adam Cohena32edd42010-10-26 10:35:01 -070033import android.view.MotionEvent;
Adam Cohen3db40672010-07-19 22:41:57 -070034import android.view.View;
Adam Cohena32edd42010-10-26 10:35:01 -070035import android.view.ViewConfiguration;
Adam Cohen44729e32010-07-22 16:00:07 -070036import android.view.ViewGroup;
Adam Cohen3db40672010-07-19 22:41:57 -070037
38/**
39 * Base class for a {@link AdapterView} that will perform animations
40 * when switching between its views.
41 *
42 * @attr ref android.R.styleable#AdapterViewAnimator_inAnimation
43 * @attr ref android.R.styleable#AdapterViewAnimator_outAnimation
44 * @attr ref android.R.styleable#AdapterViewAnimator_animateFirstView
Adam Cohen1b065cd2010-09-28 14:53:47 -070045 * @attr ref android.R.styleable#AdapterViewAnimator_loopViews
Adam Cohen3db40672010-07-19 22:41:57 -070046 */
Adam Cohen44729e32010-07-22 16:00:07 -070047public abstract class AdapterViewAnimator extends AdapterView<Adapter>
Adam Cohena02fdf12010-11-03 13:27:40 -070048 implements RemoteViewsAdapter.RemoteAdapterConnectionCallback, Advanceable {
Adam Cohen3db40672010-07-19 22:41:57 -070049 private static final String TAG = "RemoteViewAnimator";
50
Adam Cohen44729e32010-07-22 16:00:07 -070051 /**
52 * The index of the current child, which appears anywhere from the beginning
53 * to the end of the current set of children, as specified by {@link #mActiveOffset}
54 */
Adam Cohen3db40672010-07-19 22:41:57 -070055 int mWhichChild = 0;
Adam Cohen44729e32010-07-22 16:00:07 -070056
57 /**
58 * Whether or not the first view(s) should be animated in
59 */
Adam Cohen3db40672010-07-19 22:41:57 -070060 boolean mAnimateFirstTime = true;
61
Adam Cohen44729e32010-07-22 16:00:07 -070062 /**
63 * Represents where the in the current window of
64 * views the current <code>mDisplayedChild</code> sits
65 */
66 int mActiveOffset = 0;
67
68 /**
69 * The number of views that the {@link AdapterViewAnimator} keeps as children at any
70 * given time (not counting views that are pending removal, see {@link #mPreviousViews}).
71 */
Adam Cohen96d8d562010-10-24 11:12:18 -070072 int mMaxNumActiveViews = 1;
Adam Cohen44729e32010-07-22 16:00:07 -070073
74 /**
Adam Cohen1b065cd2010-09-28 14:53:47 -070075 * Map of the children of the {@link AdapterViewAnimator}.
Adam Cohen44729e32010-07-22 16:00:07 -070076 */
Adam Cohen96d8d562010-10-24 11:12:18 -070077 HashMap<Integer, ViewAndIndex> mViewsMap = new HashMap<Integer, ViewAndIndex>();
Adam Cohen44729e32010-07-22 16:00:07 -070078
79 /**
80 * List of views pending removal from the {@link AdapterViewAnimator}
81 */
Adam Cohen1b065cd2010-09-28 14:53:47 -070082 ArrayList<Integer> mPreviousViews;
Adam Cohen44729e32010-07-22 16:00:07 -070083
84 /**
85 * The index, relative to the adapter, of the beginning of the window of views
86 */
87 int mCurrentWindowStart = 0;
88
89 /**
90 * The index, relative to the adapter, of the end of the window of views
91 */
92 int mCurrentWindowEnd = -1;
93
94 /**
95 * The same as {@link #mCurrentWindowStart}, except when the we have bounded
96 * {@link #mCurrentWindowStart} to be non-negative
97 */
98 int mCurrentWindowStartUnbounded = 0;
99
100 /**
Adam Cohen44729e32010-07-22 16:00:07 -0700101 * Handler to post events to the main thread
102 */
103 Handler mMainQueue;
104
105 /**
106 * 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) {
161 super(context);
Romain Guy5b53f912010-08-16 18:24:33 -0700162 initViewAnimator();
Adam Cohen3db40672010-07-19 22:41:57 -0700163 }
164
165 public AdapterViewAnimator(Context context, AttributeSet attrs) {
166 super(context, attrs);
167
Adam Cohen44729e32010-07-22 16:00:07 -0700168 TypedArray a = context.obtainStyledAttributes(attrs,
Adam Cohen1b065cd2010-09-28 14:53:47 -0700169 com.android.internal.R.styleable.AdapterViewAnimator);
Adam Cohen44729e32010-07-22 16:00:07 -0700170 int resource = a.getResourceId(
Adam Cohen1b065cd2010-09-28 14:53:47 -0700171 com.android.internal.R.styleable.AdapterViewAnimator_inAnimation, 0);
Adam Cohen3db40672010-07-19 22:41:57 -0700172 if (resource > 0) {
173 setInAnimation(context, resource);
Adam Cohenef521762010-10-04 13:56:11 -0700174 } else {
175 setInAnimation(getDefaultInAnimation());
Adam Cohen3db40672010-07-19 22:41:57 -0700176 }
177
Adam Cohen1b065cd2010-09-28 14:53:47 -0700178 resource = a.getResourceId(com.android.internal.R.styleable.AdapterViewAnimator_outAnimation, 0);
Adam Cohen3db40672010-07-19 22:41:57 -0700179 if (resource > 0) {
180 setOutAnimation(context, resource);
Adam Cohenef521762010-10-04 13:56:11 -0700181 } else {
182 setOutAnimation(getDefaultOutAnimation());
Adam Cohen3db40672010-07-19 22:41:57 -0700183 }
184
Adam Cohen44729e32010-07-22 16:00:07 -0700185 boolean flag = a.getBoolean(
Adam Cohen1b065cd2010-09-28 14:53:47 -0700186 com.android.internal.R.styleable.AdapterViewAnimator_animateFirstView, true);
Adam Cohen3db40672010-07-19 22:41:57 -0700187 setAnimateFirstView(flag);
188
Adam Cohen1b065cd2010-09-28 14:53:47 -0700189 mLoopViews = a.getBoolean(
190 com.android.internal.R.styleable.AdapterViewAnimator_loopViews, false);
191
Adam Cohen3db40672010-07-19 22:41:57 -0700192 a.recycle();
193
Romain Guy5b53f912010-08-16 18:24:33 -0700194 initViewAnimator();
Adam Cohen3db40672010-07-19 22:41:57 -0700195 }
196
197 /**
198 * Initialize this {@link AdapterViewAnimator}
199 */
Romain Guy5b53f912010-08-16 18:24:33 -0700200 private void initViewAnimator() {
Adam Cohen3db40672010-07-19 22:41:57 -0700201 mMainQueue = new Handler(Looper.myLooper());
Adam Cohen1b065cd2010-09-28 14:53:47 -0700202 mPreviousViews = new ArrayList<Integer>();
Adam Cohen44729e32010-07-22 16:00:07 -0700203 }
204
Adam Cohen96d8d562010-10-24 11:12:18 -0700205 class ViewAndIndex {
Adam Cohen1b065cd2010-09-28 14:53:47 -0700206 ViewAndIndex(View v, int i) {
207 view = v;
208 index = i;
209 }
210 View view;
211 int index;
212 }
213
Adam Cohen44729e32010-07-22 16:00:07 -0700214 /**
215 * This method is used by subclasses to configure the animator to display the
216 * desired number of views, and specify the offset
217 *
218 * @param numVisibleViews The number of views the animator keeps in the {@link ViewGroup}
Romain Guy5b53f912010-08-16 18:24:33 -0700219 * @param activeOffset This parameter specifies where the current index ({@link #mWhichChild})
Adam Cohen44729e32010-07-22 16:00:07 -0700220 * sits within the window. For example if activeOffset is 1, and numVisibleViews is 3,
Romain Guy5b53f912010-08-16 18:24:33 -0700221 * and {@link #setDisplayedChild(int)} is called with 10, then the effective window will
222 * be the indexes 9, 10, and 11. In the same example, if activeOffset were 0, then the
Adam Cohen44729e32010-07-22 16:00:07 -0700223 * window would instead contain indexes 10, 11 and 12.
Adam Cohenb04f7ad2010-08-15 13:22:42 -0700224 * @param shouldLoop If the animator is show view 0, and setPrevious() is called, do we
225 * we loop back to the end, or do we do nothing
Adam Cohen44729e32010-07-22 16:00:07 -0700226 */
Adam Cohen1b065cd2010-09-28 14:53:47 -0700227 void configureViewAnimator(int numVisibleViews, int activeOffset) {
Adam Cohen44729e32010-07-22 16:00:07 -0700228 if (activeOffset > numVisibleViews - 1) {
229 // Throw an exception here.
230 }
Adam Cohen96d8d562010-10-24 11:12:18 -0700231 mMaxNumActiveViews = numVisibleViews;
Adam Cohen44729e32010-07-22 16:00:07 -0700232 mActiveOffset = activeOffset;
Adam Cohen44729e32010-07-22 16:00:07 -0700233 mPreviousViews.clear();
Adam Cohen1b065cd2010-09-28 14:53:47 -0700234 mViewsMap.clear();
Adam Cohen44729e32010-07-22 16:00:07 -0700235 removeAllViewsInLayout();
236 mCurrentWindowStart = 0;
237 mCurrentWindowEnd = -1;
238 }
239
240 /**
241 * This class should be overridden by subclasses to customize view transitions within
242 * the set of visible views
243 *
244 * @param fromIndex The relative index within the window that the view was in, -1 if it wasn't
245 * in the window
246 * @param toIndex The relative index within the window that the view is going to, -1 if it is
247 * being removed
248 * @param view The view that is being animated
249 */
250 void animateViewForTransition(int fromIndex, int toIndex, View view) {
Adam Cohen44729e32010-07-22 16:00:07 -0700251 if (fromIndex == -1) {
Adam Cohenef521762010-10-04 13:56:11 -0700252 mInAnimation.setTarget(view);
253 mInAnimation.start();
Adam Cohen44729e32010-07-22 16:00:07 -0700254 } else if (toIndex == -1) {
Adam Cohenef521762010-10-04 13:56:11 -0700255 mOutAnimation.setTarget(view);
256 mOutAnimation.start();
Adam Cohen44729e32010-07-22 16:00:07 -0700257 }
Adam Cohen3db40672010-07-19 22:41:57 -0700258 }
259
Chet Haase2794eb32010-10-12 16:29:28 -0700260 ObjectAnimator getDefaultInAnimation() {
261 ObjectAnimator anim = ObjectAnimator.ofFloat(null, "alpha", 0.0f, 1.0f);
262 anim.setDuration(DEFAULT_ANIMATION_DURATION);
263 return anim;
Adam Cohenef521762010-10-04 13:56:11 -0700264 }
265
Chet Haase2794eb32010-10-12 16:29:28 -0700266 ObjectAnimator getDefaultOutAnimation() {
267 ObjectAnimator anim = ObjectAnimator.ofFloat(null, "alpha", 1.0f, 0.0f);
268 anim.setDuration(DEFAULT_ANIMATION_DURATION);
269 return anim;
Adam Cohenef521762010-10-04 13:56:11 -0700270 }
271
Adam Cohen3db40672010-07-19 22:41:57 -0700272 /**
273 * Sets which child view will be displayed.
274 *
275 * @param whichChild the index of the child view to display
276 */
277 public void setDisplayedChild(int whichChild) {
278 if (mAdapter != null) {
279 mWhichChild = whichChild;
Adam Cohen96d8d562010-10-24 11:12:18 -0700280 if (whichChild >= getWindowSize()) {
281 mWhichChild = mLoopViews ? 0 : getWindowSize() - 1;
Adam Cohen3db40672010-07-19 22:41:57 -0700282 } else if (whichChild < 0) {
Adam Cohen96d8d562010-10-24 11:12:18 -0700283 mWhichChild = mLoopViews ? getWindowSize() - 1 : 0;
Adam Cohen3db40672010-07-19 22:41:57 -0700284 }
285
286 boolean hasFocus = getFocusedChild() != null;
287 // This will clear old focus if we had it
288 showOnly(mWhichChild);
289 if (hasFocus) {
290 // Try to retake focus if we had it
291 requestFocus(FOCUS_FORWARD);
292 }
293 }
294 }
295
296 /**
Adam Cohen44729e32010-07-22 16:00:07 -0700297 * To be overridden by subclasses. This method applies a view / index specific
298 * transform to the child view.
299 *
300 * @param child
301 * @param relativeIndex
302 */
303 void applyTransformForChildAtIndex(View child, int relativeIndex) {
304 }
305
306 /**
Adam Cohen3db40672010-07-19 22:41:57 -0700307 * Returns the index of the currently displayed child view.
308 */
309 public int getDisplayedChild() {
310 return mWhichChild;
311 }
312
313 /**
314 * Manually shows the next child.
315 */
316 public void showNext() {
317 setDisplayedChild(mWhichChild + 1);
318 }
319
320 /**
321 * Manually shows the previous child.
322 */
323 public void showPrevious() {
324 setDisplayedChild(mWhichChild - 1);
325 }
326
327 /**
328 * Shows only the specified child. The other displays Views exit the screen,
329 * optionally with the with the {@link #getOutAnimation() out animation} and
330 * the specified child enters the screen, optionally with the
331 * {@link #getInAnimation() in animation}.
332 *
333 * @param childIndex The index of the child to be shown.
334 * @param animate Whether or not to use the in and out animations, defaults
335 * to true.
336 */
337 void showOnly(int childIndex, boolean animate) {
338 showOnly(childIndex, animate, false);
339 }
340
Adam Cohen96d8d562010-10-24 11:12:18 -0700341 int modulo(int pos, int size) {
Adam Cohen30429442010-10-06 10:37:51 -0700342 if (size > 0) {
343 return (size + (pos % size)) % size;
344 } else {
345 return 0;
346 }
Adam Cohen3db40672010-07-19 22:41:57 -0700347 }
348
Adam Cohen44729e32010-07-22 16:00:07 -0700349 /**
350 * Get the view at this index relative to the current window's start
351 *
352 * @param relativeIndex Position relative to the current window's start
353 * @return View at this index, null if the index is outside the bounds
354 */
355 View getViewAtRelativeIndex(int relativeIndex) {
Adam Cohen96d8d562010-10-24 11:12:18 -0700356 if (relativeIndex >= 0 && relativeIndex <= getNumActiveViews() - 1 && mAdapter != null) {
357 int i = modulo(mCurrentWindowStartUnbounded + relativeIndex, getWindowSize());
Adam Cohen6f279622010-10-14 10:37:32 -0700358 if (mViewsMap.get(i) != null) {
359 return mViewsMap.get(i).view;
360 }
Adam Cohen44729e32010-07-22 16:00:07 -0700361 }
362 return null;
363 }
Adam Cohen3db40672010-07-19 22:41:57 -0700364
Adam Cohen96d8d562010-10-24 11:12:18 -0700365 int getNumActiveViews() {
366 if (mAdapter != null) {
367 return Math.min(mAdapter.getCount() + 1, mMaxNumActiveViews);
368 } else {
369 return mMaxNumActiveViews;
370 }
371 }
372
373 int getWindowSize() {
374 if (mAdapter != null) {
375 int adapterCount = mAdapter.getCount();
376 if (adapterCount <= getNumActiveViews() && mLoopViews) {
377 return adapterCount*mMaxNumActiveViews;
378 } else {
379 return adapterCount;
380 }
381 } else {
382 return 0;
383 }
384 }
385
Adam Cohen9b073942010-08-19 16:49:52 -0700386 LayoutParams createOrReuseLayoutParams(View v) {
Romain Guy5b53f912010-08-16 18:24:33 -0700387 final ViewGroup.LayoutParams currentLp = v.getLayoutParams();
Adam Cohen9b073942010-08-19 16:49:52 -0700388 if (currentLp instanceof ViewGroup.LayoutParams) {
Adam Cohenb04f7ad2010-08-15 13:22:42 -0700389 LayoutParams lp = (LayoutParams) currentLp;
Adam Cohenb04f7ad2010-08-15 13:22:42 -0700390 return lp;
Adam Cohen44729e32010-07-22 16:00:07 -0700391 }
Adam Cohen9b073942010-08-19 16:49:52 -0700392 return new ViewGroup.LayoutParams(0, 0);
Adam Cohen44729e32010-07-22 16:00:07 -0700393 }
Adam Cohen3db40672010-07-19 22:41:57 -0700394
Winson Chung6364f2b2010-09-29 11:14:30 -0700395 void refreshChildren() {
Adam Cohena9238c82010-10-25 14:01:29 -0700396 if (mAdapter == null) return;
Adam Cohenbd0136a2010-09-08 14:19:39 -0700397 for (int i = mCurrentWindowStart; i <= mCurrentWindowEnd; i++) {
Adam Cohena9238c82010-10-25 14:01:29 -0700398 int index = modulo(i, getWindowSize());
Adam Cohenbd0136a2010-09-08 14:19:39 -0700399
Adam Cohena9238c82010-10-25 14:01:29 -0700400 int adapterCount = mAdapter.getCount();
Adam Cohenbd0136a2010-09-08 14:19:39 -0700401 // get the fresh child from the adapter
Adam Cohena9238c82010-10-25 14:01:29 -0700402 View updatedChild = mAdapter.getView(modulo(i, adapterCount), null, this);
Adam Cohenbd0136a2010-09-08 14:19:39 -0700403
Adam Cohen1b065cd2010-09-28 14:53:47 -0700404 if (mViewsMap.containsKey(index)) {
405 FrameLayout fl = (FrameLayout) mViewsMap.get(index).view;
Adam Cohenbd0136a2010-09-08 14:19:39 -0700406 // flush out the old child
407 fl.removeAllViewsInLayout();
408 // add the new child to the frame, if it exists
409 if (updatedChild != null) {
410 fl.addView(updatedChild);
411 }
412 }
413 }
414 }
415
Adam Cohendfcdddd2010-09-10 14:38:40 -0700416 /**
417 * This method can be overridden so that subclasses can provide a custom frame in which their
418 * children can live. For example, StackView adds padding to its childrens' frames so as to
419 * accomodate for the highlight effect.
420 *
421 * @return The FrameLayout into which children can be placed.
422 */
423 FrameLayout getFrameForChild() {
424 return new FrameLayout(mContext);
425 }
426
Adam Cohen44729e32010-07-22 16:00:07 -0700427 void showOnly(int childIndex, boolean animate, boolean onLayout) {
428 if (mAdapter == null) return;
Adam Cohen30429442010-10-06 10:37:51 -0700429 final int adapterCount = mAdapter.getCount();
430 if (adapterCount == 0) return;
Adam Cohen3db40672010-07-19 22:41:57 -0700431
Adam Cohen44729e32010-07-22 16:00:07 -0700432 for (int i = 0; i < mPreviousViews.size(); i++) {
Adam Cohen1b065cd2010-09-28 14:53:47 -0700433 View viewToRemove = mViewsMap.get(mPreviousViews.get(i)).view;
434 mViewsMap.remove(mPreviousViews.get(i));
Adam Cohen44729e32010-07-22 16:00:07 -0700435 viewToRemove.clearAnimation();
Adam Cohen3d07af02010-08-18 17:46:23 -0700436 if (viewToRemove instanceof ViewGroup) {
437 ViewGroup vg = (ViewGroup) viewToRemove;
438 vg.removeAllViewsInLayout();
439 }
Adam Cohen44729e32010-07-22 16:00:07 -0700440 // applyTransformForChildAtIndex here just allows for any cleanup
441 // associated with this view that may need to be done by a subclass
442 applyTransformForChildAtIndex(viewToRemove, -1);
Adam Cohen3d07af02010-08-18 17:46:23 -0700443
Adam Cohen44729e32010-07-22 16:00:07 -0700444 removeViewInLayout(viewToRemove);
445 }
446 mPreviousViews.clear();
447 int newWindowStartUnbounded = childIndex - mActiveOffset;
Adam Cohen96d8d562010-10-24 11:12:18 -0700448 int newWindowEndUnbounded = newWindowStartUnbounded + getNumActiveViews() - 1;
Adam Cohen44729e32010-07-22 16:00:07 -0700449 int newWindowStart = Math.max(0, newWindowStartUnbounded);
Adam Cohen1b065cd2010-09-28 14:53:47 -0700450 int newWindowEnd = Math.min(adapterCount - 1, newWindowEndUnbounded);
Adam Cohen3db40672010-07-19 22:41:57 -0700451
Adam Cohen1b065cd2010-09-28 14:53:47 -0700452 if (mLoopViews) {
453 newWindowStart = newWindowStartUnbounded;
454 newWindowEnd = newWindowEndUnbounded;
455 }
Adam Cohen96d8d562010-10-24 11:12:18 -0700456 int rangeStart = modulo(newWindowStart, getWindowSize());
457 int rangeEnd = modulo(newWindowEnd, getWindowSize());
Adam Cohen1b065cd2010-09-28 14:53:47 -0700458
459 boolean wrap = false;
460 if (rangeStart > rangeEnd) {
461 wrap = true;
462 }
463
464 // This section clears out any items that are in our active views list
Adam Cohen44729e32010-07-22 16:00:07 -0700465 // but are outside the effective bounds of our window (this is becomes an issue
466 // at the extremities of the list, eg. where newWindowStartUnbounded < 0 or
467 // newWindowEndUnbounded > mAdapter.getCount() - 1
Adam Cohen1b065cd2010-09-28 14:53:47 -0700468 for (Integer index : mViewsMap.keySet()) {
469 boolean remove = false;
470 if (!wrap && (index < rangeStart || index > rangeEnd)) {
471 remove = true;
472 } else if (wrap && (index > rangeEnd && index < rangeStart)) {
473 remove = true;
474 }
475
476 if (remove) {
477 View previousView = mViewsMap.get(index).view;
478 int oldRelativeIndex = mViewsMap.get(index).index;
479
480 mPreviousViews.add(index);
481 animateViewForTransition(oldRelativeIndex, -1, previousView);
Adam Cohen3db40672010-07-19 22:41:57 -0700482 }
Adam Cohen44729e32010-07-22 16:00:07 -0700483 }
Adam Cohen3db40672010-07-19 22:41:57 -0700484
Adam Cohen44729e32010-07-22 16:00:07 -0700485 // If the window has changed
Adam Cohen96d8d562010-10-24 11:12:18 -0700486 if (!(newWindowStart == mCurrentWindowStart && newWindowEnd == mCurrentWindowEnd &&
487 newWindowStartUnbounded == mCurrentWindowStartUnbounded)) {
Adam Cohen44729e32010-07-22 16:00:07 -0700488 // Run through the indices in the new range
489 for (int i = newWindowStart; i <= newWindowEnd; i++) {
Adam Cohen3db40672010-07-19 22:41:57 -0700490
Adam Cohen96d8d562010-10-24 11:12:18 -0700491 int index = modulo(i, getWindowSize());
Adam Cohen1b065cd2010-09-28 14:53:47 -0700492 int oldRelativeIndex;
493 if (mViewsMap.containsKey(index)) {
494 oldRelativeIndex = mViewsMap.get(index).index;
495 } else {
496 oldRelativeIndex = -1;
497 }
Adam Cohen44729e32010-07-22 16:00:07 -0700498 int newRelativeIndex = i - newWindowStartUnbounded;
Adam Cohen44729e32010-07-22 16:00:07 -0700499
500 // If this item is in the current window, great, we just need to apply
501 // the transform for it's new relative position in the window, and animate
502 // between it's current and new relative positions
Adam Cohen1b065cd2010-09-28 14:53:47 -0700503 boolean inOldRange = mViewsMap.containsKey(index) && !mPreviousViews.contains(index);
504
505 if (inOldRange) {
506 View view = mViewsMap.get(index).view;
507 mViewsMap.get(index).index = newRelativeIndex;
Adam Cohen44729e32010-07-22 16:00:07 -0700508 applyTransformForChildAtIndex(view, newRelativeIndex);
509 animateViewForTransition(oldRelativeIndex, newRelativeIndex, view);
510
Adam Cohen1b065cd2010-09-28 14:53:47 -0700511 // Otherwise this view is new to the window
Adam Cohen44729e32010-07-22 16:00:07 -0700512 } else {
Adam Cohen1b065cd2010-09-28 14:53:47 -0700513 // Get the new view from the adapter, add it and apply any transform / animation
514 View newView = mAdapter.getView(modulo(i, adapterCount), null, this);
Adam Cohenbd0136a2010-09-08 14:19:39 -0700515
516 // We wrap the new view in a FrameLayout so as to respect the contract
517 // with the adapter, that is, that we don't modify this view directly
Adam Cohendfcdddd2010-09-10 14:38:40 -0700518 FrameLayout fl = getFrameForChild();
Adam Cohenbd0136a2010-09-08 14:19:39 -0700519
520 // If the view from the adapter is null, we still keep an empty frame in place
Adam Cohen44729e32010-07-22 16:00:07 -0700521 if (newView != null) {
Adam Cohenbd0136a2010-09-08 14:19:39 -0700522 fl.addView(newView);
Adam Cohen44729e32010-07-22 16:00:07 -0700523 }
Adam Cohen1b065cd2010-09-28 14:53:47 -0700524 mViewsMap.put(index, new ViewAndIndex(fl, newRelativeIndex));
Adam Cohenbd0136a2010-09-08 14:19:39 -0700525 addChild(fl);
526 applyTransformForChildAtIndex(fl, newRelativeIndex);
527 animateViewForTransition(-1, newRelativeIndex, fl);
Adam Cohen44729e32010-07-22 16:00:07 -0700528 }
Adam Cohen1b065cd2010-09-28 14:53:47 -0700529 mViewsMap.get(index).view.bringToFront();
Adam Cohen3db40672010-07-19 22:41:57 -0700530 }
Adam Cohen44729e32010-07-22 16:00:07 -0700531 mCurrentWindowStart = newWindowStart;
532 mCurrentWindowEnd = newWindowEnd;
533 mCurrentWindowStartUnbounded = newWindowStartUnbounded;
534 }
535
536 mFirstTime = false;
537 if (!onLayout) {
538 requestLayout();
539 invalidate();
540 } else {
541 // If the Adapter tries to layout the current view when we get it using getView
542 // above the layout will end up being ignored since we are currently laying out, so
543 // we post a delayed requestLayout and invalidate
544 mMainQueue.post(new Runnable() {
Adam Cohen44729e32010-07-22 16:00:07 -0700545 public void run() {
546 requestLayout();
547 invalidate();
548 }
549 });
Adam Cohen3db40672010-07-19 22:41:57 -0700550 }
551 }
552
Adam Cohen839f4a52010-08-26 17:36:48 -0700553 private void addChild(View child) {
554 addViewInLayout(child, -1, createOrReuseLayoutParams(child));
555
556 // This code is used to obtain a reference width and height of a child in case we need
557 // to decide our own size. TODO: Do we want to update the size of the child that we're
558 // using for reference size? If so, when?
559 if (mReferenceChildWidth == -1 || mReferenceChildHeight == -1) {
560 int measureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
561 child.measure(measureSpec, measureSpec);
562 mReferenceChildWidth = child.getMeasuredWidth();
563 mReferenceChildHeight = child.getMeasuredHeight();
564 }
565 }
566
Adam Cohena32edd42010-10-26 10:35:01 -0700567 void showTapFeedback(View v) {
568 v.setPressed(true);
569 }
570
571 void hideTapFeedback(View v) {
572 v.setPressed(false);
573 }
574
575 void cancelHandleClick() {
576 View v = getCurrentView();
577 if (v != null) {
578 hideTapFeedback(v);
579 }
580 mTouchMode = TOUCH_MODE_NONE;
581 }
582
583 final class CheckForTap implements Runnable {
584 public void run() {
585 if (mTouchMode == TOUCH_MODE_DOWN_IN_CURRENT_VIEW) {
586 View v = getCurrentView();
587 showTapFeedback(v);
588 }
589 }
590 }
591
592 @Override
593 public boolean onTouchEvent(MotionEvent ev) {
594 int action = ev.getAction();
595 boolean handled = false;
596 switch (action) {
597 case MotionEvent.ACTION_DOWN: {
598 View v = getCurrentView();
599 if (v != null) {
600 if (isTransformedTouchPointInView(ev.getX(), ev.getY(), v, null)) {
601 if (mPendingCheckForTap == null) {
602 mPendingCheckForTap = new CheckForTap();
603 }
604 mTouchMode = TOUCH_MODE_DOWN_IN_CURRENT_VIEW;
605 postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
606 }
607 }
608 break;
609 }
610 case MotionEvent.ACTION_MOVE: break;
611 case MotionEvent.ACTION_POINTER_UP: break;
612 case MotionEvent.ACTION_UP: {
613 if (mTouchMode == TOUCH_MODE_DOWN_IN_CURRENT_VIEW) {
614 final View v = getCurrentView();
615 if (v != null) {
616 if (isTransformedTouchPointInView(ev.getX(), ev.getY(), v, null)) {
617 final Handler handler = getHandler();
618 if (handler != null) {
619 handler.removeCallbacks(mPendingCheckForTap);
620 }
621 showTapFeedback(v);
622 postDelayed(new Runnable() {
623 public void run() {
624 hideTapFeedback(v);
625 post(new Runnable() {
626 public void run() {
627 performItemClick(v, 0, 0);
628 }
629 });
630 }
631 }, ViewConfiguration.getPressedStateDuration());
632 handled = true;
633 }
634 }
635 }
636 mTouchMode = TOUCH_MODE_NONE;
637 break;
638 }
639 case MotionEvent.ACTION_CANCEL: {
640 View v = getCurrentView();
641 if (v != null) {
642 hideTapFeedback(v);
643 }
644 mTouchMode = TOUCH_MODE_NONE;
645 }
646 }
647 return handled;
648 }
649
Adam Cohen839f4a52010-08-26 17:36:48 -0700650 private void measureChildren() {
651 final int count = getChildCount();
652 final int childWidth = mMeasuredWidth - mPaddingLeft - mPaddingRight;
653 final int childHeight = mMeasuredHeight - mPaddingTop - mPaddingBottom;
654
655 for (int i = 0; i < count; i++) {
656 final View child = getChildAt(i);
657 child.measure(MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.EXACTLY),
658 MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY));
659 }
660 }
661
662 @Override
663 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
664 int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
665 int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
666 final int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
667 final int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
668
669 boolean haveChildRefSize = (mReferenceChildWidth != -1 && mReferenceChildHeight != -1);
670
671 // We need to deal with the case where our parent hasn't told us how
672 // big we should be. In this case we try to use the desired size of the first
673 // child added.
674 if (heightSpecMode == MeasureSpec.UNSPECIFIED) {
675 heightSpecSize = haveChildRefSize ? mReferenceChildHeight + mPaddingTop +
676 mPaddingBottom : 0;
677 } else if (heightSpecMode == MeasureSpec.AT_MOST) {
678 heightSpecSize = haveChildRefSize ? Math.min(mReferenceChildHeight + mPaddingTop +
679 mPaddingBottom, heightSpecSize) : 0;
680 }
681
682 if (widthSpecMode == MeasureSpec.UNSPECIFIED) {
683 widthSpecSize = haveChildRefSize ? mReferenceChildWidth + mPaddingLeft +
684 mPaddingRight : 0;
685 } else if (heightSpecMode == MeasureSpec.AT_MOST) {
686 widthSpecSize = haveChildRefSize ? Math.min(mReferenceChildWidth + mPaddingLeft +
687 mPaddingRight, widthSpecSize) : 0;
688 }
689
690 setMeasuredDimension(widthSpecSize, heightSpecSize);
691 measureChildren();
692 }
693
Adam Cohen3db40672010-07-19 22:41:57 -0700694 @Override
695 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
696 boolean dataChanged = mDataChanged;
697 if (dataChanged) {
698 handleDataChanged();
699
700 // if the data changes, mWhichChild might be out of the bounds of the adapter
701 // in this case, we reset mWhichChild to the beginning
Adam Cohenbd0136a2010-09-08 14:19:39 -0700702 if (mWhichChild >= mAdapter.getCount()) {
Adam Cohen3db40672010-07-19 22:41:57 -0700703 mWhichChild = 0;
704
Adam Cohenbd0136a2010-09-08 14:19:39 -0700705 showOnly(mWhichChild, true, true);
706 }
707 refreshChildren();
Adam Cohen3db40672010-07-19 22:41:57 -0700708 }
709
710 final int childCount = getChildCount();
711 for (int i = 0; i < childCount; i++) {
712 final View child = getChildAt(i);
713
714 int childRight = mPaddingLeft + child.getMeasuredWidth();
715 int childBottom = mPaddingTop + child.getMeasuredHeight();
716
Adam Cohen839f4a52010-08-26 17:36:48 -0700717 child.layout(mPaddingLeft, mPaddingTop, childRight, childBottom);
Adam Cohen3db40672010-07-19 22:41:57 -0700718 }
Adam Cohen3db40672010-07-19 22:41:57 -0700719 mDataChanged = false;
720 }
721
Adam Cohenb04f7ad2010-08-15 13:22:42 -0700722 static class SavedState extends BaseSavedState {
723 int whichChild;
724
725 /**
726 * Constructor called from {@link AdapterViewAnimator#onSaveInstanceState()}
727 */
728 SavedState(Parcelable superState, int whichChild) {
729 super(superState);
730 this.whichChild = whichChild;
731 }
732
733 /**
734 * Constructor called from {@link #CREATOR}
735 */
736 private SavedState(Parcel in) {
737 super(in);
Winson Chung3ec9a452010-09-23 16:40:28 -0700738 this.whichChild = in.readInt();
Adam Cohenb04f7ad2010-08-15 13:22:42 -0700739 }
740
741 @Override
742 public void writeToParcel(Parcel out, int flags) {
743 super.writeToParcel(out, flags);
Winson Chung3ec9a452010-09-23 16:40:28 -0700744 out.writeInt(this.whichChild);
Adam Cohenb04f7ad2010-08-15 13:22:42 -0700745 }
746
747 @Override
748 public String toString() {
Winson Chung3ec9a452010-09-23 16:40:28 -0700749 return "AdapterViewAnimator.SavedState{ whichChild = " + this.whichChild + " }";
Adam Cohenb04f7ad2010-08-15 13:22:42 -0700750 }
751
752 public static final Parcelable.Creator<SavedState> CREATOR
753 = new Parcelable.Creator<SavedState>() {
754 public SavedState createFromParcel(Parcel in) {
755 return new SavedState(in);
756 }
757
758 public SavedState[] newArray(int size) {
759 return new SavedState[size];
760 }
761 };
762 }
763
764 @Override
765 public Parcelable onSaveInstanceState() {
766 Parcelable superState = super.onSaveInstanceState();
767 return new SavedState(superState, mWhichChild);
768 }
769
770 @Override
771 public void onRestoreInstanceState(Parcelable state) {
772 SavedState ss = (SavedState) state;
773 super.onRestoreInstanceState(ss.getSuperState());
774
775 // Here we set mWhichChild in addition to setDisplayedChild
776 // We do the former in case mAdapter is null, and hence setDisplayedChild won't
777 // set mWhichChild
778 mWhichChild = ss.whichChild;
779 setDisplayedChild(mWhichChild);
780 }
781
Adam Cohen3db40672010-07-19 22:41:57 -0700782 /**
783 * Shows only the specified child. The other displays Views exit the screen
784 * with the {@link #getOutAnimation() out animation} and the specified child
785 * enters the screen with the {@link #getInAnimation() in animation}.
786 *
787 * @param childIndex The index of the child to be shown.
788 */
789 void showOnly(int childIndex) {
790 final boolean animate = (!mFirstTime || mAnimateFirstTime);
791 showOnly(childIndex, animate);
792 }
793
794 /**
795 * Returns the View corresponding to the currently displayed child.
796 *
797 * @return The View currently displayed.
798 *
799 * @see #getDisplayedChild()
800 */
801 public View getCurrentView() {
Adam Cohen44729e32010-07-22 16:00:07 -0700802 return getViewAtRelativeIndex(mActiveOffset);
Adam Cohen3db40672010-07-19 22:41:57 -0700803 }
804
805 /**
806 * Returns the current animation used to animate a View that enters the screen.
807 *
808 * @return An Animation or null if none is set.
809 *
Chet Haase2794eb32010-10-12 16:29:28 -0700810 * @see #setInAnimation(android.animation.ObjectAnimator)
Adam Cohen3db40672010-07-19 22:41:57 -0700811 * @see #setInAnimation(android.content.Context, int)
812 */
Chet Haase2794eb32010-10-12 16:29:28 -0700813 public ObjectAnimator getInAnimation() {
Adam Cohen3db40672010-07-19 22:41:57 -0700814 return mInAnimation;
815 }
816
817 /**
818 * Specifies the animation used to animate a View that enters the screen.
819 *
820 * @param inAnimation The animation started when a View enters the screen.
821 *
822 * @see #getInAnimation()
823 * @see #setInAnimation(android.content.Context, int)
824 */
Chet Haase2794eb32010-10-12 16:29:28 -0700825 public void setInAnimation(ObjectAnimator inAnimation) {
Adam Cohen3db40672010-07-19 22:41:57 -0700826 mInAnimation = inAnimation;
827 }
828
829 /**
830 * Returns the current animation used to animate a View that exits the screen.
831 *
832 * @return An Animation or null if none is set.
833 *
Chet Haase2794eb32010-10-12 16:29:28 -0700834 * @see #setOutAnimation(android.animation.ObjectAnimator)
Adam Cohen3db40672010-07-19 22:41:57 -0700835 * @see #setOutAnimation(android.content.Context, int)
836 */
Chet Haase2794eb32010-10-12 16:29:28 -0700837 public ObjectAnimator getOutAnimation() {
Adam Cohen3db40672010-07-19 22:41:57 -0700838 return mOutAnimation;
839 }
840
841 /**
842 * Specifies the animation used to animate a View that exit the screen.
843 *
844 * @param outAnimation The animation started when a View exit the screen.
845 *
846 * @see #getOutAnimation()
847 * @see #setOutAnimation(android.content.Context, int)
848 */
Chet Haase2794eb32010-10-12 16:29:28 -0700849 public void setOutAnimation(ObjectAnimator outAnimation) {
Adam Cohen3db40672010-07-19 22:41:57 -0700850 mOutAnimation = outAnimation;
851 }
852
853 /**
854 * Specifies the animation used to animate a View that enters the screen.
855 *
856 * @param context The application's environment.
857 * @param resourceID The resource id of the animation.
858 *
859 * @see #getInAnimation()
Chet Haase2794eb32010-10-12 16:29:28 -0700860 * @see #setInAnimation(android.animation.ObjectAnimator)
Adam Cohen3db40672010-07-19 22:41:57 -0700861 */
862 public void setInAnimation(Context context, int resourceID) {
Chet Haase2794eb32010-10-12 16:29:28 -0700863 setInAnimation((ObjectAnimator) AnimatorInflater.loadAnimator(context, resourceID));
Adam Cohen3db40672010-07-19 22:41:57 -0700864 }
865
866 /**
867 * Specifies the animation used to animate a View that exit the screen.
868 *
869 * @param context The application's environment.
870 * @param resourceID The resource id of the animation.
871 *
872 * @see #getOutAnimation()
Chet Haase2794eb32010-10-12 16:29:28 -0700873 * @see #setOutAnimation(android.animation.ObjectAnimator)
Adam Cohen3db40672010-07-19 22:41:57 -0700874 */
875 public void setOutAnimation(Context context, int resourceID) {
Chet Haase2794eb32010-10-12 16:29:28 -0700876 setOutAnimation((ObjectAnimator) AnimatorInflater.loadAnimator(context, resourceID));
Adam Cohen3db40672010-07-19 22:41:57 -0700877 }
878
879 /**
880 * Indicates whether the current View should be animated the first time
881 * the ViewAnimation is displayed.
882 *
883 * @param animate True to animate the current View the first time it is displayed,
884 * false otherwise.
885 */
886 public void setAnimateFirstView(boolean animate) {
887 mAnimateFirstTime = animate;
888 }
889
890 @Override
891 public int getBaseline() {
892 return (getCurrentView() != null) ? getCurrentView().getBaseline() : super.getBaseline();
893 }
894
895 @Override
896 public Adapter getAdapter() {
897 return mAdapter;
898 }
899
900 @Override
901 public void setAdapter(Adapter adapter) {
Adam Cohen83228342010-08-10 16:47:30 -0700902 if (mAdapter != null && mDataSetObserver != null) {
903 mAdapter.unregisterDataSetObserver(mDataSetObserver);
904 }
905
Adam Cohen3db40672010-07-19 22:41:57 -0700906 mAdapter = adapter;
Adam Cohen1480fdd2010-08-25 17:24:53 -0700907 checkFocus();
Adam Cohen3db40672010-07-19 22:41:57 -0700908
909 if (mAdapter != null) {
Adam Cohen3db40672010-07-19 22:41:57 -0700910 mDataSetObserver = new AdapterDataSetObserver();
911 mAdapter.registerDataSetObserver(mDataSetObserver);
912 }
Adam Cohen44729e32010-07-22 16:00:07 -0700913 setFocusable(true);
Adam Cohen3db40672010-07-19 22:41:57 -0700914 }
915
916 /**
Adam Cohen44729e32010-07-22 16:00:07 -0700917 * Sets up this AdapterViewAnimator to use a remote views adapter which connects to a
918 * RemoteViewsService through the specified intent.
919 *
920 * @param intent the intent used to identify the RemoteViewsService for the adapter to
921 * connect to.
Adam Cohen3db40672010-07-19 22:41:57 -0700922 */
923 @android.view.RemotableViewMethod
924 public void setRemoteViewsAdapter(Intent intent) {
Winson Chung9b3a2cf2010-09-16 14:45:32 -0700925 // Ensure that we don't already have a RemoteViewsAdapter that is bound to an existing
926 // service handling the specified intent.
Winson Chung3ec9a452010-09-23 16:40:28 -0700927 if (mRemoteViewsAdapter != null) {
928 Intent.FilterComparison fcNew = new Intent.FilterComparison(intent);
929 Intent.FilterComparison fcOld = new Intent.FilterComparison(
930 mRemoteViewsAdapter.getRemoteViewsServiceIntent());
931 if (fcNew.equals(fcOld)) {
932 return;
933 }
Winson Chung9b3a2cf2010-09-16 14:45:32 -0700934 }
935
936 // Otherwise, create a new RemoteViewsAdapter for binding
Adam Cohen3db40672010-07-19 22:41:57 -0700937 mRemoteViewsAdapter = new RemoteViewsAdapter(getContext(), intent, this);
938 }
939
940 @Override
941 public void setSelection(int position) {
942 setDisplayedChild(position);
943 }
944
945 @Override
946 public View getSelectedView() {
Adam Cohen44729e32010-07-22 16:00:07 -0700947 return getViewAtRelativeIndex(mActiveOffset);
Adam Cohen3db40672010-07-19 22:41:57 -0700948 }
949
950 /**
951 * Called back when the adapter connects to the RemoteViewsService.
952 */
953 public void onRemoteAdapterConnected() {
954 if (mRemoteViewsAdapter != mAdapter) {
955 setAdapter(mRemoteViewsAdapter);
956 }
957 }
958
959 /**
960 * Called back when the adapter disconnects from the RemoteViewsService.
961 */
962 public void onRemoteAdapterDisconnected() {
963 if (mRemoteViewsAdapter != mAdapter) {
964 mRemoteViewsAdapter = null;
965 setAdapter(mRemoteViewsAdapter);
966 }
967 }
Adam Cohena02fdf12010-11-03 13:27:40 -0700968
969 public void advance() {
970 showNext();
971 }
972
973 public void willBeAdvancedByHost() {
974 }
Adam Cohen3db40672010-07-19 22:41:57 -0700975}