MediaRouter: Implement group volume list expand/collapse animation
Bug: 23970686
Change-Id: I85545b8f7c645d12949db5e0387447f5975f34e0
diff --git a/v7/mediarouter/res/layout/mr_controller_material_dialog_b.xml b/v7/mediarouter/res/layout/mr_controller_material_dialog_b.xml
index 0a07a92..8d17c25 100644
--- a/v7/mediarouter/res/layout/mr_controller_material_dialog_b.xml
+++ b/v7/mediarouter/res/layout/mr_controller_material_dialog_b.xml
@@ -47,36 +47,16 @@
<FrameLayout android:id="@+id/mr_default_control"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
- <ImageView android:id="@+id/mr_art"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:adjustViewBounds="true"
- android:scaleType="fitXY"
- android:background="?attr/colorPrimary"
- android:layout_gravity="top"
- android:visibility="gone" />
- <LinearLayout android:id="@+id/mr_media_control"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical"
- android:layout_gravity="bottom">
- <LinearLayout android:id="@+id/mr_media_main_control"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical"
- android:paddingTop="16dp"
- android:paddingBottom="16dp"
- android:background="?attr/colorPrimary">
- <include android:id="@+id/mr_playback_control"
- layout="@layout/mr_playback_control" />
- <View android:id="@+id/mr_control_divider"
- android:layout_width="fill_parent"
- android:layout_height="8dp"
- android:background="?attr/colorPrimary"
- android:visibility="gone" />
- <include android:id="@+id/mr_volume_control"
- layout="@layout/mr_volume_control" />
- </LinearLayout>
+ <FrameLayout android:layout_width="fill_parent"
+ android:layout_height="fill_parent">
+ <ImageView android:id="@+id/mr_art"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:adjustViewBounds="true"
+ android:scaleType="fitXY"
+ android:background="?attr/colorPrimary"
+ android:layout_gravity="top"
+ android:visibility="gone" />
<ListView android:id="@+id/mr_volume_group_list"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
@@ -84,7 +64,26 @@
android:scrollbarStyle="outsideInset"
android:clipToPadding="false"
android:background="?attr/colorPrimaryDark"
+ android:layout_gravity="bottom"
android:visibility="gone" />
+ </FrameLayout>
+ <LinearLayout android:id="@+id/mr_media_main_control"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:paddingTop="16dp"
+ android:paddingBottom="16dp"
+ android:background="?attr/colorPrimary"
+ android:layout_gravity="bottom">
+ <include android:id="@+id/mr_playback_control"
+ layout="@layout/mr_playback_control" />
+ <View android:id="@+id/mr_control_divider"
+ android:layout_width="fill_parent"
+ android:layout_height="8dp"
+ android:background="?attr/colorPrimary"
+ android:visibility="gone" />
+ <include android:id="@+id/mr_volume_control"
+ layout="@layout/mr_volume_control" />
</LinearLayout>
</FrameLayout>
diff --git a/v7/mediarouter/res/values/dimens.xml b/v7/mediarouter/res/values/dimens.xml
index c230d03..10e5751 100644
--- a/v7/mediarouter/res/values/dimens.xml
+++ b/v7/mediarouter/res/values/dimens.xml
@@ -27,11 +27,13 @@
<!-- MediaRouteController's volume group list -->
<eat-comment />
<!-- Maximum height of volume group list. -->
- <dimen name="mr_controller_volume_group_list_max_height">256dp</dimen>
+ <dimen name="mr_controller_volume_group_list_max_height">288dp</dimen>
<!-- Height of volume group item. -->
<dimen name="mr_controller_volume_group_list_item_height">68dp</dimen>
<!-- Size of an item's icon. -->
<dimen name="mr_controller_volume_group_list_item_icon_size">24dp</dimen>
<dimen name="mr_controller_volume_group_list_padding_top">16dp</dimen>
+ <!-- Group list expand/collapse animation duration. -->
+ <integer name="mr_controller_volume_group_list_animation_duration_ms">200</integer>
</resources>
diff --git a/v7/mediarouter/src/android/support/v7/app/MediaRouteControllerDialog.java b/v7/mediarouter/src/android/support/v7/app/MediaRouteControllerDialog.java
index b07189e..31ae4b5 100644
--- a/v7/mediarouter/src/android/support/v7/app/MediaRouteControllerDialog.java
+++ b/v7/mediarouter/src/android/support/v7/app/MediaRouteControllerDialog.java
@@ -50,6 +50,8 @@
import android.view.ViewTreeObserver;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
+import android.view.animation.Animation;
+import android.view.animation.Transformation;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.FrameLayout;
@@ -113,10 +115,7 @@
private TextView mRouteNameTextView;
private boolean mVolumeControlEnabled = true;
- // Layout for media controllers including play/pause button, volume slider,
- // and group volume sliders.
- private LinearLayout mMediaControlLayout;
- // Layout for media controllers including play/pause button and volume slider.
+ // Layout for media controllers including play/pause button and the main volume slider.
private LinearLayout mMediaMainControlLayout;
private RelativeLayout mPlaybackControl;
private LinearLayout mVolumeControl;
@@ -140,6 +139,8 @@
private Bitmap mArtIconBitmap;
private Uri mArtIconUri;
private boolean mIsGroupExpanded;
+ private boolean mIsGroupListAnimationNeeded;
+ private int mGroupListAnimationDurationMs;
private final AccessibilityManager mAccessibilityManager;
@@ -295,7 +296,6 @@
mDefaultControlLayout = (FrameLayout) findViewById(R.id.mr_default_control);
mArtView = (ImageView) findViewById(R.id.mr_art);
- mMediaControlLayout = (LinearLayout) findViewById(R.id.mr_media_control);
mMediaMainControlLayout = (LinearLayout) findViewById(R.id.mr_media_main_control);
mDividerView = findViewById(R.id.mr_control_divider);
@@ -323,11 +323,15 @@
mVolumeGroupList.setAdapter(
new VolumeGroupAdapter(getContext(), getGroup().getRoutes()));
} else {
- mVolumeGroupList.setVisibility(View.GONE);
+ // Request layout to update UI based on {@code mIsGroupExpanded}.
+ mDefaultControlLayout.requestLayout();
}
+ mIsGroupListAnimationNeeded = true;
updateLayoutHeight();
}
});
+ mGroupListAnimationDurationMs = getContext().getResources().getInteger(
+ R.integer.mr_controller_volume_group_list_animation_duration_ms);
mCustomControlView = onCreateMediaControlView(savedInstanceState);
if (mCustomControlView != null) {
@@ -454,7 +458,7 @@
// height.
mDividerView.setVisibility((mVolumeControl.getVisibility() == View.VISIBLE
&& showPlaybackControl) ? View.VISIBLE : View.GONE);
- mMediaControlLayout.setVisibility((mVolumeControl.getVisibility() == View.GONE
+ mMediaMainControlLayout.setVisibility((mVolumeControl.getVisibility() == View.GONE
&& !showPlaybackControl) ? View.GONE : View.VISIBLE);
}
@@ -479,11 +483,15 @@
if (mCustomControlView != null) {
return;
}
+ // Measure the size of widgets and get the height of main components.
updateMediaControlVisibility(isPlaybackControlAvailable());
+ int oldBottomMargin = getLayoutBottomMargin(mMediaMainControlLayout);
+ setLayoutBottomMargin(mMediaMainControlLayout, 0);
View decorView = getWindow().getDecorView();
decorView.measure(
MeasureSpec.makeMeasureSpec(getWindow().getAttributes().width, MeasureSpec.EXACTLY),
MeasureSpec.UNSPECIFIED);
+ setLayoutBottomMargin(mMediaMainControlLayout, oldBottomMargin);
int artViewHeight = 0;
if (mArtView.getDrawable() instanceof BitmapDrawable) {
Bitmap art = ((BitmapDrawable) mArtView.getDrawable()).getBitmap();
@@ -494,7 +502,7 @@
}
}
int mainControllerHeight = getMainControllerHeight(isPlaybackControlAvailable());
- int volumeGroupListCount = mVolumeGroupList.getVisibility() == View.VISIBLE
+ int volumeGroupListCount = mVolumeGroupList.getAdapter() != null
? mVolumeGroupList.getAdapter().getCount() : 0;
// Scale down volume group list items in landscape mode.
for (int i = 0; i < volumeGroupListCount; i++) {
@@ -505,12 +513,15 @@
mVolumeGroupListItemIconSize);
}
}
- int volumeGroupHeight = (volumeGroupListCount == 0) ? 0
- : mVolumeGroupListItemHeight * volumeGroupListCount + mVolumeGroupListPaddingTop;
- volumeGroupHeight = Math.min(volumeGroupHeight, mVolumeGroupListMaxHeight);
+ int expandedGroupListHeight = mVolumeGroupListItemHeight * volumeGroupListCount;
+ if (volumeGroupListCount > 0) {
+ expandedGroupListHeight += mVolumeGroupListPaddingTop;
+ }
+ expandedGroupListHeight = Math.min(expandedGroupListHeight, mVolumeGroupListMaxHeight);
+ int visibleGroupListHeight = mIsGroupExpanded ? expandedGroupListHeight : 0;
int desiredControlLayoutHeight =
- Math.max(artViewHeight, volumeGroupHeight) + mainControllerHeight;
+ Math.max(artViewHeight, visibleGroupListHeight) + mainControllerHeight;
Rect visibleRect = new Rect();
decorView.getWindowVisibleDisplayFrame(visibleRect);
// Height of non-control views in decor view.
@@ -521,15 +532,13 @@
// Maximum allowed height for controls to fit screen.
int maximumControlViewHeight = visibleRect.height() - nonControlViewHeight;
- // Show artwork if it fits the screen and expanded volume group list has fewer than 3 items.
- if (artViewHeight > 0 && volumeGroupListCount < 3
- && desiredControlLayoutHeight <= maximumControlViewHeight) {
+ // Show artwork if it fits the screen.
+ if (artViewHeight > 0 && desiredControlLayoutHeight <= maximumControlViewHeight) {
mArtView.setVisibility(View.VISIBLE);
setLayoutHeight(mArtView, artViewHeight);
} else {
- mArtView.setVisibility(View.GONE);
artViewHeight = 0;
- desiredControlLayoutHeight = volumeGroupHeight + mainControllerHeight;
+ desiredControlLayoutHeight = visibleGroupListHeight + mainControllerHeight;
}
// Show control if it fits the screen
if (isPlaybackControlAvailable()
@@ -542,15 +551,85 @@
mainControllerHeight = getMainControllerHeight(
mPlaybackControl.getVisibility() == View.VISIBLE);
desiredControlLayoutHeight =
- Math.max(artViewHeight, volumeGroupHeight) + mainControllerHeight;
+ Math.max(artViewHeight, visibleGroupListHeight) + mainControllerHeight;
// Limit the volume group list height to fit the screen.
if (desiredControlLayoutHeight > maximumControlViewHeight) {
- volumeGroupHeight -= (desiredControlLayoutHeight - maximumControlViewHeight);
+ visibleGroupListHeight -= (desiredControlLayoutHeight - maximumControlViewHeight);
desiredControlLayoutHeight = maximumControlViewHeight;
}
- setLayoutHeight(mVolumeGroupList, volumeGroupHeight);
setLayoutHeight(mDefaultControlLayout, desiredControlLayoutHeight);
+
+ // Animate the main control position if needed.
+ if (mVolumeGroupList.getVisibility() == View.VISIBLE
+ && mArtView.getVisibility() == View.VISIBLE && mIsGroupListAnimationNeeded) {
+ setLayoutHeight(mVolumeGroupList, mIsGroupExpanded ? expandedGroupListHeight
+ : Math.min(mArtView.getHeight(), getLayoutHeight(mVolumeGroupList)));
+ updateMainControlBottomMargin(visibleGroupListHeight, mainControllerHeight,
+ true /* animation */);
+ } else {
+ // Rely on AlertDialog's animation if there is no art work.
+ // TODO: Add group list animation even when there is no art work.
+ setLayoutHeight(mVolumeGroupList, visibleGroupListHeight);
+ updateMainControlBottomMargin(visibleGroupListHeight, mainControllerHeight,
+ false /* animation */);
+ if (artViewHeight == 0) {
+ mArtView.setVisibility(View.GONE);
+ }
+ if (!mIsGroupExpanded) {
+ mVolumeGroupList.setVisibility(View.GONE);
+ }
+ }
+ mIsGroupListAnimationNeeded = false;
+ }
+
+ private void updateMainControlBottomMargin(final int bottomMargin,
+ final int mainControllerHeight, boolean animation) {
+ final boolean isExpanding = bottomMargin != 0;
+ if (!animation) {
+ setLayoutBottomMargin(mMediaMainControlLayout, bottomMargin);
+ (isExpanding ? mVolumeGroupList : mArtView).bringToFront();
+ } else {
+ Animation existingAnim = mMediaMainControlLayout.getAnimation();
+ boolean animationInProgress = existingAnim != null && !existingAnim.hasEnded();
+ if (animationInProgress) {
+ mMediaMainControlLayout.clearAnimation();
+ }
+ final int volumeGroupListHeight = getLayoutHeight(mVolumeGroupList);
+ int rightBelowArtWork = getLayoutHeight(mDefaultControlLayout)
+ - mArtView.getHeight() - mainControllerHeight;
+ final int startValue = animationInProgress
+ ? getLayoutBottomMargin(mMediaMainControlLayout)
+ : isExpanding ? rightBelowArtWork : volumeGroupListHeight;
+ final int endValue = bottomMargin;
+ Animation anim = new Animation() {
+ private boolean mReordered;
+
+ @Override
+ protected void applyTransformation(float interpolatedTime, Transformation t) {
+ int margin = startValue - (int) ((startValue - endValue) * interpolatedTime);
+ setLayoutBottomMargin(mMediaMainControlLayout, margin);
+ // Since there could be an overlapping area of the artwork and volume group list
+ // , z-order of the art work and volume group list should be exchanged when the
+ // main control covers the overlapping area.
+ if (!mReordered) {
+ if (isExpanding) {
+ if (margin + mainControllerHeight >= volumeGroupListHeight) {
+ mVolumeGroupList.bringToFront();
+ mReordered = true;
+ }
+ } else {
+ if (volumeGroupListHeight >= margin + mainControllerHeight) {
+ mArtView.bringToFront();
+ mReordered = true;
+ }
+ }
+ }
+ }
+ };
+ anim.setDuration(mGroupListAnimationDurationMs);
+ mMediaMainControlLayout.startAnimation(anim);
+ }
}
private void updateVolumeControl() {
@@ -654,12 +733,26 @@
== MediaRouter.RouteInfo.PLAYBACK_VOLUME_VARIABLE;
}
+ private static int getLayoutHeight(View view) {
+ return view.getLayoutParams().height;
+ }
+
private static void setLayoutHeight(View view, int height) {
ViewGroup.LayoutParams lp = view.getLayoutParams();
lp.height = height;
view.setLayoutParams(lp);
}
+ private static int getLayoutBottomMargin(View view) {
+ return ((ViewGroup.MarginLayoutParams) view.getLayoutParams()).bottomMargin;
+ }
+
+ private static void setLayoutBottomMargin(View view, int bottomMargin) {
+ ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) view.getLayoutParams();
+ params.bottomMargin = bottomMargin;
+ view.setLayoutParams(params);
+ }
+
/**
* Returns desired art height to fit into controller dialog.
*/