仿今日头条最强顶部导航指示器,支持6种模式-b

项目中经常会用到类似今日头条中顶部的导航指示器,我也经常用一个类似的库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

感谢分享

时间: 2024-10-07 05:18:09

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

iOS仿今日头条滑动导航

之前写了篇博客网易首页导航封装类.网易首页导航封装类优化,今天在前两个的基础上仿下今日头条. 1.网易首页导航封装类中主要解决了上面导航的ScrollView和下面的页面的ScrollView联动的问题,以及上面导航栏的便宜量. 2.网易首页导航封装类优化中主要解决iOS7以上滑动返回功能中UIScreenEdgePanGestureRecognizer与ScrollView的滑动的手势冲突问题. 今天仿今日头条滑动导航和网易首页导航封装类优化相似,这个也是解决手势冲突,UIPanGesture

[转]灯灯小程序开发手记:仿今日头条(上)

本文转自:http://www.jianshu.com/p/a1e0b8abb12d 写在前面 新的一年,祝大家新年快乐!当然对于程序员来说,新的一年,也要有新的改变.因此灯灯决定凑热闹编写微信小程序啦! 上一篇文章<记一次小程序开发过程>中,灯灯大致写了下自己第一次开发小程序的感受和流程.这一次灯灯会详细记录下自己制作一个小程序的思路.遇到的问题.涉及到的代码等和大家分享.    视频教程地址:http://study.163.com/course/introduction.htm?cour

仿今日头条和qq侧滑和智慧北京的小项目 3

仿今日头条和QQ侧滑和智慧北京的小项目3 本项目图片素材均来自今日头条,QQ侧滑没有使用Android原生的NavigationDrawer,而使用的是第三方SlidingMenu,原因是这个控件暂时没有仔细研究(后期会研究并写demo),项目整体可以说是使用了一个Activity加多个Fragment,全部采用沉寂式. 前文摘要:仿今日头条和QQ侧滑和智慧北京的小项目2 TabPager(NewsPager新闻页面对应的11个子页面) 此页面相对比较复杂,所以单独用一篇blog来说明都处理了哪

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

vue-toutiao 这是用 vue.js 2.0 高仿 今日头条 的移动端项目,结合了原生app的部分功能以及网页版. 前言 本人是 今日头条 的重度用户,在学习vue.js过程中,在GitHub上看到了很多高仿webapp的好项目.由此在有了一定的技术积累后,开始构思使用Vue写今日头条,一是自己对于头条的喜爱,另外也是对于自己学习成果的检验. 技术栈 vue.js 2.0全家桶(vue.vuex.vue-router) axios.jsonp element-ui.iview vue-l

基于Vue 2.0高仿 &lt;今日头条&gt; 单页应用。

这是用 vue.js 2.0 高仿 今日头条 的移动端项目,结合了原生app的部分功能以及网页版. 技术栈 vue.js 2.0全家桶(vue.vuex.vue-router) axios.jsonp element-ui.iview vue-lazyload.animate.css.moment.flexible.js 在线地址 线上地址(预览地址) GitHub源码地址 说明 项目内定死 账号: admin, 密码: admin. 因为数据原因,首页请求的数据接口来自网页版今日头条,修改了一

仿今日头条的graidview拖动

下面先上这次实现功能的效果图:(注:这个效果图没有拖拽的时候移动动画,DEMO里面有,可以下载看看) 三.开发思路 1.  获取数据库中频道的列表,如果为空,赋予默认列表,并存入数据库,之后通过对应的适配器赋给对应的GridView 2.  2个GridView--(1.DragGrid   2. OtherGridView) DragGrid 用于显示我的频道,带有长按拖拽效果 OtherGridView用于显示更多频道,不带推拽效果 注:由于屏幕大小不一定,外层使用ScrollView,所以

iOS新闻应用源码,高仿今日头条源码等

iOS精选源码 城市列表选择 一款非常时尚的照片选择插件 优酷播放按钮动画 BRPickerView是iOS的选择器组件,主要包括:日期选择器.时... 选择位置坐下动画Demo BAButton 图片.文字.倒计时等 git 功能最全的 button 分类 企业级完整iOS项目-<新闻来了> 较为美观的多级展开列表 高仿今日头条6.2.6 Swift 简单画板的swift实现 iOS优质博客 创建一个私有的 Pods 详解 前言骚年,你听说过组件化吗?没有?但你一定玩过乐高玩具,乐高玩具本身

项目记录,仿今日头条app

项目记录,仿今日头条app,五六月份主要做的项目,第一版已经完成上架,二次开发正在进行中

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

前言 上篇博客我们说到了今日头条频道管理的操作交互体验,我也介绍了2个GridView之间Item的相互移动.详情请參考:Android 仿今日头条频道管理(上)(GridView之间Item的移动和拖拽) 今天把相对照较复杂的gridView的拖拽也记录下.在開始之前我们事先要了解下Android的事件分发机制.网上这方面的资料也比較多.由于自己定义控件大部分要用到事件分发机制的知识. 实现思路 要实现Item的拖拽.事实上并非真正要去拖拽GridView的Item.而是使用WindowMan