Merge "Use themed context to inflate preference views" into mnc-ub-dev
diff --git a/design/api/current.txt b/design/api/current.txt
index 6c8592e..a5feb3d 100644
--- a/design/api/current.txt
+++ b/design/api/current.txt
@@ -12,7 +12,7 @@
method public void setTargetElevation(float);
}
- public static class AppBarLayout.Behavior extends android.support.design.widget.ViewOffsetBehavior {
+ public static class AppBarLayout.Behavior extends android.support.design.widget.HeaderBehavior {
ctor public AppBarLayout.Behavior();
ctor public AppBarLayout.Behavior(android.content.Context, android.util.AttributeSet);
method public boolean onInterceptTouchEvent(android.support.design.widget.CoordinatorLayout, android.support.design.widget.AppBarLayout, android.view.MotionEvent);
@@ -56,14 +56,14 @@
method public abstract void onOffsetChanged(android.support.design.widget.AppBarLayout, int);
}
- public static class AppBarLayout.ScrollingViewBehavior extends android.support.design.widget.ViewOffsetBehavior {
+ public static class AppBarLayout.ScrollingViewBehavior extends android.support.design.widget.HeaderScrollingViewBehavior {
ctor public AppBarLayout.ScrollingViewBehavior();
ctor public AppBarLayout.ScrollingViewBehavior(android.content.Context, android.util.AttributeSet);
+ method protected android.view.View findFirstDependency(java.util.List<android.view.View>);
method public int getOverlayTop();
method public boolean layoutDependsOn(android.support.design.widget.CoordinatorLayout, android.view.View, android.view.View);
method public boolean onDependentViewChanged(android.support.design.widget.CoordinatorLayout, android.view.View, android.view.View);
method public boolean onLayoutChild(android.support.design.widget.CoordinatorLayout, android.view.View, int);
- method public boolean onMeasureChild(android.support.design.widget.CoordinatorLayout, android.view.View, int, int, int, int);
method public void setOverlayTop(int);
}
@@ -203,6 +203,42 @@
method public boolean onLayoutChild(android.support.design.widget.CoordinatorLayout, android.support.design.widget.FloatingActionButton, int);
}
+ abstract class HeaderBehavior extends android.support.design.widget.ViewOffsetBehavior {
+ ctor public HeaderBehavior();
+ ctor public HeaderBehavior(android.content.Context, android.util.AttributeSet);
+ method protected boolean fling(android.support.design.widget.CoordinatorLayout, android.view.View, int, int, float);
+ method protected int getTopBottomOffsetForScrollingSibling();
+ method protected int scroll(android.support.design.widget.CoordinatorLayout, android.view.View, int, int, int);
+ method protected int setHeaderTopBottomOffset(android.support.design.widget.CoordinatorLayout, android.view.View, int);
+ method protected int setHeaderTopBottomOffset(android.support.design.widget.CoordinatorLayout, android.view.View, int, int, int);
+ }
+
+ abstract class HeaderScrollingViewBehavior extends android.support.design.widget.ViewOffsetBehavior {
+ ctor public HeaderScrollingViewBehavior();
+ ctor public HeaderScrollingViewBehavior(android.content.Context, android.util.AttributeSet);
+ method protected abstract android.view.View findFirstDependency(java.util.List<android.view.View>);
+ method protected int getScrollRange(android.view.View);
+ method public boolean onMeasureChild(android.support.design.widget.CoordinatorLayout, android.view.View, int, int, int, int);
+ }
+
+ public final class NavigationHeaderBehavior extends android.support.design.widget.HeaderBehavior {
+ ctor public NavigationHeaderBehavior();
+ ctor public NavigationHeaderBehavior(android.content.Context, android.util.AttributeSet);
+ method public boolean layoutDependsOn(android.support.design.widget.CoordinatorLayout, android.view.View, android.view.View);
+ method public boolean onNestedFling(android.support.design.widget.CoordinatorLayout, android.view.View, android.view.View, float, float, boolean);
+ method public void onNestedPreScroll(android.support.design.widget.CoordinatorLayout, android.view.View, android.view.View, int, int, int[]);
+ method public void onNestedScroll(android.support.design.widget.CoordinatorLayout, android.view.View, android.view.View, int, int, int, int);
+ method public boolean onStartNestedScroll(android.support.design.widget.CoordinatorLayout, android.view.View, android.view.View, android.view.View, int);
+ }
+
+ public static class NavigationHeaderBehavior.MenuViewBehavior extends android.support.design.widget.HeaderScrollingViewBehavior {
+ ctor public NavigationHeaderBehavior.MenuViewBehavior();
+ ctor public NavigationHeaderBehavior.MenuViewBehavior(android.content.Context, android.util.AttributeSet);
+ method protected android.view.View findFirstDependency(java.util.List<android.view.View>);
+ method public boolean layoutDependsOn(android.support.design.widget.CoordinatorLayout, android.view.View, android.view.View);
+ method public boolean onDependentViewChanged(android.support.design.widget.CoordinatorLayout, android.view.View, android.view.View);
+ }
+
public class NavigationView extends android.widget.FrameLayout {
ctor public NavigationView(android.content.Context);
ctor public NavigationView(android.content.Context, android.util.AttributeSet);
diff --git a/design/res/layout/design_navigation_item_header.xml b/design/res/layout/design_navigation_item_header.xml
index 33fd199..8d03f69 100644
--- a/design/res/layout/design_navigation_item_header.xml
+++ b/design/res/layout/design_navigation_item_header.xml
@@ -15,6 +15,7 @@
~ limitations under the License.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/navigation_header_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
diff --git a/design/src/android/support/design/internal/NavigationMenuPresenter.java b/design/src/android/support/design/internal/NavigationMenuPresenter.java
index f238bcb..8184d16 100644
--- a/design/src/android/support/design/internal/NavigationMenuPresenter.java
+++ b/design/src/android/support/design/internal/NavigationMenuPresenter.java
@@ -23,7 +23,6 @@
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Parcelable;
-import android.support.annotation.LayoutRes;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.StyleRes;
@@ -40,7 +39,6 @@
import android.view.SubMenu;
import android.view.View;
import android.view.ViewGroup;
-import android.widget.LinearLayout;
import android.widget.TextView;
import java.util.ArrayList;
@@ -54,7 +52,6 @@
private static final String STATE_ADAPTER = "android:menu:adapter";
private NavigationMenuView mMenuView;
- private LinearLayout mHeaderLayout;
private Callback mCallback;
private MenuBuilder mMenu;
@@ -99,9 +96,6 @@
if (mAdapter == null) {
mAdapter = new NavigationMenuAdapter();
}
- mHeaderLayout = (LinearLayout) mLayoutInflater
- .inflate(R.layout.design_navigation_item_header,
- mMenuView, false);
mMenuView.setAdapter(mAdapter);
}
return mMenuView;
@@ -186,21 +180,14 @@
mAdapter.setCheckedItem(item);
}
- public View inflateHeaderView(@LayoutRes int res) {
- View view = mLayoutInflater.inflate(res, mHeaderLayout, false);
- addHeaderView(view);
- return view;
- }
-
public void addHeaderView(@NonNull View view) {
- mHeaderLayout.addView(view);
- // The padding on top should be cleared.
+ // The padding on top should be cleared when there's a header.
mMenuView.setPadding(0, 0, 0, mMenuView.getPaddingBottom());
}
- public void removeHeaderView(@NonNull View view) {
- mHeaderLayout.removeView(view);
- if (mHeaderLayout.getChildCount() == 0) {
+ public void removeHeaderView(@NonNull View view, boolean hasHeaders) {
+ if (!hasHeaders) {
+ // Padding should be added when there are no headers.
mMenuView.setPadding(0, mPaddingTopDefault, 0, mMenuView.getPaddingBottom());
}
}
@@ -279,14 +266,6 @@
}
- private static class HeaderViewHolder extends ViewHolder {
-
- public HeaderViewHolder(View itemView) {
- super(itemView);
- }
-
- }
-
/**
* Handles click events for the menu items. The items has to be {@link NavigationMenuItemView}.
*/
@@ -315,7 +294,6 @@
private static final int VIEW_TYPE_NORMAL = 0;
private static final int VIEW_TYPE_SUBHEADER = 1;
private static final int VIEW_TYPE_SEPARATOR = 2;
- private static final int VIEW_TYPE_HEADER = 3;
private final ArrayList<NavigationMenuItem> mItems = new ArrayList<>();
private MenuItemImpl mCheckedItem;
@@ -341,8 +319,6 @@
NavigationMenuItem item = mItems.get(position);
if (item instanceof NavigationMenuSeparatorItem) {
return VIEW_TYPE_SEPARATOR;
- } else if (item instanceof NavigationMenuHeaderItem) {
- return VIEW_TYPE_HEADER;
} else if (item instanceof NavigationMenuTextItem) {
NavigationMenuTextItem textItem = (NavigationMenuTextItem) item;
if (textItem.getMenuItem().hasSubMenu()) {
@@ -363,8 +339,6 @@
return new SubheaderViewHolder(mLayoutInflater, parent);
case VIEW_TYPE_SEPARATOR:
return new SeparatorViewHolder(mLayoutInflater, parent);
- case VIEW_TYPE_HEADER:
- return new HeaderViewHolder(mHeaderLayout);
}
return null;
}
@@ -400,9 +374,6 @@
item.getPaddingBottom());
break;
}
- case VIEW_TYPE_HEADER: {
- break;
- }
}
}
@@ -429,7 +400,6 @@
}
mUpdateSuspended = true;
mItems.clear();
- mItems.add(new NavigationMenuHeaderItem());
int currentGroupId = -1;
int currentGroupStart = 0;
@@ -622,12 +592,4 @@
}
}
-
- /**
- * Header (not subheader) items.
- */
- private static class NavigationMenuHeaderItem implements NavigationMenuItem {
- // The actual content is hold by NavigationMenuPresenter#mHeaderLayout.
- }
-
}
diff --git a/design/src/android/support/design/widget/AppBarLayout.java b/design/src/android/support/design/widget/AppBarLayout.java
index acfd28b..23cd9702 100644
--- a/design/src/android/support/design/widget/AppBarLayout.java
+++ b/design/src/android/support/design/widget/AppBarLayout.java
@@ -25,7 +25,6 @@
import android.support.v4.view.MotionEventCompat;
import android.support.v4.view.ViewCompat;
import android.support.v4.view.WindowInsetsCompat;
-import android.support.v4.widget.ScrollerCompat;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
@@ -645,7 +644,7 @@
* The default {@link Behavior} for {@link AppBarLayout}. Implements the necessary nested
* scroll handling with offsetting.
*/
- public static class Behavior extends ViewOffsetBehavior<AppBarLayout> {
+ public static class Behavior extends HeaderBehavior<AppBarLayout> {
private static final int INVALID_POINTER = -1;
private static final int INVALID_POSITION = -1;
@@ -653,8 +652,6 @@
private boolean mSkipNestedPreScroll;
private boolean mWasFlung;
- private Runnable mFlingRunnable;
- private ScrollerCompat mScroller;
private ValueAnimatorCompat mAnimator;
@@ -901,7 +898,7 @@
mAnimator.setUpdateListener(new ValueAnimatorCompat.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimatorCompat animator) {
- setAppBarTopBottomOffset(coordinatorLayout, child,
+ setHeaderTopBottomOffset(coordinatorLayout, child,
animator.getAnimatedIntValue());
}
});
@@ -913,31 +910,6 @@
mAnimator.start();
}
- private boolean fling(CoordinatorLayout coordinatorLayout, AppBarLayout layout, int minOffset,
- int maxOffset, float velocityY) {
- if (mFlingRunnable != null) {
- layout.removeCallbacks(mFlingRunnable);
- }
-
- if (mScroller == null) {
- mScroller = ScrollerCompat.create(layout.getContext());
- }
-
- mScroller.fling(
- 0, getTopBottomOffsetForScrollingSibling(), // curr
- 0, Math.round(velocityY), // velocity.
- 0, 0, // x
- minOffset, maxOffset); // y
-
- if (mScroller.computeScrollOffset()) {
- mFlingRunnable = new FlingRunnable(coordinatorLayout, layout);
- ViewCompat.postOnAnimation(layout, mFlingRunnable);
- return true;
- } else {
- mFlingRunnable = null;
- return false;
- }
- }
private View getChildOnOffset(AppBarLayout abl, final int offset) {
for (int i = 0, count = abl.getChildCount(); i < count; i++) {
@@ -964,26 +936,6 @@
}
}
- private class FlingRunnable implements Runnable {
- private final CoordinatorLayout mParent;
- private final AppBarLayout mLayout;
-
- FlingRunnable(CoordinatorLayout parent, AppBarLayout layout) {
- mParent = parent;
- mLayout = layout;
- }
-
- @Override
- public void run() {
- if (mLayout != null && mScroller != null && mScroller.computeScrollOffset()) {
- setAppBarTopBottomOffset(mParent, mLayout, mScroller.getCurrY());
-
- // Post ourselves so that we run on the next animation
- ViewCompat.postOnAnimation(mLayout, this);
- }
- }
- }
-
@Override
public boolean onLayoutChild(CoordinatorLayout parent, AppBarLayout abl,
int layoutDirection) {
@@ -997,13 +949,13 @@
if (animate) {
animateOffsetTo(parent, abl, offset);
} else {
- setAppBarTopBottomOffset(parent, abl, offset);
+ setHeaderTopBottomOffset(parent, abl, offset);
}
} else if ((pendingAction & PENDING_ACTION_EXPANDED) != 0) {
if (animate) {
animateOffsetTo(parent, abl, 0);
} else {
- setAppBarTopBottomOffset(parent, abl, 0);
+ setHeaderTopBottomOffset(parent, abl, 0);
}
}
} else if (mOffsetToChildIndexOnLayout >= 0) {
@@ -1027,12 +979,6 @@
return handled;
}
- private int scroll(CoordinatorLayout coordinatorLayout, AppBarLayout appBarLayout,
- int dy, int minOffset, int maxOffset) {
- return setAppBarTopBottomOffset(coordinatorLayout, appBarLayout,
- getTopBottomOffsetForScrollingSibling() - dy, minOffset, maxOffset);
- }
-
private boolean canDragAppBarLayout() {
if (mLastNestedScrollingChildRef != null) {
final View view = mLastNestedScrollingChildRef.get();
@@ -1041,22 +987,18 @@
return false;
}
- final int setAppBarTopBottomOffset(CoordinatorLayout coordinatorLayout,
- AppBarLayout appBarLayout, int newOffset) {
- return setAppBarTopBottomOffset(coordinatorLayout, appBarLayout, newOffset,
- Integer.MIN_VALUE, Integer.MAX_VALUE);
- }
-
- final int setAppBarTopBottomOffset(CoordinatorLayout coordinatorLayout,
- AppBarLayout appBarLayout, int newOffset, int minOffset, int maxOffset) {
+ @Override
+ protected int setHeaderTopBottomOffset(CoordinatorLayout coordinatorLayout,
+ View header, int newOffset, int minOffset, int maxOffset) {
final int curOffset = getTopBottomOffsetForScrollingSibling();
int consumed = 0;
- if (minOffset != 0 && curOffset >= minOffset && curOffset <= maxOffset) {
+ if ((header instanceof AppBarLayout) && minOffset != 0 && curOffset >= minOffset
+ && curOffset <= maxOffset) {
// If we have some scrolling range, and we're currently within the min and max
// offsets, calculate a new offset
newOffset = MathUtils.constrain(newOffset, minOffset, maxOffset);
-
+ AppBarLayout appBarLayout = (AppBarLayout) header;
if (curOffset != newOffset) {
final int interpolatedOffset = appBarLayout.hasChildWithInterpolator()
? interpolateOffset(appBarLayout, newOffset)
@@ -1145,7 +1087,8 @@
return offset;
}
- final int getTopBottomOffsetForScrollingSibling() {
+ @Override
+ protected int getTopBottomOffsetForScrollingSibling() {
return getTopAndBottomOffset() + mOffsetDelta;
}
@@ -1231,7 +1174,7 @@
* Behavior which should be used by {@link View}s which can scroll vertically and support
* nested scrolling to automatically scroll any {@link AppBarLayout} siblings.
*/
- public static class ScrollingViewBehavior extends ViewOffsetBehavior<View> {
+ public static class ScrollingViewBehavior extends HeaderScrollingViewBehavior {
private int mOverlayTop;
public ScrollingViewBehavior() {}
@@ -1253,65 +1196,6 @@
}
@Override
- public boolean onMeasureChild(CoordinatorLayout parent, final View child,
- int parentWidthMeasureSpec, int widthUsed,
- int parentHeightMeasureSpec, int heightUsed) {
- final int childLpHeight = child.getLayoutParams().height;
- if (childLpHeight == LayoutParams.MATCH_PARENT
- || childLpHeight == LayoutParams.WRAP_CONTENT) {
- // If the child's height is set to match_parent/wrap_content then measure it
- // with the maximum visible height
-
- final List<View> dependencies = parent.getDependencies(child);
- if (dependencies.isEmpty()) {
- // If we don't have any dependencies, return false
- return false;
- }
-
- final AppBarLayout appBar = findFirstAppBarLayout(dependencies);
- if (appBar != null && appBar.getVisibility() == View.VISIBLE) {
- if (appBar.getMeasuredHeight() <= 0 && appBar.getMeasuredWidth() <= 0) {
- // The AppBar hasn't been measured yet, so we'll post a Runnable to delay
- // our measure until after the ABLs has happened
- child.post(new Runnable() {
- @Override
- public void run() {
- child.requestLayout();
- }
- });
- return false;
- }
-
- if (ViewCompat.getFitsSystemWindows(appBar)) {
- // If the AppBarLayout is fitting system windows then we need to also,
- // otherwise we'll get CoL's compatible layout functionality
- ViewCompat.setFitsSystemWindows(child, true);
- }
-
- int availableHeight = MeasureSpec.getSize(parentHeightMeasureSpec);
- if (availableHeight == 0) {
- // If the measure spec doesn't specify a size, use the current height
- availableHeight = parent.getHeight();
- }
-
- final int height = availableHeight - appBar.getMeasuredHeight()
- + appBar.getTotalScrollRange();
- final int heightMeasureSpec = MeasureSpec.makeMeasureSpec(height,
- childLpHeight == LayoutParams.MATCH_PARENT
- ? MeasureSpec.EXACTLY
- : MeasureSpec.AT_MOST);
-
- // Now measure the scrolling child with the correct height
- parent.onMeasureChild(child, parentWidthMeasureSpec,
- widthUsed, heightMeasureSpec, heightUsed);
-
- return true;
- }
- }
- return false;
- }
-
- @Override
public boolean onLayoutChild(CoordinatorLayout parent, View child, int layoutDirection) {
// First lay out the child as normal
super.onLayoutChild(parent, child, layoutDirection);
@@ -1386,7 +1270,8 @@
return mOverlayTop;
}
- private static AppBarLayout findFirstAppBarLayout(List<View> views) {
+ @Override
+ protected View findFirstDependency(List<View> views) {
for (int i = 0, z = views.size(); i < z; i++) {
View view = views.get(i);
if (view instanceof AppBarLayout) {
@@ -1395,5 +1280,14 @@
}
return null;
}
+
+ @Override
+ protected int getScrollRange(View v) {
+ if (v instanceof AppBarLayout) {
+ return ((AppBarLayout) v).getTotalScrollRange();
+ } else {
+ return super.getScrollRange(v);
+ }
+ }
}
}
diff --git a/design/src/android/support/design/widget/HeaderBehavior.java b/design/src/android/support/design/widget/HeaderBehavior.java
new file mode 100644
index 0000000..ae61b62
--- /dev/null
+++ b/design/src/android/support/design/widget/HeaderBehavior.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2015 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 android.support.design.widget;
+
+import android.content.Context;
+import android.support.design.widget.CoordinatorLayout.Behavior;
+import android.support.v4.view.ViewCompat;
+import android.support.v4.widget.ScrollerCompat;
+import android.util.AttributeSet;
+import android.view.View;
+
+/**
+ * The {@link Behavior} for a view that sits vertically above scrolling a view.
+ * See {@link HeaderScrollingViewBehavior}.
+ */
+abstract class HeaderBehavior<V extends View> extends ViewOffsetBehavior<V> {
+
+ private Runnable mFlingRunnable;
+ private ScrollerCompat mScroller;
+
+ public HeaderBehavior() {
+ super();
+ }
+
+ public HeaderBehavior(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ protected int setHeaderTopBottomOffset(CoordinatorLayout coordinatorLayout,
+ View header, int newOffset) {
+ return setHeaderTopBottomOffset(coordinatorLayout, header, newOffset,
+ Integer.MIN_VALUE, Integer.MAX_VALUE);
+ }
+
+ protected int setHeaderTopBottomOffset(CoordinatorLayout coordinatorLayout,
+ View header, int newOffset, int minOffset, int maxOffset) {
+ final int curOffset = getTopAndBottomOffset();
+ int consumed = 0;
+
+ if (minOffset != 0 && curOffset >= minOffset && curOffset <= maxOffset) {
+ // If we have some scrolling range, and we're currently within the min and max
+ // offsets, calculate a new offset
+ newOffset = MathUtils.constrain(newOffset, minOffset, maxOffset);
+
+ if (curOffset != newOffset) {
+ setTopAndBottomOffset(newOffset);
+ // Update how much dy we have consumed
+ consumed = curOffset - newOffset;
+ }
+ }
+
+ return consumed;
+ }
+
+ protected int getTopBottomOffsetForScrollingSibling() {
+ return getTopAndBottomOffset();
+ }
+
+ protected int scroll(CoordinatorLayout coordinatorLayout, View header,
+ int dy, int minOffset, int maxOffset) {
+ return setHeaderTopBottomOffset(coordinatorLayout, header,
+ getTopBottomOffsetForScrollingSibling() - dy, minOffset, maxOffset);
+ }
+
+ protected boolean fling(CoordinatorLayout coordinatorLayout, View layout, int minOffset,
+ int maxOffset, float velocityY) {
+ if (mFlingRunnable != null) {
+ layout.removeCallbacks(mFlingRunnable);
+ }
+
+ if (mScroller == null) {
+ mScroller = ScrollerCompat.create(layout.getContext());
+ }
+
+ mScroller.fling(
+ 0, getTopAndBottomOffset(), // curr
+ 0, Math.round(velocityY), // velocity.
+ 0, 0, // x
+ minOffset, maxOffset); // y
+
+ if (mScroller.computeScrollOffset()) {
+ mFlingRunnable = new FlingRunnable(coordinatorLayout, layout);
+ ViewCompat.postOnAnimation(layout, mFlingRunnable);
+ return true;
+ } else {
+ mFlingRunnable = null;
+ return false;
+ }
+ }
+
+ private class FlingRunnable implements Runnable {
+ private final CoordinatorLayout mParent;
+ private final View mLayout;
+
+ FlingRunnable(CoordinatorLayout parent, View layout) {
+ mParent = parent;
+ mLayout = layout;
+ }
+
+ @Override
+ public void run() {
+ if (mLayout != null && mScroller != null && mScroller.computeScrollOffset()) {
+ setHeaderTopBottomOffset(mParent, mLayout, mScroller.getCurrY());
+
+ // Post ourselves so that we run on the next animation
+ ViewCompat.postOnAnimation(mLayout, this);
+ }
+ }
+ }
+}
diff --git a/design/src/android/support/design/widget/HeaderScrollingViewBehavior.java b/design/src/android/support/design/widget/HeaderScrollingViewBehavior.java
new file mode 100644
index 0000000..3b43a91
--- /dev/null
+++ b/design/src/android/support/design/widget/HeaderScrollingViewBehavior.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2015 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 android.support.design.widget;
+
+import android.content.Context;
+import android.support.design.widget.CoordinatorLayout.Behavior;
+import android.support.v4.view.ViewCompat;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+
+import java.util.List;
+
+/**
+ * The {@link Behavior} for a scrolling view that is positioned vertically below another view.
+ * See {@link HeaderBehavior}.
+ */
+abstract class HeaderScrollingViewBehavior extends ViewOffsetBehavior<View> {
+
+ public HeaderScrollingViewBehavior() {
+ super();
+ }
+
+ public HeaderScrollingViewBehavior(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ protected abstract View findFirstDependency(List<View> views);
+
+ @Override
+ public boolean onMeasureChild(CoordinatorLayout parent, View child,
+ int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec,
+ int heightUsed) {
+ final int childLpHeight = child.getLayoutParams().height;
+ if (childLpHeight == ViewGroup.LayoutParams.MATCH_PARENT
+ || childLpHeight == ViewGroup.LayoutParams.WRAP_CONTENT) {
+ // If the menu's height is set to match_parent/wrap_content then measure it
+ // with the maximum visible height
+
+ final List<View> dependencies = parent.getDependencies(child);
+ if (dependencies.isEmpty()) {
+ // If we don't have any dependencies, return false
+ return false;
+ }
+
+ final View header = findFirstDependency(dependencies);
+ if (header != null && ViewCompat.isLaidOut(header)) {
+ if (ViewCompat.getFitsSystemWindows(header)) {
+ // If the header is fitting system windows then we need to also,
+ // otherwise we'll get CoL's compatible layout functionality
+ ViewCompat.setFitsSystemWindows(child, true);
+ }
+
+ int availableHeight = View.MeasureSpec.getSize(parentHeightMeasureSpec);
+ if (availableHeight == 0) {
+ // If the measure spec doesn't specify a size, use the current height
+ availableHeight = parent.getHeight();
+ }
+
+ final int height = availableHeight - header.getMeasuredHeight()
+ + getScrollRange(header);
+ final int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(height,
+ childLpHeight == ViewGroup.LayoutParams.MATCH_PARENT
+ ? View.MeasureSpec.EXACTLY
+ : View.MeasureSpec.AT_MOST);
+
+ // Now measure the scrolling menu with the correct height
+ parent.onMeasureChild(child, parentWidthMeasureSpec,
+ widthUsed, heightMeasureSpec, heightUsed);
+
+ return true;
+ }
+ }
+ return false;
+ }
+
+ protected int getScrollRange(View v) {
+ return v.getMeasuredHeight();
+ }
+}
\ No newline at end of file
diff --git a/design/src/android/support/design/widget/NavigationHeaderBehavior.java b/design/src/android/support/design/widget/NavigationHeaderBehavior.java
new file mode 100644
index 0000000..4b38146
--- /dev/null
+++ b/design/src/android/support/design/widget/NavigationHeaderBehavior.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2015 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 android.support.design.widget;
+
+import android.content.Context;
+import android.support.design.R;
+import android.support.design.widget.CoordinatorLayout.Behavior;
+import android.support.v4.view.ViewCompat;
+import android.support.v7.widget.RecyclerView;
+import android.util.AttributeSet;
+import android.view.View;
+
+import java.util.List;
+
+/**
+ * The {@link Behavior} for the {@link NavigationView} header. This handles scrolling the
+ * header view with the list that displays the menu in {@link NavigationView}.
+ */
+public final class NavigationHeaderBehavior extends HeaderBehavior<View> {
+
+ public NavigationHeaderBehavior() {
+ super();
+ }
+
+ public NavigationHeaderBehavior(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ public boolean layoutDependsOn(CoordinatorLayout parent, View header, View dependency) {
+ return dependency instanceof RecyclerView;
+ }
+
+ public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, View header,
+ View directTargetChild, View target, int nestedScrollAxes) {
+ // Return true if we're nested scrolling vertically, and the scrolling view is big enough
+ // to scroll.
+ boolean canTargetScroll = ViewCompat.canScrollVertically(target, -1)
+ || ViewCompat.canScrollVertically(target,1);
+ final boolean started = (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0
+ && coordinatorLayout.getHeight() - target.getHeight() <= header.getHeight()
+ && canTargetScroll;
+ return started;
+ }
+
+ @Override
+ public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, View header, View target,
+ int dx, int dy, int[] consumed) {
+ if (dy > 0) {
+ // We're scrolling up
+ consumed[1] = scroll(coordinatorLayout, header, dy, -header.getMeasuredHeight(), 0);
+ }
+ }
+
+ @Override
+ public void onNestedScroll(CoordinatorLayout coordinatorLayout, View header, View target,
+ int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
+ if (dyUnconsumed < 0) {
+ // If the scrolling view is scrolling down but not consuming, it's probably at
+ // the top of it's content, scroll the header down.
+ scroll(coordinatorLayout, header, dyUnconsumed, -header.getMeasuredHeight(), 0);
+ }
+ }
+
+ @Override
+ public boolean onNestedFling(CoordinatorLayout coordinatorLayout, View header, View target,
+ float velocityX, float velocityY, boolean consumed) {
+ if (!consumed) {
+ // It hasn't been consumed so let's fling ourselves
+ return fling(coordinatorLayout, header, -header.getMeasuredHeight(), 0, -velocityY);
+ }
+ return false;
+ }
+
+ /**
+ * Defines the behavior for the scrolling view used to back {@link NavigationView}.
+ */
+ public static class MenuViewBehavior extends HeaderScrollingViewBehavior {
+
+ public MenuViewBehavior() {
+ }
+
+ public MenuViewBehavior(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ public boolean layoutDependsOn(CoordinatorLayout parent, View menu, View dependency) {
+ // We depend on the header container
+ return (dependency.getId() == R.id.navigation_header_container);
+ }
+
+ @Override
+ public boolean onDependentViewChanged(CoordinatorLayout parent, View menu,
+ View dependency) {
+ final CoordinatorLayout.Behavior behavior =
+ ((CoordinatorLayout.LayoutParams) dependency.getLayoutParams()).getBehavior();
+ if (behavior instanceof NavigationHeaderBehavior) {
+ // Offset the menu so that it is below the header.
+ final int headerOffset = ((NavigationHeaderBehavior) behavior)
+ .getTopAndBottomOffset();
+ setTopAndBottomOffset(dependency.getHeight() + headerOffset);
+ }
+ return false;
+ }
+
+ @Override
+ protected View findFirstDependency(List<View> views) {
+ for (int i = 0, z = views.size(); i < z; i++) {
+ View view = views.get(i);
+ if (view.getId() == R.id.navigation_header_container) {
+ return view;
+ }
+ }
+ return null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/design/src/android/support/design/widget/NavigationView.java b/design/src/android/support/design/widget/NavigationView.java
index 45fd088..6f606d4 100644
--- a/design/src/android/support/design/widget/NavigationView.java
+++ b/design/src/android/support/design/widget/NavigationView.java
@@ -32,6 +32,7 @@
import android.support.design.R;
import android.support.design.internal.NavigationMenu;
import android.support.design.internal.NavigationMenuPresenter;
+import android.support.design.internal.NavigationMenuView;
import android.support.design.internal.ScrimInsetsFrameLayout;
import android.support.v4.content.ContextCompat;
import android.support.v4.view.ViewCompat;
@@ -40,10 +41,12 @@
import android.support.v7.internal.view.menu.MenuItemImpl;
import android.util.AttributeSet;
import android.util.TypedValue;
+import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
+import android.widget.LinearLayout;
/**
* Represents a standard navigation menu for application. The menu contents can be populated
@@ -79,10 +82,14 @@
private final NavigationMenu mMenu;
private final NavigationMenuPresenter mPresenter = new NavigationMenuPresenter();
+ private final CoordinatorLayout mCoordinatorLayout;
+ private final LinearLayout mHeaderContainer;
+
private OnNavigationItemSelectedListener mListener;
private int mMaxWidth;
private MenuInflater mMenuInflater;
+ private LayoutInflater mLayoutInflater;
public NavigationView(Context context) {
this(context, null);
@@ -160,7 +167,32 @@
mPresenter.setItemTextColor(itemTextColor);
mPresenter.setItemBackground(itemBackground);
mMenu.addMenuPresenter(mPresenter);
- addView((View) mPresenter.getMenuView(this));
+
+ // Create the coordinator layout and add the header and menu view.
+ mCoordinatorLayout = new CoordinatorLayout(context);
+ mLayoutInflater = LayoutInflater.from(context);
+
+ mHeaderContainer = (LinearLayout) mLayoutInflater.inflate(R.layout.design_navigation_item_header,
+ this, false);
+ mCoordinatorLayout.addView(mHeaderContainer);
+
+ NavigationMenuView menuView = (NavigationMenuView) mPresenter.getMenuView(this);
+ mCoordinatorLayout.addView(menuView);
+
+ // Set the header behavior
+ CoordinatorLayout.LayoutParams headerParams =
+ (CoordinatorLayout.LayoutParams) mHeaderContainer.getLayoutParams();
+ headerParams.setBehavior(new NavigationHeaderBehavior());
+ mHeaderContainer.setLayoutParams(headerParams);
+
+ // Set the menu behavior
+ CoordinatorLayout.LayoutParams menuParams =
+ (CoordinatorLayout.LayoutParams) menuView.getLayoutParams();
+ menuParams.setBehavior(new NavigationHeaderBehavior.MenuViewBehavior());
+ menuView.setLayoutParams(menuParams);
+
+ // Add coordinator to hierarchy
+ addView(mCoordinatorLayout);
if (a.hasValue(R.styleable.NavigationView_menu)) {
inflateMenu(a.getResourceId(R.styleable.NavigationView_menu, 0));
@@ -245,7 +277,9 @@
* @return a newly inflated View.
*/
public View inflateHeaderView(@LayoutRes int res) {
- return mPresenter.inflateHeaderView(res);
+ View view = mLayoutInflater.inflate(res, mHeaderContainer, false);
+ addHeaderView(view);
+ return view;
}
/**
@@ -255,6 +289,7 @@
*/
public void addHeaderView(@NonNull View view) {
mPresenter.addHeaderView(view);
+ mHeaderContainer.addView(view);
}
/**
@@ -263,7 +298,8 @@
* @param view The view to remove
*/
public void removeHeaderView(@NonNull View view) {
- mPresenter.removeHeaderView(view);
+ mPresenter.removeHeaderView(view, (mHeaderContainer.getChildCount() > 0) /* hasHeaders */);
+ mHeaderContainer.removeView(view);
}
/**
diff --git a/v4/api/current.txt b/v4/api/current.txt
index b0be7a0..669b4a4 100644
--- a/v4/api/current.txt
+++ b/v4/api/current.txt
@@ -1071,6 +1071,11 @@
method public void unregisterReceiver(android.content.BroadcastReceiver);
}
+ public class ParallelExecutorCompat {
+ ctor public ParallelExecutorCompat();
+ method public static java.util.concurrent.Executor getParallelExecutor();
+ }
+
public final class PermissionChecker {
method public static int checkCallingOrSelfPermission(android.content.Context, java.lang.String);
method public static int checkCallingPermission(android.content.Context, java.lang.String, java.lang.String);
@@ -1093,11 +1098,6 @@
method public static android.support.v4.content.SharedPreferencesCompat.EditorCompat getInstance();
}
- public class ParallelExecutorCompat {
- ctor public ParallelExecutorCompat();
- method public static java.util.concurrent.Executor getParallelExecutor();
- }
-
public abstract class WakefulBroadcastReceiver extends android.content.BroadcastReceiver {
ctor public WakefulBroadcastReceiver();
method public static boolean completeWakefulIntent(android.content.Intent);
diff --git a/v7/mediarouter/jellybean/android/support/v7/media/SeekBarJellybean.java b/v7/mediarouter/jellybean/android/support/v7/media/SeekBarJellybean.java
deleted file mode 100644
index 76bf6fd..0000000
--- a/v7/mediarouter/jellybean/android/support/v7/media/SeekBarJellybean.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright (C) 2015 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 android.support.v7.media;
-
-import android.graphics.drawable.Drawable;
-import android.widget.SeekBar;
-
-/**
- * @hide
- */
-public class SeekBarJellybean {
- public static Drawable getThumb(SeekBar seekBar) {
- return seekBar.getThumb();
- }
-}
diff --git a/v7/mediarouter/res/drawable/mr_seekbar_thumb.xml b/v7/mediarouter/res/drawable/mr_seekbar_thumb.xml
new file mode 100644
index 0000000..dc1a194
--- /dev/null
+++ b/v7/mediarouter/res/drawable/mr_seekbar_thumb.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:constantSize="true">
+ <!-- Place fake item to keep the ripple animation size which is determined by thumb's size. -->
+ <item android:state_enabled="false" android:state_pressed="true">
+ <bitmap android:src="@drawable/abc_scrubber_control_off_mtrl_alpha"
+ android:gravity="center"/>
+ </item>
+ <item android:state_pressed="true">
+ <bitmap android:src="@drawable/abc_scrubber_control_to_pressed_mtrl_005"
+ android:gravity="center" />
+ </item>
+ <item>
+ <bitmap android:src="@drawable/abc_scrubber_control_to_pressed_mtrl_000"
+ android:gravity="center" />
+ </item>
+</selector>
\ No newline at end of file
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 d58a451..0a07a92 100644
--- a/v7/mediarouter/res/layout/mr_controller_material_dialog_b.xml
+++ b/v7/mediarouter/res/layout/mr_controller_material_dialog_b.xml
@@ -80,6 +80,9 @@
<ListView android:id="@+id/mr_volume_group_list"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
+ android:paddingTop="@dimen/mr_controller_volume_group_list_padding_top"
+ android:scrollbarStyle="outsideInset"
+ android:clipToPadding="false"
android:background="?attr/colorPrimaryDark"
android:visibility="gone" />
</LinearLayout>
diff --git a/v7/mediarouter/res/layout/mr_controller_volume_item.xml b/v7/mediarouter/res/layout/mr_controller_volume_item.xml
index dc539eb..6cd0fd6 100644
--- a/v7/mediarouter/res/layout/mr_controller_volume_item.xml
+++ b/v7/mediarouter/res/layout/mr_controller_volume_item.xml
@@ -14,29 +14,31 @@
limitations under the License.
-->
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="@dimen/mr_controller_volume_group_list_item_height"
android:paddingLeft="24dp"
android:paddingRight="60dp"
- android:paddingTop="16dp" >
+ android:paddingBottom="8dp"
+ android:orientation="vertical" >
<TextView android:id="@+id/mr_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_alignParentTop="true"
- android:layout_alignParentLeft="true"
android:textAppearance="?attr/mediaRouteControllerSecondaryTextStyle"
android:singleLine="true" />
- <ImageView android:id="@+id/mr_volume_item_icon"
- android:layout_width="24dp"
- android:layout_height="24dp"
- android:layout_marginTop="12dp"
- android:layout_below="@id/mr_name"
- android:src="?attr/mediaRouteAudioTrackDrawable" />
- <SeekBar android:id="@+id/mr_volume_slider"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:layout_marginTop="8dp"
- android:layout_below="@id/mr_name"
- android:layout_toRightOf="@id/mr_volume_item_icon" />
-</RelativeLayout>
+ <LinearLayout android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:gravity="center_vertical">
+ <ImageView android:id="@+id/mr_volume_item_icon"
+ android:layout_width="@dimen/mr_controller_volume_group_list_item_icon_size"
+ android:layout_height="@dimen/mr_controller_volume_group_list_item_icon_size"
+ android:layout_marginTop="8dp"
+ android:layout_marginBottom="8dp"
+ android:scaleType="fitCenter"
+ android:src="?attr/mediaRouteAudioTrackDrawable" />
+ <android.support.v7.app.MediaRouteVolumeSlider android:id="@+id/mr_volume_slider"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content" />
+ </LinearLayout>
+</LinearLayout>
diff --git a/v7/mediarouter/res/layout/mr_volume_control.xml b/v7/mediarouter/res/layout/mr_volume_control.xml
index 4e0abb8..996a69f 100644
--- a/v7/mediarouter/res/layout/mr_volume_control.xml
+++ b/v7/mediarouter/res/layout/mr_volume_control.xml
@@ -27,7 +27,7 @@
android:src="?attr/mediaRouteAudioTrackDrawable"
android:gravity="center"
android:scaleType="center" />
- <SeekBar android:id="@+id/mr_volume_slider"
+ <android.support.v7.app.MediaRouteVolumeSlider android:id="@+id/mr_volume_slider"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" />
diff --git a/v7/mediarouter/res/values-land/dimens.xml b/v7/mediarouter/res/values-land/dimens.xml
index 0f0e5a1..ee606aa 100644
--- a/v7/mediarouter/res/values-land/dimens.xml
+++ b/v7/mediarouter/res/values-land/dimens.xml
@@ -15,6 +15,12 @@
-->
<resources>
- <!-- Maximum height of MediaRouteController's volume group list. -->
+ <!-- MediaRouteController's volume group list -->
+ <eat-comment />
+ <!-- Maximum height of volume group list. -->
<dimen name="mr_controller_volume_group_list_max_height">132dp</dimen>
+ <!-- Height of volume group item. -->
+ <dimen name="mr_controller_volume_group_list_item_height">61dp</dimen>
+ <!-- Size of an item's icon. -->
+ <dimen name="mr_controller_volume_group_list_item_icon_size">18dp</dimen>
</resources>
diff --git a/v7/mediarouter/res/values/dimens.xml b/v7/mediarouter/res/values/dimens.xml
index 77656f4..af413a3 100644
--- a/v7/mediarouter/res/values/dimens.xml
+++ b/v7/mediarouter/res/values/dimens.xml
@@ -15,6 +15,8 @@
-->
<resources>
+ <!-- Dialog size -->
+ <eat-comment />
<!-- The platform's desired fixed width for a dialog along the major axis
(the screen is in landscape). This may be either a fraction or a dimension.-->
<dimen name="mr_dialog_fixed_width_major">320dp</dimen>
@@ -22,11 +24,14 @@
(the screen is in portrait). This may be either a fraction or a dimension.-->
<dimen name="mr_dialog_fixed_width_minor">320dp</dimen>
- <!-- Maximum height of MediaRouteController's volume group list. -->
+ <!-- MediaRouteController's volume group list -->
+ <eat-comment />
+ <!-- Maximum height of volume group list. -->
<dimen name="mr_controller_volume_group_list_max_height">256dp</dimen>
- <!-- Height of MediaRouteController's volume group item.
- TODO: Define value for landscape once we know how to adjust SeekBar height. -->
- <dimen name="mr_controller_volume_group_list_item_height">75dp</dimen>
+ <!-- Height of volume group item. -->
+ <dimen name="mr_controller_volume_group_list_item_height">67dp</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_bottom">8dp</dimen>
+ <dimen name="mr_controller_volume_group_list_padding_top">16dp</dimen>
</resources>
diff --git a/v7/mediarouter/src/android/support/v7/app/MediaRouteControllerDialog.java b/v7/mediarouter/src/android/support/v7/app/MediaRouteControllerDialog.java
index 5b00156..80b3de1 100644
--- a/v7/mediarouter/src/android/support/v7/app/MediaRouteControllerDialog.java
+++ b/v7/mediarouter/src/android/support/v7/app/MediaRouteControllerDialog.java
@@ -20,16 +20,15 @@
import android.content.ContentResolver;
import android.content.Context;
+import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
-import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.AsyncTask;
-import android.os.Build;
import android.os.Bundle;
import android.os.RemoteException;
import android.support.v4.media.MediaDescriptionCompat;
@@ -40,7 +39,6 @@
import android.support.v7.graphics.Palette;
import android.support.v7.media.MediaRouteSelector;
import android.support.v7.media.MediaRouter;
-import android.support.v7.media.SeekBarJellybean;
import android.support.v7.mediarouter.R;
import android.text.TextUtils;
import android.util.Log;
@@ -85,6 +83,8 @@
// to allow the route provider time to propagate the change and publish a new
// route descriptor.
private static final int VOLUME_UPDATE_DELAY_MILLIS = 250;
+ private static final int VOLUME_SLIDER_TAG_MASTER = 0;
+ private static final int VOLUME_SLIDER_TAG_BASE = 100;
private static final int BUTTON_NEUTRAL_RES_ID = android.R.id.button3;
private static final int BUTTON_DISCONNECT_RES_ID = android.R.id.button2;
@@ -126,11 +126,12 @@
private ListView mVolumeGroupList;
private SeekBar mVolumeSlider;
+ private VolumeChangeListener mVolumeChangeListener;
private boolean mVolumeSliderTouched;
- private int mVolumeSliderColor;
- private final int mVolumeGroupListItemHeight;
- private final int mVolumeGroupListMaxHeight;
- private final int mVolumeGroupListPaddingBottom;
+ private int mVolumeGroupListItemIconSize;
+ private int mVolumeGroupListItemHeight;
+ private int mVolumeGroupListMaxHeight;
+ private final int mVolumeGroupListPaddingTop;
private MediaControllerCompat mMediaController;
private MediaControllerCallback mControllerCallback;
@@ -155,12 +156,8 @@
mCallback = new MediaRouterCallback();
mRoute = mRouter.getSelectedRoute();
setMediaSession(mRouter.getMediaSessionToken());
- mVolumeGroupListItemHeight = context.getResources().getDimensionPixelSize(
- R.dimen.mr_controller_volume_group_list_item_height);
- mVolumeGroupListMaxHeight = context.getResources().getDimensionPixelSize(
- R.dimen.mr_controller_volume_group_list_max_height);
- mVolumeGroupListPaddingBottom = context.getResources().getDimensionPixelSize(
- R.dimen.mr_controller_volume_group_list_padding_bottom);
+ mVolumeGroupListPaddingTop = context.getResources().getDimensionPixelSize(
+ R.dimen.mr_controller_volume_group_list_padding_top);
}
/**
@@ -256,9 +253,9 @@
}
/**
- * Gets the description being used by the default UI.
+ * Gets the associated session's token.
*
- * @return The current description.
+ * @return session token.
*/
public MediaSessionCompat.Token getMediaSession() {
return mMediaController == null ? null : mMediaController.getSessionToken();
@@ -308,43 +305,9 @@
mVolumeControl = (LinearLayout) findViewById(R.id.mr_volume_control);
mVolumeSlider = (SeekBar) findViewById(R.id.mr_volume_slider);
- mVolumeSlider.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
- private final Runnable mStopTrackingTouch = new Runnable() {
- @Override
- public void run() {
- if (mVolumeSliderTouched) {
- mVolumeSliderTouched = false;
- updateVolumeControl();
- }
- }
- };
-
- @Override
- public void onStartTrackingTouch(SeekBar seekBar) {
- if (mVolumeSliderTouched) {
- mVolumeSlider.removeCallbacks(mStopTrackingTouch);
- } else {
- mVolumeSliderTouched = true;
- }
- }
-
- @Override
- public void onStopTrackingTouch(SeekBar seekBar) {
- // Defer resetting mVolumeSliderTouched to allow the media route provider
- // a little time to settle into its new state and publish the final
- // volume update.
- mVolumeSlider.postDelayed(mStopTrackingTouch, VOLUME_UPDATE_DELAY_MILLIS);
- }
-
- @Override
- public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
- if (fromUser) {
- mRoute.requestSetVolume(progress);
- }
- }
- });
- mVolumeSliderColor = MediaRouterThemeHelper.getVolumeSliderColor(getContext());
- setVolumeSliderColor(getContext(), mVolumeSlider, mVolumeSliderColor);
+ mVolumeSlider.setTag(VOLUME_SLIDER_TAG_MASTER);
+ mVolumeChangeListener = new VolumeChangeListener();
+ mVolumeSlider.setOnSeekBarChangeListener(mVolumeChangeListener);
TypedArray styledAttributes = getContext().obtainStyledAttributes(new int[] {
R.attr.mediaRouteExpandGroupDrawable,
@@ -400,6 +363,14 @@
View decorView = getWindow().getDecorView();
mDialogContentWidth = width - decorView.getPaddingLeft() - decorView.getPaddingRight();
+ Resources res = getContext().getResources();
+ mVolumeGroupListItemIconSize = res.getDimensionPixelSize(
+ R.dimen.mr_controller_volume_group_list_item_icon_size);
+ mVolumeGroupListItemHeight = res.getDimensionPixelSize(
+ R.dimen.mr_controller_volume_group_list_item_height);
+ mVolumeGroupListMaxHeight = res.getDimensionPixelSize(
+ R.dimen.mr_controller_volume_group_list_max_height);
+
// Ensure the mArtView is updated.
mArtIconBitmap = null;
mArtIconUri = null;
@@ -498,7 +469,7 @@
mDividerView.setVisibility((mVolumeControl.getVisibility() == View.VISIBLE
&& showPlaybackControl) ? View.VISIBLE : View.GONE);
mMediaControlLayout.setVisibility((mVolumeControl.getVisibility() == View.GONE
- && showPlaybackControl) ? View.GONE : View.VISIBLE);
+ && !showPlaybackControl) ? View.GONE : View.VISIBLE);
}
private void updateLayoutHeight() {
@@ -539,8 +510,17 @@
int mainControllerHeight = getMainControllerHeight(isPlaybackControlAvailable());
int volumeGroupListCount = mVolumeGroupList.getVisibility() == View.VISIBLE
? mVolumeGroupList.getAdapter().getCount() : 0;
+ // Scale down volume group list items in landscape mode.
+ for (int i = 0; i < volumeGroupListCount; i++) {
+ View item = mVolumeGroupList.getChildAt(i);
+ if (item != null) {
+ setLayoutHeight(item, mVolumeGroupListItemHeight);
+ setLayoutHeight(item.findViewById(R.id.mr_volume_item_icon),
+ mVolumeGroupListItemIconSize);
+ }
+ }
int volumeGroupHeight = (volumeGroupListCount == 0) ? 0
- : mVolumeGroupListItemHeight * volumeGroupListCount + mVolumeGroupListPaddingBottom;
+ : mVolumeGroupListItemHeight * volumeGroupListCount + mVolumeGroupListPaddingTop;
volumeGroupHeight = Math.min(volumeGroupHeight, mVolumeGroupListMaxHeight);
int desiredControlLayoutHeight =
@@ -589,7 +569,7 @@
private void updateVolumeControl() {
if (!mVolumeSliderTouched) {
- if (isVolumeControlAvailable()) {
+ if (isVolumeControlAvailable(mRoute)) {
mVolumeControl.setVisibility(View.VISIBLE);
mVolumeSlider.setMax(mRoute.getVolumeMax());
mVolumeSlider.setProgress(mRoute.getVolume());
@@ -609,6 +589,18 @@
mVolumeControl.setVisibility(View.GONE);
}
updateLayoutHeight();
+ } else if (mVolumeControl.getVisibility() == View.VISIBLE) {
+ mVolumeSlider.setProgress(mRoute.getVolume());
+ if (mIsGroupExpanded) {
+ for (int i = 0; i < mVolumeGroupList.getChildCount(); ++i) {
+ MediaRouter.RouteInfo route = getGroup().getRouteAt(i);
+ if (isVolumeControlAvailable(route)) {
+ SeekBar volumeSlider = (SeekBar) mVolumeGroupList.getChildAt(i)
+ .findViewById(R.id.mr_volume_slider);
+ volumeSlider.setProgress(route.getVolume());
+ }
+ }
+ }
}
}
@@ -673,9 +665,9 @@
updateLayoutHeight();
}
- private boolean isVolumeControlAvailable() {
- return mVolumeControlEnabled && mRoute.getVolumeHandling() ==
- MediaRouter.RouteInfo.PLAYBACK_VOLUME_VARIABLE;
+ private boolean isVolumeControlAvailable(MediaRouter.RouteInfo route) {
+ return mVolumeControlEnabled && route.getVolumeHandling()
+ == MediaRouter.RouteInfo.PLAYBACK_VOLUME_VARIABLE;
}
private static void setLayoutHeight(View view, int height) {
@@ -684,19 +676,6 @@
view.setLayoutParams(lp);
}
- private static void setVolumeSliderColor(Context context, SeekBar volumeSlider, int color) {
- volumeSlider.getProgressDrawable().setColorFilter(color, PorterDuff.Mode.SRC_IN);
- if (Build.VERSION.SDK_INT >= 16) {
- SeekBarJellybean.getThumb(volumeSlider).setColorFilter(color, PorterDuff.Mode.SRC_IN);
- } else {
- // In case getThumb() isn't available, use the thumb drawable from AppCompat.
- Drawable thumb =
- context.getResources().getDrawable(R.drawable.abc_seekbar_thumb_material);
- thumb.setColorFilter(color, PorterDuff.Mode.SRC_IN);
- volumeSlider.setThumb(thumb);
- }
- }
-
/**
* Returns desired art height to fit into controller dialog.
*/
@@ -775,29 +754,51 @@
}
}
- private class VolumeGroupAdapter extends ArrayAdapter<MediaRouter.RouteInfo> {
- final static float DISABLED_ALPHA = .3f;
-
- final OnSeekBarChangeListener mOnSeekBarChangeListener = new OnSeekBarChangeListener() {
+ private class VolumeChangeListener implements OnSeekBarChangeListener {
+ private final Runnable mStopTrackingTouch = new Runnable() {
@Override
- public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
- if (fromUser) {
- int position = (int) seekBar.getTag();
- getGroup().getRouteAt(position).requestSetVolume(progress);
+ public void run() {
+ if (mVolumeSliderTouched) {
+ mVolumeSliderTouched = false;
+ updateVolumeControl();
}
}
-
- @Override
- public void onStartTrackingTouch(SeekBar seekBar) {
- // TODO: Implement
- }
-
- @Override
- public void onStopTrackingTouch(SeekBar seekBar) {
- // TODO: Implement
- }
};
+ @Override
+ public void onStartTrackingTouch(SeekBar seekBar) {
+ if (mVolumeSliderTouched) {
+ mVolumeSlider.removeCallbacks(mStopTrackingTouch);
+ } else {
+ mVolumeSliderTouched = true;
+ }
+ }
+
+ @Override
+ public void onStopTrackingTouch(SeekBar seekBar) {
+ // Defer resetting mVolumeSliderTouched to allow the media route provider
+ // a little time to settle into its new state and publish the final
+ // volume update.
+ mVolumeSlider.postDelayed(mStopTrackingTouch, VOLUME_UPDATE_DELAY_MILLIS);
+ }
+
+ @Override
+ public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+ if (fromUser) {
+ int tag = (int) seekBar.getTag();
+ if (tag == VOLUME_SLIDER_TAG_MASTER) {
+ mRoute.requestSetVolume(progress);
+ } else if (tag - VOLUME_SLIDER_TAG_BASE >= 0
+ && tag - VOLUME_SLIDER_TAG_BASE < getGroup().getRouteCount()) {
+ getGroup().getRouteAt(tag - VOLUME_SLIDER_TAG_BASE).requestSetVolume(progress);
+ }
+ }
+ }
+ }
+
+ private class VolumeGroupAdapter extends ArrayAdapter<MediaRouter.RouteInfo> {
+ final static float DISABLED_ALPHA = .3f;
+
public VolumeGroupAdapter(Context context, List<MediaRouter.RouteInfo> objects) {
super(context, 0, objects);
}
@@ -808,10 +809,7 @@
if (v == null) {
v = LayoutInflater.from(getContext()).inflate(
R.layout.mr_controller_volume_item, parent, false);
- setVolumeSliderColor(getContext(), (SeekBar) v.findViewById(R.id.mr_volume_slider),
- mVolumeSliderColor);
}
- setViewPaddingBottom(v, position == getCount() - 1 ? mVolumeGroupListPaddingBottom : 0);
MediaRouter.RouteInfo route = getItem(position);
if (route != null) {
@@ -821,25 +819,22 @@
routeName.setEnabled(isEnabled);
routeName.setText(route.getName());
- SeekBar volumeSlider = (SeekBar) v.findViewById(R.id.mr_volume_slider);
- volumeSlider.setEnabled(isEnabled);
- volumeSlider.setTag(position);
+ MediaRouteVolumeSlider volumeSlider =
+ (MediaRouteVolumeSlider) v.findViewById(R.id.mr_volume_slider);
+ volumeSlider.setTag(VOLUME_SLIDER_TAG_BASE + position);
+ volumeSlider.setShowThumb(isEnabled);
if (isEnabled) {
- if (route.getVolumeHandling()
- == MediaRouter.RouteInfo.PLAYBACK_VOLUME_VARIABLE) {
+ if (isVolumeControlAvailable(route)) {
volumeSlider.setMax(route.getVolumeMax());
volumeSlider.setProgress(route.getVolume());
- volumeSlider.setOnSeekBarChangeListener(mOnSeekBarChangeListener);
+ volumeSlider.setOnSeekBarChangeListener(mVolumeChangeListener);
+ volumeSlider.setEnabled(true);
} else {
volumeSlider.setMax(100);
volumeSlider.setProgress(100);
volumeSlider.setEnabled(false);
}
}
- if (Build.VERSION.SDK_INT >= 16) {
- SeekBarJellybean.getThumb(volumeSlider).mutate().setAlpha(isEnabled ? 255 : 0);
- // TODO: Still see an artifact even though the thumb is transparent. Remove it.
- }
ImageView volumeItemIcon =
(ImageView) v.findViewById(R.id.mr_volume_item_icon);
@@ -849,21 +844,6 @@
}
}
- private static void setViewPaddingBottom(View view, int newBottom) {
- int left = view.getPaddingLeft();
- int top = view.getPaddingTop();
- int right = view.getPaddingRight();
- int bottom = view.getPaddingBottom();
- view.setPadding(left, top, right, newBottom);
-
- ViewGroup.LayoutParams lp = view.getLayoutParams();
- if (lp.height != ViewGroup.LayoutParams.FILL_PARENT
- && lp.height != ViewGroup.LayoutParams.WRAP_CONTENT) {
- lp.height = lp.height - bottom + newBottom;
- view.setLayoutParams(lp);
- }
- }
-
private class FetchArtTask extends AsyncTask<Void, Void, Bitmap> {
final Bitmap mIconBitmap;
final Uri mIconUri;
@@ -937,7 +917,7 @@
}
}
if (art != null && art.getWidth() < art.getHeight()) {
- // Portrait art requires background color.
+ // Portrait art requires dominant color as background color.
Palette palette = new Palette.Builder(art).maximumColorCount(1).generate();
mBackgroundColor = palette.getSwatches().isEmpty()
? 0 : palette.getSwatches().get(0).getRgb();
diff --git a/v7/mediarouter/src/android/support/v7/app/MediaRouteVolumeSlider.java b/v7/mediarouter/src/android/support/v7/app/MediaRouteVolumeSlider.java
new file mode 100644
index 0000000..9e650fc
--- /dev/null
+++ b/v7/mediarouter/src/android/support/v7/app/MediaRouteVolumeSlider.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2015 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 android.support.v7.app;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Color;
+import android.graphics.ColorFilter;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffColorFilter;
+import android.graphics.drawable.Drawable;
+import android.support.v4.content.ContextCompat;
+import android.support.v7.mediarouter.R;
+import android.support.v7.widget.AppCompatSeekBar;
+import android.util.AttributeSet;
+
+/**
+ * Volume slider with showing, hiding, and applying alpha supports to the thumb.
+ */
+class MediaRouteVolumeSlider extends AppCompatSeekBar {
+ private boolean mShowThumb = true;
+ private Drawable mThumb;
+ private float mDisabledAlpha;
+
+ public MediaRouteVolumeSlider(Context context) {
+ super(context, null);
+ }
+
+ public MediaRouteVolumeSlider(Context context, AttributeSet attrs) {
+ this(context, attrs, android.support.v7.appcompat.R.attr.seekBarStyle);
+ }
+
+ public MediaRouteVolumeSlider(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ mThumb = ContextCompat.getDrawable(context, R.drawable.mr_seekbar_thumb);
+ setThumb(mThumb);
+ int color = MediaRouterThemeHelper.getVolumeSliderColor(context);
+ ColorFilter colorFilter = new PorterDuffColorFilter(color, PorterDuff.Mode.SRC_IN);
+ getProgressDrawable().setColorFilter(colorFilter);
+ mThumb.setColorFilter(colorFilter);
+ TypedArray ta = context.obtainStyledAttributes(
+ attrs, new int[] {android.R.attr.disabledAlpha}, defStyleAttr, 0);
+ mDisabledAlpha = ta.getFloat(0, 0.5f);
+ ta.recycle();
+ }
+
+ /**
+ * Sets whether to show/hide thumb.
+ */
+ public void setShowThumb(boolean showThumb) {
+ if (mShowThumb == showThumb) {
+ return;
+ }
+ mShowThumb = showThumb;
+ setThumb(mShowThumb ? mThumb : null);
+ }
+
+ @Override
+ protected void drawableStateChanged() {
+ mThumb.setAlpha(isEnabled() ? 0xFF : (int)(0xFF * mDisabledAlpha));
+ super.drawableStateChanged();
+ }
+}