blob: f11c5c68e48138e83e80da2380de67c8382e07e9 [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 Cohen44729e32010-07-22 16:00:07 -070020
21import android.animation.PropertyAnimator;
Adam Cohen3db40672010-07-19 22:41:57 -070022import android.content.Context;
23import android.content.Intent;
24import android.content.res.TypedArray;
Adam Cohen44729e32010-07-22 16:00:07 -070025import android.graphics.Rect;
Adam Cohen3db40672010-07-19 22:41:57 -070026import android.os.Handler;
27import android.os.Looper;
Adam Cohenb04f7ad2010-08-15 13:22:42 -070028import android.os.Parcel;
29import android.os.Parcelable;
Adam Cohen3db40672010-07-19 22:41:57 -070030import android.util.AttributeSet;
Adam Cohen3db40672010-07-19 22:41:57 -070031import android.view.View;
Adam Cohen44729e32010-07-22 16:00:07 -070032import android.view.ViewGroup;
Adam Cohen3db40672010-07-19 22:41:57 -070033import android.view.animation.Animation;
34import android.view.animation.AnimationUtils;
35
36/**
37 * Base class for a {@link AdapterView} that will perform animations
38 * when switching between its views.
39 *
40 * @attr ref android.R.styleable#AdapterViewAnimator_inAnimation
41 * @attr ref android.R.styleable#AdapterViewAnimator_outAnimation
42 * @attr ref android.R.styleable#AdapterViewAnimator_animateFirstView
43 */
Adam Cohen44729e32010-07-22 16:00:07 -070044public abstract class AdapterViewAnimator extends AdapterView<Adapter>
45 implements RemoteViewsAdapter.RemoteAdapterConnectionCallback{
Adam Cohen3db40672010-07-19 22:41:57 -070046 private static final String TAG = "RemoteViewAnimator";
47
Adam Cohen44729e32010-07-22 16:00:07 -070048 /**
49 * The index of the current child, which appears anywhere from the beginning
50 * to the end of the current set of children, as specified by {@link #mActiveOffset}
51 */
Adam Cohen3db40672010-07-19 22:41:57 -070052 int mWhichChild = 0;
Adam Cohen44729e32010-07-22 16:00:07 -070053
54 /**
55 * Whether or not the first view(s) should be animated in
56 */
Adam Cohen3db40672010-07-19 22:41:57 -070057 boolean mAnimateFirstTime = true;
58
Adam Cohen44729e32010-07-22 16:00:07 -070059 /**
60 * Represents where the in the current window of
61 * views the current <code>mDisplayedChild</code> sits
62 */
63 int mActiveOffset = 0;
64
65 /**
66 * The number of views that the {@link AdapterViewAnimator} keeps as children at any
67 * given time (not counting views that are pending removal, see {@link #mPreviousViews}).
68 */
69 int mNumActiveViews = 1;
70
71 /**
72 * Array of the children of the {@link AdapterViewAnimator}. This array
73 * is accessed in a circular fashion
74 */
75 View[] mActiveViews;
76
77 /**
78 * List of views pending removal from the {@link AdapterViewAnimator}
79 */
80 ArrayList<View> mPreviousViews;
81
82 /**
83 * The index, relative to the adapter, of the beginning of the window of views
84 */
85 int mCurrentWindowStart = 0;
86
87 /**
88 * The index, relative to the adapter, of the end of the window of views
89 */
90 int mCurrentWindowEnd = -1;
91
92 /**
93 * The same as {@link #mCurrentWindowStart}, except when the we have bounded
94 * {@link #mCurrentWindowStart} to be non-negative
95 */
96 int mCurrentWindowStartUnbounded = 0;
97
98 /**
Adam Cohen44729e32010-07-22 16:00:07 -070099 * Handler to post events to the main thread
100 */
101 Handler mMainQueue;
102
103 /**
104 * Listens for data changes from the adapter
105 */
Adam Cohen3db40672010-07-19 22:41:57 -0700106 AdapterDataSetObserver mDataSetObserver;
107
Adam Cohen44729e32010-07-22 16:00:07 -0700108 /**
109 * The {@link Adapter} for this {@link AdapterViewAnimator}
110 */
111 Adapter mAdapter;
Adam Cohen3db40672010-07-19 22:41:57 -0700112
Adam Cohen44729e32010-07-22 16:00:07 -0700113 /**
114 * The {@link RemoteViewsAdapter} for this {@link AdapterViewAnimator}
115 */
116 RemoteViewsAdapter mRemoteViewsAdapter;
117
118 /**
119 * Specifies whether this is the first time the animator is showing views
120 */
121 boolean mFirstTime = true;
122
123 /**
Adam Cohenb04f7ad2010-08-15 13:22:42 -0700124 * Specifies if the animator should wrap from 0 to the end and vice versa
125 * or have hard boundaries at the beginning and end
126 */
127 boolean mShouldLoop = true;
128
129 /**
Adam Cohen44729e32010-07-22 16:00:07 -0700130 * TODO: Animation stuff is still in flux, waiting on the new framework to settle a bit.
131 */
Adam Cohen3db40672010-07-19 22:41:57 -0700132 Animation mInAnimation;
133 Animation mOutAnimation;
Adam Cohen44729e32010-07-22 16:00:07 -0700134 private ArrayList<View> mViewsToBringToFront;
Adam Cohen3db40672010-07-19 22:41:57 -0700135
136 public AdapterViewAnimator(Context context) {
137 super(context);
Romain Guy5b53f912010-08-16 18:24:33 -0700138 initViewAnimator();
Adam Cohen3db40672010-07-19 22:41:57 -0700139 }
140
141 public AdapterViewAnimator(Context context, AttributeSet attrs) {
142 super(context, attrs);
143
Adam Cohen44729e32010-07-22 16:00:07 -0700144 TypedArray a = context.obtainStyledAttributes(attrs,
145 com.android.internal.R.styleable.ViewAnimator);
146 int resource = a.getResourceId(
147 com.android.internal.R.styleable.ViewAnimator_inAnimation, 0);
Adam Cohen3db40672010-07-19 22:41:57 -0700148 if (resource > 0) {
149 setInAnimation(context, resource);
150 }
151
152 resource = a.getResourceId(com.android.internal.R.styleable.ViewAnimator_outAnimation, 0);
153 if (resource > 0) {
154 setOutAnimation(context, resource);
155 }
156
Adam Cohen44729e32010-07-22 16:00:07 -0700157 boolean flag = a.getBoolean(
158 com.android.internal.R.styleable.ViewAnimator_animateFirstView, true);
Adam Cohen3db40672010-07-19 22:41:57 -0700159 setAnimateFirstView(flag);
160
161 a.recycle();
162
Romain Guy5b53f912010-08-16 18:24:33 -0700163 initViewAnimator();
Adam Cohen3db40672010-07-19 22:41:57 -0700164 }
165
166 /**
167 * Initialize this {@link AdapterViewAnimator}
168 */
Romain Guy5b53f912010-08-16 18:24:33 -0700169 private void initViewAnimator() {
Adam Cohen3db40672010-07-19 22:41:57 -0700170 mMainQueue = new Handler(Looper.myLooper());
Adam Cohen44729e32010-07-22 16:00:07 -0700171 mActiveViews = new View[mNumActiveViews];
172 mPreviousViews = new ArrayList<View>();
173 mViewsToBringToFront = new ArrayList<View>();
174 }
175
176 /**
177 * This method is used by subclasses to configure the animator to display the
178 * desired number of views, and specify the offset
179 *
180 * @param numVisibleViews The number of views the animator keeps in the {@link ViewGroup}
Romain Guy5b53f912010-08-16 18:24:33 -0700181 * @param activeOffset This parameter specifies where the current index ({@link #mWhichChild})
Adam Cohen44729e32010-07-22 16:00:07 -0700182 * sits within the window. For example if activeOffset is 1, and numVisibleViews is 3,
Romain Guy5b53f912010-08-16 18:24:33 -0700183 * and {@link #setDisplayedChild(int)} is called with 10, then the effective window will
184 * be the indexes 9, 10, and 11. In the same example, if activeOffset were 0, then the
Adam Cohen44729e32010-07-22 16:00:07 -0700185 * window would instead contain indexes 10, 11 and 12.
Adam Cohenb04f7ad2010-08-15 13:22:42 -0700186 * @param shouldLoop If the animator is show view 0, and setPrevious() is called, do we
187 * we loop back to the end, or do we do nothing
Adam Cohen44729e32010-07-22 16:00:07 -0700188 */
Adam Cohenb04f7ad2010-08-15 13:22:42 -0700189 void configureViewAnimator(int numVisibleViews, int activeOffset, boolean shouldLoop) {
Adam Cohen44729e32010-07-22 16:00:07 -0700190 if (activeOffset > numVisibleViews - 1) {
191 // Throw an exception here.
192 }
193 mNumActiveViews = numVisibleViews;
194 mActiveOffset = activeOffset;
195 mActiveViews = new View[mNumActiveViews];
196 mPreviousViews.clear();
197 removeAllViewsInLayout();
198 mCurrentWindowStart = 0;
199 mCurrentWindowEnd = -1;
Adam Cohenb04f7ad2010-08-15 13:22:42 -0700200 mShouldLoop = shouldLoop;
Adam Cohen44729e32010-07-22 16:00:07 -0700201 }
202
203 /**
204 * This class should be overridden by subclasses to customize view transitions within
205 * the set of visible views
206 *
207 * @param fromIndex The relative index within the window that the view was in, -1 if it wasn't
208 * in the window
209 * @param toIndex The relative index within the window that the view is going to, -1 if it is
210 * being removed
211 * @param view The view that is being animated
212 */
213 void animateViewForTransition(int fromIndex, int toIndex, View view) {
214 PropertyAnimator pa;
215 if (fromIndex == -1) {
Adam Cohenb04f7ad2010-08-15 13:22:42 -0700216 view.setAlpha(0.0f);
Adam Cohen44729e32010-07-22 16:00:07 -0700217 pa = new PropertyAnimator(400, view, "alpha", 0.0f, 1.0f);
218 pa.start();
219 } else if (toIndex == -1) {
220 pa = new PropertyAnimator(400, view, "alpha", 1.0f, 0.0f);
221 pa.start();
222 }
Adam Cohen3db40672010-07-19 22:41:57 -0700223 }
224
225 /**
226 * Sets which child view will be displayed.
227 *
228 * @param whichChild the index of the child view to display
229 */
230 public void setDisplayedChild(int whichChild) {
231 if (mAdapter != null) {
232 mWhichChild = whichChild;
233 if (whichChild >= mAdapter.getCount()) {
Adam Cohenb04f7ad2010-08-15 13:22:42 -0700234 mWhichChild = mShouldLoop ? 0 : mAdapter.getCount() - 1;
Adam Cohen3db40672010-07-19 22:41:57 -0700235 } else if (whichChild < 0) {
Adam Cohenb04f7ad2010-08-15 13:22:42 -0700236 mWhichChild = mShouldLoop ? mAdapter.getCount() - 1 : 0;
Adam Cohen3db40672010-07-19 22:41:57 -0700237 }
238
239 boolean hasFocus = getFocusedChild() != null;
240 // This will clear old focus if we had it
241 showOnly(mWhichChild);
242 if (hasFocus) {
243 // Try to retake focus if we had it
244 requestFocus(FOCUS_FORWARD);
245 }
246 }
247 }
248
249 /**
250 * Return default inAnimation. To be overriden by subclasses.
251 */
Adam Cohen44729e32010-07-22 16:00:07 -0700252 Animation getDefaultInAnimation() {
Adam Cohen3db40672010-07-19 22:41:57 -0700253 return null;
254 }
255
256 /**
Adam Cohen44729e32010-07-22 16:00:07 -0700257 * Return default outAnimation. To be overridden by subclasses.
Adam Cohen3db40672010-07-19 22:41:57 -0700258 */
Adam Cohen44729e32010-07-22 16:00:07 -0700259 Animation getDefaultOutAnimation() {
Adam Cohen3db40672010-07-19 22:41:57 -0700260 return null;
261 }
262
263 /**
Adam Cohen44729e32010-07-22 16:00:07 -0700264 * To be overridden by subclasses. This method applies a view / index specific
265 * transform to the child view.
266 *
267 * @param child
268 * @param relativeIndex
269 */
270 void applyTransformForChildAtIndex(View child, int relativeIndex) {
271 }
272
273 /**
Adam Cohen3db40672010-07-19 22:41:57 -0700274 * Returns the index of the currently displayed child view.
275 */
276 public int getDisplayedChild() {
277 return mWhichChild;
278 }
279
280 /**
281 * Manually shows the next child.
282 */
283 public void showNext() {
284 setDisplayedChild(mWhichChild + 1);
285 }
286
287 /**
288 * Manually shows the previous child.
289 */
290 public void showPrevious() {
291 setDisplayedChild(mWhichChild - 1);
292 }
293
294 /**
295 * Shows only the specified child. The other displays Views exit the screen,
296 * optionally with the with the {@link #getOutAnimation() out animation} and
297 * the specified child enters the screen, optionally with the
298 * {@link #getInAnimation() in animation}.
299 *
300 * @param childIndex The index of the child to be shown.
301 * @param animate Whether or not to use the in and out animations, defaults
302 * to true.
303 */
304 void showOnly(int childIndex, boolean animate) {
305 showOnly(childIndex, animate, false);
306 }
307
Adam Cohen44729e32010-07-22 16:00:07 -0700308 private int modulo(int pos, int size) {
309 return (size + (pos % size)) % size;
Adam Cohen3db40672010-07-19 22:41:57 -0700310 }
311
Adam Cohen44729e32010-07-22 16:00:07 -0700312 /**
313 * Get the view at this index relative to the current window's start
314 *
315 * @param relativeIndex Position relative to the current window's start
316 * @return View at this index, null if the index is outside the bounds
317 */
318 View getViewAtRelativeIndex(int relativeIndex) {
319 if (relativeIndex >= 0 && relativeIndex <= mNumActiveViews - 1) {
320 int index = mCurrentWindowStartUnbounded + relativeIndex;
321 return mActiveViews[modulo(index, mNumActiveViews)];
322 }
323 return null;
324 }
Adam Cohen3db40672010-07-19 22:41:57 -0700325
Adam Cohen44729e32010-07-22 16:00:07 -0700326 private LayoutParams createOrReuseLayoutParams(View v) {
Romain Guy5b53f912010-08-16 18:24:33 -0700327 final ViewGroup.LayoutParams currentLp = v.getLayoutParams();
Adam Cohen44729e32010-07-22 16:00:07 -0700328 if (currentLp instanceof LayoutParams) {
Adam Cohenb04f7ad2010-08-15 13:22:42 -0700329 LayoutParams lp = (LayoutParams) currentLp;
330 lp.setHorizontalOffset(0);
331 lp.setVerticalOffset(0);
332 return lp;
Adam Cohen44729e32010-07-22 16:00:07 -0700333 }
334 return new LayoutParams(v);
335 }
Adam Cohen3db40672010-07-19 22:41:57 -0700336
Adam Cohen44729e32010-07-22 16:00:07 -0700337 void showOnly(int childIndex, boolean animate, boolean onLayout) {
338 if (mAdapter == null) return;
Adam Cohen3db40672010-07-19 22:41:57 -0700339
Adam Cohen44729e32010-07-22 16:00:07 -0700340 for (int i = 0; i < mPreviousViews.size(); i++) {
341 View viewToRemove = mPreviousViews.get(i);
342 viewToRemove.clearAnimation();
343 // applyTransformForChildAtIndex here just allows for any cleanup
344 // associated with this view that may need to be done by a subclass
345 applyTransformForChildAtIndex(viewToRemove, -1);
346 removeViewInLayout(viewToRemove);
347 }
348 mPreviousViews.clear();
349 int newWindowStartUnbounded = childIndex - mActiveOffset;
350 int newWindowEndUnbounded = newWindowStartUnbounded + mNumActiveViews - 1;
351 int newWindowStart = Math.max(0, newWindowStartUnbounded);
352 int newWindowEnd = Math.min(mAdapter.getCount(), newWindowEndUnbounded);
Adam Cohen3db40672010-07-19 22:41:57 -0700353
Adam Cohen44729e32010-07-22 16:00:07 -0700354 // This section clears out any items that are in our mActiveViews list
355 // but are outside the effective bounds of our window (this is becomes an issue
356 // at the extremities of the list, eg. where newWindowStartUnbounded < 0 or
357 // newWindowEndUnbounded > mAdapter.getCount() - 1
358 for (int i = newWindowStartUnbounded; i < newWindowEndUnbounded; i++) {
359 if (i < newWindowStart || i > newWindowEnd) {
360 int index = modulo(i, mNumActiveViews);
361 if (mActiveViews[index] != null) {
362 View previousView = mActiveViews[index];
363 mPreviousViews.add(previousView);
364 int previousViewRelativeIndex = modulo(index - mCurrentWindowStart,
365 mNumActiveViews);
366 animateViewForTransition(previousViewRelativeIndex, -1, previousView);
Adam Cohenb04f7ad2010-08-15 13:22:42 -0700367 mActiveViews[index] = null;
Adam Cohen3db40672010-07-19 22:41:57 -0700368 }
Adam Cohen3db40672010-07-19 22:41:57 -0700369 }
Adam Cohen44729e32010-07-22 16:00:07 -0700370 }
Adam Cohen3db40672010-07-19 22:41:57 -0700371
Adam Cohen44729e32010-07-22 16:00:07 -0700372 // If the window has changed
373 if (! (newWindowStart == mCurrentWindowStart && newWindowEnd == mCurrentWindowEnd)) {
374 // Run through the indices in the new range
375 for (int i = newWindowStart; i <= newWindowEnd; i++) {
Adam Cohen3db40672010-07-19 22:41:57 -0700376
Adam Cohen44729e32010-07-22 16:00:07 -0700377 int oldRelativeIndex = i - mCurrentWindowStartUnbounded;
378 int newRelativeIndex = i - newWindowStartUnbounded;
379 int index = modulo(i, mNumActiveViews);
380
381 // If this item is in the current window, great, we just need to apply
382 // the transform for it's new relative position in the window, and animate
383 // between it's current and new relative positions
384 if (i >= mCurrentWindowStart && i <= mCurrentWindowEnd) {
385 View view = mActiveViews[index];
386 applyTransformForChildAtIndex(view, newRelativeIndex);
387 animateViewForTransition(oldRelativeIndex, newRelativeIndex, view);
388
389 // Otherwise this view is new, so first we have to displace the view that's
390 // taking the new view's place within our cache (a circular array)
391 } else {
392 if (mActiveViews[index] != null) {
393 View previousView = mActiveViews[index];
394 mPreviousViews.add(previousView);
395 int previousViewRelativeIndex = modulo(index - mCurrentWindowStart,
396 mNumActiveViews);
397 animateViewForTransition(previousViewRelativeIndex, -1, previousView);
398
399 if (mCurrentWindowStart > newWindowStart) {
400 mViewsToBringToFront.add(previousView);
401 }
Adam Cohen3db40672010-07-19 22:41:57 -0700402 }
Adam Cohen44729e32010-07-22 16:00:07 -0700403
404 // We've cleared a spot for the new view. Get it from the adapter, add it
405 // and apply any transform / animation
406 View newView = mAdapter.getView(i, null, this);
407 if (newView != null) {
408 mActiveViews[index] = newView;
409 addViewInLayout(newView, -1, createOrReuseLayoutParams(newView));
410 applyTransformForChildAtIndex(newView, newRelativeIndex);
411 animateViewForTransition(-1, newRelativeIndex, newView);
412 }
413 }
414 mActiveViews[index].bringToFront();
Adam Cohen3db40672010-07-19 22:41:57 -0700415 }
Adam Cohen44729e32010-07-22 16:00:07 -0700416
417 for (int i = 0; i < mViewsToBringToFront.size(); i++) {
418 View v = mViewsToBringToFront.get(i);
419 v.bringToFront();
420 }
421 mViewsToBringToFront.clear();
422
423 mCurrentWindowStart = newWindowStart;
424 mCurrentWindowEnd = newWindowEnd;
425 mCurrentWindowStartUnbounded = newWindowStartUnbounded;
426 }
427
428 mFirstTime = false;
429 if (!onLayout) {
430 requestLayout();
431 invalidate();
432 } else {
433 // If the Adapter tries to layout the current view when we get it using getView
434 // above the layout will end up being ignored since we are currently laying out, so
435 // we post a delayed requestLayout and invalidate
436 mMainQueue.post(new Runnable() {
437 @Override
438 public void run() {
439 requestLayout();
440 invalidate();
441 }
442 });
Adam Cohen3db40672010-07-19 22:41:57 -0700443 }
444 }
445
446 @Override
447 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
448 boolean dataChanged = mDataChanged;
449 if (dataChanged) {
450 handleDataChanged();
451
452 // if the data changes, mWhichChild might be out of the bounds of the adapter
453 // in this case, we reset mWhichChild to the beginning
454 if (mWhichChild >= mAdapter.getCount())
455 mWhichChild = 0;
456
457 showOnly(mWhichChild, true, true);
458 }
459
460 final int childCount = getChildCount();
461 for (int i = 0; i < childCount; i++) {
462 final View child = getChildAt(i);
463
464 int childRight = mPaddingLeft + child.getMeasuredWidth();
465 int childBottom = mPaddingTop + child.getMeasuredHeight();
Adam Cohen44729e32010-07-22 16:00:07 -0700466 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Cohen3db40672010-07-19 22:41:57 -0700467
Adam Cohen44729e32010-07-22 16:00:07 -0700468 child.layout(mPaddingLeft + lp.horizontalOffset, mPaddingTop + lp.verticalOffset,
469 childRight + lp.horizontalOffset, childBottom + lp.verticalOffset);
Adam Cohen3db40672010-07-19 22:41:57 -0700470 }
Adam Cohen3db40672010-07-19 22:41:57 -0700471 mDataChanged = false;
472 }
473
Adam Cohenb04f7ad2010-08-15 13:22:42 -0700474 static class SavedState extends BaseSavedState {
475 int whichChild;
476
477 /**
478 * Constructor called from {@link AdapterViewAnimator#onSaveInstanceState()}
479 */
480 SavedState(Parcelable superState, int whichChild) {
481 super(superState);
482 this.whichChild = whichChild;
483 }
484
485 /**
486 * Constructor called from {@link #CREATOR}
487 */
488 private SavedState(Parcel in) {
489 super(in);
490 whichChild = in.readInt();
491 }
492
493 @Override
494 public void writeToParcel(Parcel out, int flags) {
495 super.writeToParcel(out, flags);
496 out.writeInt(whichChild);
497 }
498
499 @Override
500 public String toString() {
501 return "AdapterViewAnimator.SavedState{ whichChild = " + whichChild + " }";
502 }
503
504 public static final Parcelable.Creator<SavedState> CREATOR
505 = new Parcelable.Creator<SavedState>() {
506 public SavedState createFromParcel(Parcel in) {
507 return new SavedState(in);
508 }
509
510 public SavedState[] newArray(int size) {
511 return new SavedState[size];
512 }
513 };
514 }
515
516 @Override
517 public Parcelable onSaveInstanceState() {
518 Parcelable superState = super.onSaveInstanceState();
519 return new SavedState(superState, mWhichChild);
520 }
521
522 @Override
523 public void onRestoreInstanceState(Parcelable state) {
524 SavedState ss = (SavedState) state;
525 super.onRestoreInstanceState(ss.getSuperState());
526
527 // Here we set mWhichChild in addition to setDisplayedChild
528 // We do the former in case mAdapter is null, and hence setDisplayedChild won't
529 // set mWhichChild
530 mWhichChild = ss.whichChild;
531 setDisplayedChild(mWhichChild);
532 }
533
Adam Cohen3db40672010-07-19 22:41:57 -0700534 @Override
535 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
536 final int count = getChildCount();
537
538 int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
539 int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
540
Adam Cohen3db40672010-07-19 22:41:57 -0700541 for (int i = 0; i < count; i++) {
542 final View child = getChildAt(i);
543
544 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
545
546 lp.width = widthSpecSize - mPaddingLeft - mPaddingRight;
547 lp.height = heightSpecSize - mPaddingTop - mPaddingBottom;
548
549 int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(lp.width,
550 MeasureSpec.EXACTLY);
551 int childheightMeasureSpec = MeasureSpec.makeMeasureSpec(lp.height,
552 MeasureSpec.EXACTLY);
553
554 child.measure(childWidthMeasureSpec, childheightMeasureSpec);
555 }
Adam Cohen3db40672010-07-19 22:41:57 -0700556 setMeasuredDimension(widthSpecSize, heightSpecSize);
557 }
558
559 /**
560 * Shows only the specified child. The other displays Views exit the screen
561 * with the {@link #getOutAnimation() out animation} and the specified child
562 * enters the screen with the {@link #getInAnimation() in animation}.
563 *
564 * @param childIndex The index of the child to be shown.
565 */
566 void showOnly(int childIndex) {
567 final boolean animate = (!mFirstTime || mAnimateFirstTime);
568 showOnly(childIndex, animate);
569 }
570
571 /**
572 * Returns the View corresponding to the currently displayed child.
573 *
574 * @return The View currently displayed.
575 *
576 * @see #getDisplayedChild()
577 */
578 public View getCurrentView() {
Adam Cohen44729e32010-07-22 16:00:07 -0700579 return getViewAtRelativeIndex(mActiveOffset);
Adam Cohen3db40672010-07-19 22:41:57 -0700580 }
581
582 /**
583 * Returns the current animation used to animate a View that enters the screen.
584 *
585 * @return An Animation or null if none is set.
586 *
587 * @see #setInAnimation(android.view.animation.Animation)
588 * @see #setInAnimation(android.content.Context, int)
589 */
590 public Animation getInAnimation() {
591 return mInAnimation;
592 }
593
594 /**
595 * Specifies the animation used to animate a View that enters the screen.
596 *
597 * @param inAnimation The animation started when a View enters the screen.
598 *
599 * @see #getInAnimation()
600 * @see #setInAnimation(android.content.Context, int)
601 */
602 public void setInAnimation(Animation inAnimation) {
603 mInAnimation = inAnimation;
604 }
605
606 /**
607 * Returns the current animation used to animate a View that exits the screen.
608 *
609 * @return An Animation or null if none is set.
610 *
611 * @see #setOutAnimation(android.view.animation.Animation)
612 * @see #setOutAnimation(android.content.Context, int)
613 */
614 public Animation getOutAnimation() {
615 return mOutAnimation;
616 }
617
618 /**
619 * Specifies the animation used to animate a View that exit the screen.
620 *
621 * @param outAnimation The animation started when a View exit the screen.
622 *
623 * @see #getOutAnimation()
624 * @see #setOutAnimation(android.content.Context, int)
625 */
626 public void setOutAnimation(Animation outAnimation) {
627 mOutAnimation = outAnimation;
628 }
629
630 /**
631 * Specifies the animation used to animate a View that enters the screen.
632 *
633 * @param context The application's environment.
634 * @param resourceID The resource id of the animation.
635 *
636 * @see #getInAnimation()
637 * @see #setInAnimation(android.view.animation.Animation)
638 */
639 public void setInAnimation(Context context, int resourceID) {
640 setInAnimation(AnimationUtils.loadAnimation(context, resourceID));
641 }
642
643 /**
644 * Specifies the animation used to animate a View that exit the screen.
645 *
646 * @param context The application's environment.
647 * @param resourceID The resource id of the animation.
648 *
649 * @see #getOutAnimation()
650 * @see #setOutAnimation(android.view.animation.Animation)
651 */
652 public void setOutAnimation(Context context, int resourceID) {
653 setOutAnimation(AnimationUtils.loadAnimation(context, resourceID));
654 }
655
656 /**
657 * Indicates whether the current View should be animated the first time
658 * the ViewAnimation is displayed.
659 *
660 * @param animate True to animate the current View the first time it is displayed,
661 * false otherwise.
662 */
663 public void setAnimateFirstView(boolean animate) {
664 mAnimateFirstTime = animate;
665 }
666
667 @Override
668 public int getBaseline() {
669 return (getCurrentView() != null) ? getCurrentView().getBaseline() : super.getBaseline();
670 }
671
672 @Override
673 public Adapter getAdapter() {
674 return mAdapter;
675 }
676
677 @Override
678 public void setAdapter(Adapter adapter) {
Adam Cohen83228342010-08-10 16:47:30 -0700679 if (mAdapter != null && mDataSetObserver != null) {
680 mAdapter.unregisterDataSetObserver(mDataSetObserver);
681 }
682
Adam Cohen3db40672010-07-19 22:41:57 -0700683 mAdapter = adapter;
684
685 if (mAdapter != null) {
Adam Cohen3db40672010-07-19 22:41:57 -0700686 mDataSetObserver = new AdapterDataSetObserver();
687 mAdapter.registerDataSetObserver(mDataSetObserver);
688 }
Adam Cohen44729e32010-07-22 16:00:07 -0700689 setFocusable(true);
Adam Cohen3db40672010-07-19 22:41:57 -0700690 }
691
692 /**
Adam Cohen44729e32010-07-22 16:00:07 -0700693 * Sets up this AdapterViewAnimator to use a remote views adapter which connects to a
694 * RemoteViewsService through the specified intent.
695 *
696 * @param intent the intent used to identify the RemoteViewsService for the adapter to
697 * connect to.
Adam Cohen3db40672010-07-19 22:41:57 -0700698 */
699 @android.view.RemotableViewMethod
700 public void setRemoteViewsAdapter(Intent intent) {
701 mRemoteViewsAdapter = new RemoteViewsAdapter(getContext(), intent, this);
702 }
703
704 @Override
705 public void setSelection(int position) {
706 setDisplayedChild(position);
707 }
708
709 @Override
710 public View getSelectedView() {
Adam Cohen44729e32010-07-22 16:00:07 -0700711 return getViewAtRelativeIndex(mActiveOffset);
Adam Cohen3db40672010-07-19 22:41:57 -0700712 }
713
714 /**
715 * Called back when the adapter connects to the RemoteViewsService.
716 */
717 public void onRemoteAdapterConnected() {
718 if (mRemoteViewsAdapter != mAdapter) {
719 setAdapter(mRemoteViewsAdapter);
720 }
721 }
722
723 /**
724 * Called back when the adapter disconnects from the RemoteViewsService.
725 */
726 public void onRemoteAdapterDisconnected() {
727 if (mRemoteViewsAdapter != mAdapter) {
728 mRemoteViewsAdapter = null;
729 setAdapter(mRemoteViewsAdapter);
730 }
731 }
Adam Cohen44729e32010-07-22 16:00:07 -0700732
Adam Cohenb04f7ad2010-08-15 13:22:42 -0700733 private final Rect dirtyRect = new Rect();
734 @Override
735 public void removeViewInLayout(View view) {
736 // TODO: need to investigate this block a bit more
737 // and perhaps fix some other invalidations issues.
738 View parent = null;
739 view.setVisibility(INVISIBLE);
740 if (view.getLayoutParams() instanceof LayoutParams) {
741 LayoutParams lp = (LayoutParams) view.getLayoutParams();
742 parent = lp.getParentAndDirtyRegion(dirtyRect);
743 }
744
745 super.removeViewInLayout(view);
746
747 if (parent != null)
748 parent.invalidate(dirtyRect.left, dirtyRect.top, dirtyRect.right, dirtyRect.bottom);
749 }
750
Adam Cohen44729e32010-07-22 16:00:07 -0700751 static class LayoutParams extends ViewGroup.LayoutParams {
752 int horizontalOffset;
753 int verticalOffset;
754 View mView;
755
756 LayoutParams(View view) {
757 super(0, 0);
758 horizontalOffset = 0;
759 verticalOffset = 0;
760 mView = view;
761 }
762
763 LayoutParams(Context c, AttributeSet attrs) {
764 super(c, attrs);
765 horizontalOffset = 0;
766 verticalOffset = 0;
767 }
768
Adam Cohen44729e32010-07-22 16:00:07 -0700769 private Rect parentRect = new Rect();
770 void invalidateGlobalRegion(View v, Rect r) {
771 View p = v;
772 boolean firstPass = true;
773 parentRect.set(0, 0, 0, 0);
774 while (p.getParent() != null && p.getParent() instanceof View
775 && !parentRect.contains(r)) {
776 if (!firstPass) r.offset(p.getLeft() - p.getScrollX(), p.getTop() - p.getScrollY());
777 firstPass = false;
778 p = (View) p.getParent();
779 parentRect.set(p.getLeft() - p.getScrollX(), p.getTop() - p.getScrollY(),
780 p.getRight() - p.getScrollX(), p.getBottom() - p.getScrollY());
781 }
782 p.invalidate(r.left, r.top, r.right, r.bottom);
783 }
784
Adam Cohenb04f7ad2010-08-15 13:22:42 -0700785 public View getParentAndDirtyRegion(Rect globalRect) {
786 globalRect.set(mView.getLeft(), mView.getTop(), mView.getRight(), mView.getBottom());
787 View p = mView;
788 boolean firstPass = true;
789 parentRect.set(0, 0, 0, 0);
790 while (p.getParent() != null && p.getParent() instanceof View
791 && !parentRect.contains(globalRect)) {
792 if (!firstPass) {
793 globalRect.offset(p.getLeft() - p.getScrollX(), p.getTop() - p.getScrollY());
794 }
795
796 firstPass = false;
797 p = (View) p.getParent();
798 parentRect.set(p.getLeft() - p.getScrollX(), p.getTop() - p.getScrollY(),
799 p.getRight() - p.getScrollX(), p.getBottom() - p.getScrollY());
800 }
801 return p;
802 }
803
Adam Cohen44729e32010-07-22 16:00:07 -0700804 private Rect invalidateRect = new Rect();
805 // This is public so that PropertyAnimator can access it
806 public void setVerticalOffset(int newVerticalOffset) {
807 int offsetDelta = newVerticalOffset - verticalOffset;
808 verticalOffset = newVerticalOffset;
809 if (mView != null) {
810 mView.requestLayout();
811 int top = Math.min(mView.getTop() + offsetDelta, mView.getTop());
812 int bottom = Math.max(mView.getBottom() + offsetDelta, mView.getBottom());
813 invalidateRect.set(mView.getLeft(), top, mView.getRight(), bottom);
814 invalidateGlobalRegion(mView, invalidateRect);
815 }
816 }
Adam Cohen32a42f12010-08-11 19:34:30 -0700817
818 public void setHorizontalOffset(int newHorizontalOffset) {
819 int offsetDelta = newHorizontalOffset - horizontalOffset;
820 horizontalOffset = newHorizontalOffset;
821 if (mView != null) {
822 mView.requestLayout();
823 int left = Math.min(mView.getLeft() + offsetDelta, mView.getLeft());
824 int right = Math.max(mView.getRight() + offsetDelta, mView.getRight());
825 invalidateRect.set(left, mView.getTop(), right, mView.getBottom());
826 invalidateGlobalRegion(mView, invalidateRect);
827 }
828 }
Adam Cohen44729e32010-07-22 16:00:07 -0700829 }
Adam Cohen3db40672010-07-19 22:41:57 -0700830}