summaryrefslogtreecommitdiffstats
path: root/core/java/android/widget
diff options
context:
space:
mode:
authorAdam Cohen <adamcohen@google.com>2010-07-19 22:41:57 -0700
committerAdam Cohen <adamcohen@google.com>2010-07-22 13:37:47 -0700
commit3db40678d33c2b5f90c380966d36b3e10ed11f05 (patch)
treeaa1e6ca4d79235b150158a1ef8e7e6e8f4d0cf96 /core/java/android/widget
parent950d6a984a49eac8e688a66a79a55c83e92eb869 (diff)
downloadframeworks_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/widget')
-rw-r--r--core/java/android/widget/AdapterViewAnimator.java455
-rw-r--r--core/java/android/widget/AdapterViewFlipper.java238
-rw-r--r--core/java/android/widget/RemoteViewsAdapter.java1
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);
}