1 /* 2 * Copyright (C) 2015 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 com.android.tv.ui; 18 19 import android.animation.Animator; 20 import android.animation.AnimatorListenerAdapter; 21 import android.animation.ArgbEvaluator; 22 import android.animation.ObjectAnimator; 23 import android.animation.TimeInterpolator; 24 import android.animation.TypeEvaluator; 25 import android.animation.ValueAnimator; 26 import android.animation.ValueAnimator.AnimatorUpdateListener; 27 import android.app.Activity; 28 import android.content.Context; 29 import android.content.SharedPreferences; 30 import android.content.res.Resources; 31 import android.graphics.Point; 32 import android.hardware.display.DisplayManager; 33 import android.os.Build; 34 import android.os.Handler; 35 import android.os.Message; 36 import android.preference.PreferenceManager; 37 import android.util.Log; 38 import android.util.Property; 39 import android.view.Display; 40 import android.view.ViewGroup; 41 import android.view.ViewGroup.LayoutParams; 42 import android.view.ViewGroup.MarginLayoutParams; 43 import android.view.animation.AnimationUtils; 44 import android.widget.FrameLayout; 45 import com.android.tv.R; 46 import com.android.tv.TvOptionsManager; 47 import com.android.tv.data.DisplayMode; 48 import com.android.tv.features.TvFeatures; 49 import com.android.tv.util.TvSettings; 50 51 /** 52 * The TvViewUiManager is responsible for handling UI layouting and animation of main TvView. It 53 * also control the settings regarding TvView UI such as display mode. 54 */ 55 public class TvViewUiManager { 56 private static final String TAG = "TvViewManager"; 57 private static final boolean DEBUG = false; 58 59 private static final float DISPLAY_MODE_EPSILON = 0.001f; 60 private static final float DISPLAY_ASPECT_RATIO_EPSILON = 0.01f; 61 62 private static final int MSG_SET_LAYOUT_PARAMS = 1000; 63 64 private final Context mContext; 65 private final Resources mResources; 66 private final FrameLayout mContentView; 67 private final TunableTvView mTvView; 68 private final TvOptionsManager mTvOptionsManager; 69 private final int mTvViewShrunkenStartMargin; 70 private final int mTvViewShrunkenEndMargin; 71 private int mWindowWidth; 72 private int mWindowHeight; 73 private final SharedPreferences mSharedPreferences; 74 private final TimeInterpolator mLinearOutSlowIn; 75 private final TimeInterpolator mFastOutLinearIn; 76 private final Handler mHandler = 77 new Handler() { 78 @Override 79 public void handleMessage(Message msg) { 80 switch (msg.what) { 81 case MSG_SET_LAYOUT_PARAMS: 82 FrameLayout.LayoutParams layoutParams = 83 (FrameLayout.LayoutParams) msg.obj; 84 if (DEBUG) { 85 Log.d( 86 TAG, 87 "setFixedSize: w=" 88 + layoutParams.width 89 + " h=" 90 + layoutParams.height); 91 } 92 mTvView.setTvViewLayoutParams(layoutParams); 93 mTvView.setLayoutParams(mTvViewFrame); 94 // Smooth PIP size change, we don't change surface size when 95 // isInPictureInPictureMode is true. 96 if (!TvFeatures.PICTURE_IN_PICTURE.isEnabled(mContext) 97 || (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N 98 && !((Activity) mContext).isInPictureInPictureMode())) { 99 mTvView.setFixedSurfaceSize( 100 layoutParams.width, layoutParams.height); 101 } 102 break; 103 } 104 } 105 }; 106 private int mDisplayMode; 107 // Used to restore the previous state from ShrunkenTvView state. 108 private int mTvViewStartMarginBeforeShrunken; 109 private int mTvViewEndMarginBeforeShrunken; 110 private int mDisplayModeBeforeShrunken; 111 private boolean mIsUnderShrunkenTvView; 112 private int mTvViewStartMargin; 113 private int mTvViewEndMargin; 114 private ObjectAnimator mTvViewAnimator; 115 private FrameLayout.LayoutParams mTvViewLayoutParams; 116 // TV view's position when the display mode is FULL. It is used to compute PIP location relative 117 // to TV view's position. 118 private FrameLayout.LayoutParams mTvViewFrame; 119 private FrameLayout.LayoutParams mLastAnimatedTvViewFrame; 120 private FrameLayout.LayoutParams mOldTvViewFrame; 121 private ObjectAnimator mBackgroundAnimator; 122 private int mBackgroundColor; 123 private int mAppliedDisplayedMode = DisplayMode.MODE_NOT_DEFINED; 124 private int mAppliedTvViewStartMargin; 125 private int mAppliedTvViewEndMargin; 126 private float mAppliedVideoDisplayAspectRatio; 127 TvViewUiManager( Context context, TunableTvView tvView, FrameLayout contentView, TvOptionsManager tvOptionManager)128 public TvViewUiManager( 129 Context context, 130 TunableTvView tvView, 131 FrameLayout contentView, 132 TvOptionsManager tvOptionManager) { 133 mContext = context; 134 mResources = mContext.getResources(); 135 mTvView = tvView; 136 mContentView = contentView; 137 mTvOptionsManager = tvOptionManager; 138 139 DisplayManager displayManager = 140 (DisplayManager) mContext.getSystemService(Context.DISPLAY_SERVICE); 141 Display display = displayManager.getDisplay(Display.DEFAULT_DISPLAY); 142 Point size = new Point(); 143 display.getSize(size); 144 mWindowWidth = size.x; 145 mWindowHeight = size.y; 146 147 // Have an assumption that TvView Shrinking happens only in full screen. 148 mTvViewShrunkenStartMargin = 149 mResources.getDimensionPixelOffset(R.dimen.shrunken_tvview_margin_start); 150 mTvViewShrunkenEndMargin = 151 mResources.getDimensionPixelOffset(R.dimen.shrunken_tvview_margin_end) 152 + mResources.getDimensionPixelSize(R.dimen.side_panel_width); 153 mTvViewFrame = createMarginLayoutParams(0, 0, 0, 0); 154 155 mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(mContext); 156 157 mLinearOutSlowIn = 158 AnimationUtils.loadInterpolator( 159 mContext, android.R.interpolator.linear_out_slow_in); 160 mFastOutLinearIn = 161 AnimationUtils.loadInterpolator( 162 mContext, android.R.interpolator.fast_out_linear_in); 163 } 164 onConfigurationChanged(final int windowWidth, final int windowHeight)165 public void onConfigurationChanged(final int windowWidth, final int windowHeight) { 166 if (windowWidth > 0 && windowHeight > 0) { 167 if (mWindowWidth != windowWidth || mWindowHeight != windowHeight) { 168 mWindowWidth = windowWidth; 169 mWindowHeight = windowHeight; 170 applyDisplayMode(mTvView.getVideoDisplayAspectRatio(), false, true); 171 } 172 } 173 } 174 175 /** 176 * Initializes animator in advance of using the animator to improve animation performance. For 177 * fast first tune, it is not expected to be called in Activity.onCreate, but called a few 178 * seconds later after onCreate. 179 */ initAnimatorIfNeeded()180 public void initAnimatorIfNeeded() { 181 initTvAnimatorIfNeeded(); 182 initBackgroundAnimatorIfNeeded(); 183 } 184 185 /** 186 * It is called when shrunken TvView is desired, such as EditChannelFragment and 187 * ChannelsLockedFragment. 188 */ startShrunkenTvView()189 public void startShrunkenTvView() { 190 mIsUnderShrunkenTvView = true; 191 mTvView.setIsUnderShrunken(true); 192 193 mTvViewStartMarginBeforeShrunken = mTvViewStartMargin; 194 mTvViewEndMarginBeforeShrunken = mTvViewEndMargin; 195 setTvViewMargin(mTvViewShrunkenStartMargin, mTvViewShrunkenEndMargin); 196 mDisplayModeBeforeShrunken = setDisplayMode(DisplayMode.MODE_NORMAL, false, true); 197 } 198 199 /** 200 * It is called when shrunken TvView is no longer desired, such as EditChannelFragment and 201 * ChannelsLockedFragment. 202 */ endShrunkenTvView()203 public void endShrunkenTvView() { 204 mIsUnderShrunkenTvView = false; 205 mTvView.setIsUnderShrunken(false); 206 setTvViewMargin(mTvViewStartMarginBeforeShrunken, mTvViewEndMarginBeforeShrunken); 207 setDisplayMode(mDisplayModeBeforeShrunken, false, true); 208 } 209 210 /** Returns true, if TvView is shrunken. */ isUnderShrunkenTvView()211 public boolean isUnderShrunkenTvView() { 212 return mIsUnderShrunkenTvView; 213 } 214 215 /** 216 * Returns true, if {@code displayMode} is available now. If screen ratio is matched to video 217 * ratio, other display modes than {@link DisplayMode#MODE_NORMAL} are not available. 218 */ isDisplayModeAvailable(int displayMode)219 public boolean isDisplayModeAvailable(int displayMode) { 220 if (displayMode == DisplayMode.MODE_NORMAL) { 221 return true; 222 } 223 224 int viewWidth = mContentView.getWidth(); 225 int viewHeight = mContentView.getHeight(); 226 227 float videoDisplayAspectRatio = mTvView.getVideoDisplayAspectRatio(); 228 if (viewWidth <= 0 || viewHeight <= 0 || videoDisplayAspectRatio <= 0f) { 229 Log.w(TAG, "Video size is currently unavailable"); 230 if (DEBUG) { 231 Log.d( 232 TAG, 233 "isDisplayModeAvailable: " 234 + "viewWidth=" 235 + viewWidth 236 + ", viewHeight=" 237 + viewHeight 238 + ", videoDisplayAspectRatio=" 239 + videoDisplayAspectRatio); 240 } 241 return false; 242 } 243 244 float viewRatio = viewWidth / (float) viewHeight; 245 return Math.abs(viewRatio - videoDisplayAspectRatio) >= DISPLAY_MODE_EPSILON; 246 } 247 248 /** Returns a constant defined in DisplayMode. */ getDisplayMode()249 public int getDisplayMode() { 250 if (isDisplayModeAvailable(mDisplayMode)) { 251 return mDisplayMode; 252 } 253 return DisplayMode.MODE_NORMAL; 254 } 255 256 /** 257 * Sets the display mode to the given value. 258 * 259 * @return the previous display mode. 260 */ setDisplayMode(int displayMode, boolean storeInPreference, boolean animate)261 public int setDisplayMode(int displayMode, boolean storeInPreference, boolean animate) { 262 int prev = mDisplayMode; 263 mDisplayMode = displayMode; 264 if (storeInPreference) { 265 mSharedPreferences.edit().putInt(TvSettings.PREF_DISPLAY_MODE, displayMode).apply(); 266 } 267 applyDisplayMode(mTvView.getVideoDisplayAspectRatio(), animate, false); 268 return prev; 269 } 270 271 /** Restores the display mode to the display mode stored in preference. */ restoreDisplayMode(boolean animate)272 public void restoreDisplayMode(boolean animate) { 273 int displayMode = 274 mSharedPreferences.getInt(TvSettings.PREF_DISPLAY_MODE, DisplayMode.MODE_NORMAL); 275 setDisplayMode(displayMode, false, animate); 276 } 277 278 /** Updates TvView's aspect ratio. It should be called when video resolution is changed. */ updateTvAspectRatio()279 public void updateTvAspectRatio() { 280 applyDisplayMode(mTvView.getVideoDisplayAspectRatio(), false, false); 281 if (mTvView.isVideoAvailable() && mTvView.isFadedOut()) { 282 mTvView.fadeIn( 283 mResources.getInteger(R.integer.tvview_fade_in_duration), 284 mFastOutLinearIn, 285 null); 286 } 287 } 288 289 /** Fades in TvView. */ fadeInTvView()290 public void fadeInTvView() { 291 if (mTvView.isFadedOut()) { 292 mTvView.fadeIn( 293 mResources.getInteger(R.integer.tvview_fade_in_duration), 294 mFastOutLinearIn, 295 null); 296 } 297 } 298 299 /** Fades out TvView. */ fadeOutTvView(Runnable postAction)300 public void fadeOutTvView(Runnable postAction) { 301 if (!mTvView.isFadedOut()) { 302 mTvView.fadeOut( 303 mResources.getInteger(R.integer.tvview_fade_out_duration), 304 mLinearOutSlowIn, 305 postAction); 306 } 307 } 308 309 /** This margins will be applied when applyDisplayMode is called. */ setTvViewMargin(int tvViewStartMargin, int tvViewEndMargin)310 private void setTvViewMargin(int tvViewStartMargin, int tvViewEndMargin) { 311 mTvViewStartMargin = tvViewStartMargin; 312 mTvViewEndMargin = tvViewEndMargin; 313 } 314 isTvViewFullScreen()315 private boolean isTvViewFullScreen() { 316 return mTvViewStartMargin == 0 && mTvViewEndMargin == 0; 317 } 318 setBackgroundColor( int color, FrameLayout.LayoutParams targetLayoutParams, boolean animate)319 private void setBackgroundColor( 320 int color, FrameLayout.LayoutParams targetLayoutParams, boolean animate) { 321 if (animate) { 322 initBackgroundAnimatorIfNeeded(); 323 if (mBackgroundAnimator.isStarted()) { 324 // Cancel the current animation and start new one. 325 mBackgroundAnimator.cancel(); 326 } 327 328 int decorViewWidth = mContentView.getWidth(); 329 int decorViewHeight = mContentView.getHeight(); 330 boolean hasPillarBox = 331 mTvView.getWidth() != decorViewWidth || mTvView.getHeight() != decorViewHeight; 332 boolean willHavePillarBox = 333 ((targetLayoutParams.width != LayoutParams.MATCH_PARENT) 334 && targetLayoutParams.width != decorViewWidth) 335 || ((targetLayoutParams.height != LayoutParams.MATCH_PARENT) 336 && targetLayoutParams.height != decorViewHeight); 337 338 if (!isTvViewFullScreen() && !hasPillarBox) { 339 // If there is no pillar box, no animation is needed. 340 mContentView.setBackgroundColor(color); 341 } else if (!isTvViewFullScreen() || willHavePillarBox) { 342 mBackgroundAnimator.setIntValues(mBackgroundColor, color); 343 mBackgroundAnimator.setEvaluator(new ArgbEvaluator()); 344 mBackgroundAnimator.setInterpolator(mFastOutLinearIn); 345 mBackgroundAnimator.start(); 346 } 347 // In the 'else' case (TV activity is getting out of the shrunken tv view mode and will 348 // have a pillar box), we keep the background color and don't show the animation. 349 } else { 350 mContentView.setBackgroundColor(color); 351 } 352 mBackgroundColor = color; 353 } 354 setTvViewPosition( final FrameLayout.LayoutParams layoutParams, FrameLayout.LayoutParams tvViewFrame, boolean animate)355 private void setTvViewPosition( 356 final FrameLayout.LayoutParams layoutParams, 357 FrameLayout.LayoutParams tvViewFrame, 358 boolean animate) { 359 if (DEBUG) { 360 Log.d( 361 TAG, 362 "setTvViewPosition: w=" 363 + layoutParams.width 364 + " h=" 365 + layoutParams.height 366 + " s=" 367 + layoutParams.getMarginStart() 368 + " t=" 369 + layoutParams.topMargin 370 + " e=" 371 + layoutParams.getMarginEnd() 372 + " b=" 373 + layoutParams.bottomMargin 374 + " animate=" 375 + animate); 376 } 377 FrameLayout.LayoutParams oldTvViewFrame = mTvViewFrame; 378 mTvViewLayoutParams = layoutParams; 379 mTvViewFrame = tvViewFrame; 380 if (animate) { 381 initTvAnimatorIfNeeded(); 382 if (mTvViewAnimator.isStarted()) { 383 // Cancel the current animation and start new one. 384 mTvViewAnimator.cancel(); 385 mOldTvViewFrame = new FrameLayout.LayoutParams(mLastAnimatedTvViewFrame); 386 } else { 387 mOldTvViewFrame = new FrameLayout.LayoutParams(oldTvViewFrame); 388 } 389 mTvViewAnimator.setObjectValues(mTvView.getTvViewLayoutParams(), layoutParams); 390 mTvViewAnimator.setEvaluator( 391 new TypeEvaluator<FrameLayout.LayoutParams>() { 392 FrameLayout.LayoutParams lp; 393 394 @Override 395 public FrameLayout.LayoutParams evaluate( 396 float fraction, 397 FrameLayout.LayoutParams startValue, 398 FrameLayout.LayoutParams endValue) { 399 if (lp == null) { 400 lp = new FrameLayout.LayoutParams(0, 0); 401 lp.gravity = startValue.gravity; 402 } 403 interpolateMargins(lp, startValue, endValue, fraction); 404 return lp; 405 } 406 }); 407 mTvViewAnimator.setInterpolator( 408 isTvViewFullScreen() ? mFastOutLinearIn : mLinearOutSlowIn); 409 mTvViewAnimator.start(); 410 } else { 411 if (mTvViewAnimator != null && mTvViewAnimator.isStarted()) { 412 // Continue the current animation. 413 // layoutParams will be applied when animation ends. 414 return; 415 } 416 // This block is also called when animation ends. 417 if (isTvViewFullScreen()) { 418 // When this layout is for full screen, fix the surface size after layout to make 419 // resize animation smooth. During PIP size change, the multiple messages can be 420 // queued, if we don't remove MSG_SET_LAYOUT_PARAMS. 421 mHandler.removeMessages(MSG_SET_LAYOUT_PARAMS); 422 mHandler.obtainMessage(MSG_SET_LAYOUT_PARAMS, layoutParams).sendToTarget(); 423 } else { 424 mTvView.setTvViewLayoutParams(layoutParams); 425 mTvView.setLayoutParams(mTvViewFrame); 426 } 427 } 428 } 429 initTvAnimatorIfNeeded()430 private void initTvAnimatorIfNeeded() { 431 if (mTvViewAnimator != null) { 432 return; 433 } 434 435 // TvViewAnimator animates TvView by repeatedly re-layouting TvView. 436 // TvView includes a SurfaceView on which scale/translation effects do not work. Normally, 437 // SurfaceView can be animated by changing left/top/right/bottom directly using 438 // ObjectAnimator, although it would require calling getChildAt(0) against TvView (which is 439 // supposed to be opaque). More importantly, this method does not work in case of TvView, 440 // because TvView may request layout itself during animation and layout SurfaceView with 441 // its own parameters when TvInputService requests to do so. 442 mTvViewAnimator = new ObjectAnimator(); 443 mTvViewAnimator.setTarget(mTvView.getTvView()); 444 mTvViewAnimator.setProperty( 445 Property.of(FrameLayout.class, ViewGroup.LayoutParams.class, "layoutParams")); 446 mTvViewAnimator.setDuration(mResources.getInteger(R.integer.tvview_anim_duration)); 447 mTvViewAnimator.addListener( 448 new AnimatorListenerAdapter() { 449 private boolean mCanceled = false; 450 451 @Override 452 public void onAnimationCancel(Animator animation) { 453 mCanceled = true; 454 } 455 456 @Override 457 public void onAnimationEnd(Animator animation) { 458 if (mCanceled) { 459 mCanceled = false; 460 return; 461 } 462 mHandler.post( 463 () -> setTvViewPosition(mTvViewLayoutParams, mTvViewFrame, false)); 464 } 465 }); 466 mTvViewAnimator.addUpdateListener( 467 new AnimatorUpdateListener() { 468 @Override 469 public void onAnimationUpdate(ValueAnimator animator) { 470 float fraction = animator.getAnimatedFraction(); 471 mLastAnimatedTvViewFrame = 472 (FrameLayout.LayoutParams) mTvView.getLayoutParams(); 473 interpolateMargins( 474 mLastAnimatedTvViewFrame, mOldTvViewFrame, mTvViewFrame, fraction); 475 mTvView.setLayoutParams(mLastAnimatedTvViewFrame); 476 } 477 }); 478 } 479 initBackgroundAnimatorIfNeeded()480 private void initBackgroundAnimatorIfNeeded() { 481 if (mBackgroundAnimator != null) { 482 return; 483 } 484 485 mBackgroundAnimator = new ObjectAnimator(); 486 mBackgroundAnimator.setTarget(mContentView); 487 mBackgroundAnimator.setPropertyName("backgroundColor"); 488 mBackgroundAnimator.setDuration( 489 mResources.getInteger(R.integer.tvactivity_background_anim_duration)); 490 mBackgroundAnimator.addListener( 491 new AnimatorListenerAdapter() { 492 @Override 493 public void onAnimationEnd(Animator animation) { 494 mHandler.post(() -> mContentView.setBackgroundColor(mBackgroundColor)); 495 } 496 }); 497 } 498 applyDisplayMode( float videoDisplayAspectRatio, boolean animate, boolean forceUpdate)499 private void applyDisplayMode( 500 float videoDisplayAspectRatio, boolean animate, boolean forceUpdate) { 501 if (videoDisplayAspectRatio <= 0f) { 502 videoDisplayAspectRatio = (float) mWindowWidth / mWindowHeight; 503 } 504 if (mAppliedDisplayedMode == mDisplayMode 505 && mAppliedTvViewStartMargin == mTvViewStartMargin 506 && mAppliedTvViewEndMargin == mTvViewEndMargin 507 && Math.abs(mAppliedVideoDisplayAspectRatio - videoDisplayAspectRatio) 508 < DISPLAY_ASPECT_RATIO_EPSILON) { 509 if (!forceUpdate) { 510 return; 511 } 512 } else { 513 mAppliedDisplayedMode = mDisplayMode; 514 mAppliedTvViewStartMargin = mTvViewStartMargin; 515 mAppliedTvViewEndMargin = mTvViewEndMargin; 516 mAppliedVideoDisplayAspectRatio = videoDisplayAspectRatio; 517 } 518 int availableAreaWidth = mWindowWidth - mTvViewStartMargin - mTvViewEndMargin; 519 int availableAreaHeight = availableAreaWidth * mWindowHeight / mWindowWidth; 520 int displayMode = mDisplayMode; 521 float availableAreaRatio = 0; 522 if (availableAreaWidth <= 0 || availableAreaHeight <= 0) { 523 displayMode = DisplayMode.MODE_FULL; 524 Log.w( 525 TAG, 526 "Some resolution info is missing during applyDisplayMode. (" 527 + "availableAreaWidth=" 528 + availableAreaWidth 529 + ", availableAreaHeight=" 530 + availableAreaHeight 531 + ")"); 532 } else { 533 availableAreaRatio = (float) availableAreaWidth / availableAreaHeight; 534 } 535 FrameLayout.LayoutParams layoutParams = 536 new FrameLayout.LayoutParams( 537 0, 0, ((FrameLayout.LayoutParams) mTvView.getTvViewLayoutParams()).gravity); 538 switch (displayMode) { 539 case DisplayMode.MODE_ZOOM: 540 if (videoDisplayAspectRatio < availableAreaRatio) { 541 // Y axis will be clipped. 542 layoutParams.width = availableAreaWidth; 543 layoutParams.height = Math.round(availableAreaWidth / videoDisplayAspectRatio); 544 } else { 545 // X axis will be clipped. 546 layoutParams.width = Math.round(availableAreaHeight * videoDisplayAspectRatio); 547 layoutParams.height = availableAreaHeight; 548 } 549 break; 550 case DisplayMode.MODE_NORMAL: 551 if (videoDisplayAspectRatio < availableAreaRatio) { 552 // X axis has black area. 553 layoutParams.width = Math.round(availableAreaHeight * videoDisplayAspectRatio); 554 layoutParams.height = availableAreaHeight; 555 } else { 556 // Y axis has black area. 557 layoutParams.width = availableAreaWidth; 558 layoutParams.height = Math.round(availableAreaWidth / videoDisplayAspectRatio); 559 } 560 break; 561 case DisplayMode.MODE_FULL: 562 default: 563 layoutParams.width = availableAreaWidth; 564 layoutParams.height = availableAreaHeight; 565 break; 566 } 567 // FrameLayout has an issue with centering when left and right margins differ. 568 // So stick to Gravity.START | Gravity.CENTER_VERTICAL. 569 int marginStart = (availableAreaWidth - layoutParams.width) / 2; 570 layoutParams.setMarginStart(marginStart); 571 int tvViewFrameTop = (mWindowHeight - availableAreaHeight) / 2; 572 FrameLayout.LayoutParams tvViewFrame = 573 createMarginLayoutParams( 574 mTvViewStartMargin, mTvViewEndMargin, tvViewFrameTop, tvViewFrameTop); 575 setTvViewPosition(layoutParams, tvViewFrame, animate); 576 setBackgroundColor( 577 mResources.getColor( 578 isTvViewFullScreen() 579 ? R.color.tvactivity_background 580 : R.color.tvactivity_background_on_shrunken_tvview, 581 null), 582 layoutParams, 583 animate); 584 585 // Update the current display mode. 586 mTvOptionsManager.onDisplayModeChanged(displayMode); 587 } 588 interpolate(int start, int end, float fraction)589 private static int interpolate(int start, int end, float fraction) { 590 return (int) (start + (end - start) * fraction); 591 } 592 interpolateMargins( MarginLayoutParams out, MarginLayoutParams startValue, MarginLayoutParams endValue, float fraction)593 private static void interpolateMargins( 594 MarginLayoutParams out, 595 MarginLayoutParams startValue, 596 MarginLayoutParams endValue, 597 float fraction) { 598 out.topMargin = interpolate(startValue.topMargin, endValue.topMargin, fraction); 599 out.bottomMargin = interpolate(startValue.bottomMargin, endValue.bottomMargin, fraction); 600 out.setMarginStart( 601 interpolate(startValue.getMarginStart(), endValue.getMarginStart(), fraction)); 602 out.setMarginEnd(interpolate(startValue.getMarginEnd(), endValue.getMarginEnd(), fraction)); 603 out.width = interpolate(startValue.width, endValue.width, fraction); 604 out.height = interpolate(startValue.height, endValue.height, fraction); 605 } 606 createMarginLayoutParams( int startMargin, int endMargin, int topMargin, int bottomMargin)607 private FrameLayout.LayoutParams createMarginLayoutParams( 608 int startMargin, int endMargin, int topMargin, int bottomMargin) { 609 FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(0, 0); 610 lp.setMarginStart(startMargin); 611 lp.setMarginEnd(endMargin); 612 lp.topMargin = topMargin; 613 lp.bottomMargin = bottomMargin; 614 lp.width = mWindowWidth - startMargin - endMargin; 615 lp.height = mWindowHeight - topMargin - bottomMargin; 616 return lp; 617 } 618 } 619