Added looping parameter to AdapterViewAnimator / StackView

Change-Id: Iad36f7c0e657ffbae6dd17d0c9464ddf12de8b7b
This commit is contained in:
Adam Cohen
2010-09-28 14:53:47 -07:00
parent efb8a3d6e0
commit 1b065cd140
6 changed files with 107 additions and 65 deletions

View File

@@ -6928,6 +6928,17 @@
visibility="public"
>
</field>
<field name="loopViews"
type="int"
transient="false"
volatile="false"
value="16843592"
static="true"
final="true"
deprecated="not deprecated"
visibility="public"
>
</field>
<field name="manageSpaceActivity"
type="int"
transient="false"

View File

@@ -17,6 +17,7 @@
package android.widget;
import java.util.ArrayList;
import java.util.HashMap;
import android.animation.ObjectAnimator;
import android.content.Context;
@@ -40,6 +41,7 @@ import android.view.animation.AnimationUtils;
* @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 {
@@ -69,15 +71,14 @@ public abstract class AdapterViewAnimator extends AdapterView<Adapter>
int mNumActiveViews = 1;
/**
* Array of the children of the {@link AdapterViewAnimator}. This array
* is accessed in a circular fashion
* Map of the children of the {@link AdapterViewAnimator}.
*/
View[] mActiveViews;
private HashMap<Integer, ViewAndIndex> mViewsMap = new HashMap<Integer, ViewAndIndex>();
/**
* List of views pending removal from the {@link AdapterViewAnimator}
*/
ArrayList<View> mPreviousViews;
ArrayList<Integer> mPreviousViews;
/**
* The index, relative to the adapter, of the beginning of the window of views
@@ -124,7 +125,7 @@ public abstract class AdapterViewAnimator extends AdapterView<Adapter>
* Specifies if the animator should wrap from 0 to the end and vice versa
* or have hard boundaries at the beginning and end
*/
boolean mShouldLoop = true;
boolean mLoopViews = true;
/**
* The width and height of some child, used as a size reference in-case our
@@ -149,22 +150,25 @@ public abstract class AdapterViewAnimator extends AdapterView<Adapter>
super(context, attrs);
TypedArray a = context.obtainStyledAttributes(attrs,
com.android.internal.R.styleable.ViewAnimator);
com.android.internal.R.styleable.AdapterViewAnimator);
int resource = a.getResourceId(
com.android.internal.R.styleable.ViewAnimator_inAnimation, 0);
com.android.internal.R.styleable.AdapterViewAnimator_inAnimation, 0);
if (resource > 0) {
setInAnimation(context, resource);
}
resource = a.getResourceId(com.android.internal.R.styleable.ViewAnimator_outAnimation, 0);
resource = a.getResourceId(com.android.internal.R.styleable.AdapterViewAnimator_outAnimation, 0);
if (resource > 0) {
setOutAnimation(context, resource);
}
boolean flag = a.getBoolean(
com.android.internal.R.styleable.ViewAnimator_animateFirstView, true);
com.android.internal.R.styleable.AdapterViewAnimator_animateFirstView, true);
setAnimateFirstView(flag);
mLoopViews = a.getBoolean(
com.android.internal.R.styleable.AdapterViewAnimator_loopViews, false);
a.recycle();
initViewAnimator();
@@ -175,11 +179,19 @@ public abstract class AdapterViewAnimator extends AdapterView<Adapter>
*/
private void initViewAnimator() {
mMainQueue = new Handler(Looper.myLooper());
mActiveViews = new View[mNumActiveViews];
mPreviousViews = new ArrayList<View>();
mPreviousViews = new ArrayList<Integer>();
mViewsToBringToFront = new ArrayList<View>();
}
private class ViewAndIndex {
ViewAndIndex(View v, int i) {
view = v;
index = i;
}
View view;
int index;
}
/**
* This method is used by subclasses to configure the animator to display the
* desired number of views, and specify the offset
@@ -193,18 +205,17 @@ public abstract class AdapterViewAnimator extends AdapterView<Adapter>
* @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, boolean shouldLoop) {
void configureViewAnimator(int numVisibleViews, int activeOffset) {
if (activeOffset > numVisibleViews - 1) {
// Throw an exception here.
}
mNumActiveViews = numVisibleViews;
mActiveOffset = activeOffset;
mActiveViews = new View[mNumActiveViews];
mPreviousViews.clear();
mViewsMap.clear();
removeAllViewsInLayout();
mCurrentWindowStart = 0;
mCurrentWindowEnd = -1;
mShouldLoop = shouldLoop;
}
/**
@@ -238,9 +249,9 @@ public abstract class AdapterViewAnimator extends AdapterView<Adapter>
if (mAdapter != null) {
mWhichChild = whichChild;
if (whichChild >= mAdapter.getCount()) {
mWhichChild = mShouldLoop ? 0 : mAdapter.getCount() - 1;
mWhichChild = mLoopViews ? 0 : mAdapter.getCount() - 1;
} else if (whichChild < 0) {
mWhichChild = mShouldLoop ? mAdapter.getCount() - 1 : 0;
mWhichChild = mLoopViews ? mAdapter.getCount() - 1 : 0;
}
boolean hasFocus = getFocusedChild() != null;
@@ -323,9 +334,10 @@ public abstract class AdapterViewAnimator extends AdapterView<Adapter>
* @return View at this index, null if the index is outside the bounds
*/
View getViewAtRelativeIndex(int relativeIndex) {
if (relativeIndex >= 0 && relativeIndex <= mNumActiveViews - 1) {
int index = mCurrentWindowStartUnbounded + relativeIndex;
return mActiveViews[modulo(index, mNumActiveViews)];
if (relativeIndex >= 0 && relativeIndex <= mNumActiveViews - 1 && mAdapter != null) {
int adapterCount = mAdapter.getCount();
int i = modulo(mCurrentWindowStartUnbounded + relativeIndex, adapterCount);
return mViewsMap.get(i).view;
}
return null;
}
@@ -346,8 +358,8 @@ public abstract class AdapterViewAnimator extends AdapterView<Adapter>
// get the fresh child from the adapter
View updatedChild = mAdapter.getView(i, null, this);
if (mActiveViews[index] != null) {
FrameLayout fl = (FrameLayout) mActiveViews[index];
if (mViewsMap.containsKey(index)) {
FrameLayout fl = (FrameLayout) mViewsMap.get(index).view;
// flush out the old child
fl.removeAllViewsInLayout();
// add the new child to the frame, if it exists
@@ -373,7 +385,8 @@ public abstract class AdapterViewAnimator extends AdapterView<Adapter>
if (mAdapter == null) return;
for (int i = 0; i < mPreviousViews.size(); i++) {
View viewToRemove = mPreviousViews.get(i);
View viewToRemove = mViewsMap.get(mPreviousViews.get(i)).view;
mViewsMap.remove(mPreviousViews.get(i));
viewToRemove.clearAnimation();
if (viewToRemove instanceof ViewGroup) {
ViewGroup vg = (ViewGroup) viewToRemove;
@@ -386,64 +399,74 @@ public abstract class AdapterViewAnimator extends AdapterView<Adapter>
removeViewInLayout(viewToRemove);
}
mPreviousViews.clear();
int adapterCount = mAdapter.getCount();
int newWindowStartUnbounded = childIndex - mActiveOffset;
int newWindowEndUnbounded = newWindowStartUnbounded + mNumActiveViews - 1;
int newWindowStart = Math.max(0, newWindowStartUnbounded);
int newWindowEnd = Math.min(mAdapter.getCount() - 1, newWindowEndUnbounded);
int newWindowEnd = Math.min(adapterCount - 1, newWindowEndUnbounded);
// This section clears out any items that are in our mActiveViews list
if (mLoopViews) {
newWindowStart = newWindowStartUnbounded;
newWindowEnd = newWindowEndUnbounded;
}
int rangeStart = modulo(newWindowStart, adapterCount);
int rangeEnd = modulo(newWindowEnd, adapterCount);
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 > mAdapter.getCount() - 1
for (int i = newWindowStartUnbounded; i < newWindowEndUnbounded; i++) {
if (i < newWindowStart || i > newWindowEnd) {
int index = modulo(i, mNumActiveViews);
if (mActiveViews[index] != null) {
View previousView = mActiveViews[index];
mPreviousViews.add(previousView);
int previousViewRelativeIndex = modulo(index - mCurrentWindowStart,
mNumActiveViews);
animateViewForTransition(previousViewRelativeIndex, -1, previousView);
mActiveViews[index] = null;
}
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).index;
mPreviousViews.add(index);
animateViewForTransition(oldRelativeIndex, -1, previousView);
}
}
// If the window has changed
if (! (newWindowStart == mCurrentWindowStart && newWindowEnd == mCurrentWindowEnd)) {
if (!(newWindowStart == mCurrentWindowStart && newWindowEnd == mCurrentWindowEnd)) {
// Run through the indices in the new range
for (int i = newWindowStart; i <= newWindowEnd; i++) {
int oldRelativeIndex = i - mCurrentWindowStartUnbounded;
int index = modulo(i, adapterCount);
int oldRelativeIndex;
if (mViewsMap.containsKey(index)) {
oldRelativeIndex = mViewsMap.get(index).index;
} else {
oldRelativeIndex = -1;
}
int newRelativeIndex = i - newWindowStartUnbounded;
int index = modulo(i, mNumActiveViews);
// 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
if (i >= mCurrentWindowStart && i <= mCurrentWindowEnd) {
View view = mActiveViews[index];
boolean inOldRange = mViewsMap.containsKey(index) && !mPreviousViews.contains(index);
if (inOldRange) {
View view = mViewsMap.get(index).view;
mViewsMap.get(index).index = newRelativeIndex;
applyTransformForChildAtIndex(view, newRelativeIndex);
animateViewForTransition(oldRelativeIndex, newRelativeIndex, view);
// Otherwise this view is new, so first we have to displace the view that's
// taking the new view's place within our cache (a circular array)
// Otherwise this view is new to the window
} else {
if (mActiveViews[index] != null) {
View previousView = mActiveViews[index];
mPreviousViews.add(previousView);
int previousViewRelativeIndex = modulo(index - mCurrentWindowStart,
mNumActiveViews);
animateViewForTransition(previousViewRelativeIndex, -1, previousView);
if (mCurrentWindowStart > newWindowStart) {
mViewsToBringToFront.add(previousView);
}
}
// We've cleared a spot for the new view. Get it from the adapter, add it
// and apply any transform / animation
View newView = mAdapter.getView(i, null, this);
// Get the new view from the adapter, add it and apply any transform / animation
View newView = mAdapter.getView(modulo(i, adapterCount), null, this);
// 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
@@ -453,12 +476,12 @@ public abstract class AdapterViewAnimator extends AdapterView<Adapter>
if (newView != null) {
fl.addView(newView);
}
mActiveViews[index] = fl;
mViewsMap.put(index, new ViewAndIndex(fl, newRelativeIndex));
addChild(fl);
applyTransformForChildAtIndex(fl, newRelativeIndex);
animateViewForTransition(-1, newRelativeIndex, fl);
}
mActiveViews[index].bringToFront();
mViewsMap.get(index).view.bringToFront();
}
for (int i = 0; i < mViewsToBringToFront.size(); i++) {

View File

@@ -68,6 +68,11 @@ public class AdapterViewFlipper extends AdapterViewAnimator {
com.android.internal.R.styleable.ViewFlipper_flipInterval, DEFAULT_INTERVAL);
mAutoStart = a.getBoolean(
com.android.internal.R.styleable.ViewFlipper_autoStart, false);
// By default we want the flipper to loop
mLoopViews = a.getBoolean(
com.android.internal.R.styleable.AdapterViewAnimator_loopViews, true);
a.recycle();
initDefaultAnimations();
}
@@ -194,8 +199,6 @@ public class AdapterViewFlipper extends AdapterViewAnimator {
super.showPrevious();
}
/**
/**
* Internal method to start or stop dispatching flip {@link Message} based
* on {@link #mRunning} and {@link #mVisible} state.

View File

@@ -132,7 +132,7 @@ public class StackView extends AdapterViewAnimator {
}
private void initStackView() {
configureViewAnimator(NUM_ACTIVE_VIEWS, NUM_ACTIVE_VIEWS - 2, false);
configureViewAnimator(NUM_ACTIVE_VIEWS, NUM_ACTIVE_VIEWS - 2);
setStaticTransformationsEnabled(true);
final ViewConfiguration configuration = ViewConfiguration.get(getContext());
mTouchSlop = configuration.getScaledTouchSlop();
@@ -287,7 +287,6 @@ public class StackView extends AdapterViewAnimator {
// framework level support for drawing outside of a parent's bounds.
private void disableParentalClipping() {
if (mAncestorContainingAllChildren != null) {
Log.v(TAG, "Disabling parental clipping.");
ViewGroup vg = this;
while (vg.getParent() != null && vg.getParent() instanceof ViewGroup) {
if (vg == mAncestorContainingAllChildren) break;
@@ -363,7 +362,9 @@ public class StackView extends AdapterViewAnimator {
if (mAdapter == null) return;
if (mCurrentWindowStartUnbounded + activeIndex == 0) {
if (mLoopViews) {
mStackSlider.setMode(StackSlider.NORMAL_MODE);
} else if (mCurrentWindowStartUnbounded + activeIndex == 0) {
mStackSlider.setMode(StackSlider.BEGINNING_OF_STACK_MODE);
} else if (mCurrentWindowStartUnbounded + activeIndex == mAdapter.getCount()) {
activeIndex--;

View File

@@ -2384,8 +2384,11 @@
<attr name="inAnimation" />
<!-- Identifier for the animation to use when a view is hidden. -->
<attr name="outAnimation" />
<!--Defines whether the animator loops to the first view once it
has reached the end of the list. -->
<attr name="loopViews" format="boolean" />
<!-- Defines whether to animate the current View when the ViewAnimation
is first displayed. -->
is first displayed. -->
<attr name="animateFirstView" />
</declare-styleable>
<declare-styleable name="AdapterViewFlipper">

View File

@@ -1360,6 +1360,7 @@
<public type="attr" name="breadCrumbShortTitle" />
<public type="attr" name="listDividerAlertDialog" />
<public type="attr" name="textColorAlertDialogListItem" />
<public type="attr" name="loopViews" />
<public type="anim" name="animator_fade_in" />
<public type="anim" name="animator_fade_out" />