Android开发之自定义ViewPager三角形指示器

最近公司的产品要进行改版,其中涉及到了登陆页面的UI,需要把注册和登陆功能换成ViewPager的页面切换,本想利用下原生自带的TabLayout作为ViewPager指示器就完事了,谁知UI设计在指示器上多了个小三角,虽然网上已经有封装的很完善的ViewPager指示器,不过这个小东西确实没什么难度,就没必要引入第三方了,顺手自己写了个,先来看下实现效果:

ViewPager三角形指示器

我准备从三部分来讲
1、ViewPager指示器的简单实现
2、ViewPager指示器的完整封装
3、ViewPager指示器的封装使用(两行代码完成)

1、ViewPager指示器的实现

这部分其实很简单,我们先不考虑其他过多的干扰因素,我们就单纯的把它当成一个可跟随ViewPager滑动的标题栏,来看下这张图:


简易版ViewPager指示器拆分图

实现思路:
1、首先这个页面是由一个横向排列的线性布局LinearLayout和ViewPager组成,其中这个线性布局LinearLayout里包含了多个大小一样的TextView。
2、再来我们去监听这个ViewPager的滑动事件把对应位置上的文字进行高亮显示即可。
3、然后我们要做的就是在这个线性布局LinearLayou里去绘制小三角形,让其也跟随着ViewPager的移动,位置上的相关数据我们在监听ViewPager的滑动事件里的回调方法中就可以得到,所以应该没什么大问题。

接下来我们分析下这个三角形的绘制:
所需要的数据:三角形的底边宽,高度,绘制的起始位置
为了能够适应不同屏幕大小,这里我们就不对三角形的底边宽和高度进行固定值写死了,我们通过测量的方式去得到,然后用闭合路径Path去勾勒。
三角形的底边宽:我们取Tab宽度的六分之一(屏幕宽度/可见Tab数量/6)
三角形的高度:我们取底边宽的一半(屏幕宽度/可见Tab数量/6/2)
三角形绘制的起始位置:第一个Tab的中点减去底边宽度的一半(屏幕宽度/可见Tab数量/2-屏幕宽度/可见Tab数量/6/2)
附加:怎么确定三角形跟随着ViewPager滑动的位置呢?
在ViewPager的滑动监听回调接口中的onPageScrolled里的positionOffset参数已经给我们提供好了,它代表着页面滑动的偏移值,区间在[0,1),也就是当前页面的滑动距离为:Tab的宽度*偏移值,这样三角形的某事某刻的x轴位置也就确定了,即初始位置+偏移距离

        /**
         * This method will be invoked when the current page is scrolled, either as part
         * of a programmatically initiated smooth scroll or a user initiated touch scroll.
         *
         * @param position Position index of the first page currently being displayed.
         *                 Page position+1 will be visible if positionOffset is nonzero.
         * @param positionOffset Value from [0, 1) indicating the offset from the page at position.
         * @param positionOffsetPixels Value in pixels indicating the offset from position.
         */
        void onPageScrolled(int position, float positionOffset, int positionOffsetPixels);

这里是简单版本的代码实现(固定三个Tab页):

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <com.lcw.viewpagertriangleindicator.ViewPagerTriangleIndicator
        android:id="@+id/vpti_main_tab"
        android:layout_width="match_parent"
        android:layout_height="45dp"
        android:background="@color/colorAccent"
        android:orientation="horizontal">

        <TextView
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:gravity="center"
            android:text="新闻"
            android:textColor="#FFFFFF" />

        <TextView
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:gravity="center"
            android:text="音乐"
            android:textColor="#FFFFFF" />

        <TextView
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:gravity="center"
            android:text="游戏"
            android:textColor="#FFFFFF" />

    </com.lcw.viewpagertriangleindicator.ViewPagerTriangleIndicator>

    <android.support.v4.view.ViewPager
        android:id="@+id/vp_main_content"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</LinearLayout>
