【原创】ViewPager的指示器简单写法(一)

一、先看效果

viewpager指示器.gif

二、分析

这个效果用动画来写,并不是很好实现。可以考虑用自定义view,自定义view只需要不断的重绘view,看起来和动画没有区别。
自定义view的话,我们来分析view的属性:

  1. 圆点的半径 (知道半径,高度也就知道了)
  2. 圆之间的padding
  3. 圆点个数
  4. 圆形view
  5. 滚动view (下面称呼为BarView)
    大概就这么多,其他的不需要了。再来看3,4两个view的属性:

3.1 圆心
3.2 半径
3.3 是否选中 (涉及到填充颜色)

BarView我们可以看成首尾两个圆,中间一个矩形的组合体:

4.1 左边圆的圆心和半径
4.2 右边圆的圆心和半径 (半径和左边的圆一致)
4.3 矩形的宽度高度 (这个并不需要,已知左右圆,宽高都是可以求出来的)

全部需要的条件就这么多,接下来是代码分析了。

三、代码实现

首先建立两个对象,一个圆点对象,一个滑块对象:


/**
 * 圆点对象
 */
public class PointView {
    private int x;
    private int y;
    private int radius;

    private boolean isChecked;
}

/**
 * 滑块对象
 */
public class BarView {
    private int leftX;
    private int leftY;

    private int rightX;
    private int rightY;

    private int radius;
}

可以说,这两个对象建立起来,这个view已经完成一半了。(这里不一定一开始想的很清楚,后面可以逐渐的完善这两个类)

首先当然继承自view, 初始化工作:

    public class IndicatorView extends View {
        private Context mContext;
        private Paint pointPaint;
        private int childCount;
        private Paint selectPointPaint;
        private int selectPosition;
        private int scrollPosition;  //这个是滑动的时候 要到达的position
        private float ratio;
        private int pointSpace = 80; // 球之间的空隙
        private int radius = 40; // 球半径
        private List<PointView> pointViews;
        private BarView barView;
    
        public IndicatorView(Context context) {
            this(context, null);
        }
    
        public IndicatorView(Context context, @Nullable AttributeSet attrs) {
            super(context, attrs);
            init(context, attrs);
        }
    
        private void init(Context context, AttributeSet attrs) {
            mContext = context;
            pointPaint = new Paint();
            pointPaint.setAntiAlias(true);
            pointPaint.setColor(Color.GRAY);
    
            selectPointPaint = new Paint();
            selectPointPaint.setAntiAlias(true);
            selectPointPaint.setColor(Color.parseColor("#eb1c42"));
        }
    }

这个时候需要绘制了,但是从哪里绘制呢?我们这个view是配合viewpager使用的,所以需要暴漏一个方法,设置一个viewpager进来:

/**
     * 关联viewpager
     * @param viewPager
     */
    public void setViewPager(ViewPager viewPager) {
        childCount = viewPager.getAdapter().getCount();
        initPoints();
        viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
            @Override
            public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
                scrollPosition = position;
                Log.d("------->", "position:" + position + ", positionOffset:" + positionOffset + ", positionOffsetPixels:" + positionOffsetPixels);
                ratio = positionOffset;
                if(ratio >= 1 || ratio <= 0){
                    return;
                }
                compute();
                invalidate();
            }

            @Override
            public void onPageSelected(int position) {
                selectPosition = position;
                ratio = 0;
                compute();
                invalidate();
            }

            @Override
            public void onPageScrollStateChanged(int state) {

            }
        });
        ratio = 0;
        compute();
        invalidate();
    }

当viewpager设置进来的时候,我们第一步拿到页面的个数,去initPoints()初始化小圆点:

/**
     * 初始化点
     */
    private void initPoints() {
        pointViews = new ArrayList<>();
        for (int i = 0; i < childCount; i++) {
            PointView pointView = new PointView();
            pointViews.add(pointView);
        }
        //顺便初始化bar
        barView = new BarView();
    }

