MovieControllerOverlay.java revision 7817979db0c52ffeacb951625b1e821eba303285
1/* 2 * Copyright (C) 2011 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 17package com.android.gallery3d.app; 18 19import android.content.Context; 20import android.graphics.Rect; 21import android.os.Handler; 22import android.view.Gravity; 23import android.view.KeyEvent; 24import android.view.MotionEvent; 25import android.view.View; 26import android.view.View.OnClickListener; 27import android.view.animation.Animation; 28import android.view.animation.Animation.AnimationListener; 29import android.view.animation.AnimationUtils; 30import android.widget.FrameLayout; 31import android.widget.ImageView; 32import android.widget.ImageView.ScaleType; 33import android.widget.LinearLayout; 34import android.widget.ProgressBar; 35import android.widget.RelativeLayout; 36import android.widget.TextView; 37 38import com.android.gallery3d.R; 39 40/** 41 * The playback controller for the Movie Player. 42 */ 43public class MovieControllerOverlay extends FrameLayout implements 44 ControllerOverlay, 45 OnClickListener, 46 AnimationListener, 47 TimeBar.Listener { 48 49 private enum State { 50 PLAYING, 51 PAUSED, 52 ENDED, 53 ERROR, 54 LOADING 55 } 56 57 private static final float ERROR_MESSAGE_RELATIVE_PADDING = 1.0f / 6; 58 59 private Listener listener; 60 61 private final View background; 62 private final TimeBar timeBar; 63 64 private View mainView; 65 private final LinearLayout loadingView; 66 private final TextView errorView; 67 private final ImageView playPauseReplayView; 68 69 private final Handler handler; 70 private final Runnable startHidingRunnable; 71 private final Animation hideAnimation; 72 73 private State state; 74 75 private boolean hidden; 76 77 private boolean canReplay = true; 78 79 public MovieControllerOverlay(Context context) { 80 super(context); 81 82 state = State.LOADING; 83 84 LayoutParams wrapContent = 85 new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); 86 LayoutParams matchParent = 87 new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); 88 89 background = new View(context); 90 background.setBackgroundColor(context.getResources().getColor(R.color.darker_transparent)); 91 addView(background, matchParent); 92 93 timeBar = new TimeBar(context, this); 94 addView(timeBar, wrapContent); 95 96 loadingView = new LinearLayout(context); 97 loadingView.setOrientation(LinearLayout.VERTICAL); 98 loadingView.setGravity(Gravity.CENTER_HORIZONTAL); 99 ProgressBar spinner = new ProgressBar(context); 100 spinner.setIndeterminate(true); 101 loadingView.addView(spinner, wrapContent); 102 TextView loadingText = createOverlayTextView(context); 103 loadingText.setText(R.string.loading_video); 104 loadingView.addView(loadingText, wrapContent); 105 addView(loadingView, wrapContent); 106 107 playPauseReplayView = new ImageView(context); 108 playPauseReplayView.setImageResource(R.drawable.ic_vidcontrol_play); 109 playPauseReplayView.setBackgroundResource(R.drawable.bg_vidcontrol); 110 playPauseReplayView.setScaleType(ScaleType.CENTER); 111 playPauseReplayView.setFocusable(true); 112 playPauseReplayView.setClickable(true); 113 playPauseReplayView.setOnClickListener(this); 114 addView(playPauseReplayView, wrapContent); 115 116 errorView = createOverlayTextView(context); 117 addView(errorView, matchParent); 118 119 handler = new Handler(); 120 startHidingRunnable = new Runnable() { 121 @Override 122 public void run() { 123 startHiding(); 124 } 125 }; 126 127 hideAnimation = AnimationUtils.loadAnimation(context, R.anim.player_out); 128 hideAnimation.setAnimationListener(this); 129 130 RelativeLayout.LayoutParams params = 131 new RelativeLayout.LayoutParams( 132 LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); 133 setLayoutParams(params); 134 hide(); 135 } 136 137 private TextView createOverlayTextView(Context context) { 138 TextView view = new TextView(context); 139 view.setGravity(Gravity.CENTER); 140 view.setTextColor(0xFFFFFFFF); 141 view.setPadding(0, 15, 0, 15); 142 return view; 143 } 144 145 @Override 146 public void setListener(Listener listener) { 147 this.listener = listener; 148 } 149 150 @Override 151 public void setCanReplay(boolean canReplay) { 152 this.canReplay = canReplay; 153 } 154 155 @Override 156 public View getView() { 157 return this; 158 } 159 160 @Override 161 public void showPlaying() { 162 state = State.PLAYING; 163 showMainView(playPauseReplayView); 164 } 165 166 @Override 167 public void showPaused() { 168 state = State.PAUSED; 169 showMainView(playPauseReplayView); 170 } 171 172 @Override 173 public void showEnded() { 174 state = State.ENDED; 175 showMainView(playPauseReplayView); 176 } 177 178 @Override 179 public void showLoading() { 180 state = State.LOADING; 181 showMainView(loadingView); 182 } 183 184 @Override 185 public void showErrorMessage(String message) { 186 state = State.ERROR; 187 int padding = (int) (getMeasuredWidth() * ERROR_MESSAGE_RELATIVE_PADDING); 188 errorView.setPadding( 189 padding, errorView.getPaddingTop(), padding, errorView.getPaddingBottom()); 190 errorView.setText(message); 191 showMainView(errorView); 192 } 193 194 @Override 195 public void setTimes(int currentTime, int totalTime) { 196 timeBar.setTime(currentTime, totalTime); 197 } 198 199 public void hide() { 200 boolean wasHidden = hidden; 201 hidden = true; 202 playPauseReplayView.setVisibility(View.INVISIBLE); 203 loadingView.setVisibility(View.INVISIBLE); 204 background.setVisibility(View.INVISIBLE); 205 timeBar.setVisibility(View.INVISIBLE); 206 setVisibility(View.INVISIBLE); 207 setFocusable(true); 208 requestFocus(); 209 if (listener != null && wasHidden != hidden) { 210 listener.onHidden(); 211 } 212 } 213 214 private void showMainView(View view) { 215 mainView = view; 216 errorView.setVisibility(mainView == errorView ? View.VISIBLE : View.INVISIBLE); 217 loadingView.setVisibility(mainView == loadingView ? View.VISIBLE : View.INVISIBLE); 218 playPauseReplayView.setVisibility( 219 mainView == playPauseReplayView ? View.VISIBLE : View.INVISIBLE); 220 show(); 221 } 222 223 @Override 224 public void show() { 225 boolean wasHidden = hidden; 226 hidden = false; 227 updateViews(); 228 setVisibility(View.VISIBLE); 229 setFocusable(false); 230 if (listener != null && wasHidden != hidden) { 231 listener.onShown(); 232 } 233 maybeStartHiding(); 234 } 235 236 private void maybeStartHiding() { 237 cancelHiding(); 238 if (state == State.PLAYING) { 239 handler.postDelayed(startHidingRunnable, 2500); 240 } 241 } 242 243 private void startHiding() { 244 startHideAnimation(background); 245 startHideAnimation(timeBar); 246 startHideAnimation(playPauseReplayView); 247 } 248 249 private void startHideAnimation(View view) { 250 if (view.getVisibility() == View.VISIBLE) { 251 view.startAnimation(hideAnimation); 252 } 253 } 254 255 private void cancelHiding() { 256 handler.removeCallbacks(startHidingRunnable); 257 background.setAnimation(null); 258 timeBar.setAnimation(null); 259 playPauseReplayView.setAnimation(null); 260 } 261 262 @Override 263 public void onAnimationStart(Animation animation) { 264 // Do nothing. 265 } 266 267 @Override 268 public void onAnimationRepeat(Animation animation) { 269 // Do nothing. 270 } 271 272 @Override 273 public void onAnimationEnd(Animation animation) { 274 hide(); 275 } 276 277 @Override 278 public void onClick(View view) { 279 if (listener != null) { 280 if (view == playPauseReplayView) { 281 if (state == State.ENDED) { 282 if (canReplay) { 283 listener.onReplay(); 284 } 285 } else if (state == State.PAUSED || state == State.PLAYING) { 286 listener.onPlayPause(); 287 } 288 } 289 } 290 } 291 292 @Override 293 public boolean onKeyDown(int keyCode, KeyEvent event) { 294 if (hidden) { 295 show(); 296 } 297 return super.onKeyDown(keyCode, event); 298 } 299 300 @Override 301 public boolean onTouchEvent(MotionEvent event) { 302 if (super.onTouchEvent(event)) { 303 return true; 304 } 305 306 if (hidden) { 307 show(); 308 return true; 309 } 310 switch (event.getAction()) { 311 case MotionEvent.ACTION_DOWN: 312 cancelHiding(); 313 if (state == State.PLAYING || state == State.PAUSED) { 314 listener.onPlayPause(); 315 } 316 break; 317 case MotionEvent.ACTION_UP: 318 maybeStartHiding(); 319 break; 320 } 321 return true; 322 } 323 324 // The paddings of 4 sides which covered by system components. E.g. 325 // +-----------------+\ 326 // | Action Bar | insets.top 327 // +-----------------+/ 328 // | | 329 // | Content Area | insets.right = insets.left = 0 330 // | | 331 // +-----------------+\ 332 // | Navigation Bar | insets.bottom 333 // +-----------------+/ 334 // Please see View.fitSystemWindows() for more details. 335 private final Rect mWindowInsets = new Rect(); 336 337 @Override 338 protected boolean fitSystemWindows(Rect insets) { 339 // We don't set the paddings of this View, otherwise, 340 // the content will get cropped outside window 341 mWindowInsets.set(insets); 342 return true; 343 } 344 345 @Override 346 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 347 Rect insets = mWindowInsets; 348 int pl = insets.left; // the left paddings 349 int pr = insets.right; 350 int pt = insets.top; 351 int pb = insets.bottom; 352 353 int h = bottom - top; 354 int w = right - left; 355 boolean error = errorView.getVisibility() == View.VISIBLE; 356 357 int y = h - pb; 358 // Put both TimeBar and Background just above the bottom system 359 // component. 360 // But extend the background to the width of the screen, since we don't 361 // care if it will be covered by a system component and it looks better. 362 background.layout(0, y - timeBar.getBarHeight(), w, y); 363 timeBar.layout(pl, y - timeBar.getPreferredHeight(), w - pr, y); 364 365 // Needed, otherwise the framework will not re-layout in case only the 366 // padding is changed 367 timeBar.requestLayout(); 368 369 // Put the play/pause/next/ previous button in the center of the screen 370 layoutCenteredView(playPauseReplayView, 0, 0, w, h); 371 372 if (mainView != null) { 373 layoutCenteredView(mainView, 0, 0, w, h); 374 } 375 } 376 377 private void layoutCenteredView(View view, int l, int t, int r, int b) { 378 int cw = view.getMeasuredWidth(); 379 int ch = view.getMeasuredHeight(); 380 int cl = (r - l - cw) / 2; 381 int ct = (b - t - ch) / 2; 382 view.layout(cl, ct, cl + cw, ct + ch); 383 } 384 385 @Override 386 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 387 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 388 measureChildren(widthMeasureSpec, heightMeasureSpec); 389 } 390 391 private void updateViews() { 392 if (hidden) { 393 return; 394 } 395 background.setVisibility(View.VISIBLE); 396 timeBar.setVisibility(View.VISIBLE); 397 playPauseReplayView.setImageResource( 398 state == State.PAUSED ? R.drawable.ic_vidcontrol_play : 399 state == State.PLAYING ? R.drawable.ic_vidcontrol_pause : 400 R.drawable.ic_vidcontrol_reload); 401 playPauseReplayView.setVisibility( 402 (state != State.LOADING && state != State.ERROR && 403 !(state == State.ENDED && !canReplay)) 404 ? View.VISIBLE : View.GONE); 405 requestLayout(); 406 } 407 408 // TimeBar listener 409 410 @Override 411 public void onScrubbingStart() { 412 cancelHiding(); 413 listener.onSeekStart(); 414 } 415 416 @Override 417 public void onScrubbingMove(int time) { 418 cancelHiding(); 419 listener.onSeekMove(time); 420 } 421 422 @Override 423 public void onScrubbingEnd(int time) { 424 maybeStartHiding(); 425 listener.onSeekEnd(time); 426 } 427} 428