Merge "MediaRouter: Clean up resources"
diff --git a/api/current.txt b/api/current.txt
index 2f64e50..7dc5ab7 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -160,6 +160,7 @@
field public static final java.lang.String EXTRA_ACTION_BUTTON_BUNDLE = "android.support.customtabs.extra.ACTION_BUTTON_BUNDLE";
field public static final java.lang.String EXTRA_CLOSE_BUTTON_ICON = "android.support.customtabs.extra.CLOSE_BUTTON_ICON";
field public static final java.lang.String EXTRA_DEFAULT_SHARE_MENU_ITEM = "android.support.customtabs.extra.SHARE_MENU_ITEM";
+ field public static final java.lang.String EXTRA_ENABLE_INSTANT_APPS = "android.support.customtabs.extra.EXTRA_ENABLE_INSTANT_APPS";
field public static final java.lang.String EXTRA_ENABLE_URLBAR_HIDING = "android.support.customtabs.extra.ENABLE_URLBAR_HIDING";
field public static final java.lang.String EXTRA_EXIT_ANIMATION_BUNDLE = "android.support.customtabs.extra.EXIT_ANIMATION_BUNDLE";
field public static final java.lang.String EXTRA_MENU_ITEMS = "android.support.customtabs.extra.MENU_ITEMS";
@@ -197,6 +198,7 @@
method public android.support.customtabs.CustomTabsIntent.Builder setActionButton(android.graphics.Bitmap, java.lang.String, android.app.PendingIntent);
method public android.support.customtabs.CustomTabsIntent.Builder setCloseButtonIcon(android.graphics.Bitmap);
method public android.support.customtabs.CustomTabsIntent.Builder setExitAnimations(android.content.Context, int, int);
+ method public android.support.customtabs.CustomTabsIntent.Builder setInstantAppsEnabled(boolean);
method public android.support.customtabs.CustomTabsIntent.Builder setSecondaryToolbarColor(int);
method public android.support.customtabs.CustomTabsIntent.Builder setSecondaryToolbarViews(android.widget.RemoteViews, int[], android.app.PendingIntent);
method public android.support.customtabs.CustomTabsIntent.Builder setShowTitle(boolean);
@@ -307,6 +309,24 @@
method public boolean onDependentViewChanged(android.support.design.widget.CoordinatorLayout, android.view.View, android.view.View);
}
+ public class BottomNavigationView extends android.widget.FrameLayout {
+ ctor public BottomNavigationView(android.content.Context);
+ ctor public BottomNavigationView(android.content.Context, android.util.AttributeSet);
+ ctor public BottomNavigationView(android.content.Context, android.util.AttributeSet, int);
+ method public android.content.res.ColorStateList getItemIconTintList();
+ method public android.content.res.ColorStateList getItemTextColor();
+ method public android.view.Menu getMenu();
+ method public void inflateMenu(int);
+ method public void setItemBackgroundResource(int);
+ method public void setItemIconTintList(android.content.res.ColorStateList);
+ method public void setItemTextColor(android.content.res.ColorStateList);
+ method public void setOnNavigationItemSelectedListener(android.support.design.widget.BottomNavigationView.OnNavigationItemSelectedListener);
+ }
+
+ public static abstract interface BottomNavigationView.OnNavigationItemSelectedListener {
+ method public abstract boolean onNavigationItemSelected(android.view.MenuItem);
+ }
+
public class BottomSheetBehavior extends android.support.design.widget.CoordinatorLayout.Behavior {
ctor public BottomSheetBehavior();
ctor public BottomSheetBehavior(android.content.Context, android.util.AttributeSet);
@@ -722,11 +742,14 @@
method public android.widget.EditText getEditText();
method public java.lang.CharSequence getError();
method public java.lang.CharSequence getHint();
+ method public java.lang.CharSequence getPasswordVisibilityToggleContentDescription();
+ method public android.graphics.drawable.Drawable getPasswordVisibilityToggleDrawable();
method public android.graphics.Typeface getTypeface();
method public boolean isCounterEnabled();
method public boolean isErrorEnabled();
method public boolean isHintAnimationEnabled();
method public boolean isHintEnabled();
+ method public boolean isPasswordVisibilityToggleEnabled();
method public android.os.Parcelable onSaveInstanceState();
method public void setCounterEnabled(boolean);
method public void setCounterMaxLength(int);
@@ -736,6 +759,13 @@
method public void setHintAnimationEnabled(boolean);
method public void setHintEnabled(boolean);
method public void setHintTextAppearance(int);
+ method public void setPasswordVisibilityToggleContentDescription(int);
+ method public void setPasswordVisibilityToggleContentDescription(java.lang.CharSequence);
+ method public void setPasswordVisibilityToggleDrawable(int);
+ method public void setPasswordVisibilityToggleDrawable(android.graphics.drawable.Drawable);
+ method public void setPasswordVisibilityToggleEnabled(boolean);
+ method public void setPasswordVisibilityToggleTintList(android.content.res.ColorStateList);
+ method public void setPasswordVisibilityToggleTintMode(android.graphics.PorterDuff.Mode);
method public void setTypeface(android.graphics.Typeface);
}
@@ -2074,15 +2104,21 @@
ctor public AbstractMediaItemPresenter(int);
method protected android.support.v17.leanback.widget.RowPresenter.ViewHolder createRowViewHolder(android.view.ViewGroup);
method public android.support.v17.leanback.widget.Presenter getActionPresenter();
+ method protected int getMediaPlayState(java.lang.Object);
method public int getThemeId();
method public boolean hasMediaRowSeparator();
method protected abstract void onBindMediaDetails(android.support.v17.leanback.widget.AbstractMediaItemPresenter.ViewHolder, java.lang.Object);
+ method public void onBindMediaPlayState(android.support.v17.leanback.widget.AbstractMediaItemPresenter.ViewHolder);
method protected void onBindRowActions(android.support.v17.leanback.widget.AbstractMediaItemPresenter.ViewHolder);
method protected void onUnbindMediaDetails(android.support.v17.leanback.widget.AbstractMediaItemPresenter.ViewHolder);
+ method public void onUnbindMediaPlayState(android.support.v17.leanback.widget.AbstractMediaItemPresenter.ViewHolder);
method public void setActionPresenter(android.support.v17.leanback.widget.Presenter);
method public void setBackgroundColor(int);
method public void setHasMediaRowSeparator(boolean);
method public void setThemeId(int);
+ field public static final int PLAY_STATE_INITIAL = 0; // 0x0
+ field public static final int PLAY_STATE_PAUSED = 1; // 0x1
+ field public static final int PLAY_STATE_PLAYING = 2; // 0x2
}
public static class AbstractMediaItemPresenter.ViewHolder extends android.support.v17.leanback.widget.RowPresenter.ViewHolder {
@@ -2092,12 +2128,17 @@
method public android.widget.TextView getMediaItemDurationView();
method public android.widget.TextView getMediaItemNameView();
method public android.widget.TextView getMediaItemNumberView();
+ method public android.widget.ViewFlipper getMediaItemNumberViewFlipper();
+ method public android.view.View getMediaItemPausedView();
+ method public android.view.View getMediaItemPlayingView();
method public android.support.v17.leanback.widget.MultiActionsProvider.MultiAction[] getMediaItemRowActions();
method public android.view.View getMediaItemRowSeparator();
method public android.view.View getSelectorView();
method public void notifyActionChanged(android.support.v17.leanback.widget.MultiActionsProvider.MultiAction);
method public void notifyDetailsChanged();
+ method public void notifyPlayStateChanged();
method public void onBindRowActions();
+ method public void setSelectedMediaItemNumberView(int);
}
public abstract class AbstractMediaListHeaderPresenter extends android.support.v17.leanback.widget.RowPresenter {
@@ -2839,6 +2880,10 @@
method public android.support.v17.leanback.widget.HorizontalGridView getGridView();
}
+ public class MediaNowPlayingView extends android.widget.LinearLayout {
+ ctor public MediaNowPlayingView(android.content.Context, android.util.AttributeSet);
+ }
+
public abstract interface MultiActionsProvider {
method public abstract android.support.v17.leanback.widget.MultiActionsProvider.MultiAction[] getActions();
}
@@ -4166,12 +4211,14 @@
method public android.support.v4.app.NotificationCompat.Action.Builder extend(android.support.v4.app.NotificationCompat.Action.Builder);
method public java.lang.CharSequence getCancelLabel();
method public java.lang.CharSequence getConfirmLabel();
+ method public boolean getHintDisplayActionInline();
method public boolean getHintLaunchesActivity();
method public java.lang.CharSequence getInProgressLabel();
method public boolean isAvailableOffline();
method public android.support.v4.app.NotificationCompat.Action.WearableExtender setAvailableOffline(boolean);
method public android.support.v4.app.NotificationCompat.Action.WearableExtender setCancelLabel(java.lang.CharSequence);
method public android.support.v4.app.NotificationCompat.Action.WearableExtender setConfirmLabel(java.lang.CharSequence);
+ method public android.support.v4.app.NotificationCompat.Action.WearableExtender setHintDisplayActionInline(boolean);
method public android.support.v4.app.NotificationCompat.Action.WearableExtender setHintLaunchesActivity(boolean);
method public android.support.v4.app.NotificationCompat.Action.WearableExtender setInProgressLabel(java.lang.CharSequence);
}
@@ -7315,6 +7362,7 @@
}
public final class TextViewCompat {
+ method public static android.graphics.drawable.Drawable[] getCompoundDrawablesRelative(android.widget.TextView);
method public static int getMaxLines(android.widget.TextView);
method public static int getMinLines(android.widget.TextView);
method public static void setCompoundDrawablesRelative(android.widget.TextView, android.graphics.drawable.Drawable, android.graphics.drawable.Drawable, android.graphics.drawable.Drawable, android.graphics.drawable.Drawable);
diff --git a/compat/api20/android/support/v4/app/NotificationCompatApi20.java b/compat/api20/android/support/v4/app/NotificationCompatApi20.java
index 036fb24..88ea703 100644
--- a/compat/api20/android/support/v4/app/NotificationCompatApi20.java
+++ b/compat/api20/android/support/v4/app/NotificationCompatApi20.java
@@ -126,7 +126,7 @@
}
actionExtras.putBoolean(NotificationCompatJellybean.EXTRA_ALLOW_GENERATED_REPLIES,
action.getAllowGeneratedReplies());
- actionBuilder.addExtras(action.getExtras());
+ actionBuilder.addExtras(actionExtras);
b.addAction(actionBuilder.build());
}
diff --git a/compat/api24/android/support/v4/app/NotificationCompatApi24.java b/compat/api24/android/support/v4/app/NotificationCompatApi24.java
index e533b21..a631f23 100644
--- a/compat/api24/android/support/v4/app/NotificationCompatApi24.java
+++ b/compat/api24/android/support/v4/app/NotificationCompatApi24.java
@@ -119,9 +119,15 @@
actionBuilder.addRemoteInput(remoteInput);
}
}
+ Bundle actionExtras;
if (action.getExtras() != null) {
- actionBuilder.addExtras(action.getExtras());
+ actionExtras = new Bundle(action.getExtras());
+ } else {
+ actionExtras = new Bundle();
}
+ actionExtras.putBoolean(NotificationCompatJellybean.EXTRA_ALLOW_GENERATED_REPLIES,
+ action.getAllowGeneratedReplies());
+ actionBuilder.addExtras(actionExtras);
actionBuilder.setAllowGeneratedReplies(action.getAllowGeneratedReplies());
b.addAction(actionBuilder.build());
}
diff --git a/compat/honeycomb/android/support/v4/widget/SearchViewCompatHoneycomb.java b/compat/honeycomb/android/support/v4/widget/SearchViewCompatHoneycomb.java
index eeadfab..9143214 100644
--- a/compat/honeycomb/android/support/v4/widget/SearchViewCompatHoneycomb.java
+++ b/compat/honeycomb/android/support/v4/widget/SearchViewCompatHoneycomb.java
@@ -29,13 +29,23 @@
*/
class SearchViewCompatHoneycomb {
+ public static void checkIfLegalArg(View searchView) {
+ if (searchView == null) {
+ throw new IllegalArgumentException("searchView must be non-null");
+ }
+ if (!(searchView instanceof SearchView)) {
+ throw new IllegalArgumentException("searchView must be an instance of" +
+ "android.widget.SearchView");
+ }
+ }
+
interface OnQueryTextListenerCompatBridge {
- public boolean onQueryTextSubmit(String query);
- public boolean onQueryTextChange(String newText);
+ boolean onQueryTextSubmit(String query);
+ boolean onQueryTextChange(String newText);
}
interface OnCloseListenerCompatBridge {
- public boolean onClose();
+ boolean onClose();
}
public static View newSearchView(Context context) {
@@ -63,7 +73,7 @@
};
}
- public static void setOnQueryTextListener(Object searchView, Object listener) {
+ public static void setOnQueryTextListener(View searchView, Object listener) {
((SearchView) searchView).setOnQueryTextListener((OnQueryTextListener) listener);
}
@@ -76,7 +86,7 @@
};
}
- public static void setOnCloseListener(Object searchView, Object listener) {
+ public static void setOnCloseListener(View searchView, Object listener) {
((SearchView) searchView).setOnCloseListener((OnCloseListener) listener);
}
diff --git a/compat/java/android/support/v4/app/NotificationCompat.java b/compat/java/android/support/v4/app/NotificationCompat.java
index 0c515a1..c5a6628 100644
--- a/compat/java/android/support/v4/app/NotificationCompat.java
+++ b/compat/java/android/support/v4/app/NotificationCompat.java
@@ -2541,6 +2541,7 @@
// Flags bitwise-ored to mFlags
private static final int FLAG_AVAILABLE_OFFLINE = 0x1;
private static final int FLAG_HINT_LAUNCHES_ACTIVITY = 1 << 1;
+ private static final int FLAG_HINT_DISPLAY_INLINE = 1 << 2;
// Default value for flags integer
private static final int DEFAULT_FLAGS = FLAG_AVAILABLE_OFFLINE;
@@ -2727,6 +2728,29 @@
public boolean getHintLaunchesActivity() {
return (mFlags & FLAG_HINT_LAUNCHES_ACTIVITY) != 0;
}
+
+ /**
+ * Set a hint that this Action should be displayed inline.
+ *
+ * @param hintDisplayInline {@code true} if action should be displayed inline, false
+ * otherwise
+ * @return this object for method chaining
+ */
+ public WearableExtender setHintDisplayActionInline(
+ boolean hintDisplayInline) {
+ setFlag(FLAG_HINT_DISPLAY_INLINE, hintDisplayInline);
+ return this;
+ }
+
+ /**
+ * Get a hint that this Action should be displayed inline.
+ *
+ * @return {@code true} if the Action should be displayed inline, {@code false}
+ * otherwise. The default value is {@code false} if this was never set.
+ */
+ public boolean getHintDisplayActionInline() {
+ return (mFlags & FLAG_HINT_DISPLAY_INLINE) != 0;
+ }
}
/** @hide */
diff --git a/compat/java/android/support/v4/content/ContextCompat.java b/compat/java/android/support/v4/content/ContextCompat.java
index 3c6100e..58e2057 100644
--- a/compat/java/android/support/v4/content/ContextCompat.java
+++ b/compat/java/android/support/v4/content/ContextCompat.java
@@ -25,6 +25,9 @@
import android.os.Bundle;
import android.os.Environment;
import android.os.Process;
+import android.support.annotation.ColorInt;
+import android.support.annotation.ColorRes;
+import android.support.annotation.DrawableRes;
import android.support.annotation.NonNull;
import android.support.v4.os.BuildCompat;
import android.support.v4.os.EnvironmentCompat;
@@ -328,7 +331,7 @@
* The value 0 is an invalid identifier.
* @return Drawable An object that can be used to draw this resource.
*/
- public static final Drawable getDrawable(Context context, int id) {
+ public static final Drawable getDrawable(Context context, @DrawableRes int id) {
final int version = Build.VERSION.SDK_INT;
if (version >= 21) {
return ContextCompatApi21.getDrawable(context, id);
@@ -365,7 +368,7 @@
* @throws android.content.res.Resources.NotFoundException if the given ID
* does not exist.
*/
- public static final ColorStateList getColorStateList(Context context, int id) {
+ public static final ColorStateList getColorStateList(Context context, @ColorRes int id) {
final int version = Build.VERSION.SDK_INT;
if (version >= 23) {
return ContextCompatApi23.getColorStateList(context, id);
@@ -387,7 +390,8 @@
* @throws android.content.res.Resources.NotFoundException if the given ID
* does not exist.
*/
- public static final int getColor(Context context, int id) {
+ @ColorInt
+ public static final int getColor(Context context, @ColorRes int id) {
final int version = Build.VERSION.SDK_INT;
if (version >= 23) {
return ContextCompatApi23.getColor(context, id);
diff --git a/compat/java/android/support/v4/view/accessibility/AccessibilityWindowInfoCompat.java b/compat/java/android/support/v4/view/accessibility/AccessibilityWindowInfoCompat.java
index 2232430..53f57fc 100644
--- a/compat/java/android/support/v4/view/accessibility/AccessibilityWindowInfoCompat.java
+++ b/compat/java/android/support/v4/view/accessibility/AccessibilityWindowInfoCompat.java
@@ -424,7 +424,7 @@
* @return An instance.
*/
public static AccessibilityWindowInfoCompat obtain(AccessibilityWindowInfoCompat info) {
- return wrapNonNullInstance(IMPL.obtain(info.mInfo));
+ return info == null ? null : wrapNonNullInstance(IMPL.obtain(info.mInfo));
}
/**
diff --git a/compat/java/android/support/v4/widget/SearchViewCompat.java b/compat/java/android/support/v4/widget/SearchViewCompat.java
index 297ee84..48fbc5b 100644
--- a/compat/java/android/support/v4/widget/SearchViewCompat.java
+++ b/compat/java/android/support/v4/widget/SearchViewCompat.java
@@ -35,9 +35,9 @@
void setImeOptions(View searchView, int imeOptions);
void setInputType(View searchView, int inputType);
Object newOnQueryTextListener(OnQueryTextListenerCompat listener);
- void setOnQueryTextListener(Object searchView, Object listener);
+ void setOnQueryTextListener(View searchView, Object listener);
Object newOnCloseListener(OnCloseListenerCompat listener);
- void setOnCloseListener(Object searchView, Object listener);
+ void setOnCloseListener(View searchView, Object listener);
CharSequence getQuery(View searchView);
void setQuery(View searchView, CharSequence query, boolean submit);
void setQueryHint(View searchView, CharSequence hint);
@@ -75,7 +75,7 @@
}
@Override
- public void setOnQueryTextListener(Object searchView, Object listener) {
+ public void setOnQueryTextListener(View searchView, Object listener) {
}
@Override
@@ -84,7 +84,7 @@
}
@Override
- public void setOnCloseListener(Object searchView, Object listener) {
+ public void setOnCloseListener(View searchView, Object listener) {
}
@Override
@@ -141,6 +141,7 @@
@Override
public void setSearchableInfo(View searchView, ComponentName searchableComponent) {
+ checkIfLegalArg(searchView);
SearchViewCompatHoneycomb.setSearchableInfo(searchView, searchableComponent);
}
@@ -160,7 +161,8 @@
}
@Override
- public void setOnQueryTextListener(Object searchView, Object listener) {
+ public void setOnQueryTextListener(View searchView, Object listener) {
+ checkIfLegalArg(searchView);
SearchViewCompatHoneycomb.setOnQueryTextListener(searchView, listener);
}
@@ -176,59 +178,74 @@
}
@Override
- public void setOnCloseListener(Object searchView, Object listener) {
+ public void setOnCloseListener(View searchView, Object listener) {
+ checkIfLegalArg(searchView);
SearchViewCompatHoneycomb.setOnCloseListener(searchView, listener);
}
@Override
public CharSequence getQuery(View searchView) {
+ checkIfLegalArg(searchView);
return SearchViewCompatHoneycomb.getQuery(searchView);
}
@Override
public void setQuery(View searchView, CharSequence query, boolean submit) {
+ checkIfLegalArg(searchView);
SearchViewCompatHoneycomb.setQuery(searchView, query, submit);
}
@Override
public void setQueryHint(View searchView, CharSequence hint) {
+ checkIfLegalArg(searchView);
SearchViewCompatHoneycomb.setQueryHint(searchView, hint);
}
@Override
public void setIconified(View searchView, boolean iconify) {
+ checkIfLegalArg(searchView);
SearchViewCompatHoneycomb.setIconified(searchView, iconify);
}
@Override
public boolean isIconified(View searchView) {
+ checkIfLegalArg(searchView);
return SearchViewCompatHoneycomb.isIconified(searchView);
}
@Override
public void setSubmitButtonEnabled(View searchView, boolean enabled) {
+ checkIfLegalArg(searchView);
SearchViewCompatHoneycomb.setSubmitButtonEnabled(searchView, enabled);
}
@Override
public boolean isSubmitButtonEnabled(View searchView) {
+ checkIfLegalArg(searchView);
return SearchViewCompatHoneycomb.isSubmitButtonEnabled(searchView);
}
@Override
public void setQueryRefinementEnabled(View searchView, boolean enable) {
+ checkIfLegalArg(searchView);
SearchViewCompatHoneycomb.setQueryRefinementEnabled(searchView, enable);
}
@Override
public boolean isQueryRefinementEnabled(View searchView) {
+ checkIfLegalArg(searchView);
return SearchViewCompatHoneycomb.isQueryRefinementEnabled(searchView);
}
@Override
public void setMaxWidth(View searchView, int maxpixels) {
+ checkIfLegalArg(searchView);
SearchViewCompatHoneycomb.setMaxWidth(searchView, maxpixels);
}
+
+ protected void checkIfLegalArg(View searchView) {
+ SearchViewCompatHoneycomb.checkIfLegalArg(searchView);
+ }
}
static class SearchViewCompatIcsImpl extends SearchViewCompatHoneycombImpl {
@@ -240,11 +257,13 @@
@Override
public void setImeOptions(View searchView, int imeOptions) {
+ checkIfLegalArg(searchView);
SearchViewCompatIcs.setImeOptions(searchView, imeOptions);
}
@Override
public void setInputType(View searchView, int inputType) {
+ checkIfLegalArg(searchView);
SearchViewCompatIcs.setInputType(searchView, inputType);
}
}
diff --git a/compat/java/android/support/v4/widget/TextViewCompat.java b/compat/java/android/support/v4/widget/TextViewCompat.java
index 3239587..d61ddc2 100644
--- a/compat/java/android/support/v4/widget/TextViewCompat.java
+++ b/compat/java/android/support/v4/widget/TextViewCompat.java
@@ -46,6 +46,7 @@
int getMaxLines(TextView textView);
int getMinLines(TextView textView);
void setTextAppearance(@NonNull TextView textView, @StyleRes int resId);
+ Drawable[] getCompoundDrawablesRelative(@NonNull TextView textView);
}
static class BaseTextViewCompatImpl implements TextViewCompatImpl {
@@ -84,6 +85,11 @@
public void setTextAppearance(TextView textView, @StyleRes int resId) {
TextViewCompatGingerbread.setTextAppearance(textView, resId);
}
+
+ @Override
+ public Drawable[] getCompoundDrawablesRelative(@NonNull TextView textView) {
+ return TextViewCompatGingerbread.getCompoundDrawablesRelative(textView);
+ }
}
static class JbTextViewCompatImpl extends BaseTextViewCompatImpl {
@@ -121,6 +127,11 @@
TextViewCompatJbMr1.setCompoundDrawablesRelativeWithIntrinsicBounds(textView,
start, top, end, bottom);
}
+
+ @Override
+ public Drawable[] getCompoundDrawablesRelative(@NonNull TextView textView) {
+ return TextViewCompatJbMr1.getCompoundDrawablesRelative(textView);
+ }
}
static class JbMr2TextViewCompatImpl extends JbMr1TextViewCompatImpl {
@@ -268,4 +279,11 @@
public static void setTextAppearance(@NonNull TextView textView, @StyleRes int resId) {
IMPL.setTextAppearance(textView, resId);
}
+
+ /**
+ * Returns drawables for the start, top, end, and bottom borders from the given text view.
+ */
+ public static Drawable[] getCompoundDrawablesRelative(@NonNull TextView textView) {
+ return textView.getCompoundDrawables();
+ }
}
diff --git a/compat/java/android/support/v4/widget/TextViewCompatGingerbread.java b/compat/java/android/support/v4/widget/TextViewCompatGingerbread.java
index 65056f5..faa0939 100644
--- a/compat/java/android/support/v4/widget/TextViewCompatGingerbread.java
+++ b/compat/java/android/support/v4/widget/TextViewCompatGingerbread.java
@@ -16,6 +16,8 @@
package android.support.v4.widget;
+import android.graphics.drawable.Drawable;
+import android.support.annotation.NonNull;
import android.util.Log;
import android.widget.TextView;
@@ -95,4 +97,8 @@
static void setTextAppearance(TextView textView, int resId) {
textView.setTextAppearance(textView.getContext(), resId);
}
+
+ static Drawable[] getCompoundDrawablesRelative(@NonNull TextView textView) {
+ return textView.getCompoundDrawables();
+ }
}
diff --git a/compat/jellybean-mr1/android/support/v4/widget/TextViewCompatJbMr1.java b/compat/jellybean-mr1/android/support/v4/widget/TextViewCompatJbMr1.java
index 7c073c6..fc088ed 100644
--- a/compat/jellybean-mr1/android/support/v4/widget/TextViewCompatJbMr1.java
+++ b/compat/jellybean-mr1/android/support/v4/widget/TextViewCompatJbMr1.java
@@ -46,4 +46,8 @@
bottom);
}
+ static Drawable[] getCompoundDrawablesRelative(@NonNull TextView textView) {
+ return textView.getCompoundDrawablesRelative();
+ }
+
}
diff --git a/customtabs/src/android/support/customtabs/CustomTabsIntent.java b/customtabs/src/android/support/customtabs/CustomTabsIntent.java
index cd26670..d2aabbe 100644
--- a/customtabs/src/android/support/customtabs/CustomTabsIntent.java
+++ b/customtabs/src/android/support/customtabs/CustomTabsIntent.java
@@ -207,6 +207,11 @@
public static final String EXTRA_REMOTEVIEWS_CLICKED_ID =
"android.support.customtabs.extra.EXTRA_REMOTEVIEWS_CLICKED_ID";
+ /**
+ * Extra that specifies whether Instant Apps is enabled.
+ */
+ public static final String EXTRA_ENABLE_INSTANT_APPS =
+ "android.support.customtabs.extra.EXTRA_ENABLE_INSTANT_APPS";
/**
* Key that specifies the unique ID for an action button. To make a button to show on the
@@ -257,6 +262,7 @@
private ArrayList<Bundle> mMenuItems = null;
private Bundle mStartAnimationBundle = null;
private ArrayList<Bundle> mActionButtons = null;
+ private boolean mInstantAppsEnabled = true;
/**
* Creates a {@link CustomTabsIntent.Builder} object associated with no
@@ -452,6 +458,16 @@
}
/**
+ * Sets whether Instant Apps is enabled for this Custom Tab.
+
+ * @param enabled Whether Instant Apps should be enabled.
+ */
+ public Builder setInstantAppsEnabled(boolean enabled) {
+ mInstantAppsEnabled = enabled;
+ return this;
+ }
+
+ /**
* Sets the start animations.
*
* @param context Application context.
@@ -491,6 +507,7 @@
if (mActionButtons != null) {
mIntent.putParcelableArrayListExtra(EXTRA_TOOLBAR_ITEMS, mActionButtons);
}
+ mIntent.putExtra(EXTRA_ENABLE_INSTANT_APPS, mInstantAppsEnabled);
return new CustomTabsIntent(mIntent, mStartAnimationBundle);
}
}
diff --git a/design/res-public/values/public_styles.xml b/design/res-public/values/public_styles.xml
index a7c0af6..0dcde45 100644
--- a/design/res-public/values/public_styles.xml
+++ b/design/res-public/values/public_styles.xml
@@ -24,6 +24,7 @@
<public type="style" name="TextAppearance.Design.Snackbar.Message"/>
<public type="style" name="TextAppearance.Design.Tab"/>
<public type="style" name="Theme.Design"/>
+ <public type="style" name="Theme.Design.BottomNavigationView"/>
<public type="style" name="Theme.Design.BottomSheetDialog"/>
<public type="style" name="Theme.Design.Light"/>
<public type="style" name="Theme.Design.Light.BottomSheetDialog"/>
diff --git a/design/res/color-v23/design_tint_password_toggle.xml b/design/res/color-v23/design_tint_password_toggle.xml
new file mode 100644
index 0000000..3a09d32a
--- /dev/null
+++ b/design/res/color-v23/design_tint_password_toggle.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2016 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">
+ <item android:state_checked="true" android:color="?android:attr/colorForeground" android:alpha="0.54"/>
+ <item android:color="?android:attr/colorForeground" android:alpha="0.38"/>
+</selector>
\ No newline at end of file
diff --git a/design/res/color/design_tint_password_toggle.xml b/design/res/color/design_tint_password_toggle.xml
new file mode 100644
index 0000000..15016e4
--- /dev/null
+++ b/design/res/color/design_tint_password_toggle.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2016 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"
+ xmlns:app="http://schemas.android.com/apk/res-auto">
+ <item android:state_checked="true" android:color="?android:attr/colorForeground" app:alpha="0.54"/>
+ <item android:color="?android:attr/colorForeground" app:alpha="0.38"/>
+</selector>
\ No newline at end of file
diff --git a/design/res/drawable-v21/design_bottom_navigation_item_background.xml b/design/res/drawable-v21/design_bottom_navigation_item_background.xml
new file mode 100644
index 0000000..f30f08b
--- /dev/null
+++ b/design/res/drawable-v21/design_bottom_navigation_item_background.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2016 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.
+ -->
+
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+ android:color="?attr/colorPrimary" />
\ No newline at end of file
diff --git a/design/res/drawable/design_bottom_navigation_item_background.xml b/design/res/drawable/design_bottom_navigation_item_background.xml
new file mode 100644
index 0000000..7674f42
--- /dev/null
+++ b/design/res/drawable/design_bottom_navigation_item_background.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2016 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">
+ <item android:state_pressed="true">
+ <shape android:shape="rectangle">
+ <solid android:color="#ff0000"/>
+ </shape>
+ </item>
+ <item>
+ <shape android:shape="rectangle">
+ <solid android:color="#ffffff"/>
+ </shape>
+ </item>
+</selector>
\ No newline at end of file
diff --git a/design/res/drawable/design_ic_visibility.xml b/design/res/drawable/design_ic_visibility.xml
new file mode 100644
index 0000000..91d46ff
--- /dev/null
+++ b/design/res/drawable/design_ic_visibility.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2016 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.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="@android:color/black"
+ android:pathData="M12,4.5C7,4.5 2.73,7.61 1,12c1.73,4.39 6,7.5 11,7.5s9.27,-3.11 11,-7.5c-1.73,-4.39 -6,-7.5 -11,-7.5zM12,17c-2.76,0 -5,-2.24 -5,-5s2.24,-5 5,-5 5,2.24 5,5 -2.24,5 -5,5zM12,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3 3,-1.34 3,-3 -1.34,-3 -3,-3z"/>
+</vector>
\ No newline at end of file
diff --git a/design/res/drawable/navigation_empty_icon.xml b/design/res/drawable/navigation_empty_icon.xml
new file mode 100644
index 0000000..6cd938d
--- /dev/null
+++ b/design/res/drawable/navigation_empty_icon.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2016 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.
+-->
+<shape
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <size
+ android:width="@dimen/design_navigation_icon_size"
+ android:height="@dimen/design_navigation_icon_size" />
+</shape>
diff --git a/design/res/layout/design_bottom_navigation_item.xml b/design/res/layout/design_bottom_navigation_item.xml
new file mode 100644
index 0000000..7f93eba
--- /dev/null
+++ b/design/res/layout/design_bottom_navigation_item.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2016 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.
+ -->
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
+ <ImageView
+ android:id="@+id/icon"
+ android:layout_width="24dp"
+ android:layout_height="24dp"
+ android:duplicateParentState="true" />
+ <TextView
+ android:id="@+id/label"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:textSize="@dimen/design_bottom_navigation_text_size"
+ android:gravity="center"
+ android:duplicateParentState="true" />
+</merge>
\ No newline at end of file
diff --git a/design/res/layout/design_text_input_password_icon.xml b/design/res/layout/design_text_input_password_icon.xml
new file mode 100644
index 0000000..0dbb9fa
--- /dev/null
+++ b/design/res/layout/design_text_input_password_icon.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2016 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.
+-->
+
+<android.support.design.widget.CheckableImageButton
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/text_input_password_toggle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:background="?attr/selectableItemBackgroundBorderless"
+ android:layout_gravity="center_vertical|end|right"/>
\ No newline at end of file
diff --git a/design/res/values/attrs.xml b/design/res/values/attrs.xml
index fc233da..f132d7a 100644
--- a/design/res/values/attrs.xml
+++ b/design/res/values/attrs.xml
@@ -187,25 +187,50 @@
<declare-styleable name="TextInputLayout">
<attr name="hintTextAppearance" format="reference"/>
- <!-- The hint to display in the floating label -->
+ <!-- The hint to display in the floating label. -->
<attr name="android:hint"/>
- <!-- Whether the layout's floating label functionality is enabled -->
+ <!-- Whether the layout's floating label functionality is enabled. -->
<attr name="hintEnabled" format="boolean"/>
- <!-- Whether the layout is laid out as if an error will be displayed -->
+ <!-- Whether the layout is laid out as if an error will be displayed. -->
<attr name="errorEnabled" format="boolean"/>
- <!-- TextAppearance of any error message displayed -->
+ <!-- TextAppearance of any error message displayed. -->
<attr name="errorTextAppearance" format="reference"/>
- <!-- Whether the layout is laid out as if the character counter will be displayed -->
+ <!-- Whether the layout is laid out as if the character counter will be displayed. -->
<attr name="counterEnabled" format="boolean"/>
- <!-- The max length to display in the character counter -->
+ <!-- The max length to display in the character counter. -->
<attr name="counterMaxLength" format="integer" />
- <!-- TextAppearance of the character counter -->
+ <!-- TextAppearance of the character counter. -->
<attr name="counterTextAppearance" format="reference"/>
- <!-- TextAppearance of the character counter when the text is longer than the max -->
+ <!-- TextAppearance of the character counter when the text is longer than the max. -->
<attr name="counterOverflowTextAppearance" format="reference"/>
<attr name="android:textColorHint"/>
<!-- Whether to animate hint state changes. -->
<attr name="hintAnimationEnabled" format="boolean"/>
+ <!-- Whether the view will display a toggle when the EditText has a password. -->
+ <attr name="passwordToggleEnabled" format="boolean"/>
+ <!-- Drawable to use as the password input visibility toggle icon. -->
+ <attr name="passwordToggleDrawable" format="reference"/>
+ <!-- Text to set as the content description for the password input visibility toggle. -->
+ <attr name="passwordToggleContentDescription" format="string"/>
+ <!-- Icon to use for the password input visibility toggle -->
+ <attr name="passwordToggleTint" format="color"/>
+ <!-- Blending mode used to apply the background tint. -->
+ <attr name="passwordToggleTintMode">
+ <!-- The tint is drawn on top of the drawable.
+ [Sa + (1 - Sa)*Da, Rc = Sc + (1 - Sa)*Dc] -->
+ <enum name="src_over" value="3" />
+ <!-- The tint is masked by the alpha channel of the drawable. The drawable’s
+ color channels are thrown out. [Sa * Da, Sc * Da] -->
+ <enum name="src_in" value="5" />
+ <!-- The tint is drawn above the drawable, but with the drawable’s alpha
+ channel masking the result. [Da, Sc * Da + (1 - Sa) * Dc] -->
+ <enum name="src_atop" value="9" />
+ <!-- Multiplies the color and alpha channels of the drawable with those of
+ the tint. [Sa * Da, Sc * Dc] -->
+ <enum name="multiply" value="14" />
+ <!-- [Sa + Da - Sa * Da, Sc + Dc - Sc * Dc] -->
+ <enum name="screen" value="15" />
+ </attr>
</declare-styleable>
<declare-styleable name="SnackbarLayout">
@@ -397,5 +422,12 @@
<attr name="textColorError" format="color" />
</declare-styleable>
-</resources>
+ <declare-styleable name="BottomNavigationView">
+ <!-- The menu resource to inflate and populate items from. -->
+ <attr name="menu"/>
+ <attr name="itemIconTint"/>
+ <attr name="itemTextColor"/>
+ <attr name="itemBackground"/>
+ </declare-styleable>
+</resources>
diff --git a/design/res/values/dimens.xml b/design/res/values/dimens.xml
index 604ac52..6439b57e3 100644
--- a/design/res/values/dimens.xml
+++ b/design/res/values/dimens.xml
@@ -58,4 +58,14 @@
<dimen name="design_bottom_sheet_modal_elevation">16dp</dimen>
<dimen name="design_bottom_sheet_modal_peek_height">256dp</dimen>
+ <dimen name="design_bottom_navigation_height">56dp</dimen>
+ <dimen name="design_bottom_navigation_text_size">12sp</dimen>
+ <dimen name="design_bottom_navigation_active_text_size">14sp</dimen>
+ <dimen name="design_bottom_navigation_top_padding">8dp</dimen>
+ <dimen name="design_bottom_navigation_active_top_padding">6dp</dimen>
+ <dimen name="design_bottom_navigation_horizontal_padding">12dp</dimen>
+ <dimen name="design_bottom_navigation_bottom_padding">8dp</dimen>
+ <dimen name="design_bottom_navigation_item_max_width">96dp</dimen>
+ <dimen name="design_bottom_navigation_active_item_max_width">168dp</dimen>
+
</resources>
diff --git a/design/res/values/styles.xml b/design/res/values/styles.xml
index b25646c..c5c6051 100644
--- a/design/res/values/styles.xml
+++ b/design/res/values/styles.xml
@@ -42,6 +42,10 @@
<item name="tabMode">fixed</item>
</style>
+ <style name="Widget.Design.BottomNavigationView" parent="">
+ <item name="itemBackground">?attr/selectableItemBackgroundBorderless</item>
+ </style>
+
<style name="Base.Widget.Design.TabLayout" parent="android:Widget">
<item name="tabMaxWidth">@dimen/design_tab_max_width</item>
<item name="tabIndicatorColor">?attr/colorAccent</item>
@@ -64,6 +68,8 @@
<item name="errorTextAppearance">@style/TextAppearance.Design.Error</item>
<item name="counterTextAppearance">@style/TextAppearance.Design.Counter</item>
<item name="counterOverflowTextAppearance">@style/TextAppearance.Design.Counter.Overflow</item>
+ <item name="passwordToggleDrawable">@drawable/design_ic_visibility</item>
+ <item name="passwordToggleTint">@color/design_tint_password_toggle</item>
</style>
<style name="TextAppearance.Design.Hint" parent="TextAppearance.AppCompat.Caption">
diff --git a/design/src/android/support/design/internal/BottomNavigationItemView.java b/design/src/android/support/design/internal/BottomNavigationItemView.java
new file mode 100644
index 0000000..0d7df1a
--- /dev/null
+++ b/design/src/android/support/design/internal/BottomNavigationItemView.java
@@ -0,0 +1,244 @@
+/*
+ * Copyright (C) 2016 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.internal;
+
+import android.animation.Animator;
+import android.animation.AnimatorSet;
+import android.animation.ValueAnimator;
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.support.annotation.NonNull;
+import android.support.design.R;
+import android.support.v4.content.ContextCompat;
+import android.support.v4.graphics.drawable.DrawableCompat;
+import android.support.v4.view.animation.LinearOutSlowInInterpolator;
+import android.support.v7.view.menu.MenuItemImpl;
+import android.support.v7.view.menu.MenuView;
+import android.util.AttributeSet;
+import android.util.TypedValue;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+/**
+ * @hide
+ */
+public class BottomNavigationItemView extends ForegroundLinearLayout implements MenuView.ItemView {
+ public static final int INVALID_ITEM_POSTION = -1;
+
+ private static final int[] CHECKED_STATE_SET = { android.R.attr.state_checked };
+ private static final long ACTIVE_ANIMATION_DURATION_MS = 115L;
+
+ private final int mHorizontalPadding;
+ private final int mBottomPadding;
+ private final int mTopPadding;
+ private final int mActiveTopPadding;
+ private final float mInactiveLabelSize;
+ private final float mActiveLabelSize;
+
+ private ImageView mIcon;
+ private TextView mLabel;
+ private int mItemPosition = INVALID_ITEM_POSTION;
+
+ private MenuItemImpl mItemData;
+
+ private ColorStateList mIconTint;
+ private ColorStateList mTextColor;
+
+ public BottomNavigationItemView(@NonNull Context context) {
+ this(context, null);
+ }
+
+ public BottomNavigationItemView(@NonNull Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public BottomNavigationItemView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ mHorizontalPadding = getResources().getDimensionPixelSize(
+ R.dimen.design_bottom_navigation_horizontal_padding);
+ mBottomPadding = getResources().getDimensionPixelSize(
+ R.dimen.design_bottom_navigation_bottom_padding);
+ mTopPadding = getResources().getDimensionPixelSize(
+ R.dimen.design_bottom_navigation_top_padding);
+ mActiveTopPadding = getResources().getDimensionPixelSize(
+ R.dimen.design_bottom_navigation_active_top_padding);
+ mInactiveLabelSize =
+ getResources().getDimension(R.dimen.design_bottom_navigation_text_size);
+ mActiveLabelSize =
+ getResources().getDimension(R.dimen.design_bottom_navigation_active_text_size);
+
+ setOrientation(VERTICAL);
+ setGravity(Gravity.CENTER);
+ LayoutInflater.from(context).inflate(R.layout.design_bottom_navigation_item, this, true);
+ setBackgroundResource(R.drawable.design_bottom_navigation_item_background);
+ mIcon = (ImageView) findViewById(R.id.icon);
+ mLabel = (TextView) findViewById(R.id.label);
+ }
+
+ @Override
+ public void initialize(MenuItemImpl itemData, int menuType) {
+ mItemData = itemData;
+ setCheckable(itemData.isCheckable());
+ setChecked(itemData.isChecked());
+ setEnabled(itemData.isEnabled());
+ setIcon(itemData.getIcon());
+ setTitle(itemData.getTitle());
+ }
+
+ public void setItemPosition(int position) {
+ mItemPosition = position;
+ }
+
+ public int getItemPosition() {
+ return mItemPosition;
+ }
+
+ @Override
+ public MenuItemImpl getItemData() {
+ return null;
+ }
+
+ @Override
+ public void setTitle(CharSequence title) {
+ mLabel.setText(title);
+ }
+
+ @Override
+ public void setCheckable(boolean checkable) {
+ refreshDrawableState();
+ }
+
+ @Override
+ public void setChecked(boolean checked) {
+ mItemData.setChecked(checked);
+ if (checked) {
+ mLabel.setTextSize(TypedValue.COMPLEX_UNIT_PX, mActiveLabelSize);
+ setPadding(mHorizontalPadding, mActiveTopPadding, mHorizontalPadding, mBottomPadding);
+ } else {
+ mLabel.setTextSize(TypedValue.COMPLEX_UNIT_PX, mInactiveLabelSize);
+ setPadding(mHorizontalPadding, mTopPadding, mHorizontalPadding, mBottomPadding);
+ }
+ refreshDrawableState();
+ }
+
+ @Override
+ public void setEnabled(boolean enabled) {
+ super.setEnabled(enabled);
+ mLabel.setEnabled(enabled);
+ mIcon.setEnabled(enabled);
+ }
+
+ @Override
+ public int[] onCreateDrawableState(final int extraSpace) {
+ final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
+ if (mItemData != null && mItemData.isCheckable() && mItemData.isChecked()) {
+ mergeDrawableStates(drawableState, CHECKED_STATE_SET);
+ }
+ return drawableState;
+ }
+
+ @Override
+ public void setShortcut(boolean showShortcut, char shortcutKey) {
+ }
+
+ @Override
+ public void setIcon(Drawable icon) {
+ if (icon != null) {
+ Drawable.ConstantState state = icon.getConstantState();
+ icon = DrawableCompat.wrap(state == null ? icon : state.newDrawable()).mutate();
+ DrawableCompat.setTintList(icon, mIconTint);
+ }
+ mIcon.setImageDrawable(icon);
+ }
+
+ @Override
+ public boolean prefersCondensedTitle() {
+ return false;
+ }
+
+ @Override
+ public boolean showsIcon() {
+ return true;
+ }
+
+ public void setIconTintList(ColorStateList tint) {
+ mIconTint = tint;
+ if (mItemData != null) {
+ // Update the icon so that the tint takes effect
+ setIcon(mItemData.getIcon());
+ }
+ mLabel.setTextColor(mIconTint);
+ }
+
+ public void setTextColor(ColorStateList color) {
+ mTextColor = color;
+ mLabel.setTextColor(color);
+ }
+
+ public void setItemBackground(int background) {
+ Drawable backgroundDrawable = background == 0
+ ? null : ContextCompat.getDrawable(getContext(), background);
+ setBackgroundDrawable(backgroundDrawable);
+ }
+
+ @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+ public Animator getAnimator(boolean active) {
+ final float currentTextSize = mLabel.getTextSize();
+ final int currentTopPadding = getPaddingTop();
+
+ final float finalTextSize = active ? mActiveLabelSize : mInactiveLabelSize;
+ final int finalTopPadding = active ? mActiveTopPadding : mTopPadding;
+
+ if (currentTextSize == finalTextSize && currentTopPadding == finalTopPadding) {
+ return null;
+ }
+
+ // Grow or shrink the text of the tab.
+ ValueAnimator textAnimator = ValueAnimator.ofFloat(currentTextSize, finalTextSize);
+ textAnimator.setDuration(ACTIVE_ANIMATION_DURATION_MS);
+ textAnimator.setInterpolator(new LinearOutSlowInInterpolator());
+ textAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator valueAnimator) {
+ float animatedValue = (float) valueAnimator.getAnimatedValue();
+ mLabel.setTextSize(TypedValue.COMPLEX_UNIT_PX, animatedValue);
+ }
+ });
+
+ // Reduce or increase the padding top of the tab.
+ ValueAnimator paddingTopAnimator = ValueAnimator.ofInt(currentTopPadding, finalTopPadding);
+ paddingTopAnimator.setDuration(ACTIVE_ANIMATION_DURATION_MS);
+ paddingTopAnimator.setInterpolator(new LinearOutSlowInInterpolator());
+ paddingTopAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator valueAnimator) {
+ int animatedValue = (int) valueAnimator.getAnimatedValue();
+ setPadding(mHorizontalPadding, animatedValue,
+ mHorizontalPadding, mBottomPadding);
+ }
+ });
+
+ AnimatorSet set = new AnimatorSet();
+ set.playTogether(textAnimator, paddingTopAnimator);
+ return set;
+ }
+}
diff --git a/design/src/android/support/design/internal/BottomNavigationMenuView.java b/design/src/android/support/design/internal/BottomNavigationMenuView.java
new file mode 100644
index 0000000..a4df28f
--- /dev/null
+++ b/design/src/android/support/design/internal/BottomNavigationMenuView.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright (C) 2016 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.internal;
+
+import android.animation.AnimatorSet;
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.os.Build;
+import android.support.annotation.Nullable;
+import android.support.design.R;
+import android.support.v4.util.Pools;
+import android.support.v7.view.menu.MenuBuilder;
+import android.support.v7.view.menu.MenuItemImpl;
+import android.support.v7.view.menu.MenuView;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+
+/**
+ * @hide
+ */
+public class BottomNavigationMenuView extends LinearLayout implements MenuView {
+ private final int mInactiveItemMaxWidth;
+ private final int mActiveItemMaxWidth;
+ private final OnClickListener mOnClickListener;
+ private static final Pools.Pool<BottomNavigationItemView> sItemPool =
+ new Pools.SynchronizedPool<>(5);
+
+ private BottomNavigationItemView[] mButtons;
+ private int mActiveButton = 0;
+ private ColorStateList mItemIconTint;
+ private ColorStateList mItemTextColor;
+ private int mItemBackgroundRes;
+
+ private BottomNavigationPresenter mPresenter;
+ private MenuBuilder mMenu;
+
+ public BottomNavigationMenuView(Context context) {
+ this(context, null);
+ }
+
+ public BottomNavigationMenuView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public BottomNavigationMenuView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ setGravity(Gravity.CENTER);
+ setOrientation(HORIZONTAL);
+
+ mInactiveItemMaxWidth = getResources().getDimensionPixelSize(
+ R.dimen.design_bottom_navigation_item_max_width);
+ mActiveItemMaxWidth = getResources()
+ .getDimensionPixelSize(R.dimen.design_bottom_navigation_active_item_max_width);
+
+ mOnClickListener = new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ final int itemPosition = ((BottomNavigationItemView) v).getItemPosition();
+ activateNewButton(itemPosition);
+ }
+ };
+ }
+
+ @Override
+ public void initialize(MenuBuilder menu) {
+ mMenu = menu;
+ if (mMenu == null) return;
+ if (mMenu.size() > mActiveButton) {
+ mMenu.getItem(mActiveButton).setChecked(true);
+ }
+ }
+
+ @Override
+ public int getWindowAnimations() {
+ return 0;
+ }
+
+ public void setIconTintList(ColorStateList color) {
+ mItemIconTint = color;
+ if (mButtons == null) return;
+ for (BottomNavigationItemView item : mButtons) {
+ item.setIconTintList(color);
+ }
+ }
+
+ @Nullable
+ public ColorStateList getIconTintList() {
+ return mItemIconTint;
+ }
+
+ public void setItemTextColor(ColorStateList color) {
+ mItemTextColor = color;
+ if (mButtons == null) return;
+ for (BottomNavigationItemView item : mButtons) {
+ item.setTextColor(color);
+ }
+ }
+
+ public ColorStateList getItemTextColor() {
+ return mItemTextColor;
+ }
+
+ public void setItemBackgroundRes(int background) {
+ mItemBackgroundRes = background;
+ if (mButtons == null) return;
+ for (BottomNavigationItemView item : mButtons) {
+ item.setItemBackground(background);
+ }
+ }
+
+ public int getItemBackgroundRes() {
+ return mItemBackgroundRes;
+ }
+
+ public void setPresenter(BottomNavigationPresenter presenter) {
+ mPresenter = presenter;
+ }
+
+ public void buildMenuView() {
+ if (mButtons != null) {
+ for (BottomNavigationItemView item : mButtons) {
+ sItemPool.release(item);
+ }
+ }
+ removeAllViews();
+ mButtons = new BottomNavigationItemView[mMenu.size()];
+ for (int i = 0; i < mMenu.size(); i++) {
+ mPresenter.setUpdateSuspended(true);
+ mMenu.getItem(i).setCheckable(true);
+ mPresenter.setUpdateSuspended(false);
+ BottomNavigationItemView child = getNewItem();
+ mButtons[i] = child;
+ child.setIconTintList(mItemIconTint);
+ child.setTextColor(mItemTextColor);
+ child.setItemBackground(mItemBackgroundRes);
+ child.initialize((MenuItemImpl) mMenu.getItem(i), 0);
+ child.setItemPosition(i);
+ child.setOnClickListener(mOnClickListener);
+ addView(child);
+ }
+ }
+
+ public void updateMenuView() {
+ final int menuSize = mMenu.size();
+ if (menuSize != mButtons.length) {
+ // The size has changed. Rebuild menu view from scratch.
+ buildMenuView();
+ return;
+ }
+ for (int i = 0; i < menuSize; i++) {
+ mPresenter.setUpdateSuspended(true);
+ mButtons[i].initialize((MenuItemImpl) mMenu.getItem(i), 0);
+ mPresenter.setUpdateSuspended(false);
+ }
+ }
+
+ private void activateNewButton(int newButton) {
+ if (mActiveButton == newButton) return;
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
+ AnimatorSet animatorSet = new AnimatorSet();
+ animatorSet.playTogether(
+ mButtons[mActiveButton].getAnimator(false),
+ mButtons[newButton].getAnimator(true));
+ animatorSet.start();
+ }
+ mPresenter.setUpdateSuspended(true);
+ mButtons[mActiveButton].setChecked(false);
+ mButtons[newButton].setChecked(true);
+ mPresenter.setUpdateSuspended(false);
+
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
+ // Manually force UI update since we cannot use animations.
+ mPresenter.updateMenuView(true);
+ }
+ mActiveButton = newButton;
+ }
+
+ public void updateOnSizeChange(int width) {
+ if (getChildCount() == 0) return;
+ int available = width / getChildCount();
+ int itemWidth = Math.min(available, mActiveItemMaxWidth);
+
+ for (int i = 0; i < mButtons.length; i++) {
+ ViewGroup.LayoutParams params = mButtons[i].getLayoutParams();
+ if (params.width == itemWidth) {
+ continue;
+ }
+ params.width = itemWidth;
+ params.height = ViewGroup.LayoutParams.MATCH_PARENT;
+ mButtons[i].setLayoutParams(params);
+ }
+ }
+
+ private BottomNavigationItemView getNewItem() {
+ BottomNavigationItemView item = sItemPool.acquire();
+ if (item == null) {
+ item = new BottomNavigationItemView(getContext());
+ }
+ return item;
+ }
+}
diff --git a/design/src/android/support/design/internal/BottomNavigationPresenter.java b/design/src/android/support/design/internal/BottomNavigationPresenter.java
new file mode 100644
index 0000000..8dc0549
--- /dev/null
+++ b/design/src/android/support/design/internal/BottomNavigationPresenter.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2016 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.internal;
+
+import android.content.Context;
+import android.os.Parcelable;
+import android.support.v7.view.menu.MenuBuilder;
+import android.support.v7.view.menu.MenuItemImpl;
+import android.support.v7.view.menu.MenuPresenter;
+import android.support.v7.view.menu.MenuView;
+import android.support.v7.view.menu.SubMenuBuilder;
+import android.view.ViewGroup;
+
+/**
+ * @hide
+ */
+public class BottomNavigationPresenter implements MenuPresenter {
+ private MenuBuilder mMenu;
+ private BottomNavigationMenuView mMenuView;
+ private boolean mUpdateSuspended = false;
+
+ public void setBottomNavigationMenuView(BottomNavigationMenuView menuView) {
+ mMenuView = menuView;
+ }
+
+ @Override
+ public void initForMenu(Context context, MenuBuilder menu) {
+ mMenuView.initialize(mMenu);
+ mMenu = menu;
+ }
+
+ @Override
+ public MenuView getMenuView(ViewGroup root) {
+ return mMenuView;
+ }
+
+ @Override
+ public void updateMenuView(boolean cleared) {
+ if (mUpdateSuspended) return;
+ if (cleared) {
+ mMenuView.buildMenuView();
+ } else {
+ mMenuView.updateMenuView();
+ }
+ }
+
+ @Override
+ public void setCallback(Callback cb) {}
+
+ @Override
+ public boolean onSubMenuSelected(SubMenuBuilder subMenu) {
+ return false;
+ }
+
+ @Override
+ public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {}
+
+ @Override
+ public boolean flagActionItems() {
+ return false;
+ }
+
+ @Override
+ public boolean expandItemActionView(MenuBuilder menu, MenuItemImpl item) {
+ return false;
+ }
+
+ @Override
+ public boolean collapseItemActionView(MenuBuilder menu, MenuItemImpl item) {
+ return false;
+ }
+
+ @Override
+ public int getId() {
+ return -1;
+ }
+
+ @Override
+ public Parcelable onSaveInstanceState() {
+ return null;
+ }
+
+ @Override
+ public void onRestoreInstanceState(Parcelable state) {}
+
+ public void setUpdateSuspended(boolean updateSuspended) {
+ mUpdateSuspended = updateSuspended;
+ }
+}
diff --git a/design/src/android/support/design/internal/NavigationMenuItemView.java b/design/src/android/support/design/internal/NavigationMenuItemView.java
index 1e2c928..1f70a04 100644
--- a/design/src/android/support/design/internal/NavigationMenuItemView.java
+++ b/design/src/android/support/design/internal/NavigationMenuItemView.java
@@ -23,6 +23,7 @@
import android.graphics.drawable.Drawable;
import android.graphics.drawable.StateListDrawable;
import android.support.design.R;
+import android.support.v4.content.res.ResourcesCompat;
import android.support.v4.graphics.drawable.DrawableCompat;
import android.support.v4.widget.TextViewCompat;
import android.support.v7.view.menu.MenuItemImpl;
@@ -44,6 +45,8 @@
private final int mIconSize;
+ private boolean mNeedsEmptyIcon;
+
private final CheckedTextView mTextView;
private FrameLayout mActionArea;
@@ -52,6 +55,8 @@
private ColorStateList mIconTintList;
+ private Drawable mEmptyDrawable;
+
public NavigationMenuItemView(Context context) {
this(context, null);
}
@@ -86,6 +91,32 @@
setTitle(itemData.getTitle());
setIcon(itemData.getIcon());
setActionView(itemData.getActionView());
+ adjustAppearance();
+ }
+
+ private boolean shouldExpandActionArea() {
+ return mItemData.getTitle() == null &&
+ mItemData.getIcon() == null &&
+ mItemData.getActionView() != null;
+ }
+
+ private void adjustAppearance() {
+ if (shouldExpandActionArea()) {
+ // Expand the actionView area
+ mTextView.setVisibility(View.GONE);
+ if (mActionArea != null) {
+ LayoutParams params = (LayoutParams) mActionArea.getLayoutParams();
+ params.width = LayoutParams.MATCH_PARENT;
+ mActionArea.setLayoutParams(params);
+ }
+ } else {
+ mTextView.setVisibility(View.VISIBLE);
+ if (mActionArea != null) {
+ LayoutParams params = (LayoutParams) mActionArea.getLayoutParams();
+ params.width = LayoutParams.WRAP_CONTENT;
+ mActionArea.setLayoutParams(params);
+ }
+ }
}
public void recycle() {
@@ -96,12 +127,12 @@
}
private void setActionView(View actionView) {
- if (mActionArea == null) {
- mActionArea = (FrameLayout) ((ViewStub) findViewById(
- R.id.design_menu_item_action_area_stub)).inflate();
- }
- mActionArea.removeAllViews();
if (actionView != null) {
+ if (mActionArea == null) {
+ mActionArea = (FrameLayout) ((ViewStub) findViewById(
+ R.id.design_menu_item_action_area_stub)).inflate();
+ }
+ mActionArea.removeAllViews();
mActionArea.addView(actionView);
}
}
@@ -150,6 +181,15 @@
icon = DrawableCompat.wrap(state == null ? icon : state.newDrawable()).mutate();
icon.setBounds(0, 0, mIconSize, mIconSize);
DrawableCompat.setTintList(icon, mIconTintList);
+ } else if (mNeedsEmptyIcon) {
+ if (mEmptyDrawable == null) {
+ mEmptyDrawable = ResourcesCompat.getDrawable(getResources(),
+ R.drawable.navigation_empty_icon, getContext().getTheme());
+ if (mEmptyDrawable != null) {
+ mEmptyDrawable.setBounds(0, 0, mIconSize, mIconSize);
+ }
+ }
+ icon = mEmptyDrawable;
}
TextViewCompat.setCompoundDrawablesRelative(mTextView, icon, null, null, null);
}
@@ -189,4 +229,8 @@
mTextView.setTextColor(colors);
}
+ public void setNeedsEmptyIcon(boolean needsEmptyIcon) {
+ mNeedsEmptyIcon = needsEmptyIcon;
+ }
+
}
diff --git a/design/src/android/support/design/internal/NavigationMenuPresenter.java b/design/src/android/support/design/internal/NavigationMenuPresenter.java
index afdef82..ec998c2 100644
--- a/design/src/android/support/design/internal/NavigationMenuPresenter.java
+++ b/design/src/android/support/design/internal/NavigationMenuPresenter.java
@@ -19,8 +19,6 @@
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.Resources;
-import android.graphics.Color;
-import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Parcelable;
@@ -39,7 +37,6 @@
import android.support.v7.widget.RecyclerView;
import android.util.SparseArray;
import android.view.LayoutInflater;
-import android.view.MenuItem;
import android.view.SubMenu;
import android.view.View;
import android.view.ViewGroup;
@@ -341,7 +338,6 @@
private final ArrayList<NavigationMenuItem> mItems = new ArrayList<>();
private MenuItemImpl mCheckedItem;
- private ColorDrawable mTransparentIcon;
private boolean mUpdateSuspended;
NavigationMenuAdapter() {
@@ -406,6 +402,7 @@
itemView.setBackgroundDrawable(mItemBackground != null ?
mItemBackground.getConstantState().newDrawable() : null);
NavigationMenuTextItem item = (NavigationMenuTextItem) mItems.get(position);
+ itemView.setNeedsEmptyIcon(item.needsEmptyIcon);
itemView.initialize(item.getMenuItem(), 0);
break;
}
@@ -506,10 +503,9 @@
currentGroupHasIcon = true;
appendTransparentIconIfMissing(currentGroupStart, mItems.size());
}
- if (currentGroupHasIcon && item.getIcon() == null) {
- item.setIcon(android.R.color.transparent);
- }
- mItems.add(new NavigationMenuTextItem(item));
+ NavigationMenuTextItem textItem = new NavigationMenuTextItem(item);
+ textItem.needsEmptyIcon = currentGroupHasIcon;
+ mItems.add(textItem);
currentGroupId = groupId;
}
}
@@ -519,13 +515,7 @@
private void appendTransparentIconIfMissing(int startIndex, int endIndex) {
for (int i = startIndex; i < endIndex; i++) {
NavigationMenuTextItem textItem = (NavigationMenuTextItem) mItems.get(i);
- MenuItem item = textItem.getMenuItem();
- if (item.getIcon() == null) {
- if (mTransparentIcon == null) {
- mTransparentIcon = new ColorDrawable(Color.TRANSPARENT);
- }
- item.setIcon(mTransparentIcon);
- }
+ textItem.needsEmptyIcon = true;
}
}
@@ -611,6 +601,8 @@
private final MenuItemImpl mMenuItem;
+ boolean needsEmptyIcon;
+
private NavigationMenuTextItem(MenuItemImpl item) {
mMenuItem = item;
}
diff --git a/design/src/android/support/design/widget/BottomNavigationView.java b/design/src/android/support/design/widget/BottomNavigationView.java
new file mode 100644
index 0000000..1ccb6ff
--- /dev/null
+++ b/design/src/android/support/design/widget/BottomNavigationView.java
@@ -0,0 +1,287 @@
+/*
+ * Copyright (C) 2016 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.content.res.ColorStateList;
+import android.support.annotation.DrawableRes;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.design.R;
+import android.support.design.internal.BottomNavigationMenuView;
+import android.support.design.internal.BottomNavigationPresenter;
+import android.support.v7.content.res.AppCompatResources;
+import android.support.v7.view.SupportMenuInflater;
+import android.support.v7.view.menu.MenuBuilder;
+import android.support.v7.widget.TintTypedArray;
+import android.util.AttributeSet;
+import android.util.TypedValue;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.LinearLayout;
+
+/**
+ * Represents a standard bottom navigation bar for application. It is an implementation of material
+ * design bottom navigation. See https://material.google.com/components/bottom-navigation.html
+ *
+ * Bottom navigation bars make it easy for users to explore and switch between top-level views in
+ * a single tap. It should be used when application has three to five top-level destinations.
+ *
+ * The bar contents can be populated by specifying a menu resource file. Each menu item title, icon
+ * and enabled state will be used for displaying bottom navigation bar items.
+ *
+ * <android.support.design.widget.BottomNavigationView
+ * xmlns:android="http://schemas.android.com/apk/res/android"
+ * xmlns:app="http://schemas.android.com/apk/res-auto"
+ * android:id="@+id/navigation"
+ * android:layout_width="wrap_content"
+ * android:layout_height="match_parent"
+ * android:layout_gravity="start"
+ * app:menu="@menu/my_navigation_items" />
+ */
+public class BottomNavigationView extends FrameLayout {
+
+ private static final int[] CHECKED_STATE_SET = {android.R.attr.state_checked};
+ private static final int[] DISABLED_STATE_SET = {-android.R.attr.state_enabled};
+
+ private final MenuBuilder mMenu;
+ private final BottomNavigationMenuView mMenuView;
+ private final BottomNavigationPresenter mPresenter = new BottomNavigationPresenter();
+ private MenuInflater mMenuInflater;
+
+ private OnNavigationItemSelectedListener mListener;
+
+ public BottomNavigationView(Context context) {
+ this(context, null);
+ }
+
+ public BottomNavigationView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public BottomNavigationView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+
+ ThemeUtils.checkAppCompatTheme(context);
+
+ // Create the menu
+ mMenu = new MenuBuilder(context);
+
+ mMenuView = new BottomNavigationMenuView(context, attrs);
+ LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
+ mMenuView.setLayoutParams(params);
+
+ mPresenter.setBottomNavigationMenuView(mMenuView);
+ mMenuView.setPresenter(mPresenter);
+ mMenu.addMenuPresenter(mPresenter);
+
+
+ // Custom attributes
+ TintTypedArray a = TintTypedArray.obtainStyledAttributes(context, attrs,
+ R.styleable.BottomNavigationView, defStyleAttr,
+ R.style.Widget_Design_BottomNavigationView);
+
+ if (a.hasValue(R.styleable.BottomNavigationView_itemIconTint)) {
+ mMenuView.setIconTintList(
+ a.getColorStateList(R.styleable.BottomNavigationView_itemIconTint));
+ } else {
+ mMenuView.setIconTintList(
+ createDefaultColorStateList(android.R.attr.textColorSecondary));
+ }
+ if (a.hasValue(R.styleable.BottomNavigationView_itemTextColor)) {
+ mMenuView.setItemTextColor(
+ a.getColorStateList(R.styleable.BottomNavigationView_itemTextColor));
+ } else {
+ mMenuView.setItemTextColor(
+ createDefaultColorStateList(android.R.attr.textColorSecondary));
+ }
+
+ int itemBackground = a.getResourceId(R.styleable.BottomNavigationView_itemBackground, 0);
+ mMenuView.setItemBackgroundRes(itemBackground);
+
+ if (a.hasValue(R.styleable.BottomNavigationView_menu)) {
+ inflateMenu(a.getResourceId(R.styleable.BottomNavigationView_menu, 0));
+ }
+ a.recycle();
+
+ addView(mMenuView);
+
+ mMenu.setCallback(new MenuBuilder.Callback() {
+ @Override
+ public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) {
+ return mListener != null && mListener.onNavigationItemSelected(item);
+ }
+
+ @Override
+ public void onMenuModeChange(MenuBuilder menu) {}
+ });
+ }
+
+ /**
+ * Set a listener that will be notified when a bottom navigation item is selected.
+ *
+ * @param listener The listener to notify
+ */
+ public void setOnNavigationItemSelectedListener(
+ @Nullable OnNavigationItemSelectedListener listener) {
+ mListener = listener;
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ // TODO(aurimas): move updateOnSizeChange to a different location that is less expensive.
+ mMenuView.updateOnSizeChange(MeasureSpec.getSize(widthMeasureSpec));
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ }
+
+ @Override
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ super.onSizeChanged(w, h, oldw, oldh);
+ mMenuView.updateOnSizeChange(w);
+ }
+
+ /**
+ * Returns the {@link Menu} instance associated with this bottom navigation bar.
+ */
+ @NonNull
+ public Menu getMenu() {
+ return mMenu;
+ }
+
+ /**
+ * Inflate a menu resource into this navigation view.
+ *
+ * <p>Existing items in the menu will not be modified or removed.</p>
+ *
+ * @param resId ID of a menu resource to inflate
+ */
+ public void inflateMenu(int resId) {
+ mPresenter.setUpdateSuspended(true);
+ getMenuInflater().inflate(resId, mMenu);
+ mPresenter.initForMenu(getContext(), mMenu);
+ mPresenter.setUpdateSuspended(false);
+ mPresenter.updateMenuView(true);
+ }
+
+ /**
+ * Returns the tint which is applied to our menu items' icons.
+ *
+ * @see #setItemIconTintList(ColorStateList)
+ *
+ * @attr ref R.styleable#BottomNavigationView_itemIconTint
+ */
+ @Nullable
+ public ColorStateList getItemIconTintList() {
+ return mMenuView.getIconTintList();
+ }
+
+ /**
+ * Set the tint which is applied to our menu items' icons.
+ *
+ * @param tint the tint to apply.
+ *
+ * @attr ref R.styleable#BottomNavigationView_itemIconTint
+ */
+ public void setItemIconTintList(@Nullable ColorStateList tint) {
+ mMenuView.setIconTintList(tint);
+ }
+
+
+ /**
+ * Returns the tint which is applied to menu items' icons.
+ *
+ * @see #setItemTextColor(ColorStateList)
+ *
+ * @attr ref R.styleable#BottomNavigationView_itemTextColor
+ */
+ @Nullable
+ public ColorStateList getItemTextColor() {
+ return mMenuView.getItemTextColor();
+ }
+
+ /**
+ * Set the text color to be used on menu items.
+ *
+ * @see #getItemTextColor()
+ *
+ * @attr ref R.styleable#BottomNavigationView_itemTextColor
+ */
+ public void setItemTextColor(@Nullable ColorStateList textColor) {
+ mMenuView.setItemTextColor(textColor);
+ }
+
+ /**
+ * Set the background of our menu items to the given resource.
+ *
+ * @param resId The identifier of the resource.
+ *
+ * @attr ref R.styleable#BottomNavigationView_itemBackground
+ */
+ public void setItemBackgroundResource(@DrawableRes int resId) {
+ mMenuView.setItemBackgroundRes(resId);
+ }
+
+ /**
+ * Listener for handling events on bottom navigation items.
+ */
+ public interface OnNavigationItemSelectedListener {
+
+ /**
+ * Called when an item in the bottom navigation menu is selected.
+ *
+ * @param item The selected item
+ *
+ * @return true to display the item as the selected item
+ */
+ public boolean onNavigationItemSelected(@NonNull MenuItem item);
+ }
+
+ private MenuInflater getMenuInflater() {
+ if (mMenuInflater == null) {
+ mMenuInflater = new SupportMenuInflater(getContext());
+ }
+ return mMenuInflater;
+ }
+
+ private ColorStateList createDefaultColorStateList(int baseColorThemeAttr) {
+ final TypedValue value = new TypedValue();
+ if (!getContext().getTheme().resolveAttribute(baseColorThemeAttr, value, true)) {
+ return null;
+ }
+ ColorStateList baseColor = AppCompatResources.getColorStateList(
+ getContext(), value.resourceId);
+ if (!getContext().getTheme().resolveAttribute(
+ android.support.v7.appcompat.R.attr.colorPrimary, value, true)) {
+ return null;
+ }
+ int colorPrimary = value.data;
+ int defaultColor = baseColor.getDefaultColor();
+ return new ColorStateList(new int[][]{
+ DISABLED_STATE_SET,
+ CHECKED_STATE_SET,
+ EMPTY_STATE_SET
+ }, new int[]{
+ baseColor.getColorForState(DISABLED_STATE_SET, defaultColor),
+ colorPrimary,
+ defaultColor
+ });
+ }
+}
diff --git a/design/src/android/support/design/widget/CheckableImageButton.java b/design/src/android/support/design/widget/CheckableImageButton.java
new file mode 100644
index 0000000..2e7612b
--- /dev/null
+++ b/design/src/android/support/design/widget/CheckableImageButton.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2016 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.v4.view.AccessibilityDelegateCompat;
+import android.support.v4.view.ViewCompat;
+import android.support.v4.view.accessibility.AccessibilityEventCompat;
+import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
+import android.support.v7.widget.AppCompatImageButton;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.accessibility.AccessibilityEvent;
+import android.widget.Checkable;
+
+/**
+ * @hide
+ */
+public class CheckableImageButton extends AppCompatImageButton implements Checkable {
+
+ private static final int[] DRAWABLE_STATE_CHECKED = new int[]{android.R.attr.state_checked};
+
+ private boolean mChecked;
+
+ public CheckableImageButton(Context context) {
+ this(context, null);
+ }
+
+ public CheckableImageButton(Context context, AttributeSet attrs) {
+ this(context, attrs, android.support.v7.appcompat.R.attr.imageButtonStyle);
+ }
+
+ public CheckableImageButton(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+
+ ViewCompat.setAccessibilityDelegate(this, new AccessibilityDelegateCompat() {
+ @Override
+ public void onInitializeAccessibilityEvent(View host, AccessibilityEvent event) {
+ super.onInitializeAccessibilityEvent(host, event);
+ event.setChecked(isChecked());
+ }
+
+ @Override
+ public void onInitializeAccessibilityNodeInfo(View host,
+ AccessibilityNodeInfoCompat info) {
+ super.onInitializeAccessibilityNodeInfo(host, info);
+ info.setCheckable(true);
+ info.setChecked(isChecked());
+ }
+ });
+ }
+
+ @Override
+ public void setChecked(boolean checked) {
+ if (mChecked != checked) {
+ mChecked = checked;
+ refreshDrawableState();
+ sendAccessibilityEvent(
+ AccessibilityEventCompat.TYPE_WINDOW_CONTENT_CHANGED);
+ }
+ }
+
+ @Override
+ public boolean isChecked() {
+ return mChecked;
+ }
+
+ @Override
+ public void toggle() {
+ setChecked(!mChecked);
+ }
+
+ @Override
+ public int[] onCreateDrawableState(int extraSpace) {
+ if (mChecked) {
+ return mergeDrawableStates(
+ super.onCreateDrawableState(extraSpace + DRAWABLE_STATE_CHECKED.length),
+ DRAWABLE_STATE_CHECKED);
+ } else {
+ return super.onCreateDrawableState(extraSpace);
+ }
+ }
+}
diff --git a/design/src/android/support/design/widget/FloatingActionButton.java b/design/src/android/support/design/widget/FloatingActionButton.java
index f75bc1a..b25a4b2 100644
--- a/design/src/android/support/design/widget/FloatingActionButton.java
+++ b/design/src/android/support/design/widget/FloatingActionButton.java
@@ -157,7 +157,7 @@
R.styleable.FloatingActionButton, defStyleAttr,
R.style.Widget_Design_FloatingActionButton);
mBackgroundTint = a.getColorStateList(R.styleable.FloatingActionButton_backgroundTint);
- mBackgroundTintMode = parseTintMode(a.getInt(
+ mBackgroundTintMode = ViewUtils.parseTintMode(a.getInt(
R.styleable.FloatingActionButton_backgroundTintMode, -1), null);
mRippleColor = a.getColor(R.styleable.FloatingActionButton_rippleColor, 0);
mSize = a.getInt(R.styleable.FloatingActionButton_fabSize, SIZE_AUTO);
@@ -511,23 +511,6 @@
return result;
}
- static PorterDuff.Mode parseTintMode(int value, PorterDuff.Mode defaultMode) {
- switch (value) {
- case 3:
- return PorterDuff.Mode.SRC_OVER;
- case 5:
- return PorterDuff.Mode.SRC_IN;
- case 9:
- return PorterDuff.Mode.SRC_ATOP;
- case 14:
- return PorterDuff.Mode.MULTIPLY;
- case 15:
- return PorterDuff.Mode.SCREEN;
- default:
- return defaultMode;
- }
- }
-
@Override
public boolean onTouchEvent(MotionEvent ev) {
if(getContentRect(mTouchArea) && !mTouchArea.contains((int) ev.getX(), (int) ev.getY())) {
diff --git a/design/src/android/support/design/widget/TextInputLayout.java b/design/src/android/support/design/widget/TextInputLayout.java
index 171b62b..782e3d04 100644
--- a/design/src/android/support/design/widget/TextInputLayout.java
+++ b/design/src/android/support/design/widget/TextInputLayout.java
@@ -18,18 +18,21 @@
import android.content.Context;
import android.content.res.ColorStateList;
-import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.PorterDuff;
+import android.graphics.Rect;
import android.graphics.Typeface;
+import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.DrawableContainer;
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
+import android.support.annotation.DrawableRes;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
+import android.support.annotation.StringRes;
import android.support.annotation.StyleRes;
import android.support.design.R;
import android.support.v4.content.ContextCompat;
@@ -43,18 +46,23 @@
import android.support.v4.view.ViewPropertyAnimatorListenerAdapter;
import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
import android.support.v4.widget.Space;
+import android.support.v4.widget.TextViewCompat;
import android.support.v7.widget.AppCompatDrawableManager;
+import android.support.v7.widget.TintTypedArray;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
+import android.text.method.PasswordTransformationMethod;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Gravity;
+import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
import android.view.animation.AccelerateInterpolator;
import android.widget.EditText;
+import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.TextView;
@@ -90,12 +98,14 @@
private static final String LOG_TAG = "TextInputLayout";
+ private final FrameLayout mInputFrame;
private EditText mEditText;
private boolean mHintEnabled;
private CharSequence mHint;
private Paint mTmpPaint;
+ private final Rect mTmpRect = new Rect();
private LinearLayout mIndicatorArea;
private int mIndicatorsAdded;
@@ -113,6 +123,18 @@
private int mCounterOverflowTextAppearance;
private boolean mCounterOverflowed;
+ private boolean mPasswordToggleEnabled;
+ private Drawable mPasswordToggleDrawable;
+ private CharSequence mPasswordToggleContentDesc;
+ private CheckableImageButton mPasswordToggleView;
+ private boolean mPasswordToggledVisible;
+ private Drawable mPasswordToggleDummyDrawable;
+
+ private ColorStateList mPasswordToggleTintList;
+ private boolean mHasPasswordToggleTintList;
+ private PorterDuff.Mode mPasswordToggleTintMode;
+ private boolean mHasPasswordToggleTintMode;
+
private ColorStateList mDefaultTextColor;
private ColorStateList mFocusedTextColor;
@@ -141,11 +163,14 @@
setWillNotDraw(false);
setAddStatesFromChildren(true);
+ mInputFrame = new FrameLayout(context);
+ addView(mInputFrame);
+
mCollapsingTextHelper.setTextSizeInterpolator(AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR);
mCollapsingTextHelper.setPositionInterpolator(new AccelerateInterpolator());
mCollapsingTextHelper.setCollapsedTextGravity(Gravity.TOP | GravityCompat.START);
- final TypedArray a = context.obtainStyledAttributes(attrs,
+ final TintTypedArray a = TintTypedArray.obtainStyledAttributes(context, attrs,
R.styleable.TextInputLayout, defStyleAttr, R.style.Widget_Design_TextInputLayout);
mHintEnabled = a.getBoolean(R.styleable.TextInputLayout_hintEnabled, true);
setHint(a.getText(R.styleable.TextInputLayout_android_hint));
@@ -175,10 +200,28 @@
R.styleable.TextInputLayout_counterTextAppearance, 0);
mCounterOverflowTextAppearance = a.getResourceId(
R.styleable.TextInputLayout_counterOverflowTextAppearance, 0);
+
+ mPasswordToggleEnabled = a.getBoolean(
+ R.styleable.TextInputLayout_passwordToggleEnabled, true);
+ mPasswordToggleDrawable = a.getDrawable(R.styleable.TextInputLayout_passwordToggleDrawable);
+ mPasswordToggleContentDesc = a.getText(
+ R.styleable.TextInputLayout_passwordToggleContentDescription);
+ if (a.hasValue(R.styleable.TextInputLayout_passwordToggleTint)) {
+ mHasPasswordToggleTintList = true;
+ mPasswordToggleTintList = a.getColorStateList(
+ R.styleable.TextInputLayout_passwordToggleTint);
+ }
+ if (a.hasValue(R.styleable.TextInputLayout_passwordToggleTintMode)) {
+ mHasPasswordToggleTintMode = true;
+ mPasswordToggleTintMode = ViewUtils.parseTintMode(
+ a.getInt(R.styleable.TextInputLayout_passwordToggleTintMode, -1), null);
+ }
+
a.recycle();
setErrorEnabled(errorEnabled);
setCounterEnabled(counterEnabled);
+ applyPasswordToggleTint();
if (ViewCompat.getImportantForAccessibility(this)
== ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
@@ -191,10 +234,16 @@
}
@Override
- public void addView(View child, int index, ViewGroup.LayoutParams params) {
+ public void addView(View child, int index, final ViewGroup.LayoutParams params) {
if (child instanceof EditText) {
+ mInputFrame.addView(child, new FrameLayout.LayoutParams(params));
+
+ // Now use the EditText's LayoutParams as our own and update them to make enough space
+ // for the label
+ mInputFrame.setLayoutParams(params);
+ updateInputLayoutMargins();
+
setEditText((EditText) child);
- super.addView(child, 0, updateEditTextMargin(params));
} else {
// Carry on adding the View...
super.addView(child, index, params);
@@ -232,8 +281,13 @@
mEditText = editText;
+ final boolean hasPasswordTransformation = hasPasswordTransformation();
+
// Use the EditText's typeface, and it's text size for our expanded text
- mCollapsingTextHelper.setTypefaces(mEditText.getTypeface());
+ if (!hasPasswordTransformation) {
+ // We don't want a monospace font just because we have a password field
+ mCollapsingTextHelper.setTypefaces(mEditText.getTypeface());
+ }
mCollapsingTextHelper.setExpandedTextSize(mEditText.getTextSize());
final int editTextGravity = mEditText.getGravity();
@@ -278,14 +332,17 @@
adjustIndicatorPadding();
}
+ updatePasswordToggleView();
+
// Update the label visibility with no animation
updateLabelState(false);
}
- private LayoutParams updateEditTextMargin(ViewGroup.LayoutParams lp) {
+ private void updateInputLayoutMargins() {
// Create/update the LayoutParams so that we can add enough top margin
// to the EditText so make room for the label
- LayoutParams llp = lp instanceof LayoutParams ? (LayoutParams) lp : new LayoutParams(lp);
+ final LayoutParams lp = (LayoutParams) mInputFrame.getLayoutParams();
+ final int newTopMargin;
if (mHintEnabled) {
if (mTmpPaint == null) {
@@ -293,12 +350,15 @@
}
mTmpPaint.setTypeface(mCollapsingTextHelper.getCollapsedTypeface());
mTmpPaint.setTextSize(mCollapsingTextHelper.getCollapsedTextSize());
- llp.topMargin = (int) -mTmpPaint.ascent();
+ newTopMargin = (int) -mTmpPaint.ascent();
} else {
- llp.topMargin = 0;
+ newTopMargin = 0;
}
- return llp;
+ if (newTopMargin != lp.topMargin) {
+ lp.topMargin = newTopMargin;
+ mInputFrame.requestLayout();
+ }
}
private void updateLabelState(boolean animate) {
@@ -404,8 +464,7 @@
// Now update the EditText top margin
if (mEditText != null) {
- final LayoutParams lp = updateEditTextMargin(mEditText.getLayoutParams());
- mEditText.setLayoutParams(lp);
+ updateInputLayoutMargins();
}
}
}
@@ -432,11 +491,8 @@
if (mEditText != null) {
updateLabelState(false);
-
// Text size might have changed so update the top margin
- LayoutParams lp = updateEditTextMargin(mEditText.getLayoutParams());
- mEditText.setLayoutParams(lp);
- mEditText.requestLayout();
+ updateInputLayoutMargins();
}
}
@@ -874,16 +930,280 @@
}
@Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ updatePasswordToggleView();
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ }
+
+ private void updatePasswordToggleView() {
+ if (shouldShowPasswordIcon()) {
+ if (mPasswordToggleView == null) {
+ mPasswordToggleView = (CheckableImageButton) LayoutInflater.from(getContext())
+ .inflate(R.layout.design_text_input_password_icon, mInputFrame, false);
+ mPasswordToggleView.setImageDrawable(mPasswordToggleDrawable);
+ mPasswordToggleView.setContentDescription(mPasswordToggleContentDesc);
+ mInputFrame.addView(mPasswordToggleView);
+
+ mPasswordToggleView.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ passwordVisibilityToggleRequested();
+ }
+ });
+ }
+
+ mPasswordToggleView.setVisibility(VISIBLE);
+
+ // We need to add a dummy drawable as the end compound drawable so that the text is
+ // indented and doesn't display below the toggle view
+ if (mPasswordToggleDummyDrawable == null) {
+ mPasswordToggleDummyDrawable = new ColorDrawable();
+ }
+ mPasswordToggleDummyDrawable.setBounds(0, 0, mPasswordToggleView.getMeasuredWidth(), 1);
+
+ final Drawable[] compounds = TextViewCompat.getCompoundDrawablesRelative(mEditText);
+ TextViewCompat.setCompoundDrawablesRelative(mEditText, compounds[0], compounds[1],
+ mPasswordToggleDummyDrawable, compounds[2]);
+
+ // Copy over the EditText's padding so that we match
+ mPasswordToggleView.setPadding(mEditText.getPaddingLeft(),
+ mEditText.getPaddingTop(), mEditText.getPaddingRight(),
+ mEditText.getPaddingBottom());
+ } else {
+ if (mPasswordToggleView != null && mPasswordToggleView.getVisibility() == VISIBLE) {
+ mPasswordToggleView.setVisibility(View.GONE);
+ }
+
+ // Make sure that we remove the dummy end compound drawable
+ final Drawable[] compounds = TextViewCompat.getCompoundDrawablesRelative(mEditText);
+ TextViewCompat.setCompoundDrawablesRelative(mEditText, compounds[0], compounds[1],
+ null, compounds[2]);
+ }
+ }
+
+ /**
+ * Set the icon to use for the password visibility toggle button.
+ *
+ * <p>If you use an icon you should also set a description for its action
+ * using {@link #setPasswordVisibilityToggleContentDescription(CharSequence)}.
+ * This is used for accessibility.</p>
+ *
+ * @param resId resource id of the drawable to set, or 0 to clear the icon
+ *
+ * @attr ref android.support.design.R.styleable#TextInputLayout_passwordToggleDrawable
+ */
+ public void setPasswordVisibilityToggleDrawable(@DrawableRes int resId) {
+ setPasswordVisibilityToggleDrawable(resId != 0
+ ? AppCompatDrawableManager.get().getDrawable(getContext(), resId)
+ : null);
+ }
+
+ /**
+ * Set the icon to use for the password visibility toggle button.
+ *
+ * <p>If you use an icon you should also set a description for its action
+ * using {@link #setPasswordVisibilityToggleContentDescription(CharSequence)}.
+ * This is used for accessibility.</p>
+ *
+ * @param icon Drawable to set, may be null to clear the icon
+ *
+ * @attr ref android.support.design.R.styleable#TextInputLayout_passwordToggleDrawable
+ */
+ public void setPasswordVisibilityToggleDrawable(@Nullable Drawable icon) {
+ mPasswordToggleDrawable = icon;
+ if (mPasswordToggleView != null) {
+ mPasswordToggleView.setImageDrawable(icon);
+ }
+ }
+
+ /**
+ * Set a content description for the navigation button if one is present.
+ *
+ * <p>The content description will be read via screen readers or other accessibility
+ * systems to explain the action of the password visibility toggle.</p>
+ *
+ * @param resId Resource ID of a content description string to set,
+ * or 0 to clear the description
+ *
+ * @attr ref android.support.design.R.styleable#TextInputLayout_passwordToggleContentDescription
+ */
+ public void setPasswordVisibilityToggleContentDescription(@StringRes int resId) {
+ setPasswordVisibilityToggleContentDescription(
+ resId != 0 ? getResources().getText(resId) : null);
+ }
+
+ /**
+ * Set a content description for the navigation button if one is present.
+ *
+ * <p>The content description will be read via screen readers or other accessibility
+ * systems to explain the action of the password visibility toggle.</p>
+ *
+ * @param description Content description to set, or null to clear the content description
+ *
+ * @attr ref android.support.design.R.styleable#TextInputLayout_passwordToggleContentDescription
+ */
+ public void setPasswordVisibilityToggleContentDescription(@Nullable CharSequence description) {
+ mPasswordToggleContentDesc = description;
+ if (mPasswordToggleView != null) {
+ mPasswordToggleView.setContentDescription(description);
+ }
+ }
+
+ /**
+ * Returns the icon currently used for the password visibility toggle button.
+ *
+ * @see #setPasswordVisibilityToggleDrawable(Drawable)
+ *
+ * @attr ref android.support.design.R.styleable#TextInputLayout_passwordToggleDrawable
+ */
+ @Nullable
+ public Drawable getPasswordVisibilityToggleDrawable() {
+ return mPasswordToggleDrawable;
+ }
+
+ /**
+ * Returns the currently configured content description for the password visibility
+ * toggle button.
+ *
+ * <p>This will be used to describe the navigation action to users through mechanisms
+ * such as screen readers.</p>
+ */
+ @Nullable
+ public CharSequence getPasswordVisibilityToggleContentDescription() {
+ return mPasswordToggleContentDesc;
+ }
+
+ /**
+ * Returns whether the password visibility toggle functionality is currently enabled.
+ *
+ * @see #setPasswordVisibilityToggleEnabled(boolean)
+ */
+ public boolean isPasswordVisibilityToggleEnabled() {
+ return mPasswordToggleEnabled;
+ }
+
+ /**
+ * Returns whether the password visibility toggle functionality is enabled or not.
+ *
+ * <p>When enabled, a button is placed at the end of the EditText which enables the user
+ * to switch between the field's input being visibly disguised or not.</p>
+ *
+ * @param enabled true to enable the functionality
+ *
+ * @attr ref android.support.design.R.styleable#TextInputLayout_passwordToggleEnabled
+ */
+ public void setPasswordVisibilityToggleEnabled(final boolean enabled) {
+ if (mPasswordToggleEnabled != enabled) {
+ mPasswordToggleEnabled = enabled;
+
+ if (!enabled && mPasswordToggledVisible) {
+ // If the toggle is no longer enabled, but we remove the PasswordTransformation
+ // to make the password visible, add it back
+ mEditText.setTransformationMethod(PasswordTransformationMethod.getInstance());
+ }
+
+ // Reset the visibility tracking flag
+ mPasswordToggledVisible = false;
+
+ updatePasswordToggleView();
+ }
+ }
+
+ /**
+ * Applies a tint to the the password visibility toggle drawable. Does not modify the current
+ * tint mode, which is {@link PorterDuff.Mode#SRC_IN} by default.
+ *
+ * <p>Subsequent calls to {@link #setPasswordVisibilityToggleDrawable(Drawable)} will
+ * automatically mutate the drawable and apply the specified tint and tint mode using
+ * {@link DrawableCompat#setTintList(Drawable, ColorStateList)}.</p>
+ *
+ * @param tintList the tint to apply, may be null to clear tint
+ *
+ * @attr ref android.support.design.R.styleable#TextInputLayout_passwordToggleTint
+ */
+ public void setPasswordVisibilityToggleTintList(@Nullable ColorStateList tintList) {
+ mPasswordToggleTintList = tintList;
+ mHasPasswordToggleTintList = true;
+ applyPasswordToggleTint();
+ }
+
+ /**
+ * Specifies the blending mode used to apply the tint specified by
+ * {@link #setPasswordVisibilityToggleTintList(ColorStateList)} to the password
+ * visibility toggle drawable. The default mode is {@link PorterDuff.Mode#SRC_IN}.</p>
+ *
+ * @param mode the blending mode used to apply the tint, may be null to clear tint
+ *
+ * @attr ref android.support.design.R.styleable#TextInputLayout_passwordToggleTintMode
+ */
+ public void setPasswordVisibilityToggleTintMode(@Nullable PorterDuff.Mode mode) {
+ mPasswordToggleTintMode = mode;
+ mHasPasswordToggleTintMode = true;
+ applyPasswordToggleTint();
+ }
+
+ private void passwordVisibilityToggleRequested() {
+ if (mPasswordToggleEnabled) {
+ // Store the current cursor position
+ final int selection = mEditText.getSelectionEnd();
+
+ if (hasPasswordTransformation()) {
+ mEditText.setTransformationMethod(null);
+ mPasswordToggledVisible = true;
+ } else {
+ mEditText.setTransformationMethod(PasswordTransformationMethod.getInstance());
+ mPasswordToggledVisible = false;
+ }
+
+ mPasswordToggleView.setChecked(mPasswordToggledVisible);
+
+ // And restore the cursor position
+ mEditText.setSelection(selection);
+ }
+ }
+
+ private boolean hasPasswordTransformation() {
+ return mEditText != null
+ && mEditText.getTransformationMethod() instanceof PasswordTransformationMethod;
+ }
+
+ private boolean shouldShowPasswordIcon() {
+ return mPasswordToggleEnabled && (hasPasswordTransformation() || mPasswordToggledVisible);
+ }
+
+ private void applyPasswordToggleTint() {
+ if (mPasswordToggleDrawable != null
+ && (mHasPasswordToggleTintList || mHasPasswordToggleTintMode)) {
+ mPasswordToggleDrawable = DrawableCompat.wrap(mPasswordToggleDrawable).mutate();
+
+ if (mHasPasswordToggleTintList) {
+ DrawableCompat.setTintList(mPasswordToggleDrawable, mPasswordToggleTintList);
+ }
+ if (mHasPasswordToggleTintMode) {
+ DrawableCompat.setTintMode(mPasswordToggleDrawable, mPasswordToggleTintMode);
+ }
+
+ if (mPasswordToggleView != null
+ && mPasswordToggleView.getDrawable() != mPasswordToggleDrawable) {
+ mPasswordToggleView.setImageDrawable(mPasswordToggleDrawable);
+ }
+ }
+ }
+
+ @Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
if (mHintEnabled && mEditText != null) {
- final int l = mEditText.getLeft() + mEditText.getCompoundPaddingLeft();
- final int r = mEditText.getRight() - mEditText.getCompoundPaddingRight();
+ final Rect rect = mTmpRect;
+ ViewGroupUtils.getDescendantRect(this, mEditText, rect);
- mCollapsingTextHelper.setExpandedBounds(l,
- mEditText.getTop() + mEditText.getCompoundPaddingTop(),
- r, mEditText.getBottom() - mEditText.getCompoundPaddingBottom());
+ final int l = rect.left + mEditText.getCompoundPaddingLeft();
+ final int r = rect.right - mEditText.getCompoundPaddingRight();
+
+ mCollapsingTextHelper.setExpandedBounds(
+ l, rect.top + mEditText.getCompoundPaddingTop(),
+ r, rect.bottom - mEditText.getCompoundPaddingBottom());
// Set the collapsed bounds to be the the full height (minus padding) to match the
// EditText's editable area
diff --git a/design/src/android/support/design/widget/ViewUtils.java b/design/src/android/support/design/widget/ViewUtils.java
index e2eedb2..f49d836 100644
--- a/design/src/android/support/design/widget/ViewUtils.java
+++ b/design/src/android/support/design/widget/ViewUtils.java
@@ -16,6 +16,7 @@
package android.support.design.widget;
+import android.graphics.PorterDuff;
import android.os.Build;
class ViewUtils {
@@ -38,4 +39,21 @@
return (a == b) || (a != null && a.equals(b));
}
+ static PorterDuff.Mode parseTintMode(int value, PorterDuff.Mode defaultMode) {
+ switch (value) {
+ case 3:
+ return PorterDuff.Mode.SRC_OVER;
+ case 5:
+ return PorterDuff.Mode.SRC_IN;
+ case 9:
+ return PorterDuff.Mode.SRC_ATOP;
+ case 14:
+ return PorterDuff.Mode.MULTIPLY;
+ case 15:
+ return PorterDuff.Mode.SCREEN;
+ default:
+ return defaultMode;
+ }
+ }
+
}
diff --git a/design/tests/res/layout/action_layout_custom.xml b/design/tests/res/layout/action_layout_custom.xml
new file mode 100644
index 0000000..10a3268
--- /dev/null
+++ b/design/tests/res/layout/action_layout_custom.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2016 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.
+-->
+<TextView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@color/lilac_default"
+ android:text="@string/navigate_custom"/>
diff --git a/design/tests/res/layout/design_text_input.xml b/design/tests/res/layout/design_text_input.xml
index 1312b50..79b6446 100644
--- a/design/tests/res/layout/design_text_input.xml
+++ b/design/tests/res/layout/design_text_input.xml
@@ -36,4 +36,19 @@
</android.support.design.widget.TextInputLayout>
+ <android.support.design.widget.TextInputLayout
+ android:id="@+id/textinput_password"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ app:passwordToggleEnabled="true">
+
+ <android.support.design.widget.TextInputEditText
+ android:id="@+id/textinput_edittext_pwd"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:hint="@string/textinput_hint"
+ android:inputType="textPassword"/>
+
+ </android.support.design.widget.TextInputLayout>
+
</LinearLayout>
\ No newline at end of file
diff --git a/design/tests/res/menu/navigation_view_content.xml b/design/tests/res/menu/navigation_view_content.xml
index 758334f..a8fb464 100644
--- a/design/tests/res/menu/navigation_view_content.xml
+++ b/design/tests/res/menu/navigation_view_content.xml
@@ -29,5 +29,7 @@
app:actionLayout="@layout/action_layout" />
<item android:id="@+id/destination_settings"
android:title="@string/navigate_settings" />
+ <item android:id="@+id/desitination_custom"
+ app:actionLayout="@layout/action_layout_custom" />
</group>
</menu>
diff --git a/design/tests/res/values/strings.xml b/design/tests/res/values/strings.xml
index a431e2b..380cbe3 100644
--- a/design/tests/res/values/strings.xml
+++ b/design/tests/res/values/strings.xml
@@ -19,6 +19,7 @@
<string name="navigate_home">Home</string>
<string name="navigate_profile">Profile</string>
<string name="navigate_people">People</string>
+ <string name="navigate_custom">Custom</string>
<string name="navigate_settings">Settings</string>
<string name="snackbar_text">This is a test message</string>
diff --git a/design/tests/src/android/support/design/testutils/TextInputLayoutActions.java b/design/tests/src/android/support/design/testutils/TextInputLayoutActions.java
index 3360029..92ba267 100755
--- a/design/tests/src/android/support/design/testutils/TextInputLayoutActions.java
+++ b/design/tests/src/android/support/design/testutils/TextInputLayoutActions.java
@@ -23,6 +23,7 @@
import android.support.test.espresso.ViewAction;
import android.support.v4.widget.DrawerLayout;
import android.view.View;
+import android.widget.TextView;
import org.hamcrest.Matcher;
@@ -75,4 +76,30 @@
}
};
}
+
+ public static ViewAction setPasswordVisibilityToggleEnabled(final boolean enabled) {
+ return new ViewAction() {
+ @Override
+ public Matcher<View> getConstraints() {
+ return isAssignableFrom(TextInputLayout.class);
+ }
+
+ @Override
+ public String getDescription() {
+ return "Sets the error";
+ }
+
+ @Override
+ public void perform(UiController uiController, View view) {
+ uiController.loopMainThreadUntilIdle();
+
+ TextInputLayout layout = (TextInputLayout) view;
+ layout.setPasswordVisibilityToggleEnabled(enabled);
+
+ uiController.loopMainThreadUntilIdle();
+ }
+ };
+ }
+
+
}
diff --git a/design/tests/src/android/support/design/widget/NavigationViewTest.java b/design/tests/src/android/support/design/widget/NavigationViewTest.java
index 2abce52..97c43c3 100755
--- a/design/tests/src/android/support/design/widget/NavigationViewTest.java
+++ b/design/tests/src/android/support/design/widget/NavigationViewTest.java
@@ -30,6 +30,8 @@
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
+import android.widget.TextView;
+
import org.hamcrest.Matcher;
import org.junit.Before;
import org.junit.Test;
@@ -90,7 +92,7 @@
// Check the contents of the Menu object
final Menu menu = mNavigationView.getMenu();
assertNotNull("Menu should not be null", menu);
- assertEquals("Should have matching number of items", MENU_CONTENT_ITEM_IDS.length,
+ assertEquals("Should have matching number of items", MENU_CONTENT_ITEM_IDS.length + 1,
menu.size());
for (int i = 0; i < MENU_CONTENT_ITEM_IDS.length; i++) {
final MenuItem currItem = menu.getItem(i);
@@ -549,5 +551,16 @@
// makes our matcher actually run. If for some reason NavigationView fails to inflate and
// display our SwitchCompat action layout, the next line will fail in the matcher pass.
onView(menuItemMatcher).perform(click());
+
+ // Check that the full custom view is displayed without title and icon.
+ final Resources res = mActivityTestRule.getActivity().getResources();
+ Matcher customItemMatcher = allOf(
+ isDescendantOfA(withId(R.id.start_drawer)),
+ isChildOfA(isAssignableFrom(RecyclerView.class)),
+ hasDescendant(withText(res.getString(R.string.navigate_custom))),
+ hasDescendant(allOf(
+ isAssignableFrom(TextView.class),
+ withEffectiveVisibility(Visibility.GONE))));
+ onView(customItemMatcher).perform(click());
}
}
diff --git a/design/tests/src/android/support/design/widget/TextInputLayoutTest.java b/design/tests/src/android/support/design/widget/TextInputLayoutTest.java
index 558f474..d612932 100755
--- a/design/tests/src/android/support/design/widget/TextInputLayoutTest.java
+++ b/design/tests/src/android/support/design/widget/TextInputLayoutTest.java
@@ -16,22 +16,28 @@
package android.support.design.widget;
+import android.app.Activity;
+import android.support.design.test.R;
+import android.support.test.annotation.UiThreadTest;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.widget.EditText;
+
+import org.junit.Test;
+
+import static android.support.design.testutils.TestUtilsActions.setText;
import static android.support.design.testutils.TextInputLayoutActions.setError;
import static android.support.design.testutils.TextInputLayoutActions.setErrorEnabled;
+import static android.support.design.testutils.TextInputLayoutActions.setPasswordVisibilityToggleEnabled;
import static android.support.test.espresso.Espresso.onView;
+import static android.support.test.espresso.action.ViewActions.click;
import static android.support.test.espresso.assertion.ViewAssertions.doesNotExist;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
-import static android.support.test.espresso.matcher.ViewMatchers.withChild;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static android.support.test.espresso.matcher.ViewMatchers.withText;
-
-import static org.hamcrest.CoreMatchers.not;
-
-import android.support.design.test.R;
-import android.test.suitebuilder.annotation.SmallTest;
-
-import org.junit.Test;
+import static org.hamcrest.Matchers.not;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
@SmallTest
public class TextInputLayoutTest extends BaseInstrumentationTestCase<TextInputLayoutActivity> {
@@ -39,6 +45,8 @@
private static final String ERROR_MESSAGE_1 = "An error has occured";
private static final String ERROR_MESSAGE_2 = "Some other error has occured";
+ private static final String INPUT_TEXT = "Random input text";
+
public TextInputLayoutTest() {
super(TextInputLayoutActivity.class);
}
@@ -73,4 +81,43 @@
onView(withText(ERROR_MESSAGE_2)).check(matches(isDisplayed()));
}
+ @Test
+ @UiThreadTest
+ public void testPasswordToggleClick() {
+ // Set some text on the EditText
+ onView(withId(R.id.textinput_edittext_pwd)).perform(setText(INPUT_TEXT));
+
+ final Activity activity = mActivityTestRule.getActivity();
+ final EditText textInput = (EditText) activity.findViewById(R.id.textinput_edittext_pwd);
+
+ // Assert that the password is disguised
+ assertNotEquals(INPUT_TEXT, textInput.getLayout().getText());
+
+ // Now click the toggle button
+ onView(withId(R.id.text_input_password_toggle)).perform(click());
+
+ // And assert that the password is not disguised
+ assertEquals(INPUT_TEXT, textInput.getLayout().getText());
+ }
+
+ @Test
+ @UiThreadTest
+ public void testPasswordToggleDisable() {
+ final Activity activity = mActivityTestRule.getActivity();
+ final EditText textInput = (EditText) activity.findViewById(R.id.textinput_edittext_pwd);
+
+ // Set some text on the EditText
+ onView(withId(R.id.textinput_edittext_pwd)).perform(setText(INPUT_TEXT));
+ // Assert that the password is disguised
+ assertNotEquals(INPUT_TEXT, textInput.getLayout().getText());
+
+ // Disable the password toggle
+ onView(withId(R.id.text_input_password_toggle))
+ .perform(setPasswordVisibilityToggleEnabled(false));
+
+ // Check that the password toggle view is not visible
+ onView(withId(R.id.text_input_password_toggle)).check(matches(not(isDisplayed())));
+ // ...and that the password is not disguised
+ assertEquals(INPUT_TEXT, textInput.getLayout().getText());
+ }
}
diff --git a/media-compat/java/android/support/v4/media/session/MediaSessionCompat.java b/media-compat/java/android/support/v4/media/session/MediaSessionCompat.java
index d184f07..0f424d5 100644
--- a/media-compat/java/android/support/v4/media/session/MediaSessionCompat.java
+++ b/media-compat/java/android/support/v4/media/session/MediaSessionCompat.java
@@ -1514,6 +1514,7 @@
@Override
public void setExtras(Bundle extras) {
mExtras = extras;
+ sendExtras(extras);
}
// Registers/unregisters the RCC and MediaButtonEventReceiver as needed.
@@ -1729,6 +1730,18 @@
mControllerCallbacks.finishBroadcast();
}
+ private void sendExtras(Bundle extras) {
+ int size = mControllerCallbacks.beginBroadcast();
+ for (int i = size - 1; i >= 0; i--) {
+ IMediaControllerCallback cb = mControllerCallbacks.getBroadcastItem(i);
+ try {
+ cb.onExtrasChanged(extras);
+ } catch (RemoteException e) {
+ }
+ }
+ mControllerCallbacks.finishBroadcast();
+ }
+
class MediaSessionStub extends IMediaSession.Stub {
@Override
public void sendCommand(String command, Bundle args, ResultReceiverWrapper cb) {
diff --git a/samples/SupportDesignDemos/AndroidManifest.xml b/samples/SupportDesignDemos/AndroidManifest.xml
index 4d05bc6..ec97037 100644
--- a/samples/SupportDesignDemos/AndroidManifest.xml
+++ b/samples/SupportDesignDemos/AndroidManifest.xml
@@ -307,5 +307,14 @@
</intent-filter>
</activity>
+ <activity android:name=".widget.BottomNavigationViewUsage"
+ android:label="@string/design_bottom_navigation_view"
+ android:theme="@style/Theme.Design">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="com.example.android.support.design.SAMPLE_CODE" />
+ </intent-filter>
+ </activity>
+
</application>
</manifest>
diff --git a/samples/SupportDesignDemos/res/layout/action_layout_custom.xml b/samples/SupportDesignDemos/res/layout/action_layout_custom.xml
new file mode 100644
index 0000000..8ed1c8c
--- /dev/null
+++ b/samples/SupportDesignDemos/res/layout/action_layout_custom.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2016 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.
+-->
+<TextView
+ android:id="@+id/text"
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="?attr/colorPrimary"
+ android:gravity="center"
+ android:text="@string/navigation_sub_item_3"
+ android:textAppearance="@style/TextAppearance.AppCompat.Widget.Button.Inverse"/>
diff --git a/samples/SupportDesignDemos/res/layout/design_bottom_navigation_view.xml b/samples/SupportDesignDemos/res/layout/design_bottom_navigation_view.xml
new file mode 100644
index 0000000..7e66c79
--- /dev/null
+++ b/samples/SupportDesignDemos/res/layout/design_bottom_navigation_view.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2016 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.
+-->
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <Button
+ android:id="@+id/button_disable"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/bottomnavigation_disable"/>
+
+ <Button
+ android:id="@+id/button_add"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="50dp"
+ android:text="@string/bottomnavigation_add"/>
+
+
+ <android.support.design.widget.BottomNavigationView
+ android:id="@+id/bottom_navigation"
+ android:layout_width="match_parent"
+ android:layout_height="56dp"
+ android:layout_gravity="bottom"
+ android:background="#eee"
+ app:menu="@menu/sample_bottom_menu"/>
+
+</FrameLayout>
diff --git a/samples/SupportDesignDemos/res/layout/design_text_input.xml b/samples/SupportDesignDemos/res/layout/design_text_input.xml
index bd5f69b..0729d28 100644
--- a/samples/SupportDesignDemos/res/layout/design_text_input.xml
+++ b/samples/SupportDesignDemos/res/layout/design_text_input.xml
@@ -1,5 +1,4 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
+<?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");
@@ -14,62 +13,66 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:app="http://schemas.android.com/apk/res-auto"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical"
- android:padding="16dp">
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:padding="16dp">
- <android.support.design.widget.TextInputLayout
+ <android.support.design.widget.TextInputLayout
android:id="@+id/input_username"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:errorEnabled="true">
- <android.support.design.widget.TextInputEditText
+ <android.support.design.widget.TextInputEditText
android:id="@+id/edit_username"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:hint="@string/form_username"/>
+ android:hint="@string/form_username" />
- </android.support.design.widget.TextInputLayout>
+ </android.support.design.widget.TextInputLayout>
- <LinearLayout android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="horizontal">
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
- <Button
+ <Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:text="@string/show_error"
- android:onClick="showError"/>
+ android:onClick="showError"
+ android:text="@string/show_error" />
- <Button
+ <Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:text="@string/clear_error"
- android:onClick="clearError"/>
+ android:onClick="clearError"
+ android:text="@string/clear_error" />
- </LinearLayout>
+ </LinearLayout>
- <android.support.design.widget.TextInputLayout
+ <android.support.design.widget.TextInputLayout
android:id="@+id/input_email"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
app:errorEnabled="true">
- <android.support.design.widget.TextInputEditText
+ <android.support.design.widget.TextInputEditText
android:id="@+id/edit_email"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:hint="@string/form_email"/>
+ android:hint="@string/form_email" />
- </android.support.design.widget.TextInputLayout>
+ </android.support.design.widget.TextInputLayout>
- <android.support.design.widget.TextInputLayout
+ <android.support.design.widget.TextInputLayout
android:id="@+id/input_description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -77,12 +80,49 @@
app:counterEnabled="true"
app:counterMaxLength="30">
- <EditText
+ <EditText
android:id="@+id/edit_description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:hint="@string/form_description"/>
+ android:hint="@string/form_description" />
- </android.support.design.widget.TextInputLayout>
+ </android.support.design.widget.TextInputLayout>
-</LinearLayout>
\ No newline at end of file
+ <android.support.design.widget.TextInputLayout
+ android:id="@+id/input_password"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="8dp"
+ app:passwordToggleEnabled="true">
+
+ <EditText
+ android:id="@+id/edit_password"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:hint="@string/form_password"
+ android:inputType="textPassword" />
+
+ </android.support.design.widget.TextInputLayout>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:onClick="setPasswordEnabled"
+ android:text="@string/password_enable" />
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:onClick="setPasswordDisabled"
+ android:text="@string/password_disable" />
+
+ </LinearLayout>
+
+ </LinearLayout>
+
+</ScrollView>
\ No newline at end of file
diff --git a/samples/SupportDesignDemos/res/menu/navigation.xml b/samples/SupportDesignDemos/res/menu/navigation.xml
index 6425b18..de17967 100644
--- a/samples/SupportDesignDemos/res/menu/navigation.xml
+++ b/samples/SupportDesignDemos/res/menu/navigation.xml
@@ -57,6 +57,9 @@
android:id="@+id/navigation_sub_item_2"
android:icon="@drawable/ic_android"
android:title="@string/navigation_sub_item_2"/>
+ <item
+ android:id="@+id/navigation_sub_item_3"
+ app:actionLayout="@layout/action_layout_custom"/>
</menu>
</item>
diff --git a/samples/SupportDesignDemos/res/menu/sample_bottom_menu.xml b/samples/SupportDesignDemos/res/menu/sample_bottom_menu.xml
new file mode 100644
index 0000000..4294f80
--- /dev/null
+++ b/samples/SupportDesignDemos/res/menu/sample_bottom_menu.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 Google Inc.
+
+ 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.
+-->
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:id="@+id/action_search"
+ android:title="@string/menu_search"
+ android:icon="@drawable/ic_search"/>
+ <item android:id="@+id/action_settings"
+ android:title="@string/menu_settings"
+ android:icon="@drawable/ic_add"/>
+ <item android:id="@+id/action_navigation"
+ android:title="@string/tab_text"
+ android:icon="@drawable/ic_action_navigation_menu"/>
+</menu>
\ No newline at end of file
diff --git a/samples/SupportDesignDemos/res/values/strings.xml b/samples/SupportDesignDemos/res/values/strings.xml
index 8f21310..ed725ba 100644
--- a/samples/SupportDesignDemos/res/values/strings.xml
+++ b/samples/SupportDesignDemos/res/values/strings.xml
@@ -51,6 +51,7 @@
<string name="navigation_subheader">Subheader</string>
<string name="navigation_sub_item_1">Subitem 1</string>
<string name="navigation_sub_item_2">Subitem 2</string>
+ <string name="navigation_sub_item_3">Subitme 3</string>
<string name="tabs_fixed">Fixed</string>
<string name="tabs_scrollable">Scrollable</string>
@@ -66,8 +67,11 @@
<string name="form_username">Username</string>
<string name="form_email">Email address</string>
<string name="form_description">Description</string>
+ <string name="form_password">Password field</string>
<string name="show_error">Show error</string>
<string name="clear_error">Clear error</string>
+ <string name="password_enable">Enable Password</string>
+ <string name="password_disable">Disable Password</string>
<string name="design_snackbar_basic">Snackbar/Usage</string>
<string name="design_snackbar_fab">Snackbar/Coordinated with FAB</string>
@@ -106,4 +110,8 @@
<string name="bottomsheet_show">Show</string>
<string name="item_n">Item %d</string>
+ <string name="design_bottom_navigation_view">Bottom navigation view</string>
+
+ <string name="bottomnavigation_disable">Disable item</string>
+ <string name="bottomnavigation_add">Add item</string>
</resources>
diff --git a/samples/SupportDesignDemos/src/com/example/android/support/design/widget/BottomNavigationViewUsage.java b/samples/SupportDesignDemos/src/com/example/android/support/design/widget/BottomNavigationViewUsage.java
new file mode 100644
index 0000000..3bccda4
--- /dev/null
+++ b/samples/SupportDesignDemos/src/com/example/android/support/design/widget/BottomNavigationViewUsage.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2016 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 com.example.android.support.design.widget;
+
+import com.example.android.support.design.R;
+
+import android.os.Bundle;
+import android.support.design.widget.BottomNavigationView;
+import android.support.v7.app.AppCompatActivity;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.Button;
+
+/**
+ * This demonstrates idiomatic usage of the bottom navigation widget.
+ */
+public class BottomNavigationViewUsage extends AppCompatActivity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.design_bottom_navigation_view);
+ Button buttonDisable = (Button) findViewById(R.id.button_disable);
+ final BottomNavigationView bottom =
+ (BottomNavigationView) findViewById(R.id.bottom_navigation);
+ buttonDisable.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ bottom.getMenu().getItem(0).setEnabled(!bottom.getMenu().getItem(0).isEnabled());
+ }
+ });
+ Button buttonAdd = (Button) findViewById(R.id.button_add);
+ buttonAdd.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ MenuItem item = bottom.getMenu().add("Bananas");
+ item.setIcon(android.R.drawable.ic_lock_power_off);
+ }
+ });
+ }
+}
diff --git a/samples/SupportDesignDemos/src/com/example/android/support/design/widget/NavigationViewUsageBase.java b/samples/SupportDesignDemos/src/com/example/android/support/design/widget/NavigationViewUsageBase.java
index 49080c0..fc3fefb 100644
--- a/samples/SupportDesignDemos/src/com/example/android/support/design/widget/NavigationViewUsageBase.java
+++ b/samples/SupportDesignDemos/src/com/example/android/support/design/widget/NavigationViewUsageBase.java
@@ -65,6 +65,9 @@
case R.id.navigation_sub_item_2:
showToast(R.string.navigation_sub_item_2);
return true;
+ case R.id.navigation_sub_item_3:
+ showToast(R.string.navigation_sub_item_3);
+ return true;
case R.id.navigation_with_icon:
showToast(R.string.navigation_item_with_icon);
return true;
diff --git a/samples/SupportDesignDemos/src/com/example/android/support/design/widget/TextInputLayoutUsage.java b/samples/SupportDesignDemos/src/com/example/android/support/design/widget/TextInputLayoutUsage.java
index 63ac031..a5e382d 100644
--- a/samples/SupportDesignDemos/src/com/example/android/support/design/widget/TextInputLayoutUsage.java
+++ b/samples/SupportDesignDemos/src/com/example/android/support/design/widget/TextInputLayoutUsage.java
@@ -29,6 +29,7 @@
public class TextInputLayoutUsage extends AppCompatActivity {
private TextInputLayout mUsernameInputLayout;
+ private TextInputLayout mPasswordInputLayout;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -36,6 +37,7 @@
setContentView(R.layout.design_text_input);
mUsernameInputLayout = (TextInputLayout) findViewById(R.id.input_username);
+ mPasswordInputLayout = (TextInputLayout) findViewById(R.id.input_password);
}
public void showError(View view) {
@@ -46,4 +48,12 @@
mUsernameInputLayout.setError(null);
}
+ public void setPasswordEnabled(View view) {
+ mPasswordInputLayout.setPasswordVisibilityToggleEnabled(true);
+ }
+
+ public void setPasswordDisabled(View view) {
+ mPasswordInputLayout.setPasswordVisibilityToggleEnabled(false);
+ }
+
}
diff --git a/v14/preference/src/android/support/v14/preference/SwitchPreference.java b/v14/preference/src/android/support/v14/preference/SwitchPreference.java
index 3f95d1a..6e4b0d7 100644
--- a/v14/preference/src/android/support/v14/preference/SwitchPreference.java
+++ b/v14/preference/src/android/support/v14/preference/SwitchPreference.java
@@ -217,7 +217,7 @@
return;
}
- View switchView = view.findViewById(android.support.v7.preference.R.id.switchWidget);
+ View switchView = view.findViewById(AndroidResources.ANDROID_R_SWITCH_WIDGET);
syncSwitchView(switchView);
View summaryView = view.findViewById(android.R.id.summary);
diff --git a/v17/leanback/res/animator/lb_playback_now_bar1_animator.xml b/v17/leanback/res/animator/lb_playback_now_bar1_animator.xml
new file mode 100644
index 0000000..10be669
--- /dev/null
+++ b/v17/leanback/res/animator/lb_playback_now_bar1_animator.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2016 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.
+-->
+
+<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
+ android:duration="2320"
+ android:repeatCount="infinite"
+ android:interpolator="@android:anim/linear_interpolator">
+ <propertyValuesHolder android:propertyName="scaleY" >
+
+ <keyframe android:value="0.417" />
+ <keyframe android:value="0.25" />
+ <keyframe android:value="0.417" />
+ <keyframe android:value="0.583" />
+ <keyframe android:value="0.75" />
+
+ <keyframe android:value="0.833" />
+ <keyframe android:value="0.917" />
+ <keyframe android:value="1.0" />
+ <keyframe android:value="0.917" />
+ <keyframe android:value="1.0" />
+
+ <keyframe android:value="0.833" />
+ <keyframe android:value="0.667" />
+ <keyframe android:value="0.5" />
+ <keyframe android:value="0.333" />
+ <keyframe android:value="0.167" />
+
+ <keyframe android:value="0.333" />
+ <keyframe android:value="0.5" />
+ <keyframe android:value="0.583" />
+ <keyframe android:value="0.75" />
+ <keyframe android:value="0.917" />
+
+ <keyframe android:value="0.75" />
+ <keyframe android:value="0.583" />
+ <keyframe android:value="0.417" />
+ <keyframe android:value="0.25" />
+ <keyframe android:value="0.417" />
+
+ <keyframe android:value="0.667" />
+ <keyframe android:value="0.417" />
+ <keyframe android:value="0.25" />
+ <keyframe android:value="0.333" />
+ <keyframe android:value="0.417" />
+
+ </propertyValuesHolder>
+</objectAnimator>
\ No newline at end of file
diff --git a/v17/leanback/res/animator/lb_playback_now_bar2_animator.xml b/v17/leanback/res/animator/lb_playback_now_bar2_animator.xml
new file mode 100644
index 0000000..9b583b9
--- /dev/null
+++ b/v17/leanback/res/animator/lb_playback_now_bar2_animator.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2016 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.
+-->
+
+<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
+ android:duration="2080"
+ android:repeatCount="infinite"
+ android:interpolator="@android:anim/linear_interpolator">
+ <propertyValuesHolder android:propertyName="scaleY" >
+
+ <keyframe android:value="1.0" />
+ <keyframe android:value="0.917" />
+ <keyframe android:value="0.833" />
+ <keyframe android:value="0.917" />
+ <keyframe android:value="1.0" />
+
+ <keyframe android:value="0.917" />
+ <keyframe android:value="0.75" />
+ <keyframe android:value="0.583" />
+ <keyframe android:value="0.75" />
+ <keyframe android:value="0.917" />
+
+ <keyframe android:value="1.0" />
+ <keyframe android:value="0.833" />
+ <keyframe android:value="0.667" />
+ <keyframe android:value="0.833" />
+ <keyframe android:value="1.0" />
+
+ <keyframe android:value="0.917" />
+ <keyframe android:value="0.75" />
+ <keyframe android:value="0.417" />
+ <keyframe android:value="0.25" />
+ <keyframe android:value="0.417" />
+
+ <keyframe android:value="0.667" />
+ <keyframe android:value="0.833" />
+ <keyframe android:value="1.0" />
+ <keyframe android:value="0.833" />
+ <keyframe android:value="0.75" />
+
+ <keyframe android:value="0.667" />
+ <keyframe android:value="1.0" />
+
+ </propertyValuesHolder>
+</objectAnimator>
\ No newline at end of file
diff --git a/v17/leanback/res/animator/lb_playback_now_bar3_animator.xml b/v17/leanback/res/animator/lb_playback_now_bar3_animator.xml
new file mode 100644
index 0000000..c842100
--- /dev/null
+++ b/v17/leanback/res/animator/lb_playback_now_bar3_animator.xml
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2016 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.
+-->
+
+<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
+ android:duration="2000"
+ android:repeatCount="infinite"
+ android:interpolator="@android:anim/linear_interpolator">
+ <propertyValuesHolder android:propertyName="scaleY" >
+
+ <keyframe android:value="0.667" />
+ <keyframe android:value="0.75" />
+ <keyframe android:value="0.833" />
+ <keyframe android:value="1.0" />
+ <keyframe android:value="0.917" />
+
+ <keyframe android:value="0.75" />
+ <keyframe android:value="0.583" />
+ <keyframe android:value="0.417" />
+ <keyframe android:value="0.583" />
+ <keyframe android:value="0.667" />
+
+ <keyframe android:value="0.75" />
+ <keyframe android:value="1.0" />
+ <keyframe android:value="0.917" />
+ <keyframe android:value="1.0" />
+ <keyframe android:value="0.75" />
+
+ <keyframe android:value="0.583" />
+ <keyframe android:value="0.75" />
+ <keyframe android:value="0.917" />
+ <keyframe android:value="1.0" />
+ <keyframe android:value="0.833" />
+
+ <keyframe android:value="0.667" />
+ <keyframe android:value="0.75" />
+ <keyframe android:value="0.583" />
+ <keyframe android:value="0.417" />
+ <keyframe android:value="0.25" />
+
+ <keyframe android:value="0.667" />
+
+ </propertyValuesHolder>
+</objectAnimator>
\ No newline at end of file
diff --git a/v17/leanback/res/drawable-xhdpi/lb_ic_play_fit.png b/v17/leanback/res/drawable-xhdpi/lb_ic_play_fit.png
new file mode 100644
index 0000000..37d33e7
--- /dev/null
+++ b/v17/leanback/res/drawable-xhdpi/lb_ic_play_fit.png
Binary files differ
diff --git a/v17/leanback/res/drawable/lb_playback_now_playing_bar.xml b/v17/leanback/res/drawable/lb_playback_now_playing_bar.xml
new file mode 100644
index 0000000..33a1c4b
--- /dev/null
+++ b/v17/leanback/res/drawable/lb_playback_now_playing_bar.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2016 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.
+-->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <solid android:color="@color/lb_playback_now_playing_bar_color" />
+
+ <size
+ android:height="@dimen/lb_playback_now_playing_bar_height"
+ android:width="@dimen/lb_playback_now_playing_bar_width" />
+
+</shape>
\ No newline at end of file
diff --git a/v17/leanback/res/layout/lb_media_item_number_view_flipper.xml b/v17/leanback/res/layout/lb_media_item_number_view_flipper.xml
new file mode 100644
index 0000000..8625d84
--- /dev/null
+++ b/v17/leanback/res/layout/lb_media_item_number_view_flipper.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2016 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.
+-->
+
+<merge xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <TextView
+ android:id="@+id/initial"
+ style="?attr/playbackMediaItemNumberStyle"
+ />
+ <ImageView
+ android:id="@+id/paused"
+ android:layout_width="@dimen/lb_playback_play_icon_size"
+ android:layout_height="@dimen/lb_playback_play_icon_size"
+ android:layout_gravity="center_vertical"
+ android:visibility="gone"
+ android:scaleType="fitStart"
+ android:adjustViewBounds="true"
+ android:src="@drawable/lb_ic_play_fit"
+ />
+ <android.support.v17.leanback.widget.MediaNowPlayingView
+ android:id="@+id/playing"
+ android:layout_width="@dimen/lb_playback_now_playing_view_size"
+ android:layout_height="@dimen/lb_playback_now_playing_view_size"
+ android:layout_gravity="center_vertical"
+ android:visibility="gone"/>
+
+
+</merge>
\ No newline at end of file
diff --git a/v17/leanback/res/layout/lb_playback_now_playing_bars.xml b/v17/leanback/res/layout/lb_playback_now_playing_bars.xml
new file mode 100644
index 0000000..d9b30b7
--- /dev/null
+++ b/v17/leanback/res/layout/lb_playback_now_playing_bars.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2016 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.
+-->
+
+<merge xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <ImageView
+ android:id="@+id/bar1"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:layout_marginTop="@dimen/lb_playback_now_playing_bar_top_margin"
+ android:layout_marginRight="@dimen/lb_playback_now_playing_bar_margin"
+ android:src="@drawable/lb_playback_now_playing_bar"
+ />
+
+ <ImageView
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:id="@+id/bar2"
+ android:layout_marginTop="@dimen/lb_playback_now_playing_bar_top_margin"
+ android:layout_marginRight="@dimen/lb_playback_now_playing_bar_margin"
+ android:src="@drawable/lb_playback_now_playing_bar"
+ />
+
+ <ImageView
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:id="@+id/bar3"
+ android:layout_marginTop="@dimen/lb_playback_now_playing_bar_top_margin"
+ android:src="@drawable/lb_playback_now_playing_bar"
+ />
+
+</merge>
\ No newline at end of file
diff --git a/v17/leanback/res/layout/lb_row_media_item.xml b/v17/leanback/res/layout/lb_row_media_item.xml
index 72efb09..b25e922 100644
--- a/v17/leanback/res/layout/lb_row_media_item.xml
+++ b/v17/leanback/res/layout/lb_row_media_item.xml
@@ -46,9 +46,9 @@
style="?attr/playbackMediaItemDetailsStyle"
>
- <TextView
- android:id="@+id/mediaItemNumber"
- style="?attr/playbackMediaItemNumberStyle"/>
+ <ViewFlipper android:id="@+id/mediaItemNumberViewFlipper"
+ style="?attr/playbackMediaItemNumberViewFlipperStyle">
+ </ViewFlipper>
<TextView
diff --git a/v17/leanback/res/layout/lb_title_view.xml b/v17/leanback/res/layout/lb_title_view.xml
index e1c79ef..762da41 100644
--- a/v17/leanback/res/layout/lb_title_view.xml
+++ b/v17/leanback/res/layout/lb_title_view.xml
@@ -29,7 +29,7 @@
<TextView
android:id="@+id/title_text"
- android:layout_width="@dimen/lb_browse_title_text_width"
+ android:layout_width="wrap_content"
android:layout_height="@dimen/lb_browse_title_height"
android:layout_gravity="center_vertical|end"
style="?attr/browseTitleTextStyle"/>
diff --git a/v17/leanback/res/values/attrs.xml b/v17/leanback/res/values/attrs.xml
index 3a0ca19..1ff51f7 100644
--- a/v17/leanback/res/values/attrs.xml
+++ b/v17/leanback/res/values/attrs.xml
@@ -283,6 +283,8 @@
<attr name="playbackMediaListHeaderTitleStyle" format="reference"/>
<attr name="playbackMediaItemDetailsStyle" format="reference"/>
+ <attr name="playbackMediaItemNumberViewFlipperStyle" format="reference"/>
+ <attr name="playbackMediaItemNumberViewFlipperLayout" format="reference"/>
<attr name="playbackMediaItemNumberStyle" format="reference"/>
<attr name="playbackMediaItemNameStyle" format="reference"/>
<attr name="playbackMediaItemDurationStyle" format="reference"/>
diff --git a/v17/leanback/res/values/colors.xml b/v17/leanback/res/values/colors.xml
index bbae9ad..ec9102c 100644
--- a/v17/leanback/res/values/colors.xml
+++ b/v17/leanback/res/values/colors.xml
@@ -68,6 +68,7 @@
<color name="lb_playback_controls_time_text_color">#B2EEEEEE</color>
<color name="lb_playback_media_row_highlight_color">#1AFFFFFF</color>
<color name="lb_playback_media_row_separator_highlight_color">#1AFFFFFF</color>
+ <color name="lb_playback_now_playing_bar_color">#FFEEEEEE</color>
<color name="lb_search_plate_hint_text_color">#FFCCCCCC</color>
diff --git a/v17/leanback/res/values/dimens.xml b/v17/leanback/res/values/dimens.xml
index c62c281..f6ef557 100644
--- a/v17/leanback/res/values/dimens.xml
+++ b/v17/leanback/res/values/dimens.xml
@@ -30,7 +30,6 @@
<dimen name="lb_browse_title_icon_max_width">584dp</dimen>
<dimen name="lb_browse_title_icon_height">60dp</dimen>
<dimen name="lb_browse_title_text_size">44sp</dimen>
- <dimen name="lb_browse_title_text_width">584dp</dimen>
<dimen name="lb_browse_headers_width">270dp</dimen>
<integer name="lb_browse_headers_transition_delay">150</integer>
@@ -149,6 +148,13 @@
<dimen name="lb_playback_media_row_radio_selector_width">72dp</dimen>
<dimen name="lb_playback_media_row_selector_round_rect_radius">36dp</dimen>
<dimen name="lb_playback_media_row_separator_height">1dp</dimen>
+ <dimen name="lb_playback_now_playing_bar_top_margin">3dp</dimen>
+ <dimen name="lb_playback_now_playing_bar_left_margin">3dp</dimen>
+ <dimen name="lb_playback_now_playing_bar_width">5dp</dimen>
+ <dimen name="lb_playback_now_playing_bar_height">18dp</dimen>
+ <dimen name="lb_playback_now_playing_bar_margin">1dp</dimen>
+ <dimen name="lb_playback_now_playing_view_size">28dp</dimen>
+ <dimen name="lb_playback_play_icon_size">14dp</dimen>
<dimen name="lb_control_button_diameter">90dp</dimen>
<dimen name="lb_control_button_height">64dp</dimen>
diff --git a/v17/leanback/res/values/styles.xml b/v17/leanback/res/values/styles.xml
index 39e1413..8bd9b79 100644
--- a/v17/leanback/res/values/styles.xml
+++ b/v17/leanback/res/values/styles.xml
@@ -353,6 +353,13 @@
<item name="android:textAppearance">@style/TextAppearance.Leanback.PlaybackMediaListHeaderTitle</item>
</style>
+ <style name="Widget.Leanback.PlaybackMediaItemNumberViewFlipperStyle">
+ <item name="android:layout_width">56dp</item>
+ <item name="android:layout_height">match_parent</item>
+ <item name="android:gravity">center_vertical</item>
+ <item name="android:visibility">gone</item>
+ </style>
+
<style name="TextAppearance.Leanback.PlaybackMediaItemNumber">
<item name="android:textColor">#FFFFFF</item>
<item name="android:textSize">18sp</item>
@@ -360,7 +367,7 @@
</style>
<style name="Widget.Leanback.PlaybackMediaItemNumberStyle">
- <item name="android:layout_width">56dp</item>
+ <item name="android:layout_width">match_parent</item>
<item name="android:layout_height">match_parent</item>
<item name="android:gravity">center_vertical</item>
<item name="android:visibility">gone</item>
diff --git a/v17/leanback/res/values/themes.xml b/v17/leanback/res/values/themes.xml
index fd93ea8..cf3cbef 100644
--- a/v17/leanback/res/values/themes.xml
+++ b/v17/leanback/res/values/themes.xml
@@ -81,6 +81,8 @@
<item name="playbackMediaItemSeparatorStyle">@style/Widget.Leanback.PlaybackMediaItemSeparatorStyle</item>
<item name="playbackMediaListHeaderTitleStyle">@style/Widget.Leanback.PlaybackMediaListHeaderTitleStyle</item>
<item name="playbackMediaItemDetailsStyle">@style/Widget.Leanback.PlaybackMediaItemDetailsStyle</item>
+ <item name="playbackMediaItemNumberViewFlipperStyle">@style/Widget.Leanback.PlaybackMediaItemNumberViewFlipperStyle</item>
+ <item name="playbackMediaItemNumberViewFlipperLayout">@layout/lb_media_item_number_view_flipper</item>
<item name="playbackMediaItemNumberStyle">@style/Widget.Leanback.PlaybackMediaItemNumberStyle</item>
<item name="playbackMediaItemNameStyle">@style/Widget.Leanback.PlaybackMediaItemNameStyle</item>
<item name="playbackMediaItemDurationStyle">@style/Widget.Leanback.PlaybackMediaItemDurationStyle</item>
diff --git a/v17/leanback/src/android/support/v17/leanback/app/ListRowDataAdapter.java b/v17/leanback/src/android/support/v17/leanback/app/ListRowDataAdapter.java
index de4546c..5f18285 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/ListRowDataAdapter.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/ListRowDataAdapter.java
@@ -1,9 +1,11 @@
package android.support.v17.leanback.app;
-import android.support.v17.leanback.widget.DividerRow;
-import android.support.v17.leanback.widget.ListRow;
+import android.os.Handler;
+import android.support.v17.leanback.widget.ArrayObjectAdapter;
+import android.support.v17.leanback.widget.CursorObjectAdapter;
import android.support.v17.leanback.widget.ObjectAdapter;
import android.support.v17.leanback.widget.Row;
+import android.support.v17.leanback.widget.SparseArrayObjectAdapter;
/**
* Wrapper class for {@link ObjectAdapter} used by {@link BrowseFragment} to initialize
@@ -17,62 +19,67 @@
* bounds to reflect the latest data.
*/
class ListRowDataAdapter extends ObjectAdapter {
+ public static final int ON_ITEM_RANGE_CHANGED = 2;
+ public static final int ON_ITEM_RANGE_INSERTED = 4;
+ public static final int ON_ITEM_RANGE_REMOVED = 8;
+ public static final int ON_CHANGED = 16;
+
private final ObjectAdapter mAdapter;
private int mLastVisibleRowIndex;
+ private Handler mHandler;
+ private Update mPendingUpdate;
+ private int mPendingUpdateCount;
+
+ private static class Update {
+ int eventType;
+ int positionStart;
+ int itemCount;
+
+ public Update(int type, int positionStart, int itemCount) {
+ this.eventType = type;
+ this.positionStart = positionStart;
+ this.itemCount = itemCount;
+ }
+ }
+
+ private Runnable notificationTask = new Runnable() {
+ @Override
+ public void run() {
+ if (mPendingUpdateCount == 0) {
+ return;
+ } else if (mPendingUpdateCount == 1 && mPendingUpdate != null) {
+ doNotify(
+ mPendingUpdate.eventType,
+ mPendingUpdate.positionStart,
+ mPendingUpdate.itemCount);
+ } else {
+ notifyChanged();
+ }
+ mPendingUpdate = null;
+ mPendingUpdateCount = 0;
+ }
+ };
public ListRowDataAdapter(ObjectAdapter adapter) {
super(adapter.getPresenterSelector());
this.mAdapter = adapter;
initialize();
- mAdapter.registerObserver(new DataObserver() {
- @Override
- public void onItemRangeChanged(int positionStart, int itemCount) {
- if (positionStart <= mLastVisibleRowIndex) {
- notifyItemRangeChanged(positionStart,
- Math.min(itemCount, mLastVisibleRowIndex - positionStart + 1));
- }
- }
-
- @Override
- public void onItemRangeInserted(int positionStart, int itemCount) {
- if (positionStart <= mLastVisibleRowIndex) {
- mLastVisibleRowIndex += itemCount;
- notifyItemRangeInserted(positionStart, itemCount);
- return;
- }
-
- int lastVisibleRowIndex = mLastVisibleRowIndex;
- initialize();
- if (mLastVisibleRowIndex > lastVisibleRowIndex) {
- int totalItems = mLastVisibleRowIndex - lastVisibleRowIndex;
- notifyItemRangeInserted(lastVisibleRowIndex + 1, totalItems);
- }
- }
-
- @Override
- public void onItemRangeRemoved(int positionStart, int itemCount) {
- if (positionStart + itemCount - 1 < mLastVisibleRowIndex) {
- mLastVisibleRowIndex -= itemCount;
- notifyItemRangeRemoved(positionStart, itemCount);
- return;
- }
-
- int lastVisibleRowIndex = mLastVisibleRowIndex;
- initialize();
- int totalItems = lastVisibleRowIndex - mLastVisibleRowIndex;
- if (totalItems > 0) {
- notifyItemRangeRemoved(
- Math.min(lastVisibleRowIndex + 1, positionStart), totalItems);
- }
- }
-
- @Override
- public void onChanged() {
- initialize();
- notifyChanged();
- }
- });
+ // If an user implements its own ObjectAdapter, notification corresponding to data
+ // updates can be batched e.g. remove, add might be followed by notifyRemove, notifyAdd.
+ // But underlying data would have changed during the notifyRemove call by the previous add
+ // operation. To handle this case, we enqueue the updates in a queue and have a worker
+ // service that queue. The common case will be to have a single pending update in the queue.
+ // But in case the worker encounters multiple updates in the queue, it will send a
+ // notifyChanged() call to RecyclerView forcing it to do a full refresh.
+ if ((adapter instanceof ArrayObjectAdapter)
+ || (adapter instanceof CursorObjectAdapter)
+ || (adapter instanceof SparseArrayObjectAdapter)) {
+ mAdapter.registerObserver(new SimpleDataObserver());
+ } else {
+ mHandler = new Handler();
+ mAdapter.registerObserver(new QueueBasedDataObserver());
+ }
}
private void initialize() {
@@ -96,4 +103,120 @@
public Object get(int index) {
return mAdapter.get(index);
}
-}
\ No newline at end of file
+
+ private void doNotify(int eventType, int positionStart, int itemCount) {
+ switch (eventType) {
+ case ON_ITEM_RANGE_CHANGED:
+ notifyItemRangeChanged(positionStart, itemCount);
+ break;
+ case ON_ITEM_RANGE_INSERTED:
+ notifyItemRangeInserted(positionStart, itemCount);
+ break;
+ case ON_ITEM_RANGE_REMOVED:
+ notifyItemRangeRemoved(positionStart, itemCount);
+ break;
+ case ON_CHANGED:
+ notifyChanged();
+ default:
+ throw new IllegalArgumentException("Invalid event type " + eventType);
+ }
+ }
+
+ private class SimpleDataObserver extends DataObserver {
+
+ @Override
+ public void onItemRangeChanged(int positionStart, int itemCount) {
+ if (positionStart <= mLastVisibleRowIndex) {
+ onEventFired(ON_ITEM_RANGE_CHANGED, positionStart,
+ Math.min(itemCount, mLastVisibleRowIndex - positionStart + 1));
+ }
+ }
+
+ @Override
+ public void onItemRangeInserted(int positionStart, int itemCount) {
+ if (positionStart <= mLastVisibleRowIndex) {
+ mLastVisibleRowIndex += itemCount;
+ onEventFired(ON_ITEM_RANGE_INSERTED, positionStart, itemCount);
+ return;
+ }
+
+ int lastVisibleRowIndex = mLastVisibleRowIndex;
+ initialize();
+ if (mLastVisibleRowIndex > lastVisibleRowIndex) {
+ int totalItems = mLastVisibleRowIndex - lastVisibleRowIndex;
+ onEventFired(ON_ITEM_RANGE_INSERTED, lastVisibleRowIndex + 1, totalItems);
+ }
+ }
+
+ @Override
+ public void onItemRangeRemoved(int positionStart, int itemCount) {
+ if (positionStart + itemCount - 1 < mLastVisibleRowIndex) {
+ mLastVisibleRowIndex -= itemCount;
+ onEventFired(ON_ITEM_RANGE_REMOVED, positionStart, itemCount);
+ return;
+ }
+
+ int lastVisibleRowIndex = mLastVisibleRowIndex;
+ initialize();
+ int totalItems = lastVisibleRowIndex - mLastVisibleRowIndex;
+ if (totalItems > 0) {
+ onEventFired(ON_ITEM_RANGE_REMOVED,
+ Math.min(lastVisibleRowIndex + 1, positionStart),
+ totalItems);
+ }
+ }
+
+ @Override
+ public void onChanged() {
+ initialize();
+ onEventFired(ON_CHANGED, -1, -1);
+ }
+
+ protected void onEventFired(int eventType, int positionStart, int itemCount) {
+ doNotify(eventType, positionStart, itemCount);
+ }
+ }
+
+ private class QueueBasedDataObserver extends SimpleDataObserver {
+
+ @Override
+ public void onItemRangeChanged(int positionStart, int itemCount) {
+ incrementAndPost();
+ super.onItemRangeChanged(positionStart, itemCount);
+ }
+
+ @Override
+ public void onItemRangeInserted(int positionStart, int itemCount) {
+ incrementAndPost();
+ super.onItemRangeInserted(positionStart, itemCount);
+ }
+
+ @Override
+ public void onItemRangeRemoved(int positionStart, int itemCount) {
+ incrementAndPost();
+ super.onItemRangeRemoved(positionStart, itemCount);
+ }
+
+ @Override
+ public void onChanged() {
+ incrementAndPost();
+ super.onChanged();
+ }
+
+ @Override
+ protected void onEventFired(
+ final int eventType, final int positionStart, final int itemCount) {
+
+ if (mPendingUpdateCount == 1) {
+ mPendingUpdate = new Update(eventType, positionStart, itemCount);
+ }
+ }
+
+ private void incrementAndPost() {
+ mPendingUpdateCount++;
+ if (mPendingUpdateCount == 1) {
+ mHandler.post(notificationTask);
+ }
+ }
+ }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/PlaybackControlGlue.java b/v17/leanback/src/android/support/v17/leanback/app/PlaybackControlGlue.java
index 120ffa7..b896d41 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/PlaybackControlGlue.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/PlaybackControlGlue.java
@@ -824,6 +824,7 @@
/**
* Must be called appropriately by a subclass when the playback state has changed.
+ * It updates the playback state displayed on the media player.
*/
protected void onStateChanged() {
if (DEBUG) Log.v(TAG, "onStateChanged");
diff --git a/v17/leanback/src/android/support/v17/leanback/app/PlaybackControlSupportGlue.java b/v17/leanback/src/android/support/v17/leanback/app/PlaybackControlSupportGlue.java
index f0deb2c..937b510 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/PlaybackControlSupportGlue.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/PlaybackControlSupportGlue.java
@@ -826,6 +826,7 @@
/**
* Must be called appropriately by a subclass when the playback state has changed.
+ * It updates the playback state displayed on the media player.
*/
protected void onStateChanged() {
if (DEBUG) Log.v(TAG, "onStateChanged");
diff --git a/v17/leanback/src/android/support/v17/leanback/app/SearchSupportFragment.java b/v17/leanback/src/android/support/v17/leanback/app/SearchSupportFragment.java
index af2c1b6..27c4099 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/SearchSupportFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/SearchSupportFragment.java
@@ -234,7 +234,6 @@
}
};
- @Override
public void onRequestPermissionsResult(int requestCode, String[] permissions,
int[] grantResults) {
if (requestCode == AUDIO_PERMISSION_REQUEST_CODE && permissions.length > 0) {
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/AbstractMediaItemPresenter.java b/v17/leanback/src/android/support/v17/leanback/widget/AbstractMediaItemPresenter.java
index db88ba9..e744206 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/AbstractMediaItemPresenter.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/AbstractMediaItemPresenter.java
@@ -20,12 +20,14 @@
import android.graphics.Rect;
import android.support.v17.leanback.R;
import android.support.v4.view.ViewCompat;
+import android.util.TypedValue;
import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.DecelerateInterpolator;
import android.widget.TextView;
+import android.widget.ViewFlipper;
import java.util.ArrayList;
import java.util.List;
@@ -39,6 +41,14 @@
* Each media item's details and actions are separately focusable.
* The appearance of each one of the media row components can be controlled through setting
* theme's attributes.
+ * Each media item row provides a view flipper for switching between different views depending on
+ * the playback state.
+ * A default layout is provided by this presenter for rendering different playback states, or a
+ * custom layout can be provided by the user by overriding the
+ * playbackMediaItemNumberViewFlipperLayout attribute in the currently specified theme.
+ * Subclasses should also override {@link #getMediaPlayState(Object)} to provide the current play
+ * state of their media item model in case they wish to use different views depending on the
+ * playback state.
* The presenter can optionally provide line separators between media rows by setting
* {@link #setHasMediaRowSeparator(boolean)} to true.
* <p>
@@ -57,6 +67,23 @@
*/
public abstract class AbstractMediaItemPresenter extends RowPresenter {
+ /**
+ * Different playback states of a media item
+ */
+
+ /**
+ * Indicating that the media item is currently neither playing nor paused.
+ */
+ public static final int PLAY_STATE_INITIAL = 0;
+ /**
+ * Indicating that the media item is currently paused.
+ */
+ public static final int PLAY_STATE_PAUSED = 1;
+ /**
+ * Indicating that the media item is currently playing
+ */
+ public static final int PLAY_STATE_PLAYING = 2;
+
final static Rect sTempRect = new Rect();
private int mBackgroundColor = Color.TRANSPARENT;
private boolean mBackgroundColorSet;
@@ -129,7 +156,11 @@
private final View mMediaRowView;
private final View mSelectorView;
private final View mMediaItemDetailsView;
+ private final ViewFlipper mMediaItemNumberViewFlipper;
private final TextView mMediaItemNumberView;
+ private final View mMediaItemPausedView;
+
+ private final View mMediaItemPlayingView;
private final TextView mMediaItemNameView;
private final TextView mMediaItemDurationView;
private final View mMediaItemRowSeparator;
@@ -144,7 +175,6 @@
mSelectorView = view.findViewById(R.id.mediaRowSelector);
mMediaRowView = view.findViewById(R.id.mediaItemRow);
mMediaItemDetailsView = view.findViewById(R.id.mediaItemDetails);
- mMediaItemNumberView = (TextView) view.findViewById(R.id.mediaItemNumber);
mMediaItemNameView = (TextView) view.findViewById(R.id.mediaItemName);
mMediaItemDurationView = (TextView) view.findViewById(R.id.mediaItemDuration);
mMediaItemRowSeparator = view.findViewById(R.id.mediaRowSeparator);
@@ -167,7 +197,20 @@
true);
}
});
+ mMediaItemNumberViewFlipper =
+ (ViewFlipper) view.findViewById(R.id.mediaItemNumberViewFlipper);
+ TypedValue typedValue = new TypedValue();
+ boolean found = view.getContext().getTheme().resolveAttribute(
+ R.attr.playbackMediaItemNumberViewFlipperLayout, typedValue, true);
+ View mergeView = LayoutInflater.from(view.getContext()).
+ inflate(found ? typedValue.resourceId :
+ R.layout.lb_media_item_number_view_flipper,
+ mMediaItemNumberViewFlipper, true);
+
+ mMediaItemNumberView = (TextView) mergeView.findViewById(R.id.initial);
+ mMediaItemPausedView = mergeView.findViewById(R.id.paused);
+ mMediaItemPlayingView = mergeView.findViewById(R.id.playing);
}
/**
@@ -271,6 +314,16 @@
mRowPresenter.onBindMediaDetails(this, getRowObject());
}
+ /**
+ * Notifies the playback state of the media item row has changed. This in turn triggers
+ * updating of the UI for that media item row if corresponding views are specified for each
+ * playback state.
+ * By default, 3 views are provided for each playback state, or these views can be provided
+ * by the user.
+ */
+ public void notifyPlayStateChanged() {
+ mRowPresenter.onBindMediaPlayState(this);
+ }
/**
* @return The SelectorView responsible for highlighting the in-focus view within each
@@ -281,28 +334,65 @@
}
/**
- * @return The TextView responsible for rendering the track number
+ * @return The FlipperView responsible for flipping between different media item number
+ * views depending on the playback state
+ */
+ public ViewFlipper getMediaItemNumberViewFlipper() {
+ return mMediaItemNumberViewFlipper;
+ }
+
+ /**
+ * @return The TextView responsible for rendering the media item number.
+ * This view is rendered when the media item row is neither playing nor paused.
*/
public TextView getMediaItemNumberView() {
return mMediaItemNumberView;
}
/**
- * @return The TextView responsible for rendering the track name
+ * @return The view rendered when the media item row is paused.
+ */
+ public View getMediaItemPausedView() {
+ return mMediaItemPausedView;
+ }
+
+ /**
+ * @return The view rendered when the media item row is playing.
+ */
+ public View getMediaItemPlayingView() {
+ return mMediaItemPlayingView;
+ }
+
+
+ /**
+ * Flips to the view at index 'position' specified in the layout file for media item number
+ * view.
+ * @param position The position of the new view within the layout file to which the media
+ * item number view is flipped.
+ */
+ public void setSelectedMediaItemNumberView(int position) {
+ if (position >= 0 & position < mMediaItemNumberViewFlipper.getChildCount()) {
+ mMediaItemNumberViewFlipper.setDisplayedChild(position);
+ }
+ }
+ /**
+ * Returns the view displayed when the media item is neither playing nor paused,
+ * corresponding to the playback state of PLAY_STATE_INITIAL.
+ * @return The TextView responsible for rendering the media item name.
*/
public TextView getMediaItemNameView() {
return mMediaItemNameView;
}
/**
- * @return The TextView responsible for rendering the track duration
+ * @return The TextView responsible for rendering the media item duration
*/
public TextView getMediaItemDurationView() {
return mMediaItemDurationView;
}
/**
- * @return The view container of track details
+ * @return The view container of media item details
*/
public View getMediaItemDetailsView() {
return mMediaItemDetailsView;
@@ -322,6 +412,9 @@
return mMediaItemActionsContainer;
}
+ /**
+ * @return Array of MultiActions displayed for this media item row
+ */
public MultiActionsProvider.MultiAction[] getMediaItemRowActions() {
return mMediaItemRowActions;
}
@@ -364,6 +457,7 @@
mvh.getMediaItemRowSeparator().setVisibility(hasMediaRowSeparator() ? View.VISIBLE :
View.GONE);
+ onBindMediaPlayState(mvh);
onBindMediaDetails((ViewHolder) vh, item);
}
@@ -422,6 +516,60 @@
}
/**
+ * Binds the media item number view to the appropriate play state view of the media item.
+ * The play state of the media item is extracted by calling {@link #getMediaPlayState(Object)} for
+ * the media item embedded within this view.
+ * This method triggers updating of the playback state UI if corresponding views are specified
+ * for the current playback state.
+ * By default, 3 views are provided for each playback state, or these views can be provided
+ * by the user.
+ */
+ public void onBindMediaPlayState(ViewHolder vh) {
+ int childIndex = calculateMediaItemNumberFlipperIndex(vh);
+ if (childIndex != -1 && vh.mMediaItemNumberViewFlipper.getDisplayedChild() != childIndex) {
+ vh.mMediaItemNumberViewFlipper.setDisplayedChild(childIndex);
+ }
+ }
+
+ static int calculateMediaItemNumberFlipperIndex(ViewHolder vh) {
+ int childIndex = -1;
+ int newPlayState = vh.mRowPresenter.getMediaPlayState(vh.getRowObject());
+ switch (newPlayState) {
+ case PLAY_STATE_INITIAL:
+ childIndex = (vh.mMediaItemNumberView == null) ? -1 :
+ vh.mMediaItemNumberViewFlipper.indexOfChild(vh.mMediaItemNumberView);
+ break;
+ case PLAY_STATE_PAUSED:
+ childIndex = (vh.mMediaItemPausedView == null) ? -1 :
+ vh.mMediaItemNumberViewFlipper.indexOfChild(vh.mMediaItemPausedView);
+ break;
+ case PLAY_STATE_PLAYING:
+ childIndex = (vh.mMediaItemPlayingView == null) ? -1 :
+ vh.mMediaItemNumberViewFlipper.indexOfChild(vh.mMediaItemPlayingView);
+ }
+ return childIndex;
+ }
+
+ /**
+ * Unbinds the play state view from the given ViewHolder
+ * @param vh The ViewHolder to unbind from
+ */
+ public void onUnbindMediaPlayState(ViewHolder vh) {
+ }
+
+ /**
+ * Returns the current play state of the given media item. By default, this method returns
+ * PLAY_STATE_INITIAL which causes the media item number
+ * {@link ViewHolder#getMediaItemNameView()} to be displayed for different
+ * playback states. Users of this class should override this method in order to provide the
+ * play state of their custom media item data model.
+ * @param item The media item
+ * @return The current play state of this media item
+ */
+ protected int getMediaPlayState(Object item) {
+ return PLAY_STATE_INITIAL;
+ }
+ /**
* Each media item row can have multiple focusable elements; the details on the left and a set
* of optional custom actions on the right.
* The selector is a highlight that moves to highlight to cover whichever views is in focus.
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/MediaNowPlayingView.java b/v17/leanback/src/android/support/v17/leanback/widget/MediaNowPlayingView.java
new file mode 100644
index 0000000..d619d8c
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/MediaNowPlayingView.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2016 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.v17.leanback.widget;
+
+import android.animation.Animator;
+import android.animation.AnimatorInflater;
+import android.animation.ObjectAnimator;
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.support.v17.leanback.R;
+
+/**
+ * The view displaying 3 animated peak meters next to each other when a media item is playing.
+ */
+public class MediaNowPlayingView extends LinearLayout{
+
+ private final ImageView mImage1;
+ private final ImageView mImage2;
+ private final ImageView mImage3;
+ private final ObjectAnimator mObjectAnimator1;
+ private final ObjectAnimator mObjectAnimator2;
+ private final ObjectAnimator mObjectAnimator3;
+
+ public MediaNowPlayingView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+
+ LayoutInflater.from(context).inflate(R.layout.lb_playback_now_playing_bars, this, true);
+ mImage1 = (ImageView) findViewById(R.id.bar1);
+ mImage2 = (ImageView) findViewById(R.id.bar2);
+ mImage3 = (ImageView) findViewById(R.id.bar3);
+
+ mImage1.setPivotY(mImage1.getDrawable().getIntrinsicHeight());
+ mImage2.setPivotY(mImage2.getDrawable().getIntrinsicHeight());
+ mImage3.setPivotY(mImage3.getDrawable().getIntrinsicHeight());
+
+ setDropScale(mImage1);
+ setDropScale(mImage2);
+ setDropScale(mImage3);
+
+ mObjectAnimator1 = (ObjectAnimator) AnimatorInflater.loadAnimator(context,
+ R.animator.lb_playback_now_bar1_animator);
+ mObjectAnimator1.setTarget(mImage1);
+
+ mObjectAnimator2 = (ObjectAnimator) AnimatorInflater.loadAnimator(context,
+ R.animator.lb_playback_now_bar2_animator);
+ mObjectAnimator2.setTarget(mImage2);
+
+ mObjectAnimator3 = (ObjectAnimator) AnimatorInflater.loadAnimator(context,
+ R.animator.lb_playback_now_bar3_animator);
+ mObjectAnimator3.setTarget(mImage3);
+ }
+
+ static void setDropScale(View view) {
+ view.setScaleY(1f / 12f);
+ }
+
+ @Override
+ public void setVisibility(int visibility) {
+ super.setVisibility(visibility);
+ if (visibility == View.GONE) {
+ stopAnimation();
+ } else {
+ startAnimation();
+ }
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ if (getVisibility() == View.VISIBLE)
+ startAnimation();
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ stopAnimation();
+ }
+
+ private void startAnimation() {
+ startAnimation(mObjectAnimator1);
+ startAnimation(mObjectAnimator2);
+ startAnimation(mObjectAnimator3);
+ mImage1.setVisibility(View.VISIBLE);
+ mImage2.setVisibility(View.VISIBLE);
+ mImage3.setVisibility(View.VISIBLE);
+ }
+
+ private void stopAnimation() {
+ stopAnimation(mObjectAnimator1, mImage1);
+ stopAnimation(mObjectAnimator2, mImage2);
+ stopAnimation(mObjectAnimator3, mImage3);
+ mImage1.setVisibility(View.GONE);
+ mImage2.setVisibility(View.GONE);
+ mImage3.setVisibility(View.GONE);
+ }
+
+ private void startAnimation(Animator animator) {
+ if (!animator.isStarted()) {
+ animator.start();
+ }
+ }
+
+ private void stopAnimation(Animator animator, View view) {
+ if (animator.isStarted()) {
+ animator.cancel();
+ setDropScale(view);
+ }
+ }
+}
diff --git a/v7/appcompat/res/drawable-mdpi/abc_textfield_default_mtrl_alpha.9.png b/v7/appcompat/res/drawable-mdpi/abc_textfield_default_mtrl_alpha.9.png
index 0eb61f1..5d7ad2f 100644
--- a/v7/appcompat/res/drawable-mdpi/abc_textfield_default_mtrl_alpha.9.png
+++ b/v7/appcompat/res/drawable-mdpi/abc_textfield_default_mtrl_alpha.9.png
Binary files differ
diff --git a/v7/preference/src/android/support/v7/preference/CheckBoxPreference.java b/v7/preference/src/android/support/v7/preference/CheckBoxPreference.java
index b6dad92..fa4463b 100644
--- a/v7/preference/src/android/support/v7/preference/CheckBoxPreference.java
+++ b/v7/preference/src/android/support/v7/preference/CheckBoxPreference.java
@@ -109,7 +109,7 @@
return;
}
- View checkboxView = view.findViewById(android.support.v7.appcompat.R.id.checkbox);
+ View checkboxView = view.findViewById(android.R.id.checkbox);
syncCheckboxView(checkboxView);
View summaryView = view.findViewById(android.R.id.summary);