/* * Copyright (C) 2011 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.support.v7.internal.widget; import android.content.Context; import android.content.res.Configuration; import android.graphics.drawable.Drawable; import android.support.v7.app.ActionBar; import android.support.v7.appcompat.R; import android.support.v7.internal.view.ActionBarPolicy; import android.text.TextUtils.TruncateAt; import android.util.AttributeSet; import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.ViewParent; import android.widget.BaseAdapter; import android.widget.HorizontalScrollView; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.ListView; import android.widget.TextView; /** * This widget implements the dynamic action bar tab behavior that can change across different * configurations or circumstances. * * @hide */ public class ScrollingTabContainerView extends HorizontalScrollView implements AdapterViewICS.OnItemClickListener { private static final String TAG = "ScrollingTabContainerView"; Runnable mTabSelector; private TabClickListener mTabClickListener; private LinearLayout mTabLayout; private SpinnerICS mTabSpinner; private boolean mAllowCollapse; private final LayoutInflater mInflater; int mMaxTabWidth; int mStackedTabMaxWidth; private int mContentHeight; private int mSelectedTabIndex; public ScrollingTabContainerView(Context context) { super(context); mInflater = LayoutInflater.from(context); setHorizontalScrollBarEnabled(false); ActionBarPolicy abp = ActionBarPolicy.get(context); setContentHeight(abp.getTabContainerHeight()); mStackedTabMaxWidth = abp.getStackedTabMaxWidth(); mTabLayout = (LinearLayout) mInflater.inflate(R.layout.abc_action_bar_tabbar, this, false); addView(mTabLayout, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.FILL_PARENT)); } @Override public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { final int widthMode = MeasureSpec.getMode(widthMeasureSpec); final boolean lockedExpanded = widthMode == MeasureSpec.EXACTLY; setFillViewport(lockedExpanded); final int childCount = mTabLayout.getChildCount(); if (childCount > 1 && (widthMode == MeasureSpec.EXACTLY || widthMode == MeasureSpec.AT_MOST)) { if (childCount > 2) { mMaxTabWidth = (int) (MeasureSpec.getSize(widthMeasureSpec) * 0.4f); } else { mMaxTabWidth = MeasureSpec.getSize(widthMeasureSpec) / 2; } mMaxTabWidth = Math.min(mMaxTabWidth, mStackedTabMaxWidth); } else { mMaxTabWidth = -1; } heightMeasureSpec = MeasureSpec.makeMeasureSpec(mContentHeight, MeasureSpec.EXACTLY); final boolean canCollapse = !lockedExpanded && mAllowCollapse; if (canCollapse) { // See if we should expand mTabLayout.measure(MeasureSpec.UNSPECIFIED, heightMeasureSpec); if (mTabLayout.getMeasuredWidth() > MeasureSpec.getSize(widthMeasureSpec)) { performCollapse(); } else { performExpand(); } } else { performExpand(); } final int oldWidth = getMeasuredWidth(); super.onMeasure(widthMeasureSpec, heightMeasureSpec); final int newWidth = getMeasuredWidth(); if (lockedExpanded && oldWidth != newWidth) { // Recenter the tab display if we're at a new (scrollable) size. setTabSelected(mSelectedTabIndex); } } /** * Indicates whether this view is collapsed into a dropdown menu instead of traditional tabs. * * @return true if showing as a spinner */ private boolean isCollapsed() { return mTabSpinner != null && mTabSpinner.getParent() == this; } public void setAllowCollapse(boolean allowCollapse) { mAllowCollapse = allowCollapse; } private void performCollapse() { if (isCollapsed()) { return; } if (mTabSpinner == null) { mTabSpinner = createSpinner(); } removeView(mTabLayout); addView(mTabSpinner, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.FILL_PARENT)); if (mTabSpinner.getAdapter() == null) { mTabSpinner.setAdapter(new TabAdapter()); } if (mTabSelector != null) { removeCallbacks(mTabSelector); mTabSelector = null; } mTabSpinner.setSelection(mSelectedTabIndex); } private boolean performExpand() { if (!isCollapsed()) { return false; } removeView(mTabSpinner); addView(mTabLayout, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.FILL_PARENT)); setTabSelected(mTabSpinner.getSelectedItemPosition()); return false; } public void setTabSelected(int position) { mSelectedTabIndex = position; final int tabCount = mTabLayout.getChildCount(); for (int i = 0; i < tabCount; i++) { final View child = mTabLayout.getChildAt(i); final boolean isSelected = i == position; child.setSelected(isSelected); if (isSelected) { animateToTab(position); } } if (mTabSpinner != null && position >= 0) { mTabSpinner.setSelection(position); } } public void setContentHeight(int contentHeight) { mContentHeight = contentHeight; requestLayout(); } private SpinnerICS createSpinner() { final SpinnerICS spinner = new SpinnerICS(getContext(), null, R.attr.actionDropDownStyle); spinner.setLayoutParams(new LinearLayout.LayoutParams( LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.FILL_PARENT)); spinner.setOnItemClickListenerInt(this); return spinner; } protected void onConfigurationChanged(Configuration newConfig) { ActionBarPolicy abp = ActionBarPolicy.get(getContext()); // Action bar can change size on configuration changes. // Reread the desired height from the theme-specified style. setContentHeight(abp.getTabContainerHeight()); mStackedTabMaxWidth = abp.getStackedTabMaxWidth(); } public void animateToTab(final int position) { final View tabView = mTabLayout.getChildAt(position); if (mTabSelector != null) { removeCallbacks(mTabSelector); } mTabSelector = new Runnable() { public void run() { final int scrollPos = tabView.getLeft() - (getWidth() - tabView.getWidth()) / 2; smoothScrollTo(scrollPos, 0); mTabSelector = null; } }; post(mTabSelector); } @Override public void onAttachedToWindow() { super.onAttachedToWindow(); if (mTabSelector != null) { // Re-post the selector we saved post(mTabSelector); } } @Override public void onDetachedFromWindow() { super.onDetachedFromWindow(); if (mTabSelector != null) { removeCallbacks(mTabSelector); } } private TabView createTabView(ActionBar.Tab tab, boolean forAdapter) { final TabView tabView = (TabView) mInflater.inflate(R.layout.abc_action_bar_tab, mTabLayout, false); tabView.attach(this, tab, forAdapter); if (forAdapter) { tabView.setBackgroundDrawable(null); tabView.setLayoutParams(new ListView.LayoutParams(ListView.LayoutParams.FILL_PARENT, mContentHeight)); } else { tabView.setFocusable(true); if (mTabClickListener == null) { mTabClickListener = new TabClickListener(); } tabView.setOnClickListener(mTabClickListener); } return tabView; } public void addTab(ActionBar.Tab tab, boolean setSelected) { TabView tabView = createTabView(tab, false); mTabLayout.addView(tabView, new LinearLayout.LayoutParams(0, LayoutParams.FILL_PARENT, 1)); if (mTabSpinner != null) { ((TabAdapter) mTabSpinner.getAdapter()).notifyDataSetChanged(); } if (setSelected) { tabView.setSelected(true); } if (mAllowCollapse) { requestLayout(); } } public void addTab(ActionBar.Tab tab, int position, boolean setSelected) { final TabView tabView = createTabView(tab, false); mTabLayout.addView(tabView, position, new LinearLayout.LayoutParams( 0, LayoutParams.FILL_PARENT, 1)); if (mTabSpinner != null) { ((TabAdapter) mTabSpinner.getAdapter()).notifyDataSetChanged(); } if (setSelected) { tabView.setSelected(true); } if (mAllowCollapse) { requestLayout(); } } public void updateTab(int position) { ((TabView) mTabLayout.getChildAt(position)).update(); if (mTabSpinner != null) { ((TabAdapter) mTabSpinner.getAdapter()).notifyDataSetChanged(); } if (mAllowCollapse) { requestLayout(); } } public void removeTabAt(int position) { mTabLayout.removeViewAt(position); if (mTabSpinner != null) { ((TabAdapter) mTabSpinner.getAdapter()).notifyDataSetChanged(); } if (mAllowCollapse) { requestLayout(); } } public void removeAllTabs() { mTabLayout.removeAllViews(); if (mTabSpinner != null) { ((TabAdapter) mTabSpinner.getAdapter()).notifyDataSetChanged(); } if (mAllowCollapse) { requestLayout(); } } @Override public void onItemClick(AdapterViewICS parent, View view, int position, long id) { TabView tabView = (TabView) view; tabView.getTab().select(); } /** * @hide */ public static class TabView extends LinearLayout { private ActionBar.Tab mTab; private TextView mTextView; private ImageView mIconView; private View mCustomView; private ScrollingTabContainerView mParent; public TabView(Context context, AttributeSet attrs) { super(context, attrs); } void attach(ScrollingTabContainerView parent, ActionBar.Tab tab, boolean forList) { mParent = parent; mTab = tab; if (forList) { setGravity(Gravity.LEFT | Gravity.CENTER_VERTICAL); } update(); } public void bindTab(ActionBar.Tab tab) { mTab = tab; update(); } @Override public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int maxTabWidth = mParent != null ? mParent.mMaxTabWidth : 0; // Re-measure if we went beyond our maximum size. if (maxTabWidth > 0 && getMeasuredWidth() > maxTabWidth) { super.onMeasure(MeasureSpec.makeMeasureSpec(maxTabWidth, MeasureSpec.EXACTLY), heightMeasureSpec); } } public void update() { final ActionBar.Tab tab = mTab; final View custom = tab.getCustomView(); if (custom != null) { final ViewParent customParent = custom.getParent(); if (customParent != this) { if (customParent != null) { ((ViewGroup) customParent).removeView(custom); } addView(custom); } mCustomView = custom; if (mTextView != null) { mTextView.setVisibility(GONE); } if (mIconView != null) { mIconView.setVisibility(GONE); mIconView.setImageDrawable(null); } } else { if (mCustomView != null) { removeView(mCustomView); mCustomView = null; } final Drawable icon = tab.getIcon(); final CharSequence text = tab.getText(); 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); } if (text != null) { if (mTextView == null) { TextView textView = new CompatTextView(getContext(), null, R.attr.actionBarTabTextStyle); textView.setEllipsize(TruncateAt.END); LayoutParams lp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); lp.gravity = Gravity.CENTER_VERTICAL; textView.setLayoutParams(lp); addView(textView); mTextView = textView; } mTextView.setText(text); mTextView.setVisibility(VISIBLE); } else if (mTextView != null) { mTextView.setVisibility(GONE); mTextView.setText(null); } if (mIconView != null) { mIconView.setContentDescription(tab.getContentDescription()); } } } public ActionBar.Tab getTab() { return mTab; } } private class TabAdapter extends BaseAdapter { @Override public int getCount() { return mTabLayout.getChildCount(); } @Override public Object getItem(int position) { return ((TabView) mTabLayout.getChildAt(position)).getTab(); } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { if (convertView == null) { convertView = createTabView((ActionBar.Tab) getItem(position), true); } else { ((TabView) convertView).bindTab((ActionBar.Tab) getItem(position)); } return convertView; } } private class TabClickListener implements OnClickListener { public void onClick(View view) { TabView tabView = (TabView) view; tabView.getTab().select(); final int tabCount = mTabLayout.getChildCount(); for (int i = 0; i < tabCount; i++) { final View child = mTabLayout.getChildAt(i); child.setSelected(child == view); } } } }