package com.lcw.viewpagertriangleindicator;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.WindowManager;
import android.widget.LinearLayout;

/**
 * 自定义ViewPager指示器(三角形)
 * Create by: chenwei.li
 * Date: 2017/7/16
 * Time: 下午1:39
 * Email: lichenwei.me@foxmail.com
 */

public class ViewPagerTriangleIndicator extends LinearLayout {

    private int mTriangleWidth;//三角形底边宽
    private int mTriangleHeigh;//三角形高度
    private int mTriangleInitPos;//三角形起始点
    private int mTriangleMoveWidth;//三角形移动偏移

    private Paint mPaint;
    private Path mPath;

    public ViewPagerTriangleIndicator(Context context) {
        super(context, null);
    }

    public ViewPagerTriangleIndicator(Context context, AttributeSet attrs) {
        super(context, attrs);
        initPaint();
    }

    /**
     * 初始化画笔
     */
    private void initPaint() {
        mPaint = new Paint();
        mPaint.setColor(Color.parseColor("#FFFFFF"));
        mPaint.setAntiAlias(true);
        mPaint.setStyle(Paint.Style.FILL);
    }

    /**
     * 初始化三角形
     */
    private void initTriangle() {
        mPath = new Path();
        mPath.moveTo(0, 0);
        mPath.lineTo(mTriangleWidth, 0);
        mPath.lineTo(mTriangleWidth / 2, -mTriangleHeigh);
        mPath.close();
    }


    /**
     * 当布局大小发生变化时回调
     *
     * @param w
     * @param h
     * @param oldw
     * @param oldh
     */
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mTriangleWidth = w / 3 / 6;
        mTriangleHeigh = mTriangleWidth / 2 - 12;
        mTriangleInitPos = getScreenWidth() / 3 / 2 - mTriangleWidth / 2;
        initTriangle();
    }

    /**
     * 绘制子View
     *
     * @param canvas
     */
    @Override
    protected void dispatchDraw(Canvas canvas) {
        super.dispatchDraw(canvas);
        canvas.translate(mTriangleInitPos + mTriangleMoveWidth, getHeight());
        canvas.drawPath(mPath, mPaint);
    }

    /**
     * 监听ViewPager滑动,联动Indicator
     *
     * @param position
     * @param positionOffset
     */
    protected void scroll(int position, float positionOffset) {
        int tabWidth = getScreenWidth() / 3;
        mTriangleMoveWidth = (int) (tabWidth * position + tabWidth * positionOffset);
        invalidate();
    }

    /**
     * 获取屏幕宽度
     *
     * @return
     */
    private int getScreenWidth() {
        WindowManager windowManager = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
        DisplayMetrics displayMetrics = new DisplayMetrics();
        windowManager.getDefaultDisplay().getMetrics(displayMetrics);
        return displayMetrics.widthPixels;
    }
}
package com.lcw.viewpagertriangleindicator;

import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentPagerAdapter;
import android.support.v4.view.ViewPager;
import android.support.v7.app.AppCompatActivity;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * ViewPgaer三角形指示器演示
 * Create by: chenwei.li
 * Date: 2017/7/16
 * Time: 下午1:54
 * Email: lichenwei.me@foxmail.com
 */
public class MainActivity extends AppCompatActivity {

    private ViewPager mViewPager;
    private ViewPagerTriangleIndicator mViewPagerTriangleIndicator;

    private List<String> mTitles = Arrays.asList("新闻", "音乐", "游戏");
    private List<Fragment> mFragments = new ArrayList<Fragment>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mViewPager = (ViewPager) findViewById(R.id.vp_main_content);
        mViewPagerTriangleIndicator = (ViewPagerTriangleIndicator) findViewById(R.id.vpti_main_tab);
        //创建Fragment
        for (String title : mTitles) {
            SimpleFragmet simpleFragmet = SimpleFragmet.newInstance(title);
            mFragments.add(simpleFragmet);
        }
        //设置适配器
        mViewPager.setAdapter(new FragmentPagerAdapter(getSupportFragmentManager()) {
            @Override
            public Fragment getItem(int position) {
                return mFragments.get(position);
            }

            @Override
            public int getCount() {
                return mFragments.size();
            }
        });
        //添加滑动监听
        mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
            @Override
            public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
                mViewPagerTriangleIndicator.scroll(position, positionOffset);
            }

            @Override
            public void onPageSelected(int position) {

            }

            @Override
            public void onPageScrollStateChanged(int state) {

            }
        });
    }
}

