/*
 * Copyright (C) 2010 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.widget;

import android.animation.AnimatorInflater;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.content.Intent;
import android.content.res.TypedArray;
import android.os.Handler;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.widget.RemoteViews.OnClickHandler;

import java.util.ArrayList;
import java.util.HashMap;

Base class for a AdapterView that will perform animations when switching between its views.
@attrref android.R.styleable#AdapterViewAnimator_inAnimation
@attrref android.R.styleable#AdapterViewAnimator_outAnimation
@attrref android.R.styleable#AdapterViewAnimator_animateFirstView
@attrref android.R.styleable#AdapterViewAnimator_loopViews
/** * Base class for a {@link AdapterView} that will perform animations * when switching between its views. * * @attr ref android.R.styleable#AdapterViewAnimator_inAnimation * @attr ref android.R.styleable#AdapterViewAnimator_outAnimation * @attr ref android.R.styleable#AdapterViewAnimator_animateFirstView * @attr ref android.R.styleable#AdapterViewAnimator_loopViews */
public abstract class AdapterViewAnimator extends AdapterView<Adapter> implements RemoteViewsAdapter.RemoteAdapterConnectionCallback, Advanceable { private static final String TAG = "RemoteViewAnimator";
The index of the current child, which appears anywhere from the beginning to the end of the current set of children, as specified by mActiveOffset
/** * The index of the current child, which appears anywhere from the beginning * to the end of the current set of children, as specified by {@link #mActiveOffset} */
int mWhichChild = 0;
The index of the child to restore after the asynchronous connection from the RemoteViewsAdapter has been.
/** * The index of the child to restore after the asynchronous connection from the * RemoteViewsAdapter has been. */
private int mRestoreWhichChild = -1;
Whether or not the first view(s) should be animated in
/** * Whether or not the first view(s) should be animated in */
boolean mAnimateFirstTime = true;
Represents where the in the current window of views the current mDisplayedChild sits
/** * Represents where the in the current window of * views the current <code>mDisplayedChild</code> sits */
int mActiveOffset = 0;
The number of views that the AdapterViewAnimator keeps as children at any given time (not counting views that are pending removal, see mPreviousViews).
/** * The number of views that the {@link AdapterViewAnimator} keeps as children at any * given time (not counting views that are pending removal, see {@link #mPreviousViews}). */
int mMaxNumActiveViews = 1;
Map of the children of the AdapterViewAnimator.
/** * Map of the children of the {@link AdapterViewAnimator}. */
HashMap<Integer, ViewAndMetaData> mViewsMap = new HashMap<Integer, ViewAndMetaData>();
List of views pending removal from the AdapterViewAnimator
/** * List of views pending removal from the {@link AdapterViewAnimator} */
ArrayList<Integer> mPreviousViews;
The index, relative to the adapter, of the beginning of the window of views
/** * The index, relative to the adapter, of the beginning of the window of views */
int mCurrentWindowStart = 0;
The index, relative to the adapter, of the end of the window of views
/** * The index, relative to the adapter, of the end of the window of views */
int mCurrentWindowEnd = -1;
The same as mCurrentWindowStart, except when the we have bounded mCurrentWindowStart to be non-negative
/** * The same as {@link #mCurrentWindowStart}, except when the we have bounded * {@link #mCurrentWindowStart} to be non-negative */
int mCurrentWindowStartUnbounded = 0;
Listens for data changes from the adapter
/** * Listens for data changes from the adapter */
AdapterDataSetObserver mDataSetObserver; /** * The {@link Adapter} for this {@link AdapterViewAnimator} */ Adapter mAdapter; /** * The {@link RemoteViewsAdapter} for this {@link AdapterViewAnimator} */ RemoteViewsAdapter mRemoteViewsAdapter;
The remote adapter containing the data to be displayed by this view to be set
/** * The remote adapter containing the data to be displayed by this view to be set */
boolean mDeferNotifyDataSetChanged = false;
Specifies whether this is the first time the animator is showing views
/** * Specifies whether this is the first time the animator is showing views */
boolean mFirstTime = true;
Specifies if the animator should wrap from 0 to the end and vice versa or have hard boundaries at the beginning and end
/** * Specifies if the animator should wrap from 0 to the end and vice versa * or have hard boundaries at the beginning and end */
boolean mLoopViews = true;
The width and height of some child, used as a size reference in-case our dimensions are unspecified by the parent.
/** * The width and height of some child, used as a size reference in-case our * dimensions are unspecified by the parent. */
int mReferenceChildWidth = -1; int mReferenceChildHeight = -1;
In and out animations.
/** * In and out animations. */
ObjectAnimator mInAnimation; ObjectAnimator mOutAnimation;
Current touch state.
/** * Current touch state. */
private int mTouchMode = TOUCH_MODE_NONE;
Private touch states.
/** * Private touch states. */
static final int TOUCH_MODE_NONE = 0; static final int TOUCH_MODE_DOWN_IN_CURRENT_VIEW = 1; static final int TOUCH_MODE_HANDLED = 2; private Runnable mPendingCheckForTap; private static final int DEFAULT_ANIMATION_DURATION = 200; public AdapterViewAnimator(Context context) { this(context, null); } public AdapterViewAnimator(Context context, AttributeSet attrs) { this(context, attrs, 0); } public AdapterViewAnimator(Context context, AttributeSet attrs, int defStyleAttr) { this(context, attrs, defStyleAttr, 0); } public AdapterViewAnimator( Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); final TypedArray a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.AdapterViewAnimator, defStyleAttr, defStyleRes); int resource = a.getResourceId( com.android.internal.R.styleable.AdapterViewAnimator_inAnimation, 0); if (resource > 0) { setInAnimation(context, resource); } else { setInAnimation(getDefaultInAnimation()); } resource = a.getResourceId(com.android.internal.R.styleable.AdapterViewAnimator_outAnimation, 0); if (resource > 0) { setOutAnimation(context, resource); } else { setOutAnimation(getDefaultOutAnimation()); } boolean flag = a.getBoolean( com.android.internal.R.styleable.AdapterViewAnimator_animateFirstView, true); setAnimateFirstView(flag); mLoopViews = a.getBoolean( com.android.internal.R.styleable.AdapterViewAnimator_loopViews, false); a.recycle(); initViewAnimator(); }
Initialize this AdapterViewAnimator
/** * Initialize this {@link AdapterViewAnimator} */
private void initViewAnimator() { mPreviousViews = new ArrayList<Integer>(); } class ViewAndMetaData { View view; int relativeIndex; int adapterPosition; long itemId; ViewAndMetaData(View view, int relativeIndex, int adapterPosition, long itemId) { this.view = view; this.relativeIndex = relativeIndex; this.adapterPosition = adapterPosition; this.itemId = itemId; } }
This method is used by subclasses to configure the animator to display the desired number of views, and specify the offset
Params:
  • numVisibleViews – The number of views the animator keeps in the ViewGroup
  • activeOffset – This parameter specifies where the current index (mWhichChild) sits within the window. For example if activeOffset is 1, and numVisibleViews is 3, and setDisplayedChild(int) is called with 10, then the effective window will be the indexes 9, 10, and 11. In the same example, if activeOffset were 0, then the window would instead contain indexes 10, 11 and 12.
  • shouldLoop – If the animator is show view 0, and setPrevious() is called, do we we loop back to the end, or do we do nothing
