1 /*
2  * Copyright (C) 2010 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.widget;
18 
19 import android.content.Context;
20 import android.content.res.TypedArray;
21 import android.os.Message;
22 import android.util.AttributeSet;
23 import android.util.Log;
24 import android.view.RemotableViewMethod;
25 import android.widget.RemoteViews.RemoteView;
26 
27 /**
28  * Simple {@link ViewAnimator} that will animate between two or more views
29  * that have been added to it.  Only one child is shown at a time.  If
30  * requested, can automatically flip between each child at a regular interval.
31  *
32  * @attr ref android.R.styleable#AdapterViewFlipper_flipInterval
33  * @attr ref android.R.styleable#AdapterViewFlipper_autoStart
34  */
35 @RemoteView
36 public class AdapterViewFlipper extends AdapterViewAnimator {
37     private static final String TAG = "ViewFlipper";
38     private static final boolean LOGD = false;
39 
40     private static final int DEFAULT_INTERVAL = 10000;
41 
42     private int mFlipInterval = DEFAULT_INTERVAL;
43     private boolean mAutoStart = false;
44 
45     private boolean mRunning = false;
46     private boolean mStarted = false;
47     private boolean mVisible = false;
48     private boolean mAdvancedByHost = false;
49 
AdapterViewFlipper(Context context)50     public AdapterViewFlipper(Context context) {
51         super(context);
52     }
53 
AdapterViewFlipper(Context context, AttributeSet attrs)54     public AdapterViewFlipper(Context context, AttributeSet attrs) {
55         this(context, attrs, 0);
56     }
57 
AdapterViewFlipper(Context context, AttributeSet attrs, int defStyleAttr)58     public AdapterViewFlipper(Context context, AttributeSet attrs, int defStyleAttr) {
59         this(context, attrs, defStyleAttr, 0);
60     }
61 
AdapterViewFlipper( Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)62     public AdapterViewFlipper(
63             Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
64         super(context, attrs, defStyleAttr, defStyleRes);
65 
66         final TypedArray a = context.obtainStyledAttributes(attrs,
67                 com.android.internal.R.styleable.AdapterViewFlipper, defStyleAttr, defStyleRes);
68         saveAttributeDataForStyleable(context, com.android.internal.R.styleable.AdapterViewFlipper,
69                 attrs, a, defStyleAttr, defStyleRes);
70         mFlipInterval = a.getInt(
71                 com.android.internal.R.styleable.AdapterViewFlipper_flipInterval, DEFAULT_INTERVAL);
72         mAutoStart = a.getBoolean(
73                 com.android.internal.R.styleable.AdapterViewFlipper_autoStart, false);
74 
75         // A view flipper should cycle through the views
76         mLoopViews = true;
77 
78         a.recycle();
79     }
80 
81     @Override
onAttachedToWindow()82     protected void onAttachedToWindow() {
83         super.onAttachedToWindow();
84 
85         if (mAutoStart) {
86             // Automatically start when requested
87             startFlipping();
88         }
89     }
90 
91     @Override
onDetachedFromWindow()92     protected void onDetachedFromWindow() {
93         super.onDetachedFromWindow();
94         mVisible = false;
95         updateRunning();
96     }
97 
98     @Override
onWindowVisibilityChanged(int visibility)99     protected void onWindowVisibilityChanged(int visibility) {
100         super.onWindowVisibilityChanged(visibility);
101         mVisible = (visibility == VISIBLE);
102         updateRunning(false);
103     }
104 
105     @Override
setAdapter(Adapter adapter)106     public void setAdapter(Adapter adapter) {
107         super.setAdapter(adapter);
108         updateRunning();
109     }
110 
111     /**
112      * Returns the flip interval, in milliseconds.
113      *
114      * @return the flip interval in milliseconds
115      *
116      * @see #setFlipInterval(int)
117      *
118      * @attr ref android.R.styleable#AdapterViewFlipper_flipInterval
119      */
getFlipInterval()120     public int getFlipInterval() {
121         return mFlipInterval;
122     }
123 
124     /**
125      * How long to wait before flipping to the next view.
126      *
127      * @param flipInterval flip interval in milliseconds
128      *
129      * @see #getFlipInterval()
130      *
131      * @attr ref android.R.styleable#AdapterViewFlipper_flipInterval
132      */
setFlipInterval(int flipInterval)133     public void setFlipInterval(int flipInterval) {
134         mFlipInterval = flipInterval;
135     }
136 
137     /**
138      * Start a timer to cycle through child views
139      */
startFlipping()140     public void startFlipping() {
141         mStarted = true;
142         updateRunning();
143     }
144 
145     /**
146      * No more flips
147      */
stopFlipping()148     public void stopFlipping() {
149         mStarted = false;
150         updateRunning();
151     }
152 
153     /**
154     * {@inheritDoc}
155     */
156    @Override
157    @RemotableViewMethod
showNext()158    public void showNext() {
159        // if the flipper is currently flipping automatically, and showNext() is called
160        // we should we should make sure to reset the timer
161        if (mRunning) {
162            removeCallbacks(mFlipRunnable);
163            postDelayed(mFlipRunnable, mFlipInterval);
164        }
165        super.showNext();
166    }
167 
168    /**
169     * {@inheritDoc}
170     */
171    @Override
172    @RemotableViewMethod
showPrevious()173    public void showPrevious() {
174        // if the flipper is currently flipping automatically, and showPrevious() is called
175        // we should we should make sure to reset the timer
176        if (mRunning) {
177            removeCallbacks(mFlipRunnable);
178            postDelayed(mFlipRunnable, mFlipInterval);
179        }
180        super.showPrevious();
181    }
182 
183     /**
184      * Internal method to start or stop dispatching flip {@link Message} based
185      * on {@link #mRunning} and {@link #mVisible} state.
186      */
updateRunning()187     private void updateRunning() {
188         // by default when we update running, we want the
189         // current view to animate in
190         updateRunning(true);
191     }
192 
193     /**
194      * Internal method to start or stop dispatching flip {@link Message} based
195      * on {@link #mRunning} and {@link #mVisible} state.
196      *
197      * @param flipNow Determines whether or not to execute the animation now, in
198      *            addition to queuing future flips. If omitted, defaults to
199      *            true.
200      */
updateRunning(boolean flipNow)201     private void updateRunning(boolean flipNow) {
202         boolean running = !mAdvancedByHost && mVisible && mStarted && mAdapter != null;
203         if (running != mRunning) {
204             if (running) {
205                 showOnly(mWhichChild, flipNow);
206                 postDelayed(mFlipRunnable, mFlipInterval);
207             } else {
208                 removeCallbacks(mFlipRunnable);
209             }
210             mRunning = running;
211         }
212         if (LOGD) {
213             Log.d(TAG, "updateRunning() mVisible=" + mVisible + ", mStarted=" + mStarted
214                     + ", mRunning=" + mRunning);
215         }
216     }
217 
218     /**
219      * Returns true if the child views are flipping.
220      */
isFlipping()221     public boolean isFlipping() {
222         return mStarted;
223     }
224 
225     /**
226      * Set if this view automatically calls {@link #startFlipping()} when it
227      * becomes attached to a window.
228      */
setAutoStart(boolean autoStart)229     public void setAutoStart(boolean autoStart) {
230         mAutoStart = autoStart;
231     }
232 
233     /**
234      * Returns true if this view automatically calls {@link #startFlipping()}
235      * when it becomes attached to a window.
236      */
isAutoStart()237     public boolean isAutoStart() {
238         return mAutoStart;
239     }
240 
241     private final Runnable mFlipRunnable = new Runnable() {
242         @Override
243         public void run() {
244             if (mRunning) {
245                 showNext();
246             }
247         }
248     };
249 
250     /**
251      * Called by an {@link android.appwidget.AppWidgetHost} to indicate that it will be
252      * automatically advancing the views of this {@link AdapterViewFlipper} by calling
253      * {@link AdapterViewFlipper#advance()} at some point in the future. This allows
254      * {@link AdapterViewFlipper} to prepare by no longer Advancing its children.
255      */
256     @Override
fyiWillBeAdvancedByHostKThx()257     public void fyiWillBeAdvancedByHostKThx() {
258         mAdvancedByHost = true;
259         updateRunning(false);
260     }
261 
262     @Override
getAccessibilityClassName()263     public CharSequence getAccessibilityClassName() {
264         return AdapterViewFlipper.class.getName();
265     }
266 }
267