看下效果图:

简单版本ViewPager指示器实现

2、ViewPager指示器的封装

在上面我们已经实现了简单版本的ViewPager带三角形的滑动指示器实现,接下来我们需要对其进行封装,当它变得更加简单易用。
首先我们来分析下简单版本的不足:
1、Tab数量是固定的,每次都需要在XML里编写TextView,太过繁琐,当Tab数量很多的时候,并且weight都是1时,就会挤在一屏幕里。
2、调用太复杂,需要在Activity里去实现滑动监听,并调用三角形联动。
3、其他一些小细节,比如当Tab页只有1页或者2页的时候,指示器三角形会变得很大,影响美观等。
带着这些问题,我们开启ViewPagerTriangleIndicator的优化之旅吧!

首先我们需要一个当前页面可显示的最大Tab数,也就是屏幕可见区域的最大Tab数,这里引入我们的自定义属性(关于自定义属性的使用,这里就不再做过多阐述):

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <attr name="visible_tab_num" format="integer" />
    <declare-styleable name="ViewPagerTriangleIndicator">
        <attr name="visible_tab_num" />
    </declare-styleable>
</resources>
    /**
     * 获取自定义属性值(获取xml设置最大可见Tab数量)
     *
     * @param context
     * @param attrs
     */
    private void initAttr(Context context, AttributeSet attrs) {
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.ViewPagerTriangleIndicator);
        if (typedArray != null) {
            mVisibleTabNum = typedArray.getInt(R.styleable.ViewPagerTriangleIndicator_visible_tab_num, VISIBLE_COUNT_NUM);
        }
    }

通过上面的代码我们就可以拿到在XML文件中visible_tab_num的值了,根据这个值我们按照上文提到的思路就可以得到一些数据,比如三角形的底边宽,高度,起始位置等。
需要注意的是当前的Tab的宽度就需要动态去计算了,我们根据屏幕宽度/可见Tab数,就可以得到每个Tab的宽度,我们在XML加载完毕的时候去进行动态的修改。

    /**
     * 在XML布局加载完毕后回调
     */
    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        //根据可显示的Tab数量动态去改变Tab的宽度
        int totalTabNum = getChildCount();
        if (mVisibleTabNum != 0 && totalTabNum != 0) {
            for (int i = 0; i < totalTabNum; i++) {
                View view = getChildAt(i);
                LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) view.getLayoutParams();
                layoutParams.weight = 0;
                layoutParams.width = getScreenWidth() / mVisibleTabNum;
                view.setLayoutParams(layoutParams);
            }

        }
    }

还有,我们还需要对scroll方法进行一些改造:

    /**
     * 监听ViewPager滑动,联动Indicator
     *
     * @param position
     * @param positionOffset
     */
    protected void scroll(int position, float positionOffset) {
        int tabWidth = getScreenWidth() / mVisibleTabNum;
        mTriangleMoveWidth = (int) (tabWidth * position + tabWidth * positionOffset);

        if ((mVisibleTabNum - 2) <= position && positionOffset > 0 && getChildCount() > mVisibleTabNum) {
            this.scrollTo((int) ((position - (mVisibleTabNum - 2)) * tabWidth + tabWidth * positionOffset), 0);
        }

        invalidate();
    }