/** * This method is used by subclasses to configure the animator to display the * desired number of views, and specify the offset * * @param numVisibleViews The number of views the animator keeps in the {@link ViewGroup} * @param activeOffset This parameter specifies where the current index ({@link #mWhichChild}) * sits within the window. For example if activeOffset is 1, and numVisibleViews is 3, * and {@link #setDisplayedChild(int)} is called with 10, then the effective window will * be the indexes 9, 10, and 11. In the same example, if activeOffset were 0, then the * window would instead contain indexes 10, 11 and 12. * @param shouldLoop If the animator is show view 0, and setPrevious() is called, do we * we loop back to the end, or do we do nothing */
void configureViewAnimator(int numVisibleViews, int activeOffset) { if (activeOffset > numVisibleViews - 1) { // Throw an exception here. } mMaxNumActiveViews = numVisibleViews; mActiveOffset = activeOffset; mPreviousViews.clear(); mViewsMap.clear(); removeAllViewsInLayout(); mCurrentWindowStart = 0; mCurrentWindowEnd = -1; }
This class should be overridden by subclasses to customize view transitions within the set of visible views
Params:
  • fromIndex – The relative index within the window that the view was in, -1 if it wasn't in the window
  • toIndex – The relative index within the window that the view is going to, -1 if it is being removed
  • view – The view that is being animated
/** * This class should be overridden by subclasses to customize view transitions within * the set of visible views * * @param fromIndex The relative index within the window that the view was in, -1 if it wasn't * in the window * @param toIndex The relative index within the window that the view is going to, -1 if it is * being removed * @param view The view that is being animated */
void transformViewForTransition(int fromIndex, int toIndex, View view, boolean animate) { if (fromIndex == -1) { mInAnimation.setTarget(view); mInAnimation.start(); } else if (toIndex == -1) { mOutAnimation.setTarget(view); mOutAnimation.start(); } } ObjectAnimator getDefaultInAnimation() { ObjectAnimator anim = ObjectAnimator.ofFloat(null, "alpha", 0.0f, 1.0f); anim.setDuration(DEFAULT_ANIMATION_DURATION); return anim; } ObjectAnimator getDefaultOutAnimation() { ObjectAnimator anim = ObjectAnimator.ofFloat(null, "alpha", 1.0f, 0.0f); anim.setDuration(DEFAULT_ANIMATION_DURATION); return anim; }
Sets which child view will be displayed.
Params:
  • whichChild – the index of the child view to display
/** * Sets which child view will be displayed. * * @param whichChild the index of the child view to display */
@android.view.RemotableViewMethod public void setDisplayedChild(int whichChild) { setDisplayedChild(whichChild, true); } private void setDisplayedChild(int whichChild, boolean animate) { if (mAdapter != null) { mWhichChild = whichChild; if (whichChild >= getWindowSize()) { mWhichChild = mLoopViews ? 0 : getWindowSize() - 1; } else if (whichChild < 0) { mWhichChild = mLoopViews ? getWindowSize() - 1 : 0; } boolean hasFocus = getFocusedChild() != null; // This will clear old focus if we had it showOnly(mWhichChild, animate); if (hasFocus) { // Try to retake focus if we had it requestFocus(FOCUS_FORWARD); } } }
To be overridden by subclasses. This method applies a view / index specific transform to the child view.
Params:
  • child –
  • relativeIndex –
/** * To be overridden by subclasses. This method applies a view / index specific * transform to the child view. * * @param child * @param relativeIndex */
void applyTransformForChildAtIndex(View child, int relativeIndex) { }
Returns the index of the currently displayed child view.
/** * Returns the index of the currently displayed child view. */
public int getDisplayedChild() { return mWhichChild; }
Manually shows the next child.
/** * Manually shows the next child. */
public void showNext() { setDisplayedChild(mWhichChild + 1); }
Manually shows the previous child.
/** * Manually shows the previous child. */
public void showPrevious() { setDisplayedChild(mWhichChild - 1); } int modulo(int pos, int size) { if (size > 0) { return (size + (pos % size)) % size; } else { return 0; } }
Get the view at this index relative to the current window's start
Params:
  • relativeIndex – Position relative to the current window's start
Returns:View at this index, null if the index is outside the bounds
/** * Get the view at this index relative to the current window's start * * @param relativeIndex Position relative to the current window's start * @return View at this index, null if the index is outside the bounds */
View getViewAtRelativeIndex(int relativeIndex) { if (relativeIndex >= 0 && relativeIndex <= getNumActiveViews() - 1 && mAdapter != null) { int i = modulo(mCurrentWindowStartUnbounded + relativeIndex, getWindowSize()); if (mViewsMap.get(i) != null) { return mViewsMap.get(i).view; } } return null; } int getNumActiveViews() { if (mAdapter != null) { return Math.min(getCount() + 1, mMaxNumActiveViews); } else { return mMaxNumActiveViews; } } int getWindowSize() { if (mAdapter != null) { int adapterCount = getCount(); if (adapterCount <= getNumActiveViews() && mLoopViews) { return adapterCount*mMaxNumActiveViews; } else { return adapterCount; } } else { return 0; } } private ViewAndMetaData getMetaDataForChild(View child) { for (ViewAndMetaData vm: mViewsMap.values()) { if (vm.view == child) { return vm; } } return null; } LayoutParams createOrReuseLayoutParams(View v) { final LayoutParams currentLp = v.getLayoutParams(); if (currentLp != null) { return currentLp; } return new LayoutParams(0, 0); } void refreshChildren() { if (mAdapter == null) return; for (int i = mCurrentWindowStart; i <= mCurrentWindowEnd; i++) { int index = modulo(i, getWindowSize()); int adapterCount = getCount(); // get the fresh child from the adapter final View updatedChild = mAdapter.getView(modulo(i, adapterCount), null, this); if (updatedChild.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) { updatedChild.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); } if (mViewsMap.containsKey(index)) { final FrameLayout fl = (FrameLayout) mViewsMap.get(index).view; // add the new child to the frame, if it exists if (updatedChild != null) { // flush out the old child fl.removeAllViewsInLayout(); fl.addView(updatedChild); } } } }
This method can be overridden so that subclasses can provide a custom frame in which their children can live. For example, StackView adds padding to its childrens' frames so as to accomodate for the highlight effect.
Returns:The FrameLayout into which children can be placed.
/** * This method can be overridden so that subclasses can provide a custom frame in which their * children can live. For example, StackView adds padding to its childrens' frames so as to * accomodate for the highlight effect. * * @return The FrameLayout into which children can be placed. */
FrameLayout getFrameForChild() { return new FrameLayout(mContext); }
Shows only the specified child. The other displays Views exit the screen, optionally with the with the out animation and the specified child enters the screen, optionally with the in animation.
Params:
  • childIndex – The index of the child to be shown.
  • animate – Whether or not to use the in and out animations, defaults to true.
/** * Shows only the specified child. The other displays Views exit the screen, * optionally with the with the {@link #getOutAnimation() out animation} and * the specified child enters the screen, optionally with the * {@link #getInAnimation() in animation}. * * @param childIndex The index of the child to be shown. * @param animate Whether or not to use the in and out animations, defaults * to true. */
void showOnly(int childIndex, boolean animate) { if (mAdapter == null) return; final int adapterCount = getCount(); if (adapterCount == 0) return; for (int i = 0; i < mPreviousViews.size(); i++) { View viewToRemove = mViewsMap.get(mPreviousViews.get(i)).view; mViewsMap.remove(mPreviousViews.get(i)); viewToRemove.clearAnimation(); if (viewToRemove instanceof ViewGroup) { ViewGroup vg = (ViewGroup) viewToRemove; vg.removeAllViewsInLayout(); } // applyTransformForChildAtIndex here just allows for any cleanup // associated with this view that may need to be done by a subclass applyTransformForChildAtIndex(viewToRemove, -1); removeViewInLayout(viewToRemove); } mPreviousViews.clear(); int newWindowStartUnbounded = childIndex - mActiveOffset; int newWindowEndUnbounded = newWindowStartUnbounded + getNumActiveViews() - 1; int newWindowStart = Math.max(0, newWindowStartUnbounded); int newWindowEnd = Math.min(adapterCount - 1, newWindowEndUnbounded); if (mLoopViews) { newWindowStart = newWindowStartUnbounded; newWindowEnd = newWindowEndUnbounded; } int rangeStart = modulo(newWindowStart, getWindowSize()); int rangeEnd = modulo(newWindowEnd, getWindowSize()); boolean wrap = false; if (rangeStart > rangeEnd) { wrap = true; } // This section clears out any items that are in our active views list // but are outside the effective bounds of our window (this is becomes an issue // at the extremities of the list, eg. where newWindowStartUnbounded < 0 or // newWindowEndUnbounded > adapterCount - 1 for (Integer index : mViewsMap.keySet()) { boolean remove = false; if (!wrap && (index < rangeStart || index > rangeEnd)) { remove = true; } else if (wrap && (index > rangeEnd && index < rangeStart)) { remove = true; } if (remove) { View previousView = mViewsMap.get(index).view; int oldRelativeIndex = mViewsMap.get(index).relativeIndex; mPreviousViews.add(index); transformViewForTransition(oldRelativeIndex, -1, previousView, animate); } } // If the window has changed if (!(newWindowStart == mCurrentWindowStart && newWindowEnd == mCurrentWindowEnd && newWindowStartUnbounded == mCurrentWindowStartUnbounded)) { // Run through the indices in the new range for (int i = newWindowStart; i <= newWindowEnd; i++) { int index = modulo(i, getWindowSize()); int oldRelativeIndex; if (mViewsMap.containsKey(index)) { oldRelativeIndex = mViewsMap.get(index).relativeIndex; } else { oldRelativeIndex = -1; } int newRelativeIndex = i - newWindowStartUnbounded; // If this item is in the current window, great, we just need to apply // the transform for it's new relative position in the window, and animate // between it's current and new relative positions boolean inOldRange = mViewsMap.containsKey(index) && !mPreviousViews.contains(index); if (inOldRange) { View view = mViewsMap.get(index).view; mViewsMap.get(index).relativeIndex = newRelativeIndex; applyTransformForChildAtIndex(view, newRelativeIndex); transformViewForTransition(oldRelativeIndex, newRelativeIndex, view, animate); // Otherwise this view is new to the window } else { // Get the new view from the adapter, add it and apply any transform / animation final int adapterPosition = modulo(i, adapterCount); View newView = mAdapter.getView(adapterPosition, null, this); long itemId = mAdapter.getItemId(adapterPosition); // We wrap the new view in a FrameLayout so as to respect the contract // with the adapter, that is, that we don't modify this view directly FrameLayout fl = getFrameForChild(); // If the view from the adapter is null, we still keep an empty frame in place if (newView != null) { fl.addView(newView); } mViewsMap.put(index, new ViewAndMetaData(fl, newRelativeIndex, adapterPosition, itemId)); addChild(fl); applyTransformForChildAtIndex(fl, newRelativeIndex); transformViewForTransition(-1, newRelativeIndex, fl, animate); } mViewsMap.get(index).view.bringToFront(); } mCurrentWindowStart = newWindowStart; mCurrentWindowEnd = newWindowEnd; mCurrentWindowStartUnbounded = newWindowStartUnbounded; if (mRemoteViewsAdapter != null) { int adapterStart = modulo(mCurrentWindowStart, adapterCount); int adapterEnd = modulo(mCurrentWindowEnd, adapterCount); mRemoteViewsAdapter.setVisibleRangeHint(adapterStart, adapterEnd); } } requestLayout(); invalidate(); } private void addChild(View child) { addViewInLayout(child, -1, createOrReuseLayoutParams(child)); // This code is used to obtain a reference width and height of a child in case we need // to decide our own size. TODO: Do we want to update the size of the child that we're // using for reference size? If so, when? if (mReferenceChildWidth == -1 || mReferenceChildHeight == -1) { int measureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); child.measure(measureSpec, measureSpec); mReferenceChildWidth = child.getMeasuredWidth(); mReferenceChildHeight = child.getMeasuredHeight(); } } void showTapFeedback(View v) { v.setPressed(true); } void hideTapFeedback(View v) { v.setPressed(false); } void cancelHandleClick() { View v = getCurrentView(); if (v != null) { hideTapFeedback(v); } mTouchMode = TOUCH_MODE_NONE; } final class CheckForTap implements Runnable { public void run() { if (mTouchMode == TOUCH_MODE_DOWN_IN_CURRENT_VIEW) { View v = getCurrentView(); showTapFeedback(v); } } } @Override public boolean onTouchEvent(MotionEvent ev) { int action = ev.getAction(); boolean handled = false; switch (action) { case MotionEvent.ACTION_DOWN: { View v = getCurrentView(); if (v != null) { if (isTransformedTouchPointInView(ev.getX(), ev.getY(), v, null)) { if (mPendingCheckForTap == null) { mPendingCheckForTap = new CheckForTap(); } mTouchMode = TOUCH_MODE_DOWN_IN_CURRENT_VIEW; postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout()); } } break; } case MotionEvent.ACTION_MOVE: break; case MotionEvent.ACTION_POINTER_UP: break; case MotionEvent.ACTION_UP: { if (mTouchMode == TOUCH_MODE_DOWN_IN_CURRENT_VIEW) { final View v = getCurrentView(); final ViewAndMetaData viewData = getMetaDataForChild(v); if (v != null) { if (isTransformedTouchPointInView(ev.getX(), ev.getY(), v, null)) { final Handler handler = getHandler(); if (handler != null) { handler.removeCallbacks(mPendingCheckForTap); } showTapFeedback(v); postDelayed(new Runnable() { public void run() { hideTapFeedback(v); post(new Runnable() { public void run() { if (viewData != null) { performItemClick(v, viewData.adapterPosition, viewData.itemId); } else { performItemClick(v, 0, 0); } } }); } }, ViewConfiguration.getPressedStateDuration()); handled = true; } } } mTouchMode = TOUCH_MODE_NONE; break; } case MotionEvent.ACTION_CANCEL: { View v = getCurrentView(); if (v != null) { hideTapFeedback(v); } mTouchMode = TOUCH_MODE_NONE; } } return handled; } private void measureChildren() { final int count = getChildCount(); final int childWidth = getMeasuredWidth() - mPaddingLeft - mPaddingRight; final int childHeight = getMeasuredHeight() - mPaddingTop - mPaddingBottom; for (int i = 0; i < count; i++) { final View child = getChildAt(i); child.measure(MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY)); } } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec); int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec); final int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec); final int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec); boolean haveChildRefSize = (mReferenceChildWidth != -1 && mReferenceChildHeight != -1); // We need to deal with the case where our parent hasn't told us how // big we should be. In this case we try to use the desired size of the first // child added. if (heightSpecMode == MeasureSpec.UNSPECIFIED) { heightSpecSize = haveChildRefSize ? mReferenceChildHeight + mPaddingTop + mPaddingBottom : 0; } else if (heightSpecMode == MeasureSpec.AT_MOST) { if (haveChildRefSize) { int height = mReferenceChildHeight + mPaddingTop + mPaddingBottom; if (height > heightSpecSize) { heightSpecSize |= MEASURED_STATE_TOO_SMALL; } else { heightSpecSize = height; } } } if (widthSpecMode == MeasureSpec.UNSPECIFIED) { widthSpecSize = haveChildRefSize ? mReferenceChildWidth + mPaddingLeft + mPaddingRight : 0; } else if (heightSpecMode == MeasureSpec.AT_MOST) { if (haveChildRefSize) { int width = mReferenceChildWidth + mPaddingLeft + mPaddingRight; if (width > widthSpecSize) { widthSpecSize |= MEASURED_STATE_TOO_SMALL; } else { widthSpecSize = width; } } } setMeasuredDimension(widthSpecSize, heightSpecSize); measureChildren(); } void checkForAndHandleDataChanged() { boolean dataChanged = mDataChanged; if (dataChanged) { post(new Runnable() { public void run() { handleDataChanged(); // if the data changes, mWhichChild might be out of the bounds of the adapter // in this case, we reset mWhichChild to the beginning if (mWhichChild >= getWindowSize()) { mWhichChild = 0; showOnly(mWhichChild, false); } else if (mOldItemCount != getCount()) { showOnly(mWhichChild, false); } refreshChildren(); requestLayout(); } }); } mDataChanged = false; } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { checkForAndHandleDataChanged(); final int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { final View child = getChildAt(i); int childRight = mPaddingLeft + child.getMeasuredWidth(); int childBottom = mPaddingTop + child.getMeasuredHeight(); child.layout(mPaddingLeft, mPaddingTop, childRight, childBottom); } } static class SavedState extends BaseSavedState { int whichChild; /** * Constructor called from {@link AdapterViewAnimator#onSaveInstanceState()} */ SavedState(Parcelable superState, int whichChild) { super(superState); this.whichChild = whichChild; }
Constructor called from CREATOR
/** * Constructor called from {@link #CREATOR} */
private SavedState(Parcel in) { super(in); this.whichChild = in.readInt(); } @Override public void writeToParcel(Parcel out, int flags) { super.writeToParcel(out, flags); out.writeInt(this.whichChild); } @Override public String toString() { return "AdapterViewAnimator.SavedState{ whichChild = " + this.whichChild + " }"; } public static final Parcelable.Creator<SavedState> CREATOR = new Parcelable.Creator<SavedState>() { public SavedState createFromParcel(Parcel in) { return new SavedState(in); } public SavedState[] newArray(int size) { return new SavedState[size]; } }; } @Override public Parcelable onSaveInstanceState() { Parcelable superState = super.onSaveInstanceState(); if (mRemoteViewsAdapter != null) { mRemoteViewsAdapter.saveRemoteViewsCache(); } return new SavedState(superState, mWhichChild); } @Override public void onRestoreInstanceState(Parcelable state) { SavedState ss = (SavedState) state; super.onRestoreInstanceState(ss.getSuperState()); // Here we set mWhichChild in addition to setDisplayedChild // We do the former in case mAdapter is null, and hence setDisplayedChild won't // set mWhichChild mWhichChild = ss.whichChild; // When using RemoteAdapters, the async connection process can lead to // onRestoreInstanceState to be called before setAdapter(), so we need to save the previous // values to restore the list position after we connect, and can skip setting the displayed // child until then. if (mRemoteViewsAdapter != null && mAdapter == null) { mRestoreWhichChild = mWhichChild; } else { setDisplayedChild(mWhichChild, false); } }
Returns the View corresponding to the currently displayed child.
See Also:
Returns:The View currently displayed.
/** * Returns the View corresponding to the currently displayed child. * * @return The View currently displayed. * * @see #getDisplayedChild() */
public View getCurrentView() { return getViewAtRelativeIndex(mActiveOffset); }
Returns the current animation used to animate a View that enters the screen.
See Also:
Returns:An Animation or null if none is set.
/** * Returns the current animation used to animate a View that enters the screen. * * @return An Animation or null if none is set. * * @see #setInAnimation(android.animation.ObjectAnimator) * @see #setInAnimation(android.content.Context, int) */
public ObjectAnimator getInAnimation() { return mInAnimation; }
Specifies the animation used to animate a View that enters the screen.
Params:
  • inAnimation – The animation started when a View enters the screen.
See Also:
/** * Specifies the animation used to animate a View that enters the screen. * * @param inAnimation The animation started when a View enters the screen. * * @see #getInAnimation() * @see #setInAnimation(android.content.Context, int) */
public void setInAnimation(ObjectAnimator inAnimation) { mInAnimation = inAnimation; }
Returns the current animation used to animate a View that exits the screen.
See Also:
Returns:An Animation or null if none is set.
/** * Returns the current animation used to animate a View that exits the screen. * * @return An Animation or null if none is set. * * @see #setOutAnimation(android.animation.ObjectAnimator) * @see #setOutAnimation(android.content.Context, int) */
public ObjectAnimator getOutAnimation() { return mOutAnimation; }
Specifies the animation used to animate a View that exit the screen.
Params:
  • outAnimation – The animation started when a View exit the screen.
See Also:
/** * Specifies the animation used to animate a View that exit the screen. * * @param outAnimation The animation started when a View exit the screen. * * @see #getOutAnimation() * @see #setOutAnimation(android.content.Context, int) */
public void setOutAnimation(ObjectAnimator outAnimation) { mOutAnimation = outAnimation; }
Specifies the animation used to animate a View that enters the screen.
Params:
  • context – The application's environment.
  • resourceID – The resource id of the animation.
See Also:
/** * Specifies the animation used to animate a View that enters the screen. * * @param context The application's environment. * @param resourceID The resource id of the animation. * * @see #getInAnimation() * @see #setInAnimation(android.animation.ObjectAnimator) */
public void setInAnimation(Context context, int resourceID) { setInAnimation((ObjectAnimator) AnimatorInflater.loadAnimator(context, resourceID)); }
Specifies the animation used to animate a View that exit the screen.
Params:
  • context – The application's environment.
  • resourceID – The resource id of the animation.
See Also:
/** * Specifies the animation used to animate a View that exit the screen. * * @param context The application's environment. * @param resourceID The resource id of the animation. * * @see #getOutAnimation() * @see #setOutAnimation(android.animation.ObjectAnimator) */
public void setOutAnimation(Context context, int resourceID) { setOutAnimation((ObjectAnimator) AnimatorInflater.loadAnimator(context, resourceID)); }
Indicates whether the current View should be animated the first time the ViewAnimation is displayed.
Params:
  • animate – True to animate the current View the first time it is displayed, false otherwise.
/** * Indicates whether the current View should be animated the first time * the ViewAnimation is displayed. * * @param animate True to animate the current View the first time it is displayed, * false otherwise. */
public void setAnimateFirstView(boolean animate) { mAnimateFirstTime = animate; } @Override public int getBaseline() { return (getCurrentView() != null) ? getCurrentView().getBaseline() : super.getBaseline(); } @Override public Adapter getAdapter() { return mAdapter; } @Override public void setAdapter(Adapter adapter) { if (mAdapter != null && mDataSetObserver != null) { mAdapter.unregisterDataSetObserver(mDataSetObserver); } mAdapter = adapter; checkFocus(); if (mAdapter != null) { mDataSetObserver = new AdapterDataSetObserver(); mAdapter.registerDataSetObserver(mDataSetObserver); mItemCount = mAdapter.getCount(); } setFocusable(true); mWhichChild = 0; showOnly(mWhichChild, false); }
Sets up this AdapterViewAnimator to use a remote views adapter which connects to a RemoteViewsService through the specified intent.
Params:
  • intent – the intent used to identify the RemoteViewsService for the adapter to connect to.
/** * Sets up this AdapterViewAnimator to use a remote views adapter which connects to a * RemoteViewsService through the specified intent. * * @param intent the intent used to identify the RemoteViewsService for the adapter to * connect to. */
@android.view.RemotableViewMethod(asyncImpl="setRemoteViewsAdapterAsync") public void setRemoteViewsAdapter(Intent intent) { setRemoteViewsAdapter(intent, false); }
@hide
/** @hide **/
public Runnable setRemoteViewsAdapterAsync(final Intent intent) { return new RemoteViewsAdapter.AsyncRemoteAdapterAction(this, intent); }
@hide
/** @hide **/
@Override public void setRemoteViewsAdapter(Intent intent, boolean isAsync) { // Ensure that we don't already have a RemoteViewsAdapter that is bound to an existing // service handling the specified intent. if (mRemoteViewsAdapter != null) { Intent.FilterComparison fcNew = new Intent.FilterComparison(intent); Intent.FilterComparison fcOld = new Intent.FilterComparison( mRemoteViewsAdapter.getRemoteViewsServiceIntent()); if (fcNew.equals(fcOld)) { return; } } mDeferNotifyDataSetChanged = false; // Otherwise, create a new RemoteViewsAdapter for binding mRemoteViewsAdapter = new RemoteViewsAdapter(getContext(), intent, this, isAsync); if (mRemoteViewsAdapter.isDataReady()) { setAdapter(mRemoteViewsAdapter); } }
Sets up the onClickHandler to be used by the RemoteViewsAdapter when inflating RemoteViews
Params:
  • handler – The OnClickHandler to use when inflating RemoteViews.
@hide
/** * Sets up the onClickHandler to be used by the RemoteViewsAdapter when inflating RemoteViews * * @param handler The OnClickHandler to use when inflating RemoteViews. * * @hide */
public void setRemoteViewsOnClickHandler(OnClickHandler handler) { // Ensure that we don't already have a RemoteViewsAdapter that is bound to an existing // service handling the specified intent. if (mRemoteViewsAdapter != null) { mRemoteViewsAdapter.setRemoteViewsOnClickHandler(handler); } } @Override public void setSelection(int position) { setDisplayedChild(position); } @Override public View getSelectedView() { return getViewAtRelativeIndex(mActiveOffset); }
This defers a notifyDataSetChanged on the pending RemoteViewsAdapter if it has not connected yet.
/** * This defers a notifyDataSetChanged on the pending RemoteViewsAdapter if it has not * connected yet. */
public void deferNotifyDataSetChanged() { mDeferNotifyDataSetChanged = true; }
Called back when the adapter connects to the RemoteViewsService.
/** * Called back when the adapter connects to the RemoteViewsService. */
public boolean onRemoteAdapterConnected() { if (mRemoteViewsAdapter != mAdapter) { setAdapter(mRemoteViewsAdapter); if (mDeferNotifyDataSetChanged) { mRemoteViewsAdapter.notifyDataSetChanged(); mDeferNotifyDataSetChanged = false; } // Restore the previous position (see onRestoreInstanceState) if (mRestoreWhichChild > -1) { setDisplayedChild(mRestoreWhichChild, false); mRestoreWhichChild = -1; } return false; } else if (mRemoteViewsAdapter != null) { mRemoteViewsAdapter.superNotifyDataSetChanged(); return true; } return false; }
Called back when the adapter disconnects from the RemoteViewsService.
/** * Called back when the adapter disconnects from the RemoteViewsService. */
public void onRemoteAdapterDisconnected() { // If the remote adapter disconnects, we keep it around // since the currently displayed items are still cached. // Further, we want the service to eventually reconnect // when necessary, as triggered by this view requesting // items from the Adapter. }
Called by an AppWidgetHost in order to advance the current view when it is being used within an app widget.
/** * Called by an {@link android.appwidget.AppWidgetHost} in order to advance the current view when * it is being used within an app widget. */
public void advance() { showNext(); }
Called by an AppWidgetHost to indicate that it will be automatically advancing the views of this AdapterViewAnimator by calling advance() at some point in the future. This allows subclasses to perform any required setup, for example, to stop automatically advancing their children.
/** * Called by an {@link android.appwidget.AppWidgetHost} to indicate that it will be * automatically advancing the views of this {@link AdapterViewAnimator} by calling * {@link AdapterViewAnimator#advance()} at some point in the future. This allows subclasses to * perform any required setup, for example, to stop automatically advancing their children. */
public void fyiWillBeAdvancedByHostKThx() { } @Override public CharSequence getAccessibilityClassName() { return AdapterViewAnimator.class.getName(); } }