项目中经常会用到类似今日头条中顶部的导航指示器,我也经常用一个类似的库PagerSlidingTabStrip,但是有时并不能小伙伴们的所有需求,所以我在这个类的基础上就所有能用到的情况做了一个简单的封装。大家知道做一个功能比较简单,但是封装好几种功能到一个类里面就需要处理的好多逻辑了,所以对于小编这种小白也是花了好久的业余时间才搞完的,希望大家能够多多支持,更希望我的绵薄之力能够帮助大家。源码和Demo已经上传到github了,欢迎大家多多fork和star。 
github地址:https://github.com/shanyao0/TabPagerIndicatorDemo

好了废话不多说,直接上图来看下效果吧。

六种效果图

一:MODE_WEIGHT_NOEXPAND_SAME

几个标题均分宽度,不能扩展,底部导航线跟标题宽度一致

二:MODE_WEIGHT_NOEXPAND_NOSAME

几个标题均分宽度,不能扩展,底部导航线跟标题宽度不一致

三:MODE_NOWEIGHT_NOEXPAND_SAME

标题不均分宽度,不能扩展,底部导航线跟标题宽度一致

四:MODE_NOWEIGHT_NOEXPAND_NOSAME

标题不均分宽度,不能扩展,底部导航线跟标题宽度不一致

五:MODE_NOWEIGHT_EXPAND_SAME

标题不均分宽度,能扩展,底部导航线跟标题宽度一致

六:MODE_NOWEIGHT_EXPAND_NOSAME

标题不均分宽度,能扩展,底部导航线跟标题宽度不一致

使用方法

一般来说这个类是ViewPager+TabPagerIndicator+Fragment来使用的

1. 关联类库

首先,下载我上面的TabPagerIndicatorDemo,然后将里面的tabpagerindicator类库import Module到你的项目,并关联

2. xml布局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#fff"
android:orientation="vertical"> <shanyao.tabpagerindictor.TabPageIndicator
android:id="@+id/indicator"
android:layout_width="match_parent"
android:layout_height="40dp"
/>
<android.support.v4.view.ViewPager
android:id="@+id/viewPager"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#fff" />
</LinearLayout>
3. 代码使用
    @Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.viewpager_indicator);
indicator = (TabPageIndicator)findViewById(R.id.indicator);
viewPager = (ViewPager)findViewById(R.id.viewPager);
BasePagerAdapter adapter = new BasePagerAdapter(getSupportFragmentManager()); viewPager.setAdapter(adapter);// 设置adapter
indicator.setViewPager(viewPager);// 绑定indicator setTabPagerIndicator();
}
/**
* 通过一些set方法,设置控件的属性
*/
private void setTabPagerIndicator() {
indicator.setIndicatorMode(TabPageIndicator.IndicatorMode.MODE_WEIGHT_NOEXPAND_SAME);// 设置模式,一定要先设置模式
indicator.setDividerColor(Color.parseColor("#00bbcf"));// 设置分割线的颜色
indicator.setDividerPadding(10);//设置
indicator.setIndicatorColor(Color.parseColor("#43A44b"));// 设置底部导航线的颜色
indicator.setTextColorSelected(Color.parseColor("#43A44b"));// 设置tab标题选中的颜色
indicator.setTextColor(Color.parseColor("#797979"));// 设置tab标题未被选中的颜色
indicator.setTextSize(16);// 设置字体大小
}

常用方法说明

setIndicatorMode()//设置控件的模式,上面是提到的6种模式
setDividerColor()//设置两个标题之间的竖直分割线的颜色,如果不需要显示这个,设置颜色为透明即可
setDividerPadding()//设置中间竖线上下的padding值
setIndicatorColor()//设置底部导航线的颜色,就是上面演示图的绿色导航线
setIndicatorHeight()// 设置底部导航线的高度
setDividerPadding()// 设置Tab标题之间的间距
setTextColorSelected()//设置tab标题选中的颜色
setTextColor()//设置tab标题未被选中的颜色
setTextSize()//设置字体的大小
setUnderlineColor()// 设置最下面一条的横线的颜色
setUnderlineHeight()//设置最下面一条的横线的高度
setScrollOffset()// 这个方法是当选择MODE_NOWEIGHT_EXPAND_NOSAME和MODE_NOWEIGHT_EXPAND_SAME这两个模式的时候有作用
具体作用大家,可以下载Demo自己试一试

