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

作者: 李晨玮 | 来源:发表于2017-07-18 10:18 被阅读825次

    最近公司的产品要进行改版,其中涉及到了登陆页面的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):源码下载

    相关文章

      网友评论

      • DreamArea:楼主你好,请教个问题,如果这个三角形指示器以底边作镜像,就是尖尖的顶点朝下,这个效果怎么实现?还能给个思路?
      • 依然范特稀西: 思路不错👍
      • 04b1fe17bbd6:这么厉害!d=====( ̄▽ ̄*)b厉害,快教教我!

      本文标题:Android开发之自定义ViewPager三角形指示器

      本文链接:https://www.haomeiwen.com/subject/wyntkxtx.html