然后监听viewpager的滚动,在滚动里面去计算BarView的位置和设置圆点的选中状态。
在监听方法里面,需要注意 scrollPosition 和 selectPosition 的区别,这个 scrollPosition 是页面将要到达的页面的 index,而 selectPosition 是记录当前选中圆点的index。两者的区别比较大,用处也不一样。这个 scrollPosition 我们在后面还需要用到。
监听方法里面有个 compute() 计算方法:


    /**
     * 绘制之前准备, 这个计算一定要在canvas外面计算
     */
    private void compute() {
        //计算球位置
        for (int i = 0; i < pointViews.size(); i++) {
            PointView pointView = pointViews.get(i);
            pointView.setRadius(radius);
            pointView.setX(radius * (2 * i + 1) + pointSpace* i);
            pointView.setY(radius);
            pointView.setChecked(selectPosition == i);
        }
        //计算bar位置
        PointView selectPointView = pointViews.get(selectPosition);
        int selectX = selectPointView.getX();
        int selectY = selectPointView.getY();
        if(selectPosition <= scrollPosition){
            //往右是增加右边圆的圆心
            barView.setLeftX(selectX);
            barView.setLeftY(selectY);
            barView.setRightX((int) (selectX + (2 * radius + pointSpace) * ratio));
            barView.setRightY(selectY);
            barView.setRadius(radius);
        }else{
            //往左是减少左边圆的圆心
            barView.setRightX(selectX);
            barView.setRightY(selectY);
            barView.setLeftX((int) (selectX - (2 * radius + pointSpace) * (1 - ratio)));
            barView.setLeftY(selectY);
            barView.setRadius(radius);
        }
    }
    

圆点(也就是小球)的位置很好计算,半径圆心和选中状态,几乎计算一遍就行。主要是BarView的位置,它是随着viewpager滚动而改变的,viewpager滚动有向左和向右两个
方向,这个时候 scrollPosition 就起到关键性的作用了,往右,这个 index 是大于当前 selectPosition 的,往左,这个 index 是小于或者等于(经过打印是等于,严谨点,小于也判断了) selectPosition 的,
那就有两种计算方法,往右,左边圆心等于选中圆的圆心,右边圆心等于选中圆心加上偏移量,偏移量可以计算得出,代码如上,往左,右边圆心是等于选中圆的圆心,左边圆心是选中圆心减去偏移量。
计算完调用 invalidate(),自动重绘页面,调用 view 的 onDraw() 方法。接下来就是重点的onDraw()方法:


    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //画圆点
        for (PointView pointView : pointViews) {
            canvas.drawCircle(pointView.getX(), pointView.getY(), pointView.getRadius(), pointView.isChecked()?selectPointPaint:pointPaint);
        }
        //画bar的头尾圆
        canvas.drawCircle(barView.getLeftX(), barView.getLeftY(), barView.getRadius(), selectPointPaint);
        canvas.drawCircle(barView.getRightX(), barView.getRightY(), barView.getRadius(), selectPointPaint);
        //画bar的中间rect
        canvas.drawRect(new RectF(barView.getLeftX(), 0, barView.getRightX(), radius * 2), selectPointPaint);
    }
    

是不是很简单?为什么重点部分这么少代码,因为计算已经在外面计算过了,这里只需要把数据拿来绘制一下就好了。
到这是不是结束了,不,自定义view除了 onDraw() 这个关键方法还有 onMeaure() 没有用到,如果不计算view的高度,默认是充满屏幕的,就无法动态放置 view,所以最后:

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //设置view宽高 不设置就是默认全屏view,没办法改变位置
        if(pointViews != null && pointViews.size() > 0) {
            int width = pointViews.get(pointViews.size() - 1).getX() + radius;
            int height = radius * 2;
            setMeasuredDimension(width, height);
        }else{
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        }
    }

到这就结束了。当然还可以添加属性,直接在xml文件就可以设置圆点 padding 和圆点半径,这些就交给你去拓展了。附上 github地址

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

推荐阅读更多精彩内容