Source code
package android.support.v7.internal.widget;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.drawable.Drawable;
import android.os.Build.VERSION;
import android.support.v4.view.ViewCompat;
import android.support.v4.view.ViewPropertyAnimatorCompat;
import android.support.v4.view.ViewPropertyAnimatorListener;
import android.support.v7.app.ActionBar.Tab;
import android.support.v7.appcompat.R;
import android.support.v7.internal.view.ActionBarPolicy;
import android.support.v7.widget.AppCompatSpinner;
import android.support.v7.widget.AppCompatTextView;
import android.support.v7.widget.LinearLayoutCompat;
import android.support.v7.widget.LinearLayoutCompat.LayoutParams;
import android.text.TextUtils;
import android.text.TextUtils.TruncateAt;
import android.view.View;
import android.view.View.MeasureSpec;
import android.view.View.OnClickListener;
import android.view.View.OnLongClickListener;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
import android.widget.AbsListView;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.BaseAdapter;
import android.widget.HorizontalScrollView;
import android.widget.ImageView;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast;
public class ScrollingTabContainerView extends HorizontalScrollView implements OnItemSelectedListener {
private static final int FADE_DURATION = 200;
private static final String TAG = "ScrollingTabContainerView";
private static final Interpolator sAlphaInterpolator = new DecelerateInterpolator();
private boolean mAllowCollapse;
private int mContentHeight;
int mMaxTabWidth;
private int mSelectedTabIndex;
int mStackedTabMaxWidth;
private TabClickListener mTabClickListener;
private LinearLayoutCompat mTabLayout;
Runnable mTabSelector;
private Spinner mTabSpinner;
protected final VisibilityAnimListener mVisAnimListener = new VisibilityAnimListener();
protected ViewPropertyAnimatorCompat mVisibilityAnim;
private class TabAdapter extends BaseAdapter {
private TabAdapter() {
}
public int getCount() {
return ScrollingTabContainerView.this.mTabLayout.getChildCount();
}
public Object getItem(int position) {
return ((TabView) ScrollingTabContainerView.this.mTabLayout.getChildAt(position)).getTab();
}
public long getItemId(int position) {
return (long) position;
}
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
return ScrollingTabContainerView.this.createTabView((Tab) getItem(position), true);
}
((TabView) convertView).bindTab((Tab) getItem(position));
return convertView;
}
}
private class TabClickListener implements OnClickListener {
private TabClickListener() {
}
public void onClick(View view) {
((TabView) view).getTab().select();
int tabCount = ScrollingTabContainerView.this.mTabLayout.getChildCount();
for (int i = 0; i < tabCount; i++) {
View child = ScrollingTabContainerView.this.mTabLayout.getChildAt(i);
child.setSelected(child == view);
}
}
}
private class TabView extends LinearLayoutCompat implements OnLongClickListener {
private final int[] BG_ATTRS = new int[]{16842964};
private View mCustomView;
private ImageView mIconView;
private Tab mTab;
private TextView mTextView;
public TabView(Context context, Tab tab, boolean forList) {
super(context, null, R.attr.actionBarTabStyle);
this.mTab = tab;
TintTypedArray a = TintTypedArray.obtainStyledAttributes(context, null, this.BG_ATTRS, R.attr.actionBarTabStyle, 0);
if (a.hasValue(0)) {
setBackgroundDrawable(a.getDrawable(0));
}
a.recycle();
if (forList) {
setGravity(8388627);
}
update();
}
public void bindTab(Tab tab) {
this.mTab = tab;
update();
}
public void setSelected(boolean selected) {
boolean changed = isSelected() != selected;
super.setSelected(selected);
if (changed && selected) {
sendAccessibilityEvent(4);
}
}
public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
super.onInitializeAccessibilityEvent(event);
event.setClassName(Tab.class.getName());
}
public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfo(info);
if (VERSION.SDK_INT >= 14) {
info.setClassName(Tab.class.getName());
}
}
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (ScrollingTabContainerView.this.mMaxTabWidth > 0 && getMeasuredWidth() > ScrollingTabContainerView.this.mMaxTabWidth) {
super.onMeasure(MeasureSpec.makeMeasureSpec(ScrollingTabContainerView.this.mMaxTabWidth, 1073741824), heightMeasureSpec);
}
}
public void update() {
Tab tab = this.mTab;
View custom = tab.getCustomView();
if (custom != null) {
TabView customParent = custom.getParent();
if (customParent != this) {
if (customParent != null) {
customParent.removeView(custom);
}
addView(custom);
}
this.mCustomView = custom;
if (this.mTextView != null) {
this.mTextView.setVisibility(8);
}
if (this.mIconView != null) {
this.mIconView.setVisibility(8);
this.mIconView.setImageDrawable(null);
return;
}
return;
}
boolean hasText;
if (this.mCustomView != null) {
removeView(this.mCustomView);
this.mCustomView = null;
}
Drawable icon = tab.getIcon();
CharSequence text = tab.getText();
if (icon != null) {
if (this.mIconView == null) {
ImageView iconView = new ImageView(getContext());
LayoutParams lp = new LayoutParams(-2, -2);
lp.gravity = 16;
iconView.setLayoutParams(lp);
addView(iconView, 0);
this.mIconView = iconView;
}
this.mIconView.setImageDrawable(icon);
this.mIconView.setVisibility(0);
} else if (this.mIconView != null) {
this.mIconView.setVisibility(8);
this.mIconView.setImageDrawable(null);
}
if (TextUtils.isEmpty(text)) {
hasText = false;
} else {
hasText = true;
}
if (hasText) {
if (this.mTextView == null) {
TextView textView = new AppCompatTextView(getContext(), null, R.attr.actionBarTabTextStyle);
textView.setEllipsize(TruncateAt.END);
lp = new LayoutParams(-2, -2);
lp.gravity = 16;
textView.setLayoutParams(lp);
addView(textView);
this.mTextView = textView;
}
this.mTextView.setText(text);
this.mTextView.setVisibility(0);
} else if (this.mTextView != null) {
this.mTextView.setVisibility(8);
this.mTextView.setText(null);
}
if (this.mIconView != null) {
this.mIconView.setContentDescription(tab.getContentDescription());
}
if (hasText || TextUtils.isEmpty(tab.getContentDescription())) {
setOnLongClickListener(null);
setLongClickable(false);
return;
}
setOnLongClickListener(this);
}
public boolean onLongClick(View v) {
int[] screenPos = new int[2];
getLocationOnScreen(screenPos);
Context context = getContext();
int width = getWidth();
int height = getHeight();
int screenWidth = context.getResources().getDisplayMetrics().widthPixels;
Toast cheatSheet = Toast.makeText(context, this.mTab.getContentDescription(), 0);
cheatSheet.setGravity(49, (screenPos[0] + (width / 2)) - (screenWidth / 2), height);
cheatSheet.show();
return true;
}
public Tab getTab() {
return this.mTab;
}
}
protected class VisibilityAnimListener implements ViewPropertyAnimatorListener {
private boolean mCanceled = false;
private int mFinalVisibility;
protected VisibilityAnimListener() {
}
public VisibilityAnimListener withFinalVisibility(ViewPropertyAnimatorCompat animation, int visibility) {
this.mFinalVisibility = visibility;
ScrollingTabContainerView.this.mVisibilityAnim = animation;
return this;
}
public void onAnimationStart(View view) {
ScrollingTabContainerView.this.setVisibility(0);
this.mCanceled = false;
}
public void onAnimationEnd(View view) {
if (!this.mCanceled) {
ScrollingTabContainerView.this.mVisibilityAnim = null;
ScrollingTabContainerView.this.setVisibility(this.mFinalVisibility);
}
}
public void onAnimationCancel(View view) {
this.mCanceled = true;
}
}
public ScrollingTabContainerView(Context context) {
super(context);
setHorizontalScrollBarEnabled(false);
ActionBarPolicy abp = ActionBarPolicy.get(context);
setContentHeight(abp.getTabContainerHeight());
this.mStackedTabMaxWidth = abp.getStackedTabMaxWidth();
this.mTabLayout = createTabLayout();
addView(this.mTabLayout, new ViewGroup.LayoutParams(-2, -1));
}
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
boolean lockedExpanded;
boolean canCollapse;
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
if (widthMode == 1073741824) {
lockedExpanded = true;
} else {
lockedExpanded = false;
}
setFillViewport(lockedExpanded);
int childCount = this.mTabLayout.getChildCount();
if (childCount <= 1 || !(widthMode == 1073741824 || widthMode == Integer.MIN_VALUE)) {
this.mMaxTabWidth = -1;
} else {
if (childCount > 2) {
this.mMaxTabWidth = (int) (((float) MeasureSpec.getSize(widthMeasureSpec)) * 0.4f);
} else {
this.mMaxTabWidth = MeasureSpec.getSize(widthMeasureSpec) / 2;
}
this.mMaxTabWidth = Math.min(this.mMaxTabWidth, this.mStackedTabMaxWidth);
}
heightMeasureSpec = MeasureSpec.makeMeasureSpec(this.mContentHeight, 1073741824);
if (lockedExpanded || !this.mAllowCollapse) {
canCollapse = false;
} else {
canCollapse = true;
}
if (canCollapse) {
this.mTabLayout.measure(0, heightMeasureSpec);
if (this.mTabLayout.getMeasuredWidth() > MeasureSpec.getSize(widthMeasureSpec)) {
performCollapse();
} else {
performExpand();
}
} else {
performExpand();
}
int oldWidth = getMeasuredWidth();
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int newWidth = getMeasuredWidth();
if (lockedExpanded && oldWidth != newWidth) {
setTabSelected(this.mSelectedTabIndex);
}
}
private boolean isCollapsed() {
return this.mTabSpinner != null && this.mTabSpinner.getParent() == this;
}
public void setAllowCollapse(boolean allowCollapse) {
this.mAllowCollapse = allowCollapse;
}
private void performCollapse() {
if (!isCollapsed()) {
if (this.mTabSpinner == null) {
this.mTabSpinner = createSpinner();
}
removeView(this.mTabLayout);
addView(this.mTabSpinner, new ViewGroup.LayoutParams(-2, -1));
if (this.mTabSpinner.getAdapter() == null) {
this.mTabSpinner.setAdapter(new TabAdapter());
}
if (this.mTabSelector != null) {
removeCallbacks(this.mTabSelector);
this.mTabSelector = null;
}
this.mTabSpinner.setSelection(this.mSelectedTabIndex);
}
}
private boolean performExpand() {
if (isCollapsed()) {
removeView(this.mTabSpinner);
addView(this.mTabLayout, new ViewGroup.LayoutParams(-2, -1));
setTabSelected(this.mTabSpinner.getSelectedItemPosition());
}
return false;
}
public void setTabSelected(int position) {
this.mSelectedTabIndex = position;
int tabCount = this.mTabLayout.getChildCount();
int i = 0;
while (i < tabCount) {
View child = this.mTabLayout.getChildAt(i);
boolean isSelected = i == position;
child.setSelected(isSelected);
if (isSelected) {
animateToTab(position);
}
i++;
}
if (this.mTabSpinner != null && position >= 0) {
this.mTabSpinner.setSelection(position);
}
}
public void setContentHeight(int contentHeight) {
this.mContentHeight = contentHeight;
requestLayout();
}
private LinearLayoutCompat createTabLayout() {
LinearLayoutCompat tabLayout = new LinearLayoutCompat(getContext(), null, R.attr.actionBarTabBarStyle);
tabLayout.setMeasureWithLargestChildEnabled(true);
tabLayout.setGravity(17);
tabLayout.setLayoutParams(new LayoutParams(-2, -1));
return tabLayout;
}
private Spinner createSpinner() {
Spinner spinner = new AppCompatSpinner(getContext(), null, R.attr.actionDropDownStyle);
spinner.setLayoutParams(new LayoutParams(-2, -1));
spinner.setOnItemSelectedListener(this);
return spinner;
}
protected void onConfigurationChanged(Configuration newConfig) {
if (VERSION.SDK_INT >= 8) {
super.onConfigurationChanged(newConfig);
}
ActionBarPolicy abp = ActionBarPolicy.get(getContext());
setContentHeight(abp.getTabContainerHeight());
this.mStackedTabMaxWidth = abp.getStackedTabMaxWidth();
}
public void animateToVisibility(int visibility) {
if (this.mVisibilityAnim != null) {
this.mVisibilityAnim.cancel();
}
if (visibility == 0) {
if (getVisibility() != 0) {
ViewCompat.setAlpha(this, 0.0f);
}
ViewPropertyAnimatorCompat anim = ViewCompat.animate(this).alpha(1.0f);
anim.setDuration(200);
anim.setInterpolator(sAlphaInterpolator);
anim.setListener(this.mVisAnimListener.withFinalVisibility(anim, visibility));
anim.start();
return;
}
anim = ViewCompat.animate(this).alpha(0.0f);
anim.setDuration(200);
anim.setInterpolator(sAlphaInterpolator);
anim.setListener(this.mVisAnimListener.withFinalVisibility(anim, visibility));
anim.start();
}
public void animateToTab(int position) {
final View tabView = this.mTabLayout.getChildAt(position);
if (this.mTabSelector != null) {
removeCallbacks(this.mTabSelector);
}
this.mTabSelector = new Runnable() {
public void run() {
ScrollingTabContainerView.this.smoothScrollTo(tabView.getLeft() - ((ScrollingTabContainerView.this.getWidth() - tabView.getWidth()) / 2), 0);
ScrollingTabContainerView.this.mTabSelector = null;
}
};
post(this.mTabSelector);
}
public void onAttachedToWindow() {
super.onAttachedToWindow();
if (this.mTabSelector != null) {
post(this.mTabSelector);
}
}
public void onDetachedFromWindow() {
super.onDetachedFromWindow();
if (this.mTabSelector != null) {
removeCallbacks(this.mTabSelector);
}
}
private TabView createTabView(Tab tab, boolean forAdapter) {
TabView tabView = new TabView(getContext(), tab, forAdapter);
if (forAdapter) {
tabView.setBackgroundDrawable(null);
tabView.setLayoutParams(new AbsListView.LayoutParams(-1, this.mContentHeight));
} else {
tabView.setFocusable(true);
if (this.mTabClickListener == null) {
this.mTabClickListener = new TabClickListener();
}
tabView.setOnClickListener(this.mTabClickListener);
}
return tabView;
}
public void addTab(Tab tab, boolean setSelected) {
TabView tabView = createTabView(tab, false);
this.mTabLayout.addView(tabView, new LayoutParams(0, -1, 1.0f));
if (this.mTabSpinner != null) {
((TabAdapter) this.mTabSpinner.getAdapter()).notifyDataSetChanged();
}
if (setSelected) {
tabView.setSelected(true);
}
if (this.mAllowCollapse) {
requestLayout();
}
}
public void addTab(Tab tab, int position, boolean setSelected) {
TabView tabView = createTabView(tab, false);
this.mTabLayout.addView(tabView, position, new LayoutParams(0, -1, 1.0f));
if (this.mTabSpinner != null) {
((TabAdapter) this.mTabSpinner.getAdapter()).notifyDataSetChanged();
}
if (setSelected) {
tabView.setSelected(true);
}
if (this.mAllowCollapse) {
requestLayout();
}
}
public void updateTab(int position) {
((TabView) this.mTabLayout.getChildAt(position)).update();
if (this.mTabSpinner != null) {
((TabAdapter) this.mTabSpinner.getAdapter()).notifyDataSetChanged();
}
if (this.mAllowCollapse) {
requestLayout();
}
}
public void removeTabAt(int position) {
this.mTabLayout.removeViewAt(position);
if (this.mTabSpinner != null) {
((TabAdapter) this.mTabSpinner.getAdapter()).notifyDataSetChanged();
}
if (this.mAllowCollapse) {
requestLayout();
}
}
public void removeAllTabs() {
this.mTabLayout.removeAllViews();
if (this.mTabSpinner != null) {
((TabAdapter) this.mTabSpinner.getAdapter()).notifyDataSetChanged();
}
if (this.mAllowCollapse) {
requestLayout();
}
}
public void onItemSelected(AdapterView<?> adapterView, View view, int position, long id) {
((TabView) view).getTab().select();
}
public void onNothingSelected(AdapterView<?> adapterView) {
}
}