android.widget.TabWidget组件继承自LinearLayout,默认水平布局横向显示多个Tab(改垂直布局似乎就不能正常显示了)。如果要显示多行,每行水平分布若干个Tab,TabWidget是无能为力的。我继承TabWidget实现了一个MultirowTabWidget类,可以显示多行Tab。效果图如下。
图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是源码