PlaybackOverlayFragment.java revision 372d07bb41510d91a6a662a1906aceb0ee759481
1/* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 * in compliance with the License. You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software distributed under the License 10 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 * or implied. See the License for the specific language governing permissions and limitations under 12 * the License. 13 */ 14package android.support.v17.leanback.app; 15 16import android.graphics.Color; 17import android.graphics.drawable.ColorDrawable; 18import android.animation.Animator; 19import android.os.Bundle; 20import android.os.Handler; 21import android.os.Message; 22import android.support.v17.leanback.R; 23import android.support.v17.leanback.widget.ObjectAdapter; 24import android.support.v17.leanback.widget.ObjectAdapter.DataObserver; 25import android.support.v17.leanback.widget.VerticalGridView; 26import android.util.Log; 27import android.view.KeyEvent; 28import android.view.LayoutInflater; 29import android.view.MotionEvent; 30import android.view.View; 31import android.view.ViewGroup; 32import android.view.animation.Interpolator; 33import android.view.animation.LinearInterpolator; 34 35 36/** 37 * A fragment for displaying playback controls and related content. 38 * The {@link android.support.v17.leanback.widget.PlaybackControlsRow} is expected to be 39 * at position 0 in the adapter. 40 */ 41public class PlaybackOverlayFragment extends DetailsFragment { 42 43 /** 44 * No background. 45 */ 46 public static final int BG_NONE = 0; 47 48 /** 49 * A dark translucent background. 50 */ 51 public static final int BG_DARK = 1; 52 53 /** 54 * A light translucent background. 55 */ 56 public static final int BG_LIGHT = 2; 57 58 public static class OnFadeCompleteListener { 59 public void onFadeInComplete() { 60 } 61 public void onFadeOutComplete() { 62 } 63 } 64 65 private static final String TAG = "PlaybackOverlayFragment"; 66 private static final boolean DEBUG = false; 67 68 private static int START_FADE_OUT = 1; 69 70 // Fading status 71 private static final int IDLE = 0; 72 private static final int IN = 1; 73 private static final int OUT = 2; 74 75 private int mAlignPosition; 76 private View mRootView; 77 private int mBackgroundType = BG_DARK; 78 private int mBgDarkColor; 79 private int mBgLightColor; 80 private int mFadeInDurationMs; 81 private int mFadeOutDurationMs; 82 private int mShowTimeMs; 83 private OnFadeCompleteListener mFadeCompleteListener; 84 private boolean mFadingEnabled = true; 85 private int mFadingStatus = IDLE; 86 87 private final Animator.AnimatorListener mFadeListener = 88 new Animator.AnimatorListener() { 89 @Override 90 public void onAnimationStart(Animator animation) { 91 } 92 @Override 93 public void onAnimationRepeat(Animator animation) { 94 } 95 @Override 96 public void onAnimationCancel(Animator animation) { 97 } 98 @Override 99 public void onAnimationEnd(Animator animation) { 100 float alpha = getView().getAlpha(); 101 if (DEBUG) Log.v(TAG, "onAnimationEnd " + alpha); 102 if (alpha == 1) { 103 startFadeTimer(); 104 if (mFadeCompleteListener != null) { 105 mFadeCompleteListener.onFadeInComplete(); 106 } 107 } else if (alpha == 0 && mFadeCompleteListener != null) { 108 mFadeCompleteListener.onFadeOutComplete(); 109 } 110 mFadingStatus = IDLE; 111 } 112 }; 113 114 private final Handler mHandler = new Handler() { 115 @Override 116 public void handleMessage(Message message) { 117 if (message.what == START_FADE_OUT && mFadingEnabled) { 118 fade(false); 119 } 120 } 121 }; 122 123 private final Interpolator mFadeInterpolator = new LinearInterpolator(); 124 125 private final VerticalGridView.OnTouchInterceptListener mOnTouchInterceptListener = 126 new VerticalGridView.OnTouchInterceptListener() { 127 public boolean onInterceptTouchEvent(MotionEvent event) { 128 return onInterceptInputEvent(); 129 } 130 }; 131 132 private final VerticalGridView.OnMotionInterceptListener mOnMotionInterceptListener = 133 new VerticalGridView.OnMotionInterceptListener() { 134 public boolean onInterceptMotionEvent(MotionEvent event) { 135 return onInterceptInputEvent(); 136 } 137 }; 138 139 private final VerticalGridView.OnKeyInterceptListener mOnKeyInterceptListener = 140 new VerticalGridView.OnKeyInterceptListener() { 141 public boolean onInterceptKeyEvent(KeyEvent event) { 142 return onInterceptInputEvent(); 143 } 144 }; 145 146 /** 147 * Enables or disables view fading. If enabled, 148 * the view will be faded in when the fragment starts, 149 * and will fade out after a time period. The timeout 150 * period is reset each time {@link #tickle} is called. 151 * 152 */ 153 public void setFadingEnabled(boolean enabled) { 154 if (DEBUG) Log.v(TAG, "setFadingEnabled " + enabled); 155 if (enabled != mFadingEnabled) { 156 mFadingEnabled = enabled; 157 if (isResumed()) { 158 if (mFadingEnabled) { 159 if (mFadingStatus == IDLE && !mHandler.hasMessages(START_FADE_OUT)) { 160 startFadeTimer(); 161 } 162 } else { 163 // Ensure fully opaque 164 mHandler.removeMessages(START_FADE_OUT); 165 fade(true); 166 } 167 } 168 } 169 } 170 171 /** 172 * Returns true if view fading is enabled. 173 */ 174 public boolean isFadingEnabled() { 175 return mFadingEnabled; 176 } 177 178 /** 179 * Sets the listener to be called when fade in or out has completed. 180 */ 181 public void setFadeCompleteListener(OnFadeCompleteListener listener) { 182 mFadeCompleteListener = listener; 183 } 184 185 /** 186 * Returns the listener to be called when fade in or out has completed. 187 */ 188 public OnFadeCompleteListener getFadeCompleteListener() { 189 return mFadeCompleteListener; 190 } 191 192 /** 193 * Tickles the playback controls. Fades in the view if it was faded out, 194 * otherwise resets the fade out timer. Tickling on input events is handled 195 * by the fragment. 196 */ 197 public void tickle() { 198 if (DEBUG) Log.v(TAG, "tickle enabled " + mFadingEnabled + " isResumed " + isResumed()); 199 if (!mFadingEnabled || !isResumed()) { 200 return; 201 } 202 if (mHandler.hasMessages(START_FADE_OUT)) { 203 // Restart the timer 204 startFadeTimer(); 205 } else { 206 fade(true); 207 } 208 } 209 210 private boolean onInterceptInputEvent() { 211 if (DEBUG) Log.v(TAG, "onInterceptInputEvent status " + mFadingStatus + 212 " alpha " + getView().getAlpha()); 213 boolean consumeEvent = (mFadingStatus == IDLE && getView().getAlpha() == 0); 214 tickle(); 215 return consumeEvent; 216 } 217 218 @Override 219 public void onResume() { 220 super.onResume(); 221 if (mFadingEnabled) { 222 getView().setAlpha(0); 223 fade(true); 224 } 225 getVerticalGridView().setOnTouchInterceptListener(mOnTouchInterceptListener); 226 getVerticalGridView().setOnMotionInterceptListener(mOnMotionInterceptListener); 227 getVerticalGridView().setOnKeyInterceptListener(mOnKeyInterceptListener); 228 } 229 230 private void startFadeTimer() { 231 if (DEBUG) Log.v(TAG, "startFadeTime"); 232 mHandler.removeMessages(START_FADE_OUT); 233 mHandler.sendEmptyMessageDelayed(START_FADE_OUT, mShowTimeMs); 234 } 235 236 private void fade(boolean fadeIn) { 237 if (DEBUG) Log.v(TAG, "fade " + fadeIn); 238 if (getView() == null) { 239 return; 240 } 241 if ((fadeIn && mFadingStatus == IN) || (!fadeIn && mFadingStatus == OUT)) { 242 if (DEBUG) Log.v(TAG, "fade " + fadeIn + " in progress"); 243 return; 244 } 245 246 getView().animate().alpha(fadeIn ? 1 : 0) 247 .setDuration(fadeIn ? mFadeInDurationMs : mFadeOutDurationMs) 248 .setListener(mFadeListener) 249 .setInterpolator(mFadeInterpolator) 250 .start(); 251 mFadingStatus = fadeIn ? IN : OUT; 252 } 253 254 /** 255 * Sets the list of rows for the fragment. 256 */ 257 @Override 258 public void setAdapter(ObjectAdapter adapter) { 259 if (getAdapter() != null) { 260 getAdapter().unregisterObserver(mObserver); 261 } 262 super.setAdapter(adapter); 263 if (adapter != null) { 264 adapter.registerObserver(mObserver); 265 } 266 setVerticalGridViewLayout(getVerticalGridView()); 267 } 268 269 @Override 270 void setVerticalGridViewLayout(VerticalGridView listview) { 271 if (listview == null || getAdapter() == null) { 272 return; 273 } 274 final int alignPosition = getAdapter().size() > 1 ? mAlignPosition : 0; 275 listview.setItemAlignmentOffset(alignPosition); 276 listview.setItemAlignmentOffsetPercent(100); 277 listview.setWindowAlignmentOffset(0); 278 listview.setWindowAlignmentOffsetPercent(100); 279 listview.setWindowAlignment(VerticalGridView.WINDOW_ALIGN_HIGH_EDGE); 280 } 281 282 @Override 283 public void onCreate(Bundle savedInstanceState) { 284 super.onCreate(savedInstanceState); 285 286 mAlignPosition = 287 getResources().getDimensionPixelSize(R.dimen.lb_playback_controls_align_bottom); 288 mBgDarkColor = 289 getResources().getColor(R.color.lb_playback_controls_background_dark); 290 mBgLightColor = 291 getResources().getColor(R.color.lb_playback_controls_background_light); 292 mFadeInDurationMs = 293 getResources().getInteger(R.integer.lb_playback_controls_fade_in_duration_ms); 294 mFadeOutDurationMs = 295 getResources().getInteger(R.integer.lb_playback_controls_fade_out_duration_ms); 296 mShowTimeMs = 297 getResources().getInteger(R.integer.lb_playback_controls_show_time_ms); 298 } 299 300 /** 301 * Sets the background type. 302 * 303 * @param type One of BG_LIGHT, BG_DARK, or BG_NONE. 304 */ 305 public void setBackgroundType(int type) { 306 switch (type) { 307 case BG_LIGHT: 308 case BG_DARK: 309 case BG_NONE: 310 if (type != mBackgroundType) { 311 mBackgroundType = type; 312 updateBackground(); 313 } 314 break; 315 default: 316 throw new IllegalArgumentException("Invalid background type"); 317 } 318 } 319 320 /** 321 * Returns the background type. 322 */ 323 public int getBackgroundType() { 324 return mBackgroundType; 325 } 326 327 private void updateBackground() { 328 if (mRootView != null) { 329 int color = mBgDarkColor; 330 switch (mBackgroundType) { 331 case BG_DARK: break; 332 case BG_LIGHT: color = mBgLightColor; break; 333 case BG_NONE: color = Color.TRANSPARENT; break; 334 } 335 mRootView.setBackground(new ColorDrawable(color)); 336 } 337 } 338 339 @Override 340 public View onCreateView(LayoutInflater inflater, ViewGroup container, 341 Bundle savedInstanceState) { 342 mRootView = super.onCreateView(inflater, container, savedInstanceState); 343 updateBackground(); 344 return mRootView; 345 } 346 347 private final DataObserver mObserver = new DataObserver() { 348 public void onChanged() { 349 setVerticalGridViewLayout(getVerticalGridView()); 350 } 351 }; 352} 353