图1是初始状态,只显示单行Tab,图2是点击了“更多”后显示多行Tab的效果。
具体原理就是加一个maxTabCountOfRow属性用于保存每行最多显示多少个Tab,然后设置垂直布局,加进若干水平布局的LinearLayout进去,最终Tab实际被加进这些LinearLayout里。重写TabWidget的addView方法,每次有新的child加进来,先判断最后一个LinearLayout是否已加满,没加满就把新的Tab加到它里面,加满的话就创建下一个LinearLayout来放Tab。接下来贴一下主要的代码。
MultirowTabWidget.java
package com.thornbird.multirowtab.widget; import java.lang.reflect.Field; import android.content.Context; import android.graphics.drawable.Drawable; import android.os.Handler; import android.util.AttributeSet; import android.view.Gravity; import android.view.View; import android.view.ViewGroup; import android.view.animation.Animation; import android.view.animation.Animation.AnimationListener; import android.view.animation.TranslateAnimation; import android.widget.LinearLayout; import android.widget.TabWidget; public class MultirowTabWidget extends TabWidget { private static final String ROW_TAG = "MultirowTabWidgetRow"; private boolean mMultirow = false; private int mMaxTabCountOfRow = 5; private Drawable mVerticalDividerDrawable = null; public MultirowTabWidget(Context context) { super(context); initTabWidget(); } public MultirowTabWidget(Context context, AttributeSet attrs) { super(context, attrs); initTabWidget(); } public MultirowTabWidget(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); initTabWidget(); } public MultirowTabWidget(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); initTabWidget(); } @Override public void setGravity(int gravity) { super.setGravity(Gravity.TOP | Gravity.CENTER_HORIZONTAL); } @Override public void setOrientation(int orientation) { super.setOrientation(VERTICAL); } @Override public void setShowDividers(int showDividers) { super.setShowDividers(SHOW_DIVIDER_MIDDLE); } @Override public void setStripEnabled(boolean stripEnabled) { super.setStripEnabled(false); } @Override public void addView(View child) { int tabCount = getTabCount(); int currentRowCount = (int) Math.ceil((double) tabCount / (double) mMaxTabCountOfRow); int rowCount = (int) Math.ceil((double) (tabCount + 1) / (double) mMaxTabCountOfRow); LinearLayout row = null; LayoutParams rlp = null; LayoutParams tlp = null; if (currentRowCount == rowCount) { row = (LinearLayout) getChildAt(this.getChildCount() - 1); } else { row = new LinearLayout(getContext()); super.addView(row); row.setFocusable(false); row.setClickable(false); row.setOnClickListener(null); row.setOnFocusChangeListener(null); row.setDividerDrawable(mVerticalDividerDrawable); row.setShowDividers(SHOW_DIVIDER_MIDDLE); row.setOrientation(HORIZONTAL); row.setTag(ROW_TAG); rlp = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); rlp.setMargins(0, 0, 0, 0); row.setLayoutParams(rlp); } super.addView(child); removeView(child); tlp = new LayoutParams(0, LayoutParams.MATCH_PARENT, 1.0f); tlp.setMargins(0, 0, 0, 0); row.addView(child, tlp); } @Override public void removeAllViews() { int count = getChildCount(); for (int i = 0; i < count; i++) { View view = getChildAt(i); Object tag = view.getTag(); if (tag != null && tag.toString().equals(ROW_TAG)) { ViewGroup row = (ViewGroup) getChildAt(i); row.removeAllViews(); } } super.removeAllViews(); } @Override public int getTabCount() { int tabCount = 0; int count = getChildCount(); for (int i = 0; i < count; i++) { View view = getChildAt(i); Object tag = view.getTag(); if (tag != null && tag.toString().equals(ROW_TAG)) { ViewGroup row = (ViewGroup) getChildAt(i); tabCount = tabCount + row.getChildCount(); } else { tabCount = tabCount + 1; } } return tabCount; } @Override public View getChildTabViewAt(int index) { View tab = null; int currentIndex = -1; int count = getChildCount(); for (int i = 0; i < count; i++) { View view = getChildAt(i); Object tag = view.getTag(); if (tag != null && tag.toString().equals(ROW_TAG)) { ViewGroup row = (ViewGroup) getChildAt(i); int tabCount = row.getChildCount(); for (int j = 0; j < tabCount; j++) { currentIndex = currentIndex + 1; if (currentIndex == index) { tab = row.getChildAt(j); break; } } } if (tab != null) break; } return tab; } @Override protected int getChildDrawingOrder(int childCount, int i) { return i; } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int measuredWidth = getMeasuredWidth(); int measuredHeight = getExplicitHeight(true); setMeasuredDimension(measuredWidth, measuredHeight); ViewGroup.MarginLayoutParams mlp = (ViewGroup.MarginLayoutParams) getLayoutParams(); if (mlp == null) return; int bottomMargin = 0; if (!mMultirow) bottomMargin = getExplicitHeight(false) - getExplicitHeight(true); if (mlp.bottomMargin != bottomMargin) { mlp.bottomMargin = bottomMargin; setLayoutParams(mlp); } } public boolean isMultirow() { return mMultirow; } public void setMultirow(boolean multirow) { if (mMultirow == multirow) return; mMultirow = multirow; new Handler().post(new Runnable() { @Override public void run() { setVisibility(INVISIBLE); requestLayout(); playAnim(); } }); } public int getMaxTabCountOfRow() { return mMaxTabCountOfRow; } public void setMaxTabCountOfRow(int maxTabCountOfRow) { if (getChildCount() == 0) mMaxTabCountOfRow = Math.max(maxTabCountOfRow, 1); } public Drawable getVerticalDividerDrawable() { return mVerticalDividerDrawable; } public void setVerticalDividerDrawable(Drawable drawable) { mVerticalDividerDrawable = drawable; } public void setVerticalDividerDrawable(int resId) { setVerticalDividerDrawable(getResources().getDrawable(resId)); } public int getExplicitHeight(boolean multirow) { int measuredHeight = 0; Drawable divider = getDividerDrawable(); int dividerHeight = (divider == null ? 0 : divider.getIntrinsicHeight()); int count = getChildCount(); if (!multirow) count = 1; for (int i = 0; i < count; i++) { View view = getChildAt(i); if (i > 0 && i < count) measuredHeight = measuredHeight + dividerHeight; measuredHeight = measuredHeight + view.getMeasuredHeight(); } return measuredHeight; } public int getSelectedTab() { Class<TabWidget> c = TabWidget.class; Field f = null; int selectedTab = -1; try { f = c.getDeclaredField("mSelectedTab"); } catch (NoSuchFieldException e) { e.printStackTrace(); } if (f != null) { f.setAccessible(true); try { Integer value = (Integer) f.get(this); if (value != null) selectedTab = value.intValue(); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } f.setAccessible(false); } return selectedTab; } protected void initTabWidget() { setGravity(Gravity.TOP | Gravity.CENTER_HORIZONTAL); setOrientation(VERTICAL); setShowDividers(SHOW_DIVIDER_MIDDLE); setStripEnabled(false); } protected void playAnim() { clearAnimation(); if (getChildCount() <= 1 || getParent() == null) return; int fromValue = 0; int toValue = 0; if (mMultirow) { fromValue = getExplicitHeight(true) - getExplicitHeight(false); toValue = 0; } else { fromValue = getExplicitHeight(false) - getExplicitHeight(true); toValue = 0; } TranslateAnimation anim = new TranslateAnimation(0, 0, fromValue, toValue); anim.setDuration(150L); anim.setAnimationListener(new TabWidgetAnimationListener(MultirowTabWidget.this)); startAnimation(anim); } private static class TabWidgetAnimationListener implements AnimationListener { private MultirowTabWidget mTabWidget = null; public TabWidgetAnimationListener(MultirowTabWidget tabWidget) { mTabWidget = tabWidget; } @Override public void onAnimationStart(Animation animation) { } @Override public void onAnimationEnd(Animation animation) { mTabWidget.setVisibility(VISIBLE); } @Override public void onAnimationRepeat(Animation animation) { } } }
main_tab_item_view.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@drawable/main_tab_selector" android:gravity="center" android:orientation="vertical" android:padding="@dimen/main_tab_image_padding"> <ImageView android:id="@+id/imageview" android:layout_width="@dimen/main_tab_image_size" android:layout_height="@dimen/main_tab_image_size" android:contentDescription="@string/imageview_description" android:focusable="false" android:scaleType="centerInside" /> <TextView android:id="@+id/textview" android:layout_width="wrap_content" android:layout_height="@dimen/main_tab_text_height" android:gravity="center_vertical" android:textSize="@dimen/main_tab_textview_text_size" android:textColor="@color/main_tab_text_selector" /> </LinearLayout>
activity_main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.thornbird.multirowtab.MainActivity" > <android.support.v4.app.FragmentTabHost android:id="@+id/tabhost" android:layout_width="match_parent" android:layout_height="match_parent"> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <FrameLayout android:id="@+id/realtabcontent" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" /> <FrameLayout android:id="@android:id/tabcontent" android:layout_width="0dp" android:layout_height="0dp" android:layout_weight="0" /> <com.thornbird.multirowtab.widget.MultirowTabWidget android:id="@android:id/tabs" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@color/main_tab_background" /> </LinearLayout> </android.support.v4.app.FragmentTabHost> </RelativeLayout>
MainActivity.java
package com.thornbird.multirowtab; import android.annotation.SuppressLint; import android.os.Bundle; import android.support.v4.app.FragmentActivity; import android.support.v4.app.FragmentTabHost; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.widget.ImageView; import android.widget.TextView; import android.widget.TabHost.OnTabChangeListener; import android.widget.TabHost.TabSpec; import com.thornbird.multirowtab.widget.MultirowTabWidget; import com.thornbird.multirowtab.R; public class MainActivity extends FragmentActivity { private static Class<?> sFragments[] = {FragmentPage.class, FragmentPage.class, FragmentPage.class, FragmentPage.class, FragmentPage.class, FragmentPage.class, FragmentPage.class, FragmentPage.class}; private static int sTabImages[] = {R.drawable.main_tab_cart_selector, R.drawable.main_tab_mail_selector, R.drawable.main_tab_search_selector, R.drawable.main_tab_more_selector, R.drawable.main_tab_user_selector, R.drawable.main_tab_history_selector, R.drawable.main_tab_chart_selector, R.drawable.main_tab_settings_selector}; private static String sTabIds[] = {"tab1", "tab2", "tab3", "tab4", "tab5", "tab6", "tab7", "tab8"}; private static String sTabTexts[] = {"购物车", "信息", "搜索", "更多", "账户", "历史", "统计", "设置"}; private LayoutInflater mLayoutInflater; private FragmentTabHost mTabHost; private MultirowTabWidget mTabWidget; private int mLastTabId = 0; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); findViews(); init(); setListeners(); } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.main, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { int id = item.getItemId(); if (id == R.id.action_settings) { return true; } return super.onOptionsItemSelected(item); } protected void findViews() { setContentView(R.layout.activity_main); mLayoutInflater = LayoutInflater.from(this); mTabHost = (FragmentTabHost)findViewById(R.id.tabhost); mTabHost.setup(this, getSupportFragmentManager(), R.id.realtabcontent); mTabWidget = (MultirowTabWidget) mTabHost.getTabWidget(); } protected void init() { mTabWidget.setMaxTabCountOfRow(4); mTabWidget.setDividerDrawable(R.drawable.tab_divider_horizontal); mTabWidget.setVerticalDividerDrawable(R.drawable.tab_divider_vertical); int count = sFragments.length; for (int i = 0; i < count; i++) { String tag = sTabIds[i]; TabSpec tabSpec = mTabHost.newTabSpec(tag).setIndicator(getTabItemView(i)); mTabHost.addTab(tabSpec, sFragments[i], null); } } protected void setListeners() { mTabHost.setOnTabChangedListener(new OnTabChangeListener() { @Override public void onTabChanged(String tabId) { if (tabId.equals(sTabIds[3])) { mTabHost.setCurrentTab(mLastTabId); mTabWidget.setMultirow(!mTabWidget.isMultirow()); } else { mLastTabId = mTabHost.getCurrentTab(); } } }); } @SuppressLint("InflateParams") private View getTabItemView(int index) { View view = mLayoutInflater.inflate(R.layout.main_tab_item_view, null); ImageView imageView = (ImageView) view.findViewById(R.id.imageview); imageView.setImageResource(sTabImages[index]); TextView textView = (TextView) view.findViewById(R.id.textview); textView.setText(sTabTexts[index]); return view; } }
MultirowTabWidget类只适用于Tab显示在底部的FragmentTabHost或TabHost中,为了实现单行与多行间切换的动画,我设置MultirowTabWidget的底边边距用来显示单行或多行。其实最初的版本我是控制MultirowTabWidget中除第一个LinearLayout之外的LinearLayout隐藏或显示来显示单行与多行,那样的话应该能放到顶部,但加切换动画就麻烦点了。
附件中的MultirowTab.zip是源码