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.BroadcastReceiver; 20 import android.content.Context; 21 import android.content.Intent; 22 import android.content.IntentFilter; 23 import android.content.res.TypedArray; 24 import android.os.Handler; 25 import android.os.Message; 26 import android.util.AttributeSet; 27 import android.util.Log; 28 import android.view.RemotableViewMethod; 29 import android.view.accessibility.AccessibilityEvent; 30 import android.view.accessibility.AccessibilityNodeInfo; 31 import android.widget.RemoteViews.RemoteView; 32 33 /** 34 * Simple {@link ViewAnimator} that will animate between two or more views 35 * that have been added to it. Only one child is shown at a time. If 36 * requested, can automatically flip between each child at a regular interval. 37 * 38 * @attr ref android.R.styleable#AdapterViewFlipper_flipInterval 39 * @attr ref android.R.styleable#AdapterViewFlipper_autoStart 40 */ 41 @RemoteView 42 public class AdapterViewFlipper extends AdapterViewAnimator { 43 private static final String TAG = "ViewFlipper"; 44 private static final boolean LOGD = false; 45 46 private static final int DEFAULT_INTERVAL = 10000; 47 48 private int mFlipInterval = DEFAULT_INTERVAL; 49 private boolean mAutoStart = false; 50 51 private boolean mRunning = false; 52 private boolean mStarted = false; 53 private boolean mVisible = false; 54 private boolean mUserPresent = true; 55 private boolean mAdvancedByHost = false; 56 57 public AdapterViewFlipper(Context context) { 58 super(context); 59 } 60 61 public AdapterViewFlipper(Context context, AttributeSet attrs) { 62 super(context, attrs); 63 64 TypedArray a = context.obtainStyledAttributes(attrs, 65 com.android.internal.R.styleable.AdapterViewFlipper); 66 mFlipInterval = a.getInt( 67 com.android.internal.R.styleable.AdapterViewFlipper_flipInterval, DEFAULT_INTERVAL); 68 mAutoStart = a.getBoolean( 69 com.android.internal.R.styleable.AdapterViewFlipper_autoStart, false); 70 71 // A view flipper should cycle through the views 72 mLoopViews = true; 73 74 a.recycle(); 75 } 76 77 private final BroadcastReceiver mReceiver = new BroadcastReceiver() { 78 @Override 79 public void onReceive(Context context, Intent intent) { 80 final String action = intent.getAction(); 81 if (Intent.ACTION_SCREEN_OFF.equals(action)) { 82 mUserPresent = false; 83 updateRunning(); 84 } else if (Intent.ACTION_USER_PRESENT.equals(action)) { 85 mUserPresent = true; 86 updateRunning(false); 87 } 88 } 89 }; 90 91 @Override 92 protected void onAttachedToWindow() { 93 super.onAttachedToWindow(); 94 95 // Listen for broadcasts related to user-presence 96 final IntentFilter filter = new IntentFilter(); 97 filter.addAction(Intent.ACTION_SCREEN_OFF); 98 filter.addAction(Intent.ACTION_USER_PRESENT); 99 getContext().registerReceiver(mReceiver, filter); 100 101 if (mAutoStart) { 102 // Automatically start when requested 103 startFlipping(); 104 } 105 } 106 107 @Override 108 protected void onDetachedFromWindow() { 109 super.onDetachedFromWindow(); 110 mVisible = false; 111 112 getContext().unregisterReceiver(mReceiver); 113 updateRunning(); 114 } 115 116 @Override 117 protected void onWindowVisibilityChanged(int visibility) { 118 super.onWindowVisibilityChanged(visibility); 119 mVisible = (visibility == VISIBLE); 120 updateRunning(false); 121 } 122 123 @Override 124 public void setAdapter(Adapter adapter) { 125 super.setAdapter(adapter); 126 updateRunning(); 127 } 128 129 /** 130 * Returns the flip interval, in milliseconds. 131 * 132 * @return the flip interval in milliseconds 133 * 134 * @see #setFlipInterval(int) 135 * 136 * @attr ref android.R.styleable#AdapterViewFlipper_flipInterval 137 */ 138 public int getFlipInterval() { 139 return mFlipInterval; 140 } 141 142 /** 143 * How long to wait before flipping to the next view. 144 * 145 * @param flipInterval flip interval in milliseconds 146 * 147 * @see #getFlipInterval() 148 * 149 * @attr ref android.R.styleable#AdapterViewFlipper_flipInterval 150 */ 151 public void setFlipInterval(int flipInterval) { 152 mFlipInterval = flipInterval; 153 } 154 155 /** 156 * Start a timer to cycle through child views 157 */ 158 public void startFlipping() { 159 mStarted = true; 160 updateRunning(); 161 } 162 163 /** 164 * No more flips 165 */ 166 public void stopFlipping() { 167 mStarted = false; 168 updateRunning(); 169 } 170 171 /** 172 * {@inheritDoc} 173 */ 174 @Override 175 @RemotableViewMethod 176 public void showNext() { 177 // if the flipper is currently flipping automatically, and showNext() is called 178 // we should we should make sure to reset the timer 179 if (mRunning) { 180 mHandler.removeMessages(FLIP_MSG); 181 Message msg = mHandler.obtainMessage(FLIP_MSG); 182 mHandler.sendMessageDelayed(msg, mFlipInterval); 183 } 184 super.showNext(); 185 } 186 187 /** 188 * {@inheritDoc} 189 */ 190 @Override 191 @RemotableViewMethod 192 public void showPrevious() { 193 // if the flipper is currently flipping automatically, and showPrevious() is called 194 // we should we should make sure to reset the timer 195 if (mRunning) { 196 mHandler.removeMessages(FLIP_MSG); 197 Message msg = mHandler.obtainMessage(FLIP_MSG); 198 mHandler.sendMessageDelayed(msg, mFlipInterval); 199 } 200 super.showPrevious(); 201 } 202 203 /** 204 * Internal method to start or stop dispatching flip {@link Message} based 205 * on {@link #mRunning} and {@link #mVisible} state. 206 */ 207 private void updateRunning() { 208 // by default when we update running, we want the 209 // current view to animate in 210 updateRunning(true); 211 } 212 213 /** 214 * Internal method to start or stop dispatching flip {@link Message} based 215 * on {@link #mRunning} and {@link #mVisible} state. 216 * 217 * @param flipNow Determines whether or not to execute the animation now, in 218 * addition to queuing future flips. If omitted, defaults to 219 * true. 220 */ 221 private void updateRunning(boolean flipNow) { 222 boolean running = !mAdvancedByHost && mVisible && mStarted && mUserPresent 223 && mAdapter != null; 224 if (running != mRunning) { 225 if (running) { 226 showOnly(mWhichChild, flipNow); 227 Message msg = mHandler.obtainMessage(FLIP_MSG); 228 mHandler.sendMessageDelayed(msg, mFlipInterval); 229 } else { 230 mHandler.removeMessages(FLIP_MSG); 231 } 232 mRunning = running; 233 } 234 if (LOGD) { 235 Log.d(TAG, "updateRunning() mVisible=" + mVisible + ", mStarted=" + mStarted 236 + ", mUserPresent=" + mUserPresent + ", mRunning=" + mRunning); 237 } 238 } 239 240 /** 241 * Returns true if the child views are flipping. 242 */ 243 public boolean isFlipping() { 244 return mStarted; 245 } 246 247 /** 248 * Set if this view automatically calls {@link #startFlipping()} when it 249 * becomes attached to a window. 250 */ 251 public void setAutoStart(boolean autoStart) { 252 mAutoStart = autoStart; 253 } 254 255 /** 256 * Returns true if this view automatically calls {@link #startFlipping()} 257 * when it becomes attached to a window. 258 */ 259 public boolean isAutoStart() { 260 return mAutoStart; 261 } 262 263 private final int FLIP_MSG = 1; 264 265 private final Handler mHandler = new Handler() { 266 @Override 267 public void handleMessage(Message msg) { 268 if (msg.what == FLIP_MSG) { 269 if (mRunning) { 270 showNext(); 271 } 272 } 273 } 274 }; 275 276 /** 277 * Called by an {@link android.appwidget.AppWidgetHost} to indicate that it will be 278 * automatically advancing the views of this {@link AdapterViewFlipper} by calling 279 * {@link AdapterViewFlipper#advance()} at some point in the future. This allows 280 * {@link AdapterViewFlipper} to prepare by no longer Advancing its children. 281 */ 282 @Override 283 public void fyiWillBeAdvancedByHostKThx() { 284 mAdvancedByHost = true; 285 updateRunning(false); 286 } 287 288 @Override 289 public void onInitializeAccessibilityEvent(AccessibilityEvent event) { 290 super.onInitializeAccessibilityEvent(event); 291 event.setClassName(AdapterViewFlipper.class.getName()); 292 } 293 294 @Override 295 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { 296 super.onInitializeAccessibilityNodeInfo(info); 297 info.setClassName(AdapterViewFlipper.class.getName()); 298 } 299 } 300