可能还有一些不是常用的方法,大家可以自己下载Demo去试试

实现步骤和原理

这里带大家简单的分析一下原理,具体的大家可以下载源码自己研究下。
  • 创建TabPagerIndicator类继承HorizontalScrollView

    这个主要是当顶部标题超过整个屏幕的时候,我们可以滑动它,主要是后两种模式会用到 
    这里会提供一些属性让我们设置,我们可以通过set方法设置

  • 创建一个LinearLayout来维护容纳几个TextView标题

 tabsContainer = new LinearLayout(context);
tabsContainer.setOrientation(LinearLayout.HORIZONTAL);
LayoutParams layoutParams = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
tabsContainer.setLayoutParams(layoutParams);
addView(tabsContainer);//将这个线性布局添加到TabPagerIndicator
  • 给TabPagerIndicator设置ViewPager
    public void setViewPager(ViewPager pager) {
this.pager = pager; if (pager.getAdapter() == null) {
throw new IllegalStateException("ViewPager does not have adapter instance.");
} pager.setOnPageChangeListener(pageListener); notifyDataSetChanged();
}
    通过这个方法,将ViewPager和TabPagerIndicator给关联起来,并实现联动效果。拿到ViewPager的对象我们就可以获取它的adapter从而我们可以通过adapter里面的方法获取tab的标题。我们可以设置监听器,通过监听器,我们可以根据ViewPager的移动来移动我们的TabPagerIndicator。
  • LinearLayout里面添加TextView
         // 看看这里我们就用到了那个ViewPager的对象pager
tabCount = pager.getAdapter().getCount();
for (int i = 0; i < tabCount; i++) {
// 循环遍历给TextView设置文字和属性
addTextTab(i, pager.getAdapter().getPageTitle(i).toString());
} private void addTextTab(final int position, String title) { TextView tab = new TextView(getContext());
tab.setText(title);
tab.setFocusable(true);
tab.setGravity(Gravity.CENTER);
tab.setSingleLine();
tab.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
pager.setCurrentItem(position);
}
});
if (isExpand && !isExpandSameLine) {// 注意这里跟我们的几种模式有关
tab.setPadding(tabPadding, 0, tabPadding, 0);
} else {// 其实模式一和模式二就一个Padding和Margin的区别
wrapTabLayoutParams.setMargins(tabPadding, 0, tabPadding, 0);
weightTabLayoutParams.setMargins(tabPadding, 0, tabPadding, 0);
}
tabsContainer.addView(tab, position, isSameLine ? wrapTabLayoutParams : weightTabLayoutParams);
}
  • 开始onDraw里面画 
    这个里面主要是利用drawRect方法,画一些矩形线条
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (isInEditMode() || tabCount == 0) {
return;
} final int height = getHeight(); /**
* draw底部的导航线
*/ rectPaint.setColor(indicatorColor);
View currentTab = tabsContainer.getChildAt(currentPosition);
float currentOffWid;
if (isExpand) {
currentOffWid = 0;
} else {
currentOffWid = (currentTab.getWidth() - widths[currentPosition]) / 2;
} float lineLeft = currentTab.getLeft() + currentOffWid;
float lineRight = currentTab.getRight() - currentOffWid; if (currentPositionOffset > 0f && currentPosition < tabCount - 1) {
View nextTab = tabsContainer.getChildAt(currentPosition + 1);
float nextOffWid; //nextOffWid
if (isExpand) {//如果是可扩展的,尤其是模式6,这个值必须为0
nextOffWid = 0;
} else {//差值的计算,widths是一个数组,存取了几个TextView的宽度,后面具体会说
nextOffWid = (nextTab.getWidth() - widths[currentPosition + 1]) / 2;
}
// getLeft和getRight方法,是获取的相对于父类即LinearLayout的位置
final float nextTabLeft = nextTab.getLeft() + nextOffWid;
final float nextTabRight = nextTab.getRight() - nextOffWid;
// currentPositionOffset是一个0到1之间的变化值,
// 在OnPageChangeListener的方法里我们可以获取到,
lineLeft = (currentPositionOffset * nextTabLeft + (1f - currentPositionOffset) * lineLeft);
lineRight = (currentPositionOffset * nextTabRight + (1f - currentPositionOffset) * lineRight);
}
//这里的这个判断是经过我多次实验得出的。。。
if (currentIndicatorMode == IndicatorMode.MODE_NOWEIGHT_NOEXPAND_NOSAME){
canvas.drawRect(lineLeft-tabPadding, height - indicatorHeight, lineRight+tabPadding, height, rectPaint);
}else{
canvas.drawRect(lineLeft, height - indicatorHeight, lineRight, height, rectPaint);
}
/**
* draw underline
*/ rectPaint.setColor(underlineColor);
canvas.drawRect(0, height - underlineHeight, tabsContainer.getWidth(), height, rectPaint); /**
* draw divider:分割线
*/
dividerPaint.setColor(dividerColor);
for (int i = 0; i < tabCount - 1; i++) {
View tab = tabsContainer.getChildAt(i);
if (!isExpandSameLine) {
canvas.drawLine(tab.getRight(), dividerPadding, tab.getRight(), height - dividerPadding, dividerPaint);
} else {
canvas.drawLine(tab.getRight() + tabPadding, dividerPadding, tab.getRight() + tabPadding, height - dividerPadding, dividerPaint);
}
}
}
// 几个变量值得说明 /**
* nextOffWid:导航线和文字宽度长的差距
* 后面我们的left+它,right-它,我们就可以实现 导航线跟文字一样长了
*/
图示

  • OnPageChangeListener和ScrollTo决定联动 
 private class PageListener implements OnPageChangeListener {

        @Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
// positionOffset这变量的值会随着ViewPager的移动从0到1变化
currentPosition = position;
currentPositionOffset = positionOffset;
Log.e("shanyao", positionOffset + "");
scrollToChild(position, (int) (positionOffset * tabsContainer.getChildAt(position).getWidth()));
invalidate();
} @Override
public void onPageScrollStateChanged(int state) {
if (state == ViewPager.SCROLL_STATE_IDLE) {
scrollToChild(pager.getCurrentItem(), 0);
}
} @Override
public void onPageSelected(int position) {
//使当前item高亮
for (int i = 0; i < tabCount; i++) {
View v = tabsContainer.getChildAt(i);
if (v instanceof TextView) {
TextView textView = (TextView) v;
textView.setTextColor(i == pager.getCurrentItem() ? tabTextColorSelected : tabTextColor);
}
}
}
} private void scrollToChild(int position, int offset) {
if (tabCount == 0 || offset == 0) {
return;
} int newScrollX = tabsContainer.getChildAt(position).getLeft() + offset; if (position > 0 || offset > 0) {
newScrollX -= scrollOffset;
} if (newScrollX != lastScrollX) {
lastScrollX = newScrollX;
scrollTo(newScrollX, 0);
} }

