Android开发技巧——实现底部图标文字的导航栏(已更新)
本文章的导航栏代码参考了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开发技巧——实现底部图标文字的导航栏(已更新)相关推荐
- Android开发笔记(二十)顶部导航栏ActionBar
标题栏ActionBar ActionBar是在Android3.0之后引入的,所以Android2.x之前的版本不能直接使用ActionBar.现在ActionBar广泛用做APP的顶部导航栏,它在 ...
- Android开发技巧——自定义控件之自定义属性
Android开发技巧--自定义控件之自定义属性 掌握自定义控件是很重要的,因为通过自定义控件,能够:解决UI问题,优化布局性能,简化布局代码. 上一篇讲了如何通过xml把几个控件组织起来,并继承某个 ...
- Android开发技巧——大图裁剪
本篇内容是接上篇<Android开发技巧--定制仿微信图片裁剪控件> 的,先简单介绍对上篇所封装的裁剪控件的使用,再详细说明如何使用它进行大图裁剪,包括对旋转图片的裁剪. 裁剪控件的简单使 ...
- Android开发技巧——自定义控件之组合控件
Android开发技巧--自定义控件之组合控件 我准备在接下来一段时间,写一系列有关Android自定义控件的博客,包括如何进行各种自定义,并分享一下我所知道的其中的技巧,注意点等. 还是那句老话,尽 ...
- 一些很不错的Android开发技巧
一些很不错的Android开发技巧,这个项目翻译自 android-tips-tricks 去掉了一些我认为不重要的,对我使用过的东东做了评价,同时翻译了一些自己没有注意到的知识点的文章. ❤️ st ...
- android 字体倒影,Android开发中怎么实现一个文字倒影效果
Android开发中怎么实现一个文字倒影效果 发布时间:2020-11-25 17:18:19 来源:亿速云 阅读:140 作者:Leah 这期内容当中小编将会给大家带来有关Android开发中怎么实 ...
- android开发技巧杂谈
android开发技巧一 android的一些常用包是发布在国外的,所以一些包,我们下载不下来,我们可以使用阿里云的镜像地址(maven { url 'https://maven.aliyun.com ...
- 社区说|常用 Android 开发技巧
活动时间 4月7日(本周四) 20:00-21:00 活动日程 20:00-20:45 主题分享 常用 Android 开发技巧 李老师的开发技巧私房菜,一定有你没吃过的菜! 重构技巧 常用插件 阅读 ...
- 【Android开发】使用Bottom Navigation Activity去掉顶部栏(标题栏)
[Android开发]使用Bottom Navigation Activity去掉顶部栏(标题栏) 在使用android自带的模板Botton Navigation Activity后,页面顶部 ...
- dedeCMS修改文案:页眉rss文字、导航栏“首页”、页脚copyright等
首页rss文案等等(如下图)的信息修改: 所需修改文件: 首先,查找路径:C:\wamp64\www\templets\default\下的文件,如下: footer.htm footer_m.htm ...
最新文章
- openoj的一个小比赛(J题解题报告)poj1703(并查集)
- c语言结构体共用体枚举实例程序,10-C语言结构体-共用体-枚举
- EEGNet: 神经网络应用于脑电信号
- 基于MATLAB的SUI信道模型的理论分析与仿真
- 《算法》学习笔记2.1 初级排序算法
- JavaWeb学习之路——SSM框架之Spring(四)
- 【2011-04-06】SQL Server 2000 日志传送搭建
- QML的import目录爬坑记录
- Linux版本tomcat下载及安装
- [每天一个知识点]14-Java语言-字符串拼接
- 票价最低10元 北京大兴国际机场线票价方案正式启用
- color2gray 的实现
- 【动态规划】P1220:区间dp:关路灯
- python扩展库xlwt支持对_python第三方库——xlrd和xlwt操作Excel文件学习
- mvc2 在 .net 4.0 下的ValidateInput(false) 无效
- GoPose人工智能运动分析软件
- 不管是蓝牙耳机还是有线耳机长时间佩戴都是有危害的,这些问题不容小觑!
- Android NFC标签读写 配置 过滤器总结 各类NFC数据类型NfcA NfcB IsoDep MifareClassic读取
- 一、vmware的安装
- 用计算机弹起风了歌词,《起风了》歌词