当Tab数量超出一页的时候,随着三角形的滑动我们也需要对Tab进行一个滑动处理,这里举个例子:
当屏幕可见Tab数为4时,那么Tab从第3个滑动到第4个的时候,需要同时把第1个Tab往左移出屏幕,此时滑动距离相对于整个指示器为1个Tab的距离。
当屏幕可见Tab数为4时,那么Tab从第4个滑动到第5个的时候,需要同时把第2个Tab往左移出屏幕,此时滑动距离相对于整个指示器为2个Tab的距离。
哈哈,是不是有点绕,没想明白的朋友拿笔在纸上吧,上面的判断也是由此得出。

好了,接下来就要开始解决我们的问题了。
解决问题1:
既然ViewPager指示器是继承于线性布局LinearLayout,那也就是ViewGroup,我们很自然的可以想到addView这个方法,所以我们只需要让其接收一个List<String>的集合对象并根据集合元素的文字信息动态添加子View即可,实现代码如下:

    /**
     * 设置指示器标题并给标题添加监听事件
     *
     * @param titles
     */
    public void setPageTitle(List<String> titles) {
        this.mTitles = titles;
        if (mTitles != null && mTitles.size() > 0) {
            removeAllViews();
            for (int i = 0; i < mTitles.size(); i++) {
                TextView textView = new TextView(getContext());
                LinearLayout.LayoutParams layoutParams = new LayoutParams(getScreenWidth() / mVisibleTabNum, LayoutParams.MATCH_PARENT);
                layoutParams.width = getScreenWidth() / mVisibleTabNum;
                textView.setText(mTitles.get(i));
                textView.setGravity(Gravity.CENTER);
                textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 16);
                textView.setTextColor(Color.parseColor("#FFFFFF"));
                textView.setLayoutParams(layoutParams);
                addView(textView);
            }
        }
    }

解决问题2:
我们可以在ViewPager指示器内部去持有外部ViewPager的引用并且去实现滑动监听即可:

    /**
     * 绑定ViewPager
     *
     * @param viewPager
     */
    public void setViewPagerWithIndicator(ViewPager viewPager) {
        this.mViewPager = viewPager;
        mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
            @Override
            public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
                if (mAddPageChangeListener != null) {
                    mAddPageChangeListener.onPageScrolled(position, positionOffset, positionOffsetPixels);
                }
                scroll(position, positionOffset);
            }

            @Override
            public void onPageSelected(int position) {
                setInitPageTitlesColor();
                setPageTitleHighColor(position);
                mViewPager.setCurrentItem(position);
                if (mAddPageChangeListener != null) {
                    mAddPageChangeListener.onPageSelected(position);
                }
            }

            @Override
            public void onPageScrollStateChanged(int state) {
                if (mAddPageChangeListener != null) {
                    mAddPageChangeListener.onPageScrollStateChanged(state);
                }
            }
        });

    }

这里大家可能会注意到mAddPageChangeListener这个变量,这里是重写了原生自带addOnPageChangeListener的一个回调函数,因为我们在内部去实现了addOnPageChangeListener监听,当外部的ViewPager再去实现滑动监听时,此时就会把我们内部的实现方法覆盖,也就是会导致 scroll(position, positionOffset);方法失效,所以这里我们需要再去定义一个滑动监听器即可,并且提供注册监听器方法。

    /**
     * 复制官方addOnPageChangeListener对应的接口方法
     * Callback interface for responding to changing state of the selected page.
     */
    public interface AddPageChangeListener {

        /**
         * This method will be invoked when the current page is scrolled, either as part
         * of a programmatically initiated smooth scroll or a user initiated touch scroll.
         *
         * @param position             Position index of the first page currently being displayed.
         *                             Page position+1 will be visible if positionOffset is nonzero.
         * @param positionOffset       Value from [0, 1) indicating the offset from the page at position.
         * @param positionOffsetPixels Value in pixels indicating the offset from position.
         */
        void onPageScrolled(int position, float positionOffset, int positionOffsetPixels);

        /**
         * This method will be invoked when a new page becomes selected. Animation is not
         * necessarily complete.
         *
         * @param position Position index of the new selected page.
         */
        void onPageSelected(int position);

        /**
         * Called when the scroll state changes. Useful for discovering when the user
         * begins dragging, when the pager is automatically settling to the current page,
         * or when it is fully stopped/idle.
         *
         * @param state The new scroll state.
         * @see ViewPager#SCROLL_STATE_IDLE
         * @see ViewPager#SCROLL_STATE_DRAGGING
         * @see ViewPager#SCROLL_STATE_SETTLING
         */
        void onPageScrollStateChanged(int state);
    }
    /**
     * 避免用户监听ViewPager复写了addOnPageChangeListener使得三角形滑动效果失效
     *
     * @param addPageChangeListener
     */
    public void addPageChangeListener(AddPageChangeListener addPageChangeListener) {
        this.mAddPageChangeListener = addPageChangeListener;
    }

