本文章的导航栏代码参考了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;}@Overridepublic 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);}@Overridepublic void onAttachedToWindow() {super.onAttachedToWindow();if (mTabSelector != null) {// Re-post the selector we savedpost(mTabSelector);}}@Overridepublic 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));}@Overridepublic void onPageScrollStateChanged(int arg0) {if (mListener != null) {mListener.onPageScrollStateChanged(arg0);}}@Overridepublic void onPageScrolled(int arg0, float arg1, int arg2) {if (mListener != null) {mListener.onPageScrolled(arg0, arg1, arg2);}}@Overridepublic void onPageSelected(int arg0) {<span style="BACKGROUND-COLOR: #ffd700">setCurrent</span>Item(arg0);if (mListener != null) {mListener.onPageSelected(arg0);}}@Overridepublic 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();}@Overridepublic void setViewPager(ViewPager view, int initialPosition) {setViewPager(view);<span style="BACKGROUND-COLOR: #ffd700">setCurrent</span>Item(initialPosition);}@Overridepublic 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);}}}@Overridepublic 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);}@Overridepublic 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,如下:

        @Overridepublic 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"><ImageViewandroid:id="@+id/tab_image"android:layout_width="27dp"android:layout_marginTop="2dp"android:adjustViewBounds="true"android:contentDescription="@null"android:layout_height="27dp" /><TextViewandroid: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.IconTabPageIndicatorandroid:id="@+id/indicator"android:layout_alignParentBottom="true"android:layout_width="match_parent"android:layout_height="wrap_content"/><android.support.v4.view.ViewPagerandroid: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"><TextViewandroid: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;@Overrideprotected 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;}@Overridepublic Fragment getItem(int i) {return mFragments.get(i);}@Overridepublic int getIconResId(int index) {return mFragments.get(index).getIconId();}@Overridepublic int getCount() {return mFragments.size();}@Overridepublic 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;}@Overridepublic 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传送门

Android开发技巧——实现底部图标文字的导航栏(已更新)相关推荐

  1. Android开发笔记(二十)顶部导航栏ActionBar

    标题栏ActionBar ActionBar是在Android3.0之后引入的,所以Android2.x之前的版本不能直接使用ActionBar.现在ActionBar广泛用做APP的顶部导航栏,它在 ...

  2. Android开发技巧——自定义控件之自定义属性

    Android开发技巧--自定义控件之自定义属性 掌握自定义控件是很重要的,因为通过自定义控件,能够:解决UI问题,优化布局性能,简化布局代码. 上一篇讲了如何通过xml把几个控件组织起来,并继承某个 ...

  3. Android开发技巧——大图裁剪

    本篇内容是接上篇<Android开发技巧--定制仿微信图片裁剪控件> 的,先简单介绍对上篇所封装的裁剪控件的使用,再详细说明如何使用它进行大图裁剪,包括对旋转图片的裁剪. 裁剪控件的简单使 ...

  4. Android开发技巧——自定义控件之组合控件

    Android开发技巧--自定义控件之组合控件 我准备在接下来一段时间,写一系列有关Android自定义控件的博客,包括如何进行各种自定义,并分享一下我所知道的其中的技巧,注意点等. 还是那句老话,尽 ...

  5. 一些很不错的Android开发技巧

    一些很不错的Android开发技巧,这个项目翻译自 android-tips-tricks 去掉了一些我认为不重要的,对我使用过的东东做了评价,同时翻译了一些自己没有注意到的知识点的文章. ❤️ st ...

  6. android 字体倒影,Android开发中怎么实现一个文字倒影效果

    Android开发中怎么实现一个文字倒影效果 发布时间:2020-11-25 17:18:19 来源:亿速云 阅读:140 作者:Leah 这期内容当中小编将会给大家带来有关Android开发中怎么实 ...

  7. android开发技巧杂谈

    android开发技巧一 android的一些常用包是发布在国外的,所以一些包,我们下载不下来,我们可以使用阿里云的镜像地址(maven { url 'https://maven.aliyun.com ...

  8. 社区说|常用 Android 开发技巧

    活动时间 4月7日(本周四) 20:00-21:00 活动日程 20:00-20:45 主题分享 常用 Android 开发技巧 李老师的开发技巧私房菜,一定有你没吃过的菜! 重构技巧 常用插件 阅读 ...

  9. 【Android开发】使用Bottom Navigation Activity去掉顶部栏(标题栏)

    [Android开发]使用Bottom Navigation Activity去掉顶部栏(标题栏)    在使用android自带的模板Botton Navigation Activity后,页面顶部 ...

  10. dedeCMS修改文案:页眉rss文字、导航栏“首页”、页脚copyright等

    首页rss文案等等(如下图)的信息修改: 所需修改文件: 首先,查找路径:C:\wamp64\www\templets\default\下的文件,如下: footer.htm footer_m.htm ...

最新文章

  1. openoj的一个小比赛(J题解题报告)poj1703(并查集)
  2. c语言结构体共用体枚举实例程序,10-C语言结构体-共用体-枚举
  3. EEGNet: 神经网络应用于脑电信号
  4. 基于MATLAB的SUI信道模型的理论分析与仿真
  5. 《算法》学习笔记2.1 初级排序算法
  6. JavaWeb学习之路——SSM框架之Spring(四)
  7. 【2011-04-06】SQL Server 2000 日志传送搭建
  8. QML的import目录爬坑记录
  9. Linux版本tomcat下载及安装
  10. [每天一个知识点]14-Java语言-字符串拼接
  11. 票价最低10元 北京大兴国际机场线票价方案正式启用
  12. color2gray 的实现
  13. 【动态规划】P1220:区间dp:关路灯
  14. python扩展库xlwt支持对_python第三方库——xlrd和xlwt操作Excel文件学习
  15. mvc2 在 .net 4.0 下的ValidateInput(false) 无效
  16. GoPose人工智能运动分析软件
  17. 不管是蓝牙耳机还是有线耳机长时间佩戴都是有危害的,这些问题不容小觑!
  18. Android NFC标签读写 配置 过滤器总结 各类NFC数据类型NfcA NfcB IsoDep MifareClassic读取
  19. 一、vmware的安装
  20. 用计算机弹起风了歌词,《起风了》歌词

热门文章

  1. 20200210——springboot lombok
  2. 没有Realek高清音频管理器,Win10系统没有声音怎么办?
  3. 不积跬步,无以至千里
  4. Java通过代理服务器上网
  5. 计算机网络4小时速成:应用层,cs模型,p2p模型,DNS域名系统,文件传输协议FTP,电子邮件SMTP,万维网HTTP,动态主机配置协议DHCP
  6. Android中获取视频的第一帧图片的三种方法
  7. 帝国cms框架的Webshell
  8. 云计算数据中心运维管理的五大重点
  9. lsf服务器窗口显示,LSF指定进程提交到指定QUEUES中
  10. Virtual Dub——一个令人爱不释手的小工具