转载:http://blog.csdn.net/maosidiaoxian/article/details/38864679
本文章的导航栏代码参考了viewpagerindicator的实现。本文叙述的是之前版本的qq或微信中,底部的图标加文字的导航栏的实现。
2014-09-14 13:59:42更新:library的代码已经从Demo中分离出来,见文末。
本例子依赖viewpagerindicator的两个接口:IconPagerAdapter及PageIndicator。这两个接口的方法如下:
- package com.viewpagerindicator;
- public interface IconPagerAdapter {
- int getIconResId(int index);
- int getCount();
- }
- package com.viewpagerindicator;
- import android.support.v4.view.ViewPager;
- public interface PageIndicator extends ViewPager.OnPageChangeListener {
- void setViewPager(ViewPager view);
- void setViewPager(ViewPager view, int initialPosition);
- void <span style="BACKGROUND-COLOR: #ffd700">setCurrent</span>Item(int item);
- void setOnPageChangeListener(ViewPager.OnPageChangeListener listener);
- void notifyDataSetChanged();
- }
在本例子中,我把这两个类单独拿出来了。如果你的项目已经有依赖该库,则就不需要再去复制它们。
下面先上两张效果图。
在图中,上面的内容区域是viewpager,下面的是导航栏indicator。点击导航栏可以切换上面的页面,当然,滑动上面的页面下面的导航栏也可以切换。
接着说一下它的实现。类的代码不复杂,大部分参照了viewpagerindicator中的TabPageIndicator类来实现,不过在这里我继承的是LinearLayout,代码如下:
- package com.githang.navigatordemo;
- import android.content.Context;
- import android.support.v4.view.PagerAdapter;
- import android.support.v4.view.ViewPager;
- import android.util.AttributeSet;
- import android.view.View;
- import android.view.ViewGroup;
- import android.widget.ImageView;
- import android.widget.LinearLayout;
- import android.widget.TextView;
- import com.viewpagerindicator.IconPagerAdapter;
- import com.viewpagerindicator.PageIndicator;
- import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
- /**
- * User: Geek_Soledad(msdx.android@qq.com)
- * Date: 2014-08-27
- * Time: 09:20
- * FIXME
- */
- public class IconTabPageIndicator extends LinearLayout implements PageIndicator {
- /**
- * Title text used when no title is provided by the adapter.
- */
- private static final CharSequence EMPTY_TITLE = "";
- /**
- * Interface for a callback when the selected tab has been reselected.
- */
- public interface OnTabReselectedListener {
- /**
- * Callback when the selected tab has been reselected.
- *
- * @param position Position of the current center item.
- */
- void onTabReselected(int position);
- }
- private Runnable mTabSelector;
- private final View.OnClickListener mTabClickListener = new View.OnClickListener() {
- public void onClick(View view) {
- TabView tabView = (TabView) view;
- final int oldSelected = mViewPager.getCurrentItem();
- final int newSelected = tabView.getIndex();
- mViewPager.<span style="BACKGROUND-COLOR: #ffd700">setCurrent</span>Item(newSelected, false);
- if (oldSelected == newSelected && mTabReselectedListener != null) {
- mTabReselectedListener.onTabReselected(newSelected);
- }
- }
- };
- private final LinearLayout mTabLayout;
- private ViewPager mViewPager;
- private ViewPager.OnPageChangeListener mListener;
- private int mSelectedTabIndex;
- private OnTabReselectedListener mTabReselectedListener;
- private int mTabWidth;
- public IconTabPageIndicator(Context context) {
- this(context, null);
- }
- public IconTabPageIndicator(Context context, AttributeSet attrs) {
- super(context, attrs);
- setHorizontalScrollBarEnabled(false);
- mTabLayout = new LinearLayout(context, null, R.attr.tabPageIndicator);
- addView(mTabLayout, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
- }
- public void setOnTabReselectedListener(OnTabReselectedListener listener) {
- mTabReselectedListener = listener;
- }
- @Override
- public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- final int widthMode = View.MeasureSpec.getMode(widthMeasureSpec);
- final boolean lockedExpanded = widthMode == View.MeasureSpec.EXACTLY;
- final int childCount = mTabLayout.getChildCount();
- if (childCount > 1 && (widthMode == MeasureSpec.EXACTLY || widthMode == MeasureSpec.AT_MOST)) {
- mTabWidth = MeasureSpec.getSize(widthMeasureSpec) / childCount;
- } else {
- mTabWidth = -1;
- }
- 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.
- <span style="BACKGROUND-COLOR: #ffd700">setCurrent</span>Item(mSelectedTabIndex);
- }
- }
- private 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;
- 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 void addTab(int index, CharSequence text, int iconResId) {
- final TabView tabView = new TabView(getContext());
- tabView.mIndex = index;
- tabView.setOnClickListener(mTabClickListener);
- tabView.setText(text);
- if (iconResId > 0) {
- tabView.setIcon(iconResId);
- }
- mTabLayout.addView(tabView, new LinearLayout.LayoutParams(0, MATCH_PARENT, 1));
- }
- @Override
- public void onPageScrollStateChanged(int arg0) {
- if (mListener != null) {
- mListener.onPageScrollStateChanged(arg0);
- }
- }
- @Override
- public void onPageScrolled(int arg0, float arg1, int arg2) {
- if (mListener != null) {
- mListener.onPageScrolled(arg0, arg1, arg2);
- }
- }
- @Override
- public void onPageSelected(int arg0) {
- <span style="BACKGROUND-COLOR: #ffd700">setCurrent</span>Item(arg0);
- if (mListener != null) {
- mListener.onPageSelected(arg0);
- }
- }
- @Override
- public void setViewPager(ViewPager view) {
- if (mViewPager == view) {
- return;
- }
- if (mViewPager != null) {
- mViewPager.setOnPageChangeListener(null);
- }
- final PagerAdapter adapter = view.getAdapter();
- if (adapter == null) {
- throw new IllegalStateException("ViewPager does not have adapter instance.");
- }
- mViewPager = view;
- view.setOnPageChangeListener(this);
- notifyDataSetChanged();
- }
- public void notifyDataSetChanged() {
- mTabLayout.removeAllViews();
- PagerAdapter adapter = mViewPager.getAdapter();
- IconPagerAdapter iconAdapter = null;
- if (adapter instanceof IconPagerAdapter) {
- iconAdapter = (IconPagerAdapter) adapter;
- }
- final int count = adapter.getCount();
- for (int i = 0; i < count; i++) {
- CharSequence title = adapter.getPageTitle(i);
- if (title == null) {
- title = EMPTY_TITLE;
- }
- int iconResId = 0;
- if (iconAdapter != null) {
- iconResId = iconAdapter.getIconResId(i);
- }
- addTab(i, title, iconResId);
- }
- if (mSelectedTabIndex > count) {
- mSelectedTabIndex = count - 1;
- }
- <span style="BACKGROUND-COLOR: #ffd700">setCurrent</span>Item(mSelectedTabIndex);
- requestLayout();
- }
- @Override
- public void setViewPager(ViewPager view, int initialPosition) {
- setViewPager(view);
- <span style="BACKGROUND-COLOR: #ffd700">setCurrent</span>Item(initialPosition);
- }
- @Override
- public void <span style="BACKGROUND-COLOR: #ffd700">setCurrent</span>Item(int item) {
- if (mViewPager == null) {
- throw new IllegalStateException("ViewPager has not been bound.");
- }
- mSelectedTabIndex = item;
- mViewPager.<span style="BACKGROUND-COLOR: #ff9632">setCurrent</span>Item(item, false);
- final int tabCount = mTabLayout.getChildCount();
- for (int i = 0; i < tabCount; i++) {
- final View child = mTabLayout.getChildAt(i);
- final boolean isSelected = (i == item);
- child.setSelected(isSelected);
- if (isSelected) {
- animateToTab(item);
- }
- }
- }
- @Override
- public void setOnPageChangeListener(ViewPager.OnPageChangeListener listener) {
- mListener = listener;
- }
- private class TabView extends LinearLayout {
- private int mIndex;
- private ImageView mImageView;
- private TextView mTextView;
- public TabView(Context context) {
- super(context, null, R.attr.tabView);
- View view = View.inflate(context, R.layout.tab_view, null);
- mImageView = (ImageView) view.findViewById(R.id.tab_image);
- mTextView = (TextView) view.findViewById(R.id.tab_text);
- this.addView(view);
- }
- @Override
- public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- // Re-measure if we went beyond our maximum size.
- if (mTabWidth > 0) {
- super.onMeasure(MeasureSpec.makeMeasureSpec(mTabWidth, MeasureSpec.EXACTLY),
- heightMeasureSpec);
- }
- }
- public void setText(CharSequence text) {
- mTextView.setText(text);
- }
- public void setIcon(int resId) {
- if (resId > 0) {
- mImageView.setImageResource(resId);
- }
- }
- public int getIndex() {
- return mIndex;
- }
- }
- }
改动的地方主要是增加一个表示导航栏按钮宽度的变量,以及导航栏的view的实现,及两个onMeasure方法。由于在这里我继承的是LinearLayout,也就是当导航栏栏目较多时,不会通过左右滑动来显示或隐藏其他按钮,而是直接平分,该部分的代码如下:
- public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- final int widthMode = View.MeasureSpec.getMode(widthMeasureSpec);
- final boolean lockedExpanded = widthMode == View.MeasureSpec.EXACTLY;
- final int childCount = mTabLayout.getChildCount();
- if (childCount > 1 && (widthMode == MeasureSpec.EXACTLY || widthMode == MeasureSpec.AT_MOST)) {
- mTabWidth = MeasureSpec.getSize(widthMeasureSpec) / childCount;
- } else {
- mTabWidth = -1;
- }
- 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.
- <span style="BACKGROUND-COLOR: #ffd700">setCurrent</span>Item(mSelectedTabIndex);
- }
- }
当导航按钮大于1个时,直接平分。每个导航按钮的宽度即为mTabWidth。
然后重写TabView的onMeasure方法,当mTabWidth大于0时,设置它的宽度为mTabWidth,如下:
- @Override
- public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- // Re-measure if we went beyond our maximum size.
- if (mTabWidth > 0) {
- super.onMeasure(MeasureSpec.makeMeasureSpec(mTabWidth, MeasureSpec.EXACTLY),
- heightMeasureSpec);
- }
- }
在这里的TabView中,我则直接使用布局文件来写,上面是一个ImageView,下面是一个TextView,代码如下(tab_view.xml):
- <?xml version="1.0" encoding="utf-8"?>
- <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:orientation="vertical"
- android:layout_width="match_parent"
- android:background="@android:color/white"
- android:gravity="center_horizontal"
- android:addStatesFromChildren="true"
- android:layout_height="wrap_content">
- <ImageView
- android:id="@+id/tab_image"
- android:layout_width="27dp"
- android:layout_marginTop="2dp"
- android:adjustViewBounds="true"
- android:contentDescription="@null"
- android:layout_height="27dp" />
- <TextView
- android:id="@+id/tab_text"
- android:layout_marginTop="2dp"
- android:gravity="center_horizontal|bottom"
- android:padding="2dp"
- android:layout_width="wrap_content"
- android:textColor="@color/tab_text_selector"
- android:textSize="12sp"
- android:layout_height="match_parent" />
- </LinearLayout>
再看TabView内部类的构造方法代码:
- private class TabView extends LinearLayout {
- public TabView(Context context) {
- super(context, null, R.attr.tabView);
- View view = View.inflate(context, R.layout.tab_view, null);
- mImageView = (ImageView) view.findViewById(R.id.tab_image);
- mTextView = (TextView) view.findViewById(R.id.tab_text);
- this.addView(view);
- }
- }
TabView是继承自LinearLayout,然后通过布局文件tab_view获取一个view,并将它加到TabView当中。但是我们并没有定义TabView本身的布局参数,所以加到它里面的view并不是居中的,而是靠左。所以我们还需要设置这个TabView的参数,通过我们定义的属性R.attr.tabView,然后调用它父类的构造方法super(context, null, R.attr.tabView)。
在IconTabPageIndicator的构造方法当中,你同样可以看到导航栏的容器——mTabLayout,同样是通过属性来创建的。如下代码:
- public IconTabPageIndicator(Context context, AttributeSet attrs) {
- super(context, attrs);
- setHorizontalScrollBarEnabled(false);
- mTabLayout = new LinearLayout(context, null, R.attr.tabPageIndicator);
- addView(mTabLayout, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
- }
定义属性的方法如下,先在res/values下新建一个attrs.xml的文件,然后加入以下内容:
- <?xml version="1.0" encoding="utf-8"?>
- <resources>
- <declare-styleable name="TabView">
- <attr name="tabPageIndicator" format="reference" />
- <attr name="tabView" format="reference" />
- </declare-styleable>
- </resources>
即在这里声明两个属性,一个是tabPageIndicator,另一个是tabView,它们都是引用类型的。
但仅仅这样还是不够的,因为我们只是声明了两个属性,并没有设定属性的具体内容,所以我们还需要在styles.xml文件当中设置我们的主题,代码如下(styles.xml):
- <style name="AppTheme" parent="Theme.AppCompat.Light">
- <item name="tabView">@style/TabView</item>
- <item name="tabPageIndicator">@style/TabIndicator</item>
- </style>
- <style name="TabIndicator"/>
- <style name="TabView">
- <item name="android:addStatesFromChildren">true</item>
- <item name="android:orientation">vertical</item>
- <item name="android:gravity">bottom|center_horizontal</item>
- <item name="android:layout_width">0dp</item>
- <item name="android:background">@android:color/white</item>
- <item name="android:layout_height">match_parent</item>
- </style>
到此,我们的IconTabPageIndicator就实现好了。接下来在我们的程序中使用它:
activity的布局文件(activity_my.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=".MyActivity">
- <com.githang.navigatordemo.IconTabPageIndicator
- android:id="@+id/indicator"
- android:layout_alignParentBottom="true"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"/>
- <android.support.v4.view.ViewPager
- android:layout_above="@id/indicator"
- android:id="@+id/view_pager"
- android:layout_width="match_parent"
- android:layout_height="match_parent"/>
- </RelativeLayout>
fragment的布局文件(fragment.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"
- android:paddingLeft="@dimen/activity_horizontal_margin"
- android:paddingRight="@dimen/activity_horizontal_margin"
- android:paddingTop="@dimen/activity_vertical_margin"
- android:gravity="center"
- android:paddingBottom="@dimen/activity_vertical_margin"
- android:background="#eee"
- tools:context=".MyActivity">
- <TextView
- android:id="@+id/text"
- android:textAppearance="@android:style/TextAppearance.Large"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"/>
- </RelativeLayout>
MyActivity类的代码:
- package com.githang.navigatordemo;
- import android.os.Bundle;
- import android.support.v4.app.Fragment;
- import android.support.v4.app.FragmentActivity;
- import android.support.v4.app.FragmentManager;
- import android.support.v4.app.FragmentPagerAdapter;
- import android.support.v4.view.ViewPager;
- import com.viewpagerindicator.IconPagerAdapter;
- import java.util.ArrayList;
- import java.util.List;
- public class MyActivity extends FragmentActivity {
- private ViewPager mViewPager;
- private IconTabPageIndicator mIndicator;
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_my);
- initViews();
- }
- private void initViews() {
- mViewPager = (ViewPager) findViewById(R.id.view_pager);
- mIndicator = (IconTabPageIndicator) findViewById(R.id.indicator);
- List<BaseFragment> fragments = initFragments();
- FragmentAdapter adapter = new FragmentAdapter(fragments, getSupportFragmentManager());
- mViewPager.setAdapter(adapter);
- mIndicator.setViewPager(mViewPager);
- }
- private List<BaseFragment> initFragments() {
- List<BaseFragment> fragments = new ArrayList<BaseFragment>();
- BaseFragment userFragment = new BaseFragment();
- userFragment.setTitle("用户");
- userFragment.setIconId(R.drawable.tab_user_selector);
- fragments.add(userFragment);
- BaseFragment noteFragment = new BaseFragment();
- noteFragment.setTitle("记事本");
- noteFragment.setIconId(R.drawable.tab_record_selector);
- fragments.add(noteFragment);
- BaseFragment contactFragment = new BaseFragment();
- contactFragment.setTitle("联系人");
- contactFragment.setIconId(R.drawable.tab_user_selector);
- fragments.add(contactFragment);
- BaseFragment recordFragment = new BaseFragment();
- recordFragment.setTitle("记录");
- recordFragment.setIconId(R.drawable.tab_record_selector);
- fragments.add(recordFragment);
- return fragments;
- }
- class FragmentAdapter extends FragmentPagerAdapter implements IconPagerAdapter {
- private List<BaseFragment> mFragments;
- public FragmentAdapter(List<BaseFragment> fragments, FragmentManager fm) {
- super(fm);
- mFragments = fragments;
- }
- @Override
- public Fragment getItem(int i) {
- return mFragments.get(i);
- }
- @Override
- public int getIconResId(int index) {
- return mFragments.get(index).getIconId();
- }
- @Override
- public int getCount() {
- return mFragments.size();
- }
- @Override
- public CharSequence getPageTitle(int position) {
- return mFragments.get(position).getTitle();
- }
- }
- }
BaseFragment类的代码:
- package com.githang.navigatordemo;
- import android.os.Bundle;
- import android.support.annotation.Nullable;
- import android.support.v4.app.Fragment;
- import android.view.LayoutInflater;
- import android.view.View;
- import android.view.ViewGroup;
- import android.widget.TextView;
- /**
- * User: Geek_Soledad(msdx.android@qq.com)
- * Date: 2014-08-27
- * Time: 09:01
- * FIXME
- */
- public class BaseFragment extends Fragment {
- private String title;
- private int iconId;
- public String getTitle() {
- return title;
- }
- public void setTitle(String title) {
- this.title = title;
- }
- public int getIconId() {
- return iconId;
- }
- public void setIconId(int iconId) {
- this.iconId = iconId;
- }
- @Override
- public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
- View view = inflater.inflate(R.layout.fragment, null, false);
- TextView textView = (TextView) view.findViewById(R.id.text);
- textView.setText(getTitle());
- return view;
- }
- }
项目代码下载地址:http://zdz.la/xvS4Ab
修订版下载地址:http://download.csdn.net/detail/maosidiaoxian/7913269
git 代码地址:http://git.oschina.net/msdx/IconTabPageIndicator/tree/1.0
最新代码已经将library的代码分离出来: CSDN传送门