/*
* Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.media3.ui;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.content.res.Resources;
import android.view.View;
import android.view.View.OnLayoutChangeListener;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.view.ViewGroup.MarginLayoutParams;
import android.view.animation.LinearInterpolator;
import androidx.annotation.Nullable;
import java.util.ArrayList;
import java.util.List;
/* package */ final class PlayerControlViewLayoutManager {
private static final long ANIMATION_INTERVAL_MS = 2_000;
private static final long DURATION_FOR_HIDING_ANIMATION_MS = 250;
private static final long DURATION_FOR_SHOWING_ANIMATION_MS = 250;
// Int for defining the UX state where all the views (ProgressBar, BottomBar) are
// all visible.
private static final int UX_STATE_ALL_VISIBLE = 0;
// Int for defining the UX state where only the ProgressBar view is visible.
private static final int UX_STATE_ONLY_PROGRESS_VISIBLE = 1;
// Int for defining the UX state where none of the views are visible.
private static final int UX_STATE_NONE_VISIBLE = 2;
// Int for defining the UX state where the views are being animated to be hidden.
private static final int UX_STATE_ANIMATING_HIDE = 3;
// Int for defining the UX state where the views are being animated to be shown.
private static final int UX_STATE_ANIMATING_SHOW = 4;
private final PlayerControlView playerControlView;
@Nullable private final View controlsBackground;
@Nullable private final ViewGroup centerControls;
@Nullable private final ViewGroup bottomBar;
@Nullable private final ViewGroup minimalControls;
@Nullable private final ViewGroup basicControls;
@Nullable private final ViewGroup extraControls;
@Nullable private final ViewGroup extraControlsScrollView;
@Nullable private final ViewGroup timeView;
@Nullable private final View timeBar;
@Nullable private final View overflowShowButton;
private final AnimatorSet hideMainBarAnimator;
private final AnimatorSet hideProgressBarAnimator;
private final AnimatorSet hideAllBarsAnimator;
private final AnimatorSet showMainBarAnimator;
private final AnimatorSet showAllBarsAnimator;
private final ValueAnimator overflowShowAnimator;
private final ValueAnimator overflowHideAnimator;
private final Runnable showAllBarsRunnable;
private final Runnable hideAllBarsRunnable;
private final Runnable hideProgressBarRunnable;
private final Runnable hideMainBarRunnable;
private final Runnable hideControllerRunnable;
private final OnLayoutChangeListener onLayoutChangeListener;
private final List<View> shownButtons;
private int uxState;
private boolean isMinimalMode;
private boolean needToShowBars;
private boolean animationEnabled;
@SuppressWarnings({"nullness:method.invocation", "nullness:methodref.receiver.bound"})
public PlayerControlViewLayoutManager(PlayerControlView playerControlView) {
this.playerControlView = playerControlView;
showAllBarsRunnable = this::showAllBars;
hideAllBarsRunnable = this::hideAllBars;
hideProgressBarRunnable = this::hideProgressBar;
hideMainBarRunnable = this::hideMainBar;
hideControllerRunnable = this::hideController;
onLayoutChangeListener = this::onLayoutChange;
animationEnabled = true;
uxState = UX_STATE_ALL_VISIBLE;
shownButtons = new ArrayList<>();
// Relating to Center View
controlsBackground = playerControlView.findViewById(R.id.exo_controls_background);
centerControls = playerControlView.findViewById(R.id.exo_center_controls);
// Relating to Minimal Layout
minimalControls = playerControlView.findViewById(R.id.exo_minimal_controls);
// Relating to Bottom Bar View
bottomBar = playerControlView.findViewById(R.id.exo_bottom_bar);
// Relating to Bottom Bar Left View
timeView = playerControlView.findViewById(R.id.exo_time);
timeBar = playerControlView.findViewById(R.id.exo_progress);
// Relating to Bottom Bar Right View
basicControls = playerControlView.findViewById(R.id.exo_basic_controls);
extraControls = playerControlView.findViewById(R.id.exo_extra_controls);
extraControlsScrollView = playerControlView.findViewById(R.id.exo_extra_controls_scroll_view);
overflowShowButton = playerControlView.findViewById(R.id.exo_overflow_show);
View overflowHideButton = playerControlView.findViewById(R.id.exo_overflow_hide);
if (overflowShowButton != null && overflowHideButton != null) {
overflowShowButton.setOnClickListener(this::onOverflowButtonClick);
overflowHideButton.setOnClickListener(this::onOverflowButtonClick);
}
ValueAnimator fadeOutAnimator = ValueAnimator.ofFloat(1.0f, 0.0f);
fadeOutAnimator.setInterpolator(new LinearInterpolator());
fadeOutAnimator.addUpdateListener(
animation -> {
float animatedValue = (float) animation.getAnimatedValue();
if (controlsBackground != null) {
controlsBackground.setAlpha(animatedValue);
}
if (centerControls != null) {
centerControls.setAlpha(animatedValue);
}
if (minimalControls != null) {
minimalControls.setAlpha(animatedValue);
}
});
fadeOutAnimator.addListener(
new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
if (timeBar instanceof DefaultTimeBar && !isMinimalMode) {
((DefaultTimeBar) timeBar).hideScrubber(DURATION_FOR_HIDING_ANIMATION_MS);
}
}
@Override
public void onAnimationEnd(Animator animation) {
if (controlsBackground != null) {
controlsBackground.setVisibility(View.INVISIBLE);
}
if (centerControls != null) {
centerControls.setVisibility(View.INVISIBLE);
}
if (minimalControls != null) {
minimalControls.setVisibility(View.INVISIBLE);
}
}
});
ValueAnimator fadeInAnimator = ValueAnimator.ofFloat(0.0f, 1.0f);
fadeInAnimator.setInterpolator(new LinearInterpolator());
fadeInAnimator.addUpdateListener(
animation -> {
float animatedValue = (float) animation.getAnimatedValue();
if (controlsBackground != null) {
controlsBackground.setAlpha(animatedValue);
}
if (centerControls != null) {
centerControls.setAlpha(animatedValue);
}
if (minimalControls != null) {
minimalControls.setAlpha(animatedValue);
}
});
fadeInAnimator.addListener(
new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
if (controlsBackground != null) {
controlsBackground.setVisibility(View.VISIBLE);
}
if (centerControls != null) {
centerControls.setVisibility(View.VISIBLE);
}
if (minimalControls != null) {
minimalControls.setVisibility(isMinimalMode ? View.VISIBLE : View.INVISIBLE);
}
if (timeBar instanceof DefaultTimeBar && !isMinimalMode) {
((DefaultTimeBar) timeBar).showScrubber(DURATION_FOR_SHOWING_ANIMATION_MS);
}
}
});
Resources resources = playerControlView.getResources();
float translationYForProgressBar =
resources.getDimension(R.dimen.exo_styled_bottom_bar_height)
- resources.getDimension(R.dimen.exo_styled_progress_bar_height);
float translationYForNoBars = resources.getDimension(R.dimen.exo_styled_bottom_bar_height);
hideMainBarAnimator = new AnimatorSet();
hideMainBarAnimator.setDuration(DURATION_FOR_HIDING_ANIMATION_MS);
hideMainBarAnimator.addListener(
new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
setUxState(UX_STATE_ANIMATING_HIDE);
}
@Override
public void onAnimationEnd(Animator animation) {
setUxState(UX_STATE_ONLY_PROGRESS_VISIBLE);
if (needToShowBars) {
playerControlView.post(showAllBarsRunnable);
needToShowBars = false;
}
}
});
hideMainBarAnimator
.play(fadeOutAnimator)
.with(ofTranslationY(0, translationYForProgressBar, timeBar))
.with(ofTranslationY(0, translationYForProgressBar, bottomBar));
hideProgressBarAnimator = new AnimatorSet();
hideProgressBarAnimator.setDuration(DURATION_FOR_HIDING_ANIMATION_MS);
hideProgressBarAnimator.addListener(
new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
setUxState(UX_STATE_ANIMATING_HIDE);
}
@Override
public void onAnimationEnd(Animator animation) {
setUxState(UX_STATE_NONE_VISIBLE);
if (needToShowBars) {
playerControlView.post(showAllBarsRunnable);
needToShowBars = false;
}
}
});
hideProgressBarAnimator
.play(ofTranslationY(translationYForProgressBar, translationYForNoBars, timeBar))
.with(ofTranslationY(translationYForProgressBar, translationYForNoBars, bottomBar));
hideAllBarsAnimator = new AnimatorSet();
hideAllBarsAnimator.setDuration(DURATION_FOR_HIDING_ANIMATION_MS);
hideAllBarsAnimator.addListener(
new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
setUxState(UX_STATE_ANIMATING_HIDE);
}
@Override
public void onAnimationEnd(Animator animation) {
setUxState(UX_STATE_NONE_VISIBLE);
if (needToShowBars) {
playerControlView.post(showAllBarsRunnable);
needToShowBars = false;
}
}
});
hideAllBarsAnimator
.play(fadeOutAnimator)
.with(ofTranslationY(0, translationYForNoBars, timeBar))
.with(ofTranslationY(0, translationYForNoBars, bottomBar));
showMainBarAnimator = new AnimatorSet();
showMainBarAnimator.setDuration(DURATION_FOR_SHOWING_ANIMATION_MS);
showMainBarAnimator.addListener(
new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
setUxState(UX_STATE_ANIMATING_SHOW);
}
@Override
public void onAnimationEnd(Animator animation) {
setUxState(UX_STATE_ALL_VISIBLE);
}
});
showMainBarAnimator
.play(fadeInAnimator)
.with(ofTranslationY(translationYForProgressBar, 0, timeBar))
.with(ofTranslationY(translationYForProgressBar, 0, bottomBar));
showAllBarsAnimator = new AnimatorSet();
showAllBarsAnimator.setDuration(DURATION_FOR_SHOWING_ANIMATION_MS);
showAllBarsAnimator.addListener(
new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
setUxState(UX_STATE_ANIMATING_SHOW);
}
@Override
public void onAnimationEnd(Animator animation) {
setUxState(UX_STATE_ALL_VISIBLE);
}
});
showAllBarsAnimator
.play(fadeInAnimator)
.with(ofTranslationY(translationYForNoBars, 0, timeBar))
.with(ofTranslationY(translationYForNoBars, 0, bottomBar));
overflowShowAnimator = ValueAnimator.ofFloat(0.0f, 1.0f);
overflowShowAnimator.setDuration(DURATION_FOR_SHOWING_ANIMATION_MS);
overflowShowAnimator.addUpdateListener(
animation -> animateOverflow((float) animation.getAnimatedValue()));
overflowShowAnimator.addListener(
new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
if (extraControlsScrollView != null) {
extraControlsScrollView.setVisibility(View.VISIBLE);
extraControlsScrollView.setTranslationX(extraControlsScrollView.getWidth());
extraControlsScrollView.scrollTo(extraControlsScrollView.getWidth(), 0);
}
}
@Override
public void onAnimationEnd(Animator animation) {
if (basicControls != null) {
basicControls.setVisibility(View.INVISIBLE);
}
}
});
overflowHideAnimator = ValueAnimator.ofFloat(1.0f, 0.0f);
overflowHideAnimator.setDuration(DURATION_FOR_SHOWING_ANIMATION_MS);
overflowHideAnimator.addUpdateListener(
animation -> animateOverflow((float) animation.getAnimatedValue()));
overflowHideAnimator.addListener(
new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
if (basicControls != null) {
basicControls.setVisibility(View.VISIBLE);
}
}
@Override
public void onAnimationEnd(Animator animation) {
if (extraControlsScrollView != null) {
extraControlsScrollView.setVisibility(View.INVISIBLE);
}
}
});
}
public void show() {
if (!playerControlView.isVisible()) {
playerControlView.setVisibility(View.VISIBLE);
playerControlView.updateAll();
playerControlView.requestPlayPauseFocus();
}
showAllBars();
}
public void hide() {
if (uxState == UX_STATE_ANIMATING_HIDE || uxState == UX_STATE_NONE_VISIBLE) {
return;
}
removeHideCallbacks();
if (!animationEnabled) {
hideController();
} else if (uxState == UX_STATE_ONLY_PROGRESS_VISIBLE) {
hideProgressBar();
} else {
hideAllBars();
}
}
public void hideImmediately() {
if (uxState == UX_STATE_ANIMATING_HIDE || uxState == UX_STATE_NONE_VISIBLE) {
return;
}
removeHideCallbacks();
hideController();
}
public void setAnimationEnabled(boolean animationEnabled) {
this.animationEnabled = animationEnabled;
}
public boolean isAnimationEnabled() {
return animationEnabled;
}
public void resetHideCallbacks() {
if (uxState == UX_STATE_ANIMATING_HIDE) {
return;
}
removeHideCallbacks();
int showTimeoutMs = playerControlView.getShowTimeoutMs();
if (showTimeoutMs > 0) {
if (!animationEnabled) {
postDelayedRunnable(hideControllerRunnable, showTimeoutMs);
} else if (uxState == UX_STATE_ONLY_PROGRESS_VISIBLE) {
postDelayedRunnable(hideProgressBarRunnable, ANIMATION_INTERVAL_MS);
} else {
postDelayedRunnable(hideMainBarRunnable, showTimeoutMs);
}
}
}
public void removeHideCallbacks() {
playerControlView.removeCallbacks(hideControllerRunnable);
playerControlView.removeCallbacks(hideAllBarsRunnable);
playerControlView.removeCallbacks(hideMainBarRunnable);
playerControlView.removeCallbacks(hideProgressBarRunnable);
}
public void onAttachedToWindow() {
playerControlView.addOnLayoutChangeListener(onLayoutChangeListener);
}
public void onDetachedFromWindow() {
playerControlView.removeOnLayoutChangeListener(onLayoutChangeListener);
}
public boolean isFullyVisible() {
return uxState == UX_STATE_ALL_VISIBLE && playerControlView.isVisible();
}
public void setShowButton(@Nullable View button, boolean showButton) {
if (button == null) {
return;
}
if (!showButton) {
button.setVisibility(View.GONE);
shownButtons.remove(button);
return;
}
if (isMinimalMode && shouldHideInMinimalMode(button)) {
button.setVisibility(View.INVISIBLE);
} else {
button.setVisibility(View.VISIBLE);
}
shownButtons.add(button);
}
public boolean getShowButton(@Nullable View button) {
return button != null && shownButtons.contains(button);
}
private void setUxState(int uxState) {
int prevUxState = this.uxState;
this.uxState = uxState;
if (uxState == UX_STATE_NONE_VISIBLE) {
playerControlView.setVisibility(View.GONE);
} else if (prevUxState == UX_STATE_NONE_VISIBLE) {
playerControlView.setVisibility(View.VISIBLE);
}
// TODO(insun): Notify specific uxState. Currently reuses legacy visibility listener for API
// compatibility.
if (prevUxState != uxState) {
playerControlView.notifyOnVisibilityChange();
}
}
public void onLayout(boolean changed, int left, int top, int right, int bottom) {
if (controlsBackground != null) {
// The background view should occupy the entirety of the parent. This is done in code rather
// than in layout XML to stop the background view from influencing the size of the parent if
// it uses "wrap_content". See: https://github.com/google/ExoPlayer/issues/8726.
controlsBackground.layout(0, 0, right - left, bottom - top);
}
}
private void onLayoutChange(
View v,
int left,
int top,
int right,
int bottom,
int oldLeft,
int oldTop,
int oldRight,
int oldBottom) {
boolean useMinimalMode = useMinimalMode();
if (isMinimalMode != useMinimalMode) {
isMinimalMode = useMinimalMode;
v.post(this::updateLayoutForSizeChange);
}
boolean widthChanged = (right - left) != (oldRight - oldLeft);
if (!isMinimalMode && widthChanged) {
v.post(this::onLayoutWidthChanged);
}
}
private void onOverflowButtonClick(View v) {
resetHideCallbacks();
if (v.getId() == R.id.exo_overflow_show) {
overflowShowAnimator.start();
} else if (v.getId() == R.id.exo_overflow_hide) {
overflowHideAnimator.start();
}
}
private void showAllBars() {
if (!animationEnabled) {
setUxState(UX_STATE_ALL_VISIBLE);
resetHideCallbacks();
return;
}
switch (uxState) {
case UX_STATE_NONE_VISIBLE:
showAllBarsAnimator.start();
break;
case UX_STATE_ONLY_PROGRESS_VISIBLE:
showMainBarAnimator.start();
break;
case UX_STATE_ANIMATING_HIDE:
needToShowBars = true;
break;
case UX_STATE_ANIMATING_SHOW:
return;
default:
break;
}
resetHideCallbacks();
}
private void hideAllBars() {
hideAllBarsAnimator.start();
}
private void hideProgressBar() {
hideProgressBarAnimator.start();
}
private void hideMainBar() {
hideMainBarAnimator.start();
postDelayedRunnable(hideProgressBarRunnable, ANIMATION_INTERVAL_MS);
}
private void hideController() {
setUxState(UX_STATE_NONE_VISIBLE);
}
private static ObjectAnimator ofTranslationY(float startValue, float endValue, View target) {
return ObjectAnimator.ofFloat(target, "translationY", startValue, endValue);
}
private void postDelayedRunnable(Runnable runnable, long interval) {
if (interval >= 0) {
playerControlView.postDelayed(runnable, interval);
}
}
private void animateOverflow(float animatedValue) {
if (extraControlsScrollView != null) {
int extraControlTranslationX =
(int) (extraControlsScrollView.getWidth() * (1 - animatedValue));
extraControlsScrollView.setTranslationX(extraControlTranslationX);
}
if (timeView != null) {
timeView.setAlpha(1 - animatedValue);
}
if (basicControls != null) {
basicControls.setAlpha(1 - animatedValue);
}
}
private boolean useMinimalMode() {
int width =
playerControlView.getWidth()
- playerControlView.getPaddingLeft()
- playerControlView.getPaddingRight();
int height =
playerControlView.getHeight()
- playerControlView.getPaddingBottom()
- playerControlView.getPaddingTop();
int centerControlWidth =
getWidthWithMargins(centerControls)
- (centerControls != null
? (centerControls.getPaddingLeft() + centerControls.getPaddingRight())
: 0);
int centerControlHeight =
getHeightWithMargins(centerControls)
- (centerControls != null
? (centerControls.getPaddingTop() + centerControls.getPaddingBottom())
: 0);
int defaultModeMinimumWidth =
Math.max(
centerControlWidth,
getWidthWithMargins(timeView) + getWidthWithMargins(overflowShowButton));
int defaultModeMinimumHeight = centerControlHeight + (2 * getHeightWithMargins(bottomBar));
return width <= defaultModeMinimumWidth || height <= defaultModeMinimumHeight;
}
private void updateLayoutForSizeChange() {
if (minimalControls != null) {
minimalControls.setVisibility(isMinimalMode ? View.VISIBLE : View.INVISIBLE);
}
if (timeBar != null) {
int timeBarMarginBottom =
playerControlView
.getResources()
.getDimensionPixelSize(R.dimen.exo_styled_progress_margin_bottom);
@Nullable MarginLayoutParams timeBarParams = (MarginLayoutParams) timeBar.getLayoutParams();
if (timeBarParams != null) {
timeBarParams.bottomMargin = (isMinimalMode ? 0 : timeBarMarginBottom);
timeBar.setLayoutParams(timeBarParams);
}
if (timeBar instanceof DefaultTimeBar) {
DefaultTimeBar defaultTimeBar = (DefaultTimeBar) timeBar;
if (isMinimalMode) {
defaultTimeBar.hideScrubber(/* disableScrubberPadding= */ true);
} else if (uxState == UX_STATE_ONLY_PROGRESS_VISIBLE) {
defaultTimeBar.hideScrubber(/* disableScrubberPadding= */ false);
} else if (uxState != UX_STATE_ANIMATING_HIDE) {
defaultTimeBar.showScrubber();
}
}
}
for (View v : shownButtons) {
v.setVisibility(isMinimalMode && shouldHideInMinimalMode(v) ? View.INVISIBLE : View.VISIBLE);
}
}
private boolean shouldHideInMinimalMode(View button) {
int id = button.getId();
return (id == R.id.exo_bottom_bar
|| id == R.id.exo_prev
|| id == R.id.exo_next
|| id == R.id.exo_rew
|| id == R.id.exo_rew_with_amount
|| id == R.id.exo_ffwd
|| id == R.id.exo_ffwd_with_amount);
}
private void onLayoutWidthChanged() {
if (basicControls == null || extraControls == null) {
return;
}
int width =
playerControlView.getWidth()
- playerControlView.getPaddingLeft()
- playerControlView.getPaddingRight();
// Reset back to all controls being basic controls and the overflow not being needed. The last
// child of extraControls is the overflow hide button, which shouldn't be moved back.
while (extraControls.getChildCount() > 1) {
int controlViewIndex = extraControls.getChildCount() - 2;
View controlView = extraControls.getChildAt(controlViewIndex);
extraControls.removeViewAt(controlViewIndex);
basicControls.addView(controlView, /* index= */ 0);
}
if (overflowShowButton != null) {
overflowShowButton.setVisibility(View.GONE);
}
// Calculate how much of the available width is occupied. The last child of basicControls is the
// overflow show button, which we're currently assuming will not be visible.
int occupiedWidth = getWidthWithMargins(timeView);
int endIndex = basicControls.getChildCount() - 1;
for (int i = 0; i < endIndex; i++) {
View controlView = basicControls.getChildAt(i);
occupiedWidth += getWidthWithMargins(controlView);
}
if (occupiedWidth > width) {
// We need to move some controls to extraControls.
if (overflowShowButton != null) {
overflowShowButton.setVisibility(View.VISIBLE);
occupiedWidth += getWidthWithMargins(overflowShowButton);
}
ArrayList<View> controlsToMove = new ArrayList<>();
// The last child of basicControls is the overflow show button, which shouldn't be moved.
for (int i = 0; i < endIndex; i++) {
View control = basicControls.getChildAt(i);
occupiedWidth -= getWidthWithMargins(control);
controlsToMove.add(control);
if (occupiedWidth <= width) {
break;
}
}
if (!controlsToMove.isEmpty()) {
basicControls.removeViews(/* start= */ 0, controlsToMove.size());
for (int i = 0; i < controlsToMove.size(); i++) {
// The last child of extraControls is the overflow hide button. Add controls before it.
int index = extraControls.getChildCount() - 1;
extraControls.addView(controlsToMove.get(i), index);
}
}
} else {
// If extraControls are visible, hide them since they're now empty.
if (extraControlsScrollView != null
&& extraControlsScrollView.getVisibility() == View.VISIBLE
&& !overflowHideAnimator.isStarted()) {
overflowShowAnimator.cancel();
overflowHideAnimator.start();
}
}
}
private static int getWidthWithMargins(@Nullable View v) {
if (v == null) {
return 0;
}
int width = v.getWidth();
LayoutParams layoutParams = v.getLayoutParams();
if (layoutParams instanceof MarginLayoutParams) {
MarginLayoutParams marginLayoutParams = (MarginLayoutParams) layoutParams;
width += marginLayoutParams.leftMargin + marginLayoutParams.rightMargin;
}
return width;
}
private static int getHeightWithMargins(@Nullable View v) {
if (v == null) {
return 0;
}
int height = v.getHeight();
LayoutParams layoutParams = v.getLayoutParams();
if (layoutParams instanceof MarginLayoutParams) {
MarginLayoutParams marginLayoutParams = (MarginLayoutParams) layoutParams;
height += marginLayoutParams.topMargin + marginLayoutParams.bottomMargin;
}
return height;
}
}