总结

从效果图的展示,到使用,再到原理分析,我相信小伙们已经对这个库有了大致的了解,有你需要的模式,直接拿去用就行,类库很小就一个类和一个attr文件,使用起来很简单的。有能力有兴趣的可以多看看源码,自己可以根据自己的需求在完善下。第一次写开源的小项目,虽然很简单,但是我也经过了很多的设计和调试才写出来的,其中可能好多的缺陷,希望大家多多指教,我会第一时间改掉的。以后我也会带给大家一些比较实用的、比较常用的的小Demo的。希望大家能够多多支持我,去我的github上面多多star和fork,您的支持就是我最大的动力,谢谢大家。。。

github地址:https://github.com/shanyao0/TabPagerIndicatorDemo

感谢分享

仿今日头条最强顶部导航指示器,支持6种模式-b的更多相关文章

  1. iOS仿今日头条滑动导航

    之前写了篇博客网易首页导航封装类.网易首页导航封装类优化,今天在前两个的基础上仿下今日头条. 1.网易首页导航封装类中主要解决了上面导航的ScrollView和下面的页面的ScrollView联动的问 ...

  2. [Android] Android 手机下 仿 今日头条 新闻客户端

    利用一个月的时间,自学了 Android 开发 ,为了检验学习成果,特意 开发了这个  仿 今日头条 新闻客户端 AppNews 包括图文新闻+视频新闻+图片新闻 预览演示如下: 功能说明: 1)底部 ...

  3. vue 仿今日头条

    vue 仿今日头条 为了增加移动端项目的经验,近一周通过 vue 仿写今日头条,以下就项目实现过程中遇到的问题以及解决方法给出总结,有什么不正确的地方,恳请大家批评指正^ _ ^!,代码仓库地址为 g ...

  4. vue2.0仿今日头条开源项目

    vue-toutiao 这是用 vue.js 2.0 高仿 今日头条 的移动端项目,结合了原生app的部分功能以及网页版. 前言 本人是 今日头条 的重度用户,在学习vue.js过程中,在GitHub ...

  5. Android 仿今日头条频道管理(下)(GridView之间Item的移动和拖拽)

    前言 上篇博客我们说到了今日头条频道管理的操作交互体验,我也介绍了2个GridView之间Item的相互移动.详情请參考:Android 仿今日头条频道管理(上)(GridView之间Item的移动和 ...

  6. android仿今日头条App、多种漂亮加载效果、选择器汇总、记事本App、Kotlin开发等源码

    Android精选源码 android漂亮的加载效果 android各种 选择器 汇总源码 Android仿bilibili搜索框效果 Android记事本app.分类,涂鸦.添加图片或者其他附件 仿 ...

  7. 自适应 Tab 宽度可以滑动文字逐渐变色的 TabLayout(仿今日头条顶部导航)

    TabLayout相信大家都用过,2015年Google大会上发布了新的Android Support Design库里面包含了很多新的控件,其中就包含TabLayout,它可以配合ViewPager ...

  8. Android之仿今日头条顶部导航栏效果

    随着时间的推移现在的软件要求显示的内容越来越多,所以要在小的屏幕上能够更好的显示更多的内容,首先我们会想到底部菜单栏,但是有时候像今日头条新闻客户端要显示的内容太多,而且又想在主界面全部显示出来,所以 ...

  9. Android 仿今日头条频道管理(上)(GridView之间Item的移动和拖拽)

    前言 常常逛今日头条.发现它的频道管理功能做的特别赞.交互体验很好.如图: watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fo ...

随机推荐

  1. Javascript学习总结三(Array对象的用法)

    javascript Array对象的常用API 1:concat concat() 方法用于连接两个或多个数组.该方法不会改变现有的数组,而仅仅会返回被连接数组的一个副本.举例:var a1 = [ ...

  2. centos_Error: Protected multilib versions_解决方法

    在yum命令后面加入忽略参数:--setopt=protected_multilib=false you can also use --setopt=protected_multilib=false ...

  3. Lodop实现web套打

    WEB套打可选方案不多,理想的更少,利用免费控件Lodop+JavaScript实现精确套打,算是较为经典的选择.这种方案其实比较简单,利用一个htm文件就可以实现模板设计过程,几乎是“空手套”式的开 ...

  4. 支持状态对象复用的RPC框架——SnakeRPC

    SnakeRPC是我2年前(春节期间!)做的一个RPC框架,现与大家分享. 设计SnakeRPC的主要动机是,Hessian返回的状态对象(如:数据库连接对象.文件对象等)无法复用,而且它对Strea ...

  5. VS2012无法创建项目:未找到与约束……匹配的导出

    故障情况:7月10号后用VS2012创建项目时,弹出如下对话框,无法创建新项目: 而后经网络搜索确定是7月10号更新了系统补丁后造成的 解决方案: 1.卸载这两个补丁后重启电脑: 2.到http:// ...

  6. 牛客_剑指offer_重建二叉树,再后续遍历_递归思想_分两端

       总结:    重建二叉树:其实就是根据前序和中序重建得到二叉树,得到后续,只要输出那边设置输出顺序即可 [编程题]重建二叉树 输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树.假设输入的 ...

  7. 第二十六篇、因为自定item(nav)而使系统右滑返回手势失效的解决方法

    @interface ViewController () <uigesturerecognizerdelegate> @end@implementation ViewController ...

  8. java_集合框架

    一.集合框架图 二.Collection接口     Collection中可以存储的元素间无序,可以重复的元素.     Collection接口的子接口List和Set,Map不是Collecti ...

  9. Oracle PL/SQL 事物处理 银行转账

    Oracle数据库中的事务处理:添加,修改,删除时需要使用事务处理(显示事务). 1.事务的分类显示事务(添加,修改,删除)和隐式事务(除了添加,修改,删除). 2.事务的执行方式:自动提交(jdbc ...

  10. ZOJ 1122 Clock(模拟)

    Clock Time Limit: 2 Seconds      Memory Limit: 65536 KB You are given a standard 12-hour clock with ...