diff options
author | Adam Cohen <adamcohen@google.com> | 2010-07-22 16:00:07 -0700 |
---|---|---|
committer | Adam Cohen <adamcohen@google.com> | 2010-08-10 15:09:31 -0700 |
commit | 44729e3d1c01265858eec566c7b7c676c46a7916 (patch) | |
tree | 7c544b4386112fb6f857b6b5e752791373d53651 /core/java/android/widget/AdapterViewAnimator.java | |
parent | a774765686bd61b01b9b0386c35c338c61a46225 (diff) | |
download | frameworks_base-44729e3d1c01265858eec566c7b7c676c46a7916.zip frameworks_base-44729e3d1c01265858eec566c7b7c676c46a7916.tar.gz frameworks_base-44729e3d1c01265858eec566c7b7c676c46a7916.tar.bz2 |
Preliminary implementation of StackView, which extends AdapterViewAnimator.
Change-Id: I3e9d1203fc8848835f28d6bc1c9dc0a3fcf7f242
Diffstat (limited to 'core/java/android/widget/AdapterViewAnimator.java')
-rw-r--r-- | core/java/android/widget/AdapterViewAnimator.java | 419 |
1 files changed, 345 insertions, 74 deletions
diff --git a/core/java/android/widget/AdapterViewAnimator.java b/core/java/android/widget/AdapterViewAnimator.java index a6d5170..c335a8b 100644 --- a/core/java/android/widget/AdapterViewAnimator.java +++ b/core/java/android/widget/AdapterViewAnimator.java @@ -16,14 +16,22 @@ package android.widget; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedList; + +import android.animation.PropertyAnimator; import android.content.Context; import android.content.Intent; import android.content.res.TypedArray; +import android.graphics.Rect; import android.os.Handler; import android.os.Looper; import android.util.AttributeSet; import android.util.Log; import android.view.View; +import android.view.ViewGroup; +import android.view.ViewGroup.LayoutParams; import android.view.animation.Animation; import android.view.animation.AnimationUtils; @@ -35,23 +43,100 @@ import android.view.animation.AnimationUtils; * @attr ref android.R.styleable#AdapterViewAnimator_outAnimation * @attr ref android.R.styleable#AdapterViewAnimator_animateFirstView */ -public class AdapterViewAnimator extends AdapterView<Adapter> implements RemoteViewsAdapter.RemoteAdapterConnectionCallback{ +public abstract class AdapterViewAnimator extends AdapterView<Adapter> + implements RemoteViewsAdapter.RemoteAdapterConnectionCallback{ 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 {@link #mActiveOffset} + */ int mWhichChild = 0; - boolean mFirstTime = true; + + /** + * 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 <code>mDisplayedChild</code> sits + */ + int mActiveOffset = 0; + + /** + * 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 mNumActiveViews = 1; + + /** + * Array of the children of the {@link AdapterViewAnimator}. This array + * is accessed in a circular fashion + */ + View[] mActiveViews; + + /** + * List of views pending removal from the {@link AdapterViewAnimator} + */ + ArrayList<View> mPreviousViews; + + /** + * 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 + */ + int mCurrentWindowEnd = -1; + + /** + * The same as {@link #mCurrentWindowStart}, except when the we have bounded + * {@link #mCurrentWindowStart} to be non-negative + */ + int mCurrentWindowStartUnbounded = 0; + + /** + * Indicates whether to treat the adapter to be a circular structure, ie. + * the view before 0 is considered to be <code>mAdapter.getCount() - 1</code> + * + * TODO: this doesn't do anything yet + * + */ + boolean mCycleViews = false; + + /** + * Handler to post events to the main thread + */ + Handler mMainQueue; + + /** + * Listens for data changes from the adapter + */ AdapterDataSetObserver mDataSetObserver; - View mPreviousView; - View mCurrentView; + /** + * The {@link Adapter} for this {@link AdapterViewAnimator} + */ + Adapter mAdapter; + + /** + * The {@link RemoteViewsAdapter} for this {@link AdapterViewAnimator} + */ + RemoteViewsAdapter mRemoteViewsAdapter; + + /** + * Specifies whether this is the first time the animator is showing views + */ + boolean mFirstTime = true; + /** + * TODO: Animation stuff is still in flux, waiting on the new framework to settle a bit. + */ Animation mInAnimation; Animation mOutAnimation; - Adapter mAdapter; - RemoteViewsAdapter mRemoteViewsAdapter; - private Handler mMainQueue; + private ArrayList<View> mViewsToBringToFront; public AdapterViewAnimator(Context context) { super(context); @@ -61,8 +146,10 @@ public class AdapterViewAnimator extends AdapterView<Adapter> implements RemoteV public AdapterViewAnimator(Context context, AttributeSet attrs) { super(context, attrs); - TypedArray a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.ViewAnimator); - int resource = a.getResourceId(com.android.internal.R.styleable.ViewAnimator_inAnimation, 0); + TypedArray a = context.obtainStyledAttributes(attrs, + com.android.internal.R.styleable.ViewAnimator); + int resource = a.getResourceId( + com.android.internal.R.styleable.ViewAnimator_inAnimation, 0); if (resource > 0) { setInAnimation(context, resource); } @@ -72,7 +159,8 @@ public class AdapterViewAnimator extends AdapterView<Adapter> implements RemoteV setOutAnimation(context, resource); } - boolean flag = a.getBoolean(com.android.internal.R.styleable.ViewAnimator_animateFirstView, true); + boolean flag = a.getBoolean( + com.android.internal.R.styleable.ViewAnimator_animateFirstView, true); setAnimateFirstView(flag); a.recycle(); @@ -85,6 +173,54 @@ public class AdapterViewAnimator extends AdapterView<Adapter> implements RemoteV */ private void initViewAnimator(Context context, AttributeSet attrs) { mMainQueue = new Handler(Looper.myLooper()); + mActiveViews = new View[mNumActiveViews]; + mPreviousViews = new ArrayList<View>(); + mViewsToBringToFront = new ArrayList<View>(); + } + + /** + * 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} 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. + */ + void configureViewAnimator(int numVisibleViews, int activeOffset) { + if (activeOffset > numVisibleViews - 1) { + // Throw an exception here. + } + mNumActiveViews = numVisibleViews; + mActiveOffset = activeOffset; + mActiveViews = new View[mNumActiveViews]; + mPreviousViews.clear(); + removeAllViewsInLayout(); + mCurrentWindowStart = 0; + mCurrentWindowEnd = -1; + } + + /** + * 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 animateViewForTransition(int fromIndex, int toIndex, View view) { + PropertyAnimator pa; + if (fromIndex == -1) { + pa = new PropertyAnimator(400, view, "alpha", 0.0f, 1.0f); + pa.start(); + } else if (toIndex == -1) { + pa = new PropertyAnimator(400, view, "alpha", 1.0f, 0.0f); + pa.start(); + } } /** @@ -114,18 +250,28 @@ public class AdapterViewAnimator extends AdapterView<Adapter> implements RemoteV /** * Return default inAnimation. To be overriden by subclasses. */ - public Animation getDefaultInAnimation() { + Animation getDefaultInAnimation() { return null; } /** - * Return default outAnimation. To be overriden by subclasses. + * Return default outAnimation. To be overridden by subclasses. */ - public Animation getDefaultOutAnimation() { + Animation getDefaultOutAnimation() { return null; } /** + * 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. */ public int getDisplayedChild() { @@ -160,70 +306,137 @@ public class AdapterViewAnimator extends AdapterView<Adapter> implements RemoteV showOnly(childIndex, animate, false); } - private LayoutParams makeLayoutParams() { - int width = mMeasuredWidth - mPaddingLeft - mPaddingRight; - int height = mMeasuredHeight - mPaddingTop - mPaddingBottom; - return new LayoutParams(width, height); + private int modulo(int pos, int size) { + return (size + (pos % size)) % size; } - protected void showOnly(int childIndex, boolean animate, boolean onLayout) { - if (mAdapter != null) { - // The previous view should be removed from the ViewGroup - if (mPreviousView != null) { - mPreviousView.clearAnimation(); + /** + * 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 <= mNumActiveViews - 1) { + int index = mCurrentWindowStartUnbounded + relativeIndex; + return mActiveViews[modulo(index, mNumActiveViews)]; + } + return null; + } - // TODO: this is where we would store the the view for - // recycling - removeViewInLayout(mPreviousView); - } + private LayoutParams createOrReuseLayoutParams(View v) { + final LayoutParams currentLp = (LayoutParams) v.getLayoutParams(); + if (currentLp instanceof LayoutParams) { + return currentLp; + } + return new LayoutParams(v); + } - // If the current view is still being animated, we should - // force the animation to end - if (mCurrentView != null) { - mCurrentView.clearAnimation(); - } + void showOnly(int childIndex, boolean animate, boolean onLayout) { + if (mAdapter == null) return; - // load the new mCurrentView from our adapter - mPreviousView = mCurrentView; - mCurrentView = mAdapter.getView(childIndex, null, this); - if (mPreviousView != mCurrentView) { - addViewInLayout(mCurrentView, 0, makeLayoutParams(), true); - mCurrentView.bringToFront(); + for (int i = 0; i < mPreviousViews.size(); i++) { + View viewToRemove = mPreviousViews.get(i); + viewToRemove.clearAnimation(); + // 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 + mNumActiveViews - 1; + int newWindowStart = Math.max(0, newWindowStartUnbounded); + int newWindowEnd = Math.min(mAdapter.getCount(), newWindowEndUnbounded); + + // This section clears out any items that are in our mActiveViews 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); + } } + } + // If the window has changed + 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 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]; + 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) + } 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); + } + } - - // Animate as necessary - if (mPreviousView != null && mPreviousView != mCurrentView) { - if (animate && mOutAnimation != null) { - mPreviousView.startAnimation(mOutAnimation); + // 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); + if (newView != null) { + mActiveViews[index] = newView; + addViewInLayout(newView, -1, createOrReuseLayoutParams(newView)); + applyTransformForChildAtIndex(newView, newRelativeIndex); + animateViewForTransition(-1, newRelativeIndex, newView); + } } - // This line results in the view becoming invisible *after* - // the above animation is complete, or, if there is no animation - // then it becomes invisble immediately - mPreviousView.setVisibility(View.GONE); + mActiveViews[index].bringToFront(); } - if (mCurrentView != null && animate && mInAnimation != null) { - mCurrentView.startAnimation(mInAnimation); + for (int i = 0; i < mViewsToBringToFront.size(); i++) { + View v = mViewsToBringToFront.get(i); + v.bringToFront(); } + mViewsToBringToFront.clear(); - mFirstTime = false; - if (!onLayout) { - requestLayout(); - invalidate(); - } else { - // If the Adapter tries to layout the current view when we get it using getView above - // the layout will end up being ignored since we are currently laying out, so - // we post a delayed requestLayout and invalidate - mMainQueue.post(new Runnable() { - @Override - public void run() { - mCurrentView.requestLayout(); - mCurrentView.invalidate(); - } - }); - } + mCurrentWindowStart = newWindowStart; + mCurrentWindowEnd = newWindowEnd; + mCurrentWindowStartUnbounded = newWindowStartUnbounded; + } + + mFirstTime = false; + if (!onLayout) { + requestLayout(); + invalidate(); + } else { + // If the Adapter tries to layout the current view when we get it using getView + // above the layout will end up being ignored since we are currently laying out, so + // we post a delayed requestLayout and invalidate + mMainQueue.post(new Runnable() { + @Override + public void run() { + requestLayout(); + invalidate(); + } + }); } } @@ -247,10 +460,11 @@ public class AdapterViewAnimator extends AdapterView<Adapter> implements RemoteV int childRight = mPaddingLeft + child.getMeasuredWidth(); int childBottom = mPaddingTop + child.getMeasuredHeight(); + LayoutParams lp = (LayoutParams) child.getLayoutParams(); - child.layout(mPaddingLeft, mPaddingTop, childRight, childBottom); + child.layout(mPaddingLeft + lp.horizontalOffset, mPaddingTop + lp.verticalOffset, + childRight + lp.horizontalOffset, childBottom + lp.verticalOffset); } - mDataChanged = false; } @@ -261,8 +475,6 @@ public class AdapterViewAnimator extends AdapterView<Adapter> implements RemoteV int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec); int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec); - Log.v(TAG, "onMeasure"); - for (int i = 0; i < count; i++) { final View child = getChildAt(i); @@ -278,7 +490,6 @@ public class AdapterViewAnimator extends AdapterView<Adapter> implements RemoteV child.measure(childWidthMeasureSpec, childheightMeasureSpec); } - setMeasuredDimension(widthSpecSize, heightSpecSize); } @@ -302,7 +513,7 @@ public class AdapterViewAnimator extends AdapterView<Adapter> implements RemoteV * @see #getDisplayedChild() */ public View getCurrentView() { - return mCurrentView; + return getViewAtRelativeIndex(mActiveOffset); } /** @@ -412,12 +623,15 @@ public class AdapterViewAnimator extends AdapterView<Adapter> implements RemoteV mDataSetObserver = new AdapterDataSetObserver(); mAdapter.registerDataSetObserver(mDataSetObserver); } + setFocusable(true); } /** - * 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. + * 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 public void setRemoteViewsAdapter(Intent intent) { @@ -431,7 +645,7 @@ public class AdapterViewAnimator extends AdapterView<Adapter> implements RemoteV @Override public View getSelectedView() { - return mCurrentView; + return getViewAtRelativeIndex(mActiveOffset); } /** @@ -452,4 +666,61 @@ public class AdapterViewAnimator extends AdapterView<Adapter> implements RemoteV setAdapter(mRemoteViewsAdapter); } } + + static class LayoutParams extends ViewGroup.LayoutParams { + int horizontalOffset; + int verticalOffset; + View mView; + + LayoutParams(View view) { + super(0, 0); + horizontalOffset = 0; + verticalOffset = 0; + mView = view; + } + + LayoutParams(Context c, AttributeSet attrs) { + super(c, attrs); + horizontalOffset = 0; + verticalOffset = 0; + } + + void setHorizontalOffset(int newHorizontalOffset) { + horizontalOffset = newHorizontalOffset; + if (mView != null) { + mView.requestLayout(); + mView.invalidate(); + } + } + + private Rect parentRect = new Rect(); + void invalidateGlobalRegion(View v, Rect r) { + View p = v; + boolean firstPass = true; + parentRect.set(0, 0, 0, 0); + while (p.getParent() != null && p.getParent() instanceof View + && !parentRect.contains(r)) { + if (!firstPass) r.offset(p.getLeft() - p.getScrollX(), p.getTop() - p.getScrollY()); + firstPass = false; + p = (View) p.getParent(); + parentRect.set(p.getLeft() - p.getScrollX(), p.getTop() - p.getScrollY(), + p.getRight() - p.getScrollX(), p.getBottom() - p.getScrollY()); + } + p.invalidate(r.left, r.top, r.right, r.bottom); + } + + private Rect invalidateRect = new Rect(); + // This is public so that PropertyAnimator can access it + public void setVerticalOffset(int newVerticalOffset) { + int offsetDelta = newVerticalOffset - verticalOffset; + verticalOffset = newVerticalOffset; + if (mView != null) { + mView.requestLayout(); + int top = Math.min(mView.getTop() + offsetDelta, mView.getTop()); + int bottom = Math.max(mView.getBottom() + offsetDelta, mView.getBottom()); + invalidateRect.set(mView.getLeft(), top, mView.getRight(), bottom); + invalidateGlobalRegion(mView, invalidateRect); + } + } + } } |