Make TabLayout more customizable
We now try to find a suitable TextView and ImageView
from the provided custom view.
The text and icon views are now also loaded from
resources, which allows external apps to hook
into the LayoutInflater to style them.
I also went crazy with the new support annotations.
BUG: 21689777
Change-Id: I4026d0cb451f82119a8f8a4ca6c265ed50432bac
diff --git a/design/res/layout/layout_tab_icon.xml b/design/res/layout/layout_tab_icon.xml
new file mode 100644
index 0000000..6464d1f
--- /dev/null
+++ b/design/res/layout/layout_tab_icon.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"/>
\ No newline at end of file
diff --git a/design/res/layout/layout_tab_text.xml b/design/res/layout/layout_tab_text.xml
new file mode 100644
index 0000000..a83bb3d
--- /dev/null
+++ b/design/res/layout/layout_tab_text.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+-->
+
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:ellipsize="end"
+ android:gravity="center"
+ android:maxLines="2"/>
\ No newline at end of file
diff --git a/design/src/android/support/design/widget/TabLayout.java b/design/src/android/support/design/widget/TabLayout.java
index f2e44ec..6e0153a 100755
--- a/design/src/android/support/design/widget/TabLayout.java
+++ b/design/src/android/support/design/widget/TabLayout.java
@@ -25,7 +25,12 @@
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.support.annotation.ColorInt;
+import android.support.annotation.DrawableRes;
import android.support.annotation.IntDef;
+import android.support.annotation.LayoutRes;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.annotation.StringRes;
import android.support.design.R;
import android.support.v4.view.GravityCompat;
import android.support.v4.view.PagerAdapter;
@@ -33,7 +38,6 @@
import android.support.v4.view.ViewPager;
import android.support.v7.app.ActionBar;
import android.support.v7.internal.widget.TintManager;
-import android.support.v7.widget.AppCompatTextView;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.Gravity;
@@ -330,7 +334,7 @@
*
* @param tab Tab to add
*/
- public void addTab(Tab tab) {
+ public void addTab(@NonNull Tab tab) {
addTab(tab, mTabs.isEmpty());
}
@@ -341,7 +345,7 @@
* @param tab The tab to add
* @param position The new position of the tab
*/
- public void addTab(Tab tab, int position) {
+ public void addTab(@NonNull Tab tab, int position) {
addTab(tab, position, mTabs.isEmpty());
}
@@ -351,7 +355,7 @@
* @param tab Tab to add
* @param setSelected True if the added tab should become the selected tab.
*/
- public void addTab(Tab tab, boolean setSelected) {
+ public void addTab(@NonNull Tab tab, boolean setSelected) {
if (tab.mParent != this) {
throw new IllegalArgumentException("Tab belongs to a different TabLayout.");
}
@@ -370,7 +374,7 @@
* @param position The new position of the tab
* @param setSelected True if the added tab should become the selected tab.
*/
- public void addTab(Tab tab, int position, boolean setSelected) {
+ public void addTab(@NonNull Tab tab, int position, boolean setSelected) {
if (tab.mParent != this) {
throw new IllegalArgumentException("Tab belongs to a different TabLayout.");
}
@@ -399,6 +403,7 @@
* @return A new Tab
* @see #addTab(Tab)
*/
+ @NonNull
public Tab newTab() {
return new Tab(this);
}
@@ -415,6 +420,7 @@
/**
* Returns the tab at the specified index.
*/
+ @Nullable
public Tab getTabAt(int index) {
return mTabs.get(index);
}
@@ -529,7 +535,7 @@
/**
* Sets the text colors for the different states (normal, selected) used for the tabs.
*/
- public void setTabTextColors(ColorStateList textColor) {
+ public void setTabTextColors(@Nullable ColorStateList textColor) {
if (mTabTextColors != textColor) {
mTabTextColors = textColor;
updateAllTabs();
@@ -539,6 +545,7 @@
/**
* Gets the text colors for the different states (normal, selected) used for the tabs.
*/
+ @Nullable
public ColorStateList getTabTextColors() {
return mTabTextColors;
}
@@ -567,7 +574,7 @@
* @see TabLayoutOnPageChangeListener
* @see ViewPagerOnTabSelectedListener
*/
- public void setupWithViewPager(ViewPager viewPager) {
+ public void setupWithViewPager(@NonNull ViewPager viewPager) {
final PagerAdapter adapter = viewPager.getAdapter();
if (adapter == null) {
throw new IllegalArgumentException("ViewPager does not have a PagerAdapter set");
@@ -597,7 +604,7 @@
*
* @param adapter the adapter to populate from
*/
- public void setTabsFromPagerAdapter(PagerAdapter adapter) {
+ public void setTabsFromPagerAdapter(@NonNull PagerAdapter adapter) {
removeAllTabs();
for (int i = 0, count = adapter.getCount(); i < count; i++) {
addTab(newTab().setText(adapter.getPageTitle(i)));
@@ -880,6 +887,7 @@
/**
* @return This Tab's tag object.
*/
+ @Nullable
public Object getTag() {
return mTag;
}
@@ -890,7 +898,8 @@
* @param tag Object to store
* @return The current instance for call chaining
*/
- public Tab setTag(Object tag) {
+ @NonNull
+ public Tab setTag(@Nullable Object tag) {
mTag = tag;
return this;
}
@@ -900,13 +909,20 @@
}
/**
- * Set a custom view to be used for this tab. This overrides values set by {@link
- * #setText(CharSequence)} and {@link #setIcon(Drawable)}.
+ * Set a custom view to be used for this tab.
+ * <p>
+ * If the provided view contains a {@link TextView} with an ID of
+ * {@link android.R.id#text1} then that will be updated with the value given
+ * to {@link #setText(CharSequence)}. Similarly, if this layout contains an
+ * {@link ImageView} with ID {@link android.R.id#icon} then it will be updated with
+ * the value given to {@link #setIcon(Drawable)}.
+ * </p>
*
* @param view Custom view to be used as a tab.
* @return The current instance for call chaining
*/
- public Tab setCustomView(View view) {
+ @NonNull
+ public Tab setCustomView(@Nullable View view) {
mCustomView = view;
if (mPosition >= 0) {
mParent.updateTab(mPosition);
@@ -915,13 +931,20 @@
}
/**
- * Set a custom view to be used for this tab. This overrides values set by {@link
- * #setText(CharSequence)} and {@link #setIcon(Drawable)}.
+ * Set a custom view to be used for this tab.
+ * <p>
+ * If the inflated layout contains a {@link TextView} with an ID of
+ * {@link android.R.id#text1} then that will be updated with the value given
+ * to {@link #setText(CharSequence)}. Similarly, if this layout contains an
+ * {@link ImageView} with ID {@link android.R.id#icon} then it will be updated with
+ * the value given to {@link #setIcon(Drawable)}.
+ * </p>
*
* @param layoutResId A layout resource to inflate and use as a custom tab view
* @return The current instance for call chaining
*/
- public Tab setCustomView(int layoutResId) {
+ @NonNull
+ public Tab setCustomView(@LayoutRes int layoutResId) {
return setCustomView(
LayoutInflater.from(mParent.getContext()).inflate(layoutResId, null));
}
@@ -931,6 +954,7 @@
*
* @return The tab's icon
*/
+ @Nullable
public Drawable getIcon() {
return mIcon;
}
@@ -954,6 +978,7 @@
*
* @return The tab's text
*/
+ @Nullable
public CharSequence getText() {
return mText;
}
@@ -964,7 +989,8 @@
* @param icon The drawable to use as an icon
* @return The current instance for call chaining
*/
- public Tab setIcon(Drawable icon) {
+ @NonNull
+ public Tab setIcon(@Nullable Drawable icon) {
mIcon = icon;
if (mPosition >= 0) {
mParent.updateTab(mPosition);
@@ -978,7 +1004,8 @@
* @param resId A resource ID referring to the icon that should be displayed
* @return The current instance for call chaining
*/
- public Tab setIcon(int resId) {
+ @NonNull
+ public Tab setIcon(@DrawableRes int resId) {
return setIcon(TintManager.getDrawable(mParent.getContext(), resId));
}
@@ -989,7 +1016,8 @@
* @param text The text to display
* @return The current instance for call chaining
*/
- public Tab setText(CharSequence text) {
+ @NonNull
+ public Tab setText(@Nullable CharSequence text) {
mText = text;
if (mPosition >= 0) {
mParent.updateTab(mPosition);
@@ -1004,7 +1032,8 @@
* @param resId A resource ID referring to the text that should be displayed
* @return The current instance for call chaining
*/
- public Tab setText(int resId) {
+ @NonNull
+ public Tab setText(@StringRes int resId) {
return setText(mParent.getResources().getText(resId));
}
@@ -1024,7 +1053,8 @@
* @see #setContentDescription(CharSequence)
* @see #getContentDescription()
*/
- public Tab setContentDescription(int resId) {
+ @NonNull
+ public Tab setContentDescription(@StringRes int resId) {
return setContentDescription(mParent.getResources().getText(resId));
}
@@ -1037,7 +1067,8 @@
* @see #setContentDescription(int)
* @see #getContentDescription()
*/
- public Tab setContentDescription(CharSequence contentDesc) {
+ @NonNull
+ public Tab setContentDescription(@Nullable CharSequence contentDesc) {
mContentDesc = contentDesc;
if (mPosition >= 0) {
mParent.updateTab(mPosition);
@@ -1052,6 +1083,7 @@
* @see #setContentDescription(CharSequence)
* @see #setContentDescription(int)
*/
+ @Nullable
public CharSequence getContentDescription() {
return mContentDesc;
}
@@ -1061,7 +1093,10 @@
private final Tab mTab;
private TextView mTextView;
private ImageView mIconView;
+
private View mCustomView;
+ private TextView mCustomTextView;
+ private ImageView mCustomIconView;
public TabView(Context context, Tab tab) {
super(context);
@@ -1141,64 +1176,80 @@
mIconView.setVisibility(GONE);
mIconView.setImageDrawable(null);
}
+
+ mCustomTextView = (TextView) custom.findViewById(android.R.id.text1);
+ mCustomIconView = (ImageView) custom.findViewById(android.R.id.icon);
} else {
+ // We do not have a custom view. Remove one if it already exists
if (mCustomView != null) {
removeView(mCustomView);
mCustomView = null;
}
+ mCustomTextView = null;
+ mCustomIconView = null;
+ }
- final Drawable icon = tab.getIcon();
- final CharSequence text = tab.getText();
+ if (mCustomView == null) {
+ // If there isn't a custom view, we'll us our own in-built layouts
+ if (mIconView == null) {
+ ImageView iconView = (ImageView) LayoutInflater.from(getContext())
+ .inflate(R.layout.layout_tab_icon, this, false);
+ addView(iconView, 0);
+ mIconView = iconView;
+ }
+ if (mTextView == null) {
+ TextView textView = (TextView) LayoutInflater.from(getContext())
+ .inflate(R.layout.layout_tab_text, this, false);
+ addView(textView);
+ mTextView = textView;
+ }
+ mTextView.setTextAppearance(getContext(), mTabTextAppearance);
+ if (mTabTextColors != null) {
+ mTextView.setTextColor(mTabTextColors);
+ }
+ updateTextAndIcon(tab, mTextView, mIconView);
+ } else {
+ // Else, we'll see if there is a TextView or ImageView present and update them
+ if (mCustomTextView != null || mCustomIconView != null) {
+ updateTextAndIcon(tab, mCustomTextView, mCustomIconView);
+ }
+ }
+ }
+ private void updateTextAndIcon(Tab tab, TextView textView, ImageView iconView) {
+ final Drawable icon = tab.getIcon();
+ final CharSequence text = tab.getText();
+
+ if (iconView != null) {
if (icon != null) {
- if (mIconView == null) {
- ImageView iconView = new ImageView(getContext());
- LayoutParams lp = new LayoutParams(LayoutParams.WRAP_CONTENT,
- LayoutParams.WRAP_CONTENT);
- lp.gravity = Gravity.CENTER_VERTICAL;
- iconView.setLayoutParams(lp);
- addView(iconView, 0);
- mIconView = iconView;
- }
- mIconView.setImageDrawable(icon);
- mIconView.setVisibility(VISIBLE);
- } else if (mIconView != null) {
- mIconView.setVisibility(GONE);
- mIconView.setImageDrawable(null);
- }
-
- final boolean hasText = !TextUtils.isEmpty(text);
- if (hasText) {
- if (mTextView == null) {
- AppCompatTextView textView = new AppCompatTextView(getContext());
- textView.setMaxLines(MAX_TAB_TEXT_LINES);
- textView.setEllipsize(TextUtils.TruncateAt.END);
- textView.setGravity(Gravity.CENTER);
- addView(textView, LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
- mTextView = textView;
- }
- mTextView.setTextAppearance(getContext(), mTabTextAppearance);
- if (mTabTextColors != null) {
- mTextView.setTextColor(mTabTextColors);
- }
- mTextView.setText(text);
- mTextView.setContentDescription(tab.getContentDescription());
- mTextView.setVisibility(VISIBLE);
- } else if (mTextView != null) {
- mTextView.setVisibility(GONE);
- mTextView.setText(null);
- }
-
- if (mIconView != null) {
- mIconView.setContentDescription(tab.getContentDescription());
- }
-
- if (!hasText && !TextUtils.isEmpty(tab.getContentDescription())) {
- setOnLongClickListener(this);
+ iconView.setImageDrawable(icon);
+ iconView.setVisibility(VISIBLE);
+ setVisibility(VISIBLE);
} else {
- setOnLongClickListener(null);
- setLongClickable(false);
+ iconView.setVisibility(GONE);
+ iconView.setImageDrawable(null);
}
+ iconView.setContentDescription(tab.getContentDescription());
+ }
+
+ final boolean hasText = !TextUtils.isEmpty(text);
+ if (textView != null) {
+ if (hasText) {
+ textView.setText(text);
+ textView.setContentDescription(tab.getContentDescription());
+ textView.setVisibility(VISIBLE);
+ setVisibility(VISIBLE);
+ } else {
+ textView.setVisibility(GONE);
+ textView.setText(null);
+ }
+ }
+
+ if (!hasText && !TextUtils.isEmpty(tab.getContentDescription())) {
+ setOnLongClickListener(this);
+ } else {
+ setOnLongClickListener(null);
+ setLongClickable(false);
}
}