diff options
author | Adam Cohen <adamcohen@google.com> | 2010-07-19 22:41:57 -0700 |
---|---|---|
committer | Adam Cohen <adamcohen@google.com> | 2010-07-22 13:37:47 -0700 |
commit | 3db40678d33c2b5f90c380966d36b3e10ed11f05 (patch) | |
tree | aa1e6ca4d79235b150158a1ef8e7e6e8f4d0cf96 /core/java/android | |
parent | 950d6a984a49eac8e688a66a79a55c83e92eb869 (diff) | |
download | frameworks_base-3db40678d33c2b5f90c380966d36b3e10ed11f05.zip frameworks_base-3db40678d33c2b5f90c380966d36b3e10ed11f05.tar.gz frameworks_base-3db40678d33c2b5f90c380966d36b3e10ed11f05.tar.bz2 |
Added AdapterViewAnimator and AdapterViewFlipper which are versions of ViewAnimator and ViewFlipper
whos views are defined by adapters.
Change-Id: I6ca1681b4820e6a1b6b69fc6d92c11c9f969bb88
Diffstat (limited to 'core/java/android')
-rw-r--r-- | core/java/android/widget/AdapterViewAnimator.java | 455 | ||||
-rw-r--r-- | core/java/android/widget/AdapterViewFlipper.java | 238 | ||||
-rw-r--r-- | core/java/android/widget/RemoteViewsAdapter.java | 1 |
3 files changed, 694 insertions, 0 deletions
diff --git a/core/java/android/widget/AdapterViewAnimator.java b/core/java/android/widget/AdapterViewAnimator.java new file mode 100644 index 0000000..a6d5170 --- /dev/null +++ b/core/java/android/widget/AdapterViewAnimator.java @@ -0,0 +1,455 @@ +/* + * 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.content.Context; +import android.content.Intent; +import android.content.res.TypedArray; +import android.os.Handler; +import android.os.Looper; +import android.util.AttributeSet; +import android.util.Log; +import android.view.View; +import android.view.animation.Animation; +import android.view.animation.AnimationUtils; + +/** + * 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 + */ +public class AdapterViewAnimator extends AdapterView<Adapter> implements RemoteViewsAdapter.RemoteAdapterConnectionCallback{ + private static final String TAG = "RemoteViewAnimator"; + + int mWhichChild = 0; + boolean mFirstTime = true; + boolean mAnimateFirstTime = true; + + AdapterDataSetObserver mDataSetObserver; + + View mPreviousView; + View mCurrentView; + + Animation mInAnimation; + Animation mOutAnimation; + Adapter mAdapter; + RemoteViewsAdapter mRemoteViewsAdapter; + private Handler mMainQueue; + + public AdapterViewAnimator(Context context) { + super(context); + initViewAnimator(context, null); + } + + 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); + if (resource > 0) { + setInAnimation(context, resource); + } + + resource = a.getResourceId(com.android.internal.R.styleable.ViewAnimator_outAnimation, 0); + if (resource > 0) { + setOutAnimation(context, resource); + } + + boolean flag = a.getBoolean(com.android.internal.R.styleable.ViewAnimator_animateFirstView, true); + setAnimateFirstView(flag); + + a.recycle(); + + initViewAnimator(context, attrs); + } + + /** + * Initialize this {@link AdapterViewAnimator} + */ + private void initViewAnimator(Context context, AttributeSet attrs) { + mMainQueue = new Handler(Looper.myLooper()); + } + + /** + * Sets which child view will be displayed. + * + * @param whichChild the index of the child view to display + */ + public void setDisplayedChild(int whichChild) { + if (mAdapter != null) { + mWhichChild = whichChild; + if (whichChild >= mAdapter.getCount()) { + mWhichChild = 0; + } else if (whichChild < 0) { + mWhichChild = mAdapter.getCount() - 1; + } + + boolean hasFocus = getFocusedChild() != null; + // This will clear old focus if we had it + showOnly(mWhichChild); + if (hasFocus) { + // Try to retake focus if we had it + requestFocus(FOCUS_FORWARD); + } + } + } + + /** + * Return default inAnimation. To be overriden by subclasses. + */ + public Animation getDefaultInAnimation() { + return null; + } + + /** + * Return default outAnimation. To be overriden by subclasses. + */ + public Animation getDefaultOutAnimation() { + return null; + } + + /** + * Returns the index of the currently displayed child view. + */ + public int getDisplayedChild() { + return mWhichChild; + } + + /** + * Manually shows the next child. + */ + public void showNext() { + setDisplayedChild(mWhichChild + 1); + } + + /** + * Manually shows the previous child. + */ + public void showPrevious() { + setDisplayedChild(mWhichChild - 1); + } + + /** + * 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) { + showOnly(childIndex, animate, false); + } + + private LayoutParams makeLayoutParams() { + int width = mMeasuredWidth - mPaddingLeft - mPaddingRight; + int height = mMeasuredHeight - mPaddingTop - mPaddingBottom; + return new LayoutParams(width, height); + } + + 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(); + + // TODO: this is where we would store the the view for + // recycling + removeViewInLayout(mPreviousView); + } + + // If the current view is still being animated, we should + // force the animation to end + if (mCurrentView != null) { + mCurrentView.clearAnimation(); + } + + // 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(); + } + + + + // Animate as necessary + if (mPreviousView != null && mPreviousView != mCurrentView) { + if (animate && mOutAnimation != null) { + mPreviousView.startAnimation(mOutAnimation); + } + // 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); + } + + if (mCurrentView != null && animate && mInAnimation != null) { + mCurrentView.startAnimation(mInAnimation); + } + + 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(); + } + }); + } + } + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + boolean dataChanged = mDataChanged; + if (dataChanged) { + 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 >= mAdapter.getCount()) + mWhichChild = 0; + + showOnly(mWhichChild, true, true); + } + + 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); + } + + mDataChanged = false; + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + final int count = getChildCount(); + + 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); + + final LayoutParams lp = (LayoutParams) child.getLayoutParams(); + + lp.width = widthSpecSize - mPaddingLeft - mPaddingRight; + lp.height = heightSpecSize - mPaddingTop - mPaddingBottom; + + int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(lp.width, + MeasureSpec.EXACTLY); + int childheightMeasureSpec = MeasureSpec.makeMeasureSpec(lp.height, + MeasureSpec.EXACTLY); + + child.measure(childWidthMeasureSpec, childheightMeasureSpec); + } + + setMeasuredDimension(widthSpecSize, heightSpecSize); + } + + /** + * Shows only the specified child. The other displays Views exit the screen + * with the {@link #getOutAnimation() out animation} and the specified child + * enters the screen with the {@link #getInAnimation() in animation}. + * + * @param childIndex The index of the child to be shown. + */ + void showOnly(int childIndex) { + final boolean animate = (!mFirstTime || mAnimateFirstTime); + showOnly(childIndex, animate); + } + + /** + * Returns the View corresponding to the currently displayed child. + * + * @return The View currently displayed. + * + * @see #getDisplayedChild() + */ + public View getCurrentView() { + return mCurrentView; + } + + /** + * 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.view.animation.Animation) + * @see #setInAnimation(android.content.Context, int) + */ + public Animation getInAnimation() { + return mInAnimation; + } + + /** + * 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(Animation inAnimation) { + mInAnimation = inAnimation; + } + + /** + * 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.view.animation.Animation) + * @see #setOutAnimation(android.content.Context, int) + */ + public Animation getOutAnimation() { + return mOutAnimation; + } + + /** + * 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(Animation outAnimation) { + mOutAnimation = outAnimation; + } + + /** + * 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.view.animation.Animation) + */ + public void setInAnimation(Context context, int resourceID) { + setInAnimation(AnimationUtils.loadAnimation(context, resourceID)); + } + + /** + * 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.view.animation.Animation) + */ + public void setOutAnimation(Context context, int resourceID) { + setOutAnimation(AnimationUtils.loadAnimation(context, resourceID)); + } + + /** + * 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) { + mAdapter = adapter; + + if (mAdapter != null) { + if (mDataSetObserver != null) { + mAdapter.unregisterDataSetObserver(mDataSetObserver); + } + + mDataSetObserver = new AdapterDataSetObserver(); + mAdapter.registerDataSetObserver(mDataSetObserver); + } + } + + /** + * 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) { + mRemoteViewsAdapter = new RemoteViewsAdapter(getContext(), intent, this); + } + + @Override + public void setSelection(int position) { + setDisplayedChild(position); + } + + @Override + public View getSelectedView() { + return mCurrentView; + } + + /** + * Called back when the adapter connects to the RemoteViewsService. + */ + public void onRemoteAdapterConnected() { + if (mRemoteViewsAdapter != mAdapter) { + setAdapter(mRemoteViewsAdapter); + } + } + + /** + * Called back when the adapter disconnects from the RemoteViewsService. + */ + public void onRemoteAdapterDisconnected() { + if (mRemoteViewsAdapter != mAdapter) { + mRemoteViewsAdapter = null; + setAdapter(mRemoteViewsAdapter); + } + } +} diff --git a/core/java/android/widget/AdapterViewFlipper.java b/core/java/android/widget/AdapterViewFlipper.java new file mode 100644 index 0000000..901c761 --- /dev/null +++ b/core/java/android/widget/AdapterViewFlipper.java @@ -0,0 +1,238 @@ +/* + * 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.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.res.TypedArray; +import android.os.Handler; +import android.os.Message; +import android.util.AttributeSet; +import android.util.Log; +import android.view.animation.AlphaAnimation; +import android.view.animation.Animation; +import android.widget.RemoteViews.RemoteView; + +/** + * Simple {@link ViewAnimator} that will animate between two or more views + * that have been added to it. Only one child is shown at a time. If + * requested, can automatically flip between each child at a regular interval. + * + * @attr ref android.R.styleable#AdapterViewFlipper_flipInterval + * @attr ref android.R.styleable#AdapterViewFlipper_autoStart + */ +@RemoteView +public class AdapterViewFlipper extends AdapterViewAnimator { + private static final String TAG = "ViewFlipper"; + private static final boolean LOGD = false; + + private static final int DEFAULT_INTERVAL = 10000; + private static final int DEFAULT_ANIMATION_DURATION = 200; + + private int mFlipInterval = DEFAULT_INTERVAL; + private int mAnimationDuration = DEFAULT_ANIMATION_DURATION; + private boolean mAutoStart = false; + + private boolean mRunning = false; + private boolean mStarted = false; + private boolean mVisible = false; + private boolean mUserPresent = true; + + public AdapterViewFlipper(Context context) { + super(context); + initDefaultAnimations(); + } + + public AdapterViewFlipper(Context context, AttributeSet attrs) { + super(context, attrs); + + TypedArray a = context.obtainStyledAttributes(attrs, + com.android.internal.R.styleable.ViewFlipper); + mFlipInterval = a.getInt( + com.android.internal.R.styleable.ViewFlipper_flipInterval, DEFAULT_INTERVAL); + mAutoStart = a.getBoolean( + com.android.internal.R.styleable.ViewFlipper_autoStart, false); + a.recycle(); + initDefaultAnimations(); + } + + private void initDefaultAnimations() { + // Set the default animations to be fade in/out + if (mInAnimation == null) { + mInAnimation = new AlphaAnimation(0.0f, 1.0f); + mInAnimation.setDuration(mAnimationDuration); + } + if (mOutAnimation == null) { + mOutAnimation = new AlphaAnimation(1.0f, 0.0f); + mOutAnimation.setDuration(mAnimationDuration); + } + } + + private final BroadcastReceiver mReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + final String action = intent.getAction(); + if (Intent.ACTION_SCREEN_OFF.equals(action)) { + mUserPresent = false; + updateRunning(); + } else if (Intent.ACTION_USER_PRESENT.equals(action)) { + mUserPresent = true; + updateRunning(false); + } + } + }; + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + + // Listen for broadcasts related to user-presence + final IntentFilter filter = new IntentFilter(); + filter.addAction(Intent.ACTION_SCREEN_OFF); + filter.addAction(Intent.ACTION_USER_PRESENT); + getContext().registerReceiver(mReceiver, filter); + + if (mAutoStart) { + // Automatically start when requested + startFlipping(); + } + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + mVisible = false; + + getContext().unregisterReceiver(mReceiver); + updateRunning(); + } + + @Override + protected void onWindowVisibilityChanged(int visibility) { + super.onWindowVisibilityChanged(visibility); + mVisible = (visibility == VISIBLE); + updateRunning(false); + } + + @Override + public void setAdapter(Adapter adapter) { + super.setAdapter(adapter); + updateRunning(); + } + + /** + * How long to wait before flipping to the next view + * + * @param milliseconds + * time in milliseconds + */ + public void setFlipInterval(int milliseconds) { + mFlipInterval = milliseconds; + } + + /** + * Start a timer to cycle through child views + */ + public void startFlipping() { + mStarted = true; + updateRunning(); + } + + /** + * No more flips + */ + public void stopFlipping() { + mStarted = false; + updateRunning(); + } + + /** + * Internal method to start or stop dispatching flip {@link Message} based + * on {@link #mRunning} and {@link #mVisible} state. + */ + private void updateRunning() { + // by default when we update running, we want the + // current view to animate in + updateRunning(true); + } + + /** + * Internal method to start or stop dispatching flip {@link Message} based + * on {@link #mRunning} and {@link #mVisible} state. + * + * @param flipNow Determines whether or not to execute the animation now, in + * addition to queuing future flips. If omitted, defaults to + * true. + */ + private void updateRunning(boolean flipNow) { + boolean running = mVisible && mStarted && mUserPresent && mAdapter != null; + if (running != mRunning) { + if (running) { + showOnly(mWhichChild, flipNow); + Message msg = mHandler.obtainMessage(FLIP_MSG); + mHandler.sendMessageDelayed(msg, mFlipInterval); + } else { + mHandler.removeMessages(FLIP_MSG); + } + mRunning = running; + } + if (LOGD) { + Log.d(TAG, "updateRunning() mVisible=" + mVisible + ", mStarted=" + mStarted + + ", mUserPresent=" + mUserPresent + ", mRunning=" + mRunning); + } + } + + /** + * Returns true if the child views are flipping. + */ + public boolean isFlipping() { + return mStarted; + } + + /** + * Set if this view automatically calls {@link #startFlipping()} when it + * becomes attached to a window. + */ + public void setAutoStart(boolean autoStart) { + mAutoStart = autoStart; + } + + /** + * Returns true if this view automatically calls {@link #startFlipping()} + * when it becomes attached to a window. + */ + public boolean isAutoStart() { + return mAutoStart; + } + + private final int FLIP_MSG = 1; + + private final Handler mHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + if (msg.what == FLIP_MSG) { + if (mRunning) { + showNext(); + msg = obtainMessage(FLIP_MSG); + sendMessageDelayed(msg, mFlipInterval); + } + } + } + }; +} diff --git a/core/java/android/widget/RemoteViewsAdapter.java b/core/java/android/widget/RemoteViewsAdapter.java index d426033..07a54eb 100644 --- a/core/java/android/widget/RemoteViewsAdapter.java +++ b/core/java/android/widget/RemoteViewsAdapter.java @@ -467,6 +467,7 @@ public class RemoteViewsAdapter extends BaseAdapter { flipper.invalidate(); } else { // hide the loading view and show the item view + flipper.setVisibility(View.VISIBLE); for (int i = 0; i < flipper.getChildCount() - 1; ++i) { flipper.getChildAt(i).setVisibility(View.GONE); } |