解决问题3:
这里我们只需要去定义一个三角形底部宽度的默认最大值即可,这个默认值可以是屏幕宽度的三分之一的六分之一,也就是当有3个Tab时候,三角形底部的宽度。

  private int DEFAULT_TRIANGLE_WIDTH = getScreenWidth() / 3 / 6;//最大三角形底边宽度
       if (mTriangleWidth > DEFAULT_TRIANGLE_WIDTH) {
            mTriangleWidth = DEFAULT_TRIANGLE_WIDTH;
        }

再来我们还可以去做一些事情,比如设置Tab的文字默认颜色,高亮颜色,监听事件等。
这里我把完整的ViewPagerTriangleIndicator代码贴一下:

package com.lcw.viewpagertriangleindicator;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.support.v4.view.ViewPager;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.View;
import android.view.WindowManager;
import android.widget.LinearLayout;
import android.widget.TextView;

import java.util.List;

/**
 * 自定义ViewPager指示器(三角形)
 * Create by: chenwei.li
 * Date: 2017/7/16
 * Time: 下午1:39
 * Email: lichenwei.me@foxmail.com
 */

public class ViewPagerTriangleIndicator extends LinearLayout {

    private int mTriangleWidth;//三角形底边宽
    private int mTriangleHeigh;//三角形高度
    private int mTriangleInitPos;//三角形起始点
    private int mTriangleMoveWidth;//三角形移动偏移
    private int DEFAULT_TRIANGLE_WIDTH = getScreenWidth() / 3 / 6;//最大三角形底边宽度

    private Paint mPaint;
    private Path mPath;

    private int mVisibleTabNum;//最大可显示Tab数量值
    private static final int VISIBLE_COUNT_NUM = 4;//默认可显示的TAB数量为4个

    private List<String> mTitles;//保存指示器标题


    private ViewPager mViewPager;//次有外部ViewPager引用
    private AddPageChangeListener mAddPageChangeListener;

    public ViewPagerTriangleIndicator(Context context) {
        super(context, null);
    }

    public ViewPagerTriangleIndicator(Context context, AttributeSet attrs) {
        super(context, attrs);
        initAttr(context, attrs);
        initPaint();
    }

    /**
     * 获取自定义属性值(获取xml设置最大可见Tab数量)
     *
     * @param context
     * @param attrs
     */
    private void initAttr(Context context, AttributeSet attrs) {
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.ViewPagerTriangleIndicator);
        if (typedArray != null) {
            mVisibleTabNum = typedArray.getInt(R.styleable.ViewPagerTriangleIndicator_visible_tab_num, VISIBLE_COUNT_NUM);
        }
    }

    /**
     * 初始化画笔
     */
    private void initPaint() {
        mPaint = new Paint();
        mPaint.setColor(Color.parseColor("#FFFFFF"));
        mPaint.setAntiAlias(true);
        mPaint.setStyle(Paint.Style.FILL);
    }

    /**
     * 初始化三角形
     */
    private void initTriangle() {
        mPath = new Path();
        mPath.moveTo(0, 0);
        mPath.lineTo(mTriangleWidth, 0);
        mPath.lineTo(mTriangleWidth / 2, -mTriangleHeigh);
        mPath.close();
    }


    /**
     * 当布局大小发生变化时回调
     *
     * @param w
     * @param h
     * @param oldw
     * @param oldh
     */
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mTriangleWidth = w / mVisibleTabNum / 6;
        if (mTriangleWidth > DEFAULT_TRIANGLE_WIDTH) {
            mTriangleWidth = DEFAULT_TRIANGLE_WIDTH;
        }
        mTriangleHeigh = mTriangleWidth / 2 - 8;
        mTriangleInitPos = w / mVisibleTabNum / 2 - mTriangleWidth / 2;
        initTriangle();
    }

    /**
     * 在XML布局加载完毕后回调
     */
    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        //根据可显示的Tab数量动态去改变Tab的宽度
        int totalTabNum = getChildCount();
        if (mVisibleTabNum != 0 && totalTabNum != 0) {
            for (int i = 0; i < totalTabNum; i++) {
                View view = getChildAt(i);
                LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) view.getLayoutParams();
                layoutParams.weight = 0;
                layoutParams.width = getScreenWidth() / mVisibleTabNum;
                view.setLayoutParams(layoutParams);
            }

        }

    }

    /**
     * 绘制子View
     *
     * @param canvas
     */
    @Override
    protected void dispatchDraw(Canvas canvas) {
        super.dispatchDraw(canvas);
        canvas.translate(mTriangleInitPos + mTriangleMoveWidth, getHeight());
        canvas.drawPath(mPath, mPaint);
    }

    /**
     * 监听ViewPager滑动,联动Indicator
     *
     * @param position
     * @param positionOffset
     */
    protected void scroll(int position, float positionOffset) {
        int tabWidth = getScreenWidth() / mVisibleTabNum;
        mTriangleMoveWidth = (int) (tabWidth * position + tabWidth * positionOffset);

        if ((mVisibleTabNum - 2) <= position && positionOffset > 0 && getChildCount() > mVisibleTabNum) {
            this.scrollTo((int) ((position - (mVisibleTabNum - 2)) * tabWidth + tabWidth * positionOffset), 0);
        }

        invalidate();
    }

    /**
     * 获取屏幕宽度
     *
     * @return
     */
    private int getScreenWidth() {
        WindowManager windowManager = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
        DisplayMetrics displayMetrics = new DisplayMetrics();
        windowManager.getDefaultDisplay().getMetrics(displayMetrics);
        return displayMetrics.widthPixels;
    }

    /**
     * 设置指示器标题并给标题添加监听事件
     *
     * @param titles
     */
    public void setPageTitle(List<String> titles) {
        this.mTitles = titles;
        if (mTitles != null && mTitles.size() > 0) {
            removeAllViews();
            for (int i = 0; i < mTitles.size(); i++) {
                final int pageIndex = i;
                TextView textView = new TextView(getContext());
                LinearLayout.LayoutParams layoutParams = new LayoutParams(getScreenWidth() / mVisibleTabNum, LayoutParams.MATCH_PARENT);
                layoutParams.width = getScreenWidth() / mVisibleTabNum;
                textView.setText(mTitles.get(i));
                textView.setGravity(Gravity.CENTER);
                textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 16);
                textView.setTextColor(Color.parseColor("#FFFFFF"));
                textView.setLayoutParams(layoutParams);
                textView.setOnClickListener(new OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        //点击高亮文本
                        setInitPageTitlesColor();
                        ((TextView) v).setTextColor(Color.parseColor("#FFFFFF"));
                        mViewPager.setCurrentItem(pageIndex);
                    }
                });
                addView(textView);
            }
        }
        setInitPageTitlesColor();
        setPageTitleHighColor(0);
    }

    /**
     * 把所有标题颜色置为不高亮
     */
    private void setInitPageTitlesColor() {
        int totalView = getChildCount();
        for (int i = 0; i < totalView; i++) {
            View view = getChildAt(i);
            if (view instanceof TextView) {
                ((TextView) view).setTextColor(Color.parseColor("#70FFFFFF"));
            }
        }
    }

    /**
     * 设置标题高亮
     *
     * @param pos
     */
    private void setPageTitleHighColor(int pos) {
        View view = getChildAt(pos);
        if (view instanceof TextView) {
            ((TextView) view).setTextColor(Color.parseColor("#FFFFFF"));
        }

    }


    /**
     * 绑定ViewPager
     *
     * @param viewPager
     */
    public void setViewPagerWithIndicator(ViewPager viewPager) {
        this.mViewPager = viewPager;
        mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
            @Override
            public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
                if (mAddPageChangeListener != null) {
                    mAddPageChangeListener.onPageScrolled(position, positionOffset, positionOffsetPixels);
                }
                scroll(position, positionOffset);
            }

            @Override
            public void onPageSelected(int position) {
                setInitPageTitlesColor();
                setPageTitleHighColor(position);
                mViewPager.setCurrentItem(position);
                if (mAddPageChangeListener != null) {
                    mAddPageChangeListener.onPageSelected(position);
                }
            }

            @Override
            public void onPageScrollStateChanged(int state) {
                if (mAddPageChangeListener != null) {
                    mAddPageChangeListener.onPageScrollStateChanged(state);
                }
            }
        });

    }

    /**
     * 避免用户监听ViewPager复写了addOnPageChangeListener使得三角形滑动效果失效
     *
     * @param addPageChangeListener
     */
    public void addPageChangeListener(AddPageChangeListener addPageChangeListener) {
        this.mAddPageChangeListener = addPageChangeListener;
    }

    /**
     * 复制官方addOnPageChangeListener对应的接口方法
     * Callback interface for responding to changing state of the selected page.
     */
    public interface AddPageChangeListener {

        /**
         * This method will be invoked when the current page is scrolled, either as part
         * of a programmatically initiated smooth scroll or a user initiated touch scroll.
         *
         * @param position             Position index of the first page currently being displayed.
         *                             Page position+1 will be visible if positionOffset is nonzero.
         * @param positionOffset       Value from [0, 1) indicating the offset from the page at position.
         * @param positionOffsetPixels Value in pixels indicating the offset from position.
         */
        void onPageScrolled(int position, float positionOffset, int positionOffsetPixels);

        /**
         * This method will be invoked when a new page becomes selected. Animation is not
         * necessarily complete.
         *
         * @param position Position index of the new selected page.
         */
        void onPageSelected(int position);

        /**
         * Called when the scroll state changes. Useful for discovering when the user
         * begins dragging, when the pager is automatically settling to the current page,
         * or when it is fully stopped/idle.
         *
         * @param state The new scroll state.
         * @see ViewPager#SCROLL_STATE_IDLE
         * @see ViewPager#SCROLL_STATE_DRAGGING
         * @see ViewPager#SCROLL_STATE_SETTLING
         */
        void onPageScrollStateChanged(int state);
    }
}

3、ViewPager指示器的使用

经过我们封装之后,我们的调用就非常简单了,只需要短短的2行代码:

      //设置指示器标题
      mViewPagerTriangleIndicator.setPageTitle(mTitles);
      //绑定ViewPager
      mViewPagerTriangleIndicator.setViewPagerWithIndicator(mViewPager);

好了,文章到这里就结束了,由于篇幅限制,这里不能对一些东西讲的太细,比如一些自定义View的基础,大家自行查阅相关资料哈。

源码下载:

这里附上源码地址(欢迎Star,欢迎Fork):源码下载

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 156,265评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,274评论 1 288
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,087评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,479评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,782评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,218评论 1 207
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,594评论 2 309
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,316评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,955评论 1 237
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,274评论 2 240
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,803评论 1 255
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,177评论 2 250
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,732评论 3 229
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,953评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,687评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,263评论 2 267
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,189评论 2 258

推荐阅读更多精彩内容