ViewPager的滚动指示器(模仿微博,长宽颜色圆角可设置)

自己动手写了一个指示器,感觉还可以,暂时没有写多栏滚动的。
一共做了三种样式
第一种样式:指示器的大小和标题等宽


1580986653348.gif

第二种样式:类似于游动,简单模仿微博的实现方案,颜色渐变我没加。


1581006168305.gif

第三种样式:最常见的样式。


1580987316781.gif

主要的思路是对viewPager进行监听,addOnPageChangeListener。根据onPageScrolled方法的positionOffset和position进行判断。
至于标题和指示器的位置关系,我是假定它们是位于同一个父布局之中的,直接getLeft()等方法就可以完成位置的计算,如果不是同一个父布局内的话,还得用其他的方法。
把我的代码贴在下面。
布局文件:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingTop="50dp"
    tools:context=".MainActivity">
    <TextView
        android:id="@+id/tv1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="抗击肺炎"
        android:layout_marginLeft="10dp"
        android:layout_marginRight="10dp"
        android:textSize="24sp"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
    <TextView
        android:id="@+id/tv2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="武汉"
        android:textSize="24sp"
        android:layout_marginLeft="10dp"
        android:layout_marginRight="10dp"
        app:layout_constraintLeft_toRightOf="@id/tv1"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/tv3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="我"
        android:textSize="24sp"
        android:layout_marginLeft="10dp"
        android:layout_marginRight="10dp"
        app:layout_constraintLeft_toRightOf="@id/tv2"
        app:layout_constraintTop_toTopOf="parent" />
    <TextView
        android:id="@+id/tv4"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="世界和平"
        android:textSize="24sp"
        android:layout_marginLeft="10dp"
        android:layout_marginRight="10dp"
        app:layout_constraintLeft_toRightOf="@id/tv3"
        app:layout_constraintTop_toTopOf="parent" />
    <com.example.javatest.IndicactorView
        android:id="@+id/indicator"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginTop="5dp"
        app:indicator_length="15dp"
        app:indicator_height="5dp"
        app:indicator_radius="3dp"
        app:indicator_color="#a451B5"
        app:indicator_mode="MODE_FIX_TITLE"
        app:layout_constraintLeft_toLeftOf="@id/tv1"
        app:layout_constraintTop_toBottomOf="@id/tv1"
        app:layout_constraintRight_toRightOf="@id/tv4"/>
    <androidx.viewpager.widget.ViewPager
        android:id="@+id/viewPager"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintTop_toBottomOf="@id/indicator"
        app:layout_constraintBottom_toBottomOf="parent"/>

</androidx.constraintlayout.widget.ConstraintLayout>

MainActivity:

public class MainActivity extends AppCompatActivity {
    Handler mHandler;
    IndicactorView indicactorView;
    TextView tv1,tv2,tv3,tv4;
    ViewPager viewPager;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        tv1=findViewById(R.id.tv1);
        tv2=findViewById(R.id.tv2);
        tv3=findViewById(R.id.tv3);
        tv4=findViewById(R.id.tv4);
        indicactorView=findViewById(R.id.indicator);
        viewPager=findViewById(R.id.viewPager);
        indicactorView.setViewPager(viewPager)
                .setTitleViews(tv1,tv2,tv3,tv4);
        tv1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                viewPager.setCurrentItem(0);
            }
        });
        tv2.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                viewPager.setCurrentItem(1);
            }
        });
        tv3.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                viewPager.setCurrentItem(2);
            }
        });
        tv4.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                viewPager.setCurrentItem(3);
            }
        });
        viewPager.setAdapter(new PagerAdapter() {
            @Override
            public int getCount() {
                return 4;
            }

            @Override
            public boolean isViewFromObject(@NonNull View view, @NonNull Object object) {
                return view==object;
            }

            @NonNull
            @Override
            public Object instantiateItem(@NonNull ViewGroup container, int position) {
                TextView tv=new TextView(MainActivity.this);
                tv.setText(""+position);
                tv.setTextSize(30);
                tv.setGravity(CENTER);
                container.addView(tv,-1,-1);
                return tv;
            }

            @Override
            public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
                container.removeView((View) object);
            }
        });
    }
}

指示器的代码:


/**
 * viewPager的指示器
 * 需要注意:传进来的标题的View要与指示器位于同一个父布局之中,而且标题的起始位置要与指示器对齐,。
 */
public class IndicactorView extends View {
    private Paint mPaint;
    private Context mContext;
    //标题的view,也就是TextView
    private View[] titleViews;
    //指示标的长度
    private int indicatorLength;
    //指示标的高度
    private int indicatorHeight;
    //指示标的圆弧度
    private int indicatorRadius;
    //指示标的左边位置
    private int indicatorLeft;
    //指示标的右边位置
    private int indicatorRight;
    //标题最左边的位置,用于计算标题和图标的位置关系
    private int titleLeft;
    //viewPager选中页
    private int selectPosition;
    //viewPager左右移动了多少
    private float offset;
    //viewPager移动过程中,始发的那一夜
    private int fromPositon;
    //样式
    private int mode = 2;
    //指示标的颜色
    private int indicatorColor;
    //viewPager的滚动状态
    private int scrollState = SCROLL_STATE_IDLE;
    //是否进行点击跳转超过2页
    private boolean isJumpToNext;
    //跳转时的动画
    private ValueAnimator animator;
    //跳转前的页数
    private int lastPosition;
    //跳转动画的进度
    private float jumpNextOffset;
    //样式,三种
    public final static int MODE_FLOW = 1;//流动
    public final static int MODE_SCROLL = 2;//滚动
    public final static int MODE_FIX_TITLE = 3;//与标题等宽自适应

    public IndicactorView(Context context) {
        this(context, null);
    }

    public IndicactorView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public IndicactorView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.IndicactorView);
        indicatorLength = typedArray.getDimensionPixelSize(R.styleable.IndicactorView_indicator_length, 25);
        indicatorRadius = typedArray.getDimensionPixelSize(R.styleable.IndicactorView_indicator_radius, 0);
        indicatorHeight = typedArray.getDimensionPixelSize(R.styleable.IndicactorView_indicator_height, 8);
        mode = typedArray.getInt(R.styleable.IndicactorView_indicator_mode, 1);
        indicatorColor = typedArray.getColor(R.styleable.IndicactorView_indicator_color, Color.parseColor("#3F51B5"));
        init(context);
    }

    private void init(Context context) {
        mPaint = new Paint();
        mContext = context;
        animator = new ValueAnimator();
    }

    public void setIndicatorLength(int indicatorLength) {
        this.indicatorLength = indicatorLength;
    }

    public void setIndicatorHeight(int indicatorHeight) {
        this.indicatorHeight = indicatorHeight;
    }

    public void setIndicatorRadius(int indicatorRadius) {
        this.indicatorRadius = indicatorRadius;
    }

    public void setIndicatorColor(int indicatorColor) {
        this.indicatorColor = indicatorColor;
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                measureHeight(getSuggestedMinimumHeight(), heightMeasureSpec));
    }

    private int measureHeight(int size, int measureSpec) {
        int result = size;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);

        switch (specMode) {
            case MeasureSpec.UNSPECIFIED:
                result = size;
                break;
            case MeasureSpec.AT_MOST:
                result = indicatorHeight;
                break;
            case MeasureSpec.EXACTLY:
                result = specSize;
                break;
        }
        return result;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        mPaint.reset();
        mPaint.setAntiAlias(true);
        mPaint.setColor(indicatorColor);
        //点击跳转超过两个距离
        if (isJumpToNext) {
            if (titleViews.length > 0 && selectPosition < titleViews.length) {
                if (mode == MODE_FIX_TITLE) {
                    indicatorLength = titleViews[selectPosition].getWidth() - (int) ((titleViews[selectPosition].getWidth() - titleViews[lastPosition].getWidth()) * (1 - jumpNextOffset));
                }
                titleLeft = titleViews[0].getLeft();
                int startLeft = 0;
                int endLeft = 0;
                if (lastPosition < titleViews.length) {
                    startLeft = titleViews[lastPosition].getLeft() - titleLeft + titleViews[lastPosition].getWidth() / 2 - indicatorLength / 2;
                }
                if (selectPosition < titleViews.length) {
                    endLeft = titleViews[selectPosition].getLeft() - titleLeft + titleViews[selectPosition].getWidth() / 2 - indicatorLength / 2;
                }
                indicatorLeft = (int) (startLeft + (endLeft - startLeft) * jumpNextOffset);
                indicatorRight = indicatorLeft + indicatorLength;
            }
        }
        //非点击长距离跳转
        else {
            int anchorPosition=selectPosition;
            if (fromPositon == selectPosition) {
                //向右滑
                if(mode==MODE_FLOW){
                    if(offset>0.5f){
                        offset=-(1f-offset);
                        anchorPosition=selectPosition+1;
                    }
                }
                Log.d("-------", "向右滑");
            } else {
                //向左滑
                if(mode==MODE_FLOW) {
                    if(offset>0.5){
                        offset=-(1-offset);
                    }else {
                        anchorPosition=selectPosition-1;
                    }
                }else {
                    offset = -(1 - offset);
                }
                Log.d("-------", "向左滑");
            }
            Log.d("-------", "滑动"+offset);
            if (titleViews.length > 0 && anchorPosition < titleViews.length) {
                titleLeft = titleViews[0].getLeft();
                int titleCenter = titleViews[anchorPosition].getLeft() - titleLeft + titleViews[anchorPosition].getWidth() / 2;
                if (offset >= 0) {
                    //向右滑动
                    if (mode == MODE_FLOW) {
                        Log.d("-------", "滑动"+anchorPosition);
                        indicatorLeft = titleCenter - indicatorLength / 2;
                        int offsetLength = 0;
                        indicatorRight = titleCenter + indicatorLength / 2;
                        if (anchorPosition + 1 < titleViews.length) {
                            offsetLength = (int) ((titleViews[anchorPosition + 1].getLeft() - titleLeft + titleViews[anchorPosition + 1].getWidth() / 2 + indicatorLength / 2 - indicatorRight) * offset*2);
                        }
                        indicatorRight += offsetLength;
                    } else if (mode == MODE_SCROLL || mode == MODE_FIX_TITLE) {
                        if (mode == MODE_FIX_TITLE) {
                            if (anchorPosition + 1 < titleViews.length) {
                                indicatorLength = titleViews[anchorPosition].getWidth() + (int) ((titleViews[anchorPosition + 1].getWidth() - titleViews[anchorPosition].getWidth()) * offset);
                            }
                        }
                        int leftCurrent = titleCenter - indicatorLength / 2;
                        int offsetLength = 0;
                        if (anchorPosition + 1 < titleViews.length) {
                            int leftNext = titleViews[anchorPosition + 1].getLeft() - titleLeft + titleViews[anchorPosition + 1].getWidth() / 2 - indicatorLength / 2;
                            offsetLength = (int) ((leftNext - leftCurrent) * offset);
                        }
                        indicatorLeft = leftCurrent + offsetLength;
                        indicatorRight = indicatorLeft + indicatorLength;
                    }
                } else {
                    //向左滑动
                    if (mode == MODE_FLOW) {
                        Log.d("-------", "滑动"+anchorPosition);
                        indicatorRight = titleCenter + indicatorLength / 2;
                        int offsetLength = 0;
                        indicatorLeft = titleCenter - indicatorLength / 2;
                        if (anchorPosition - 1 >= 0) {
                            offsetLength = (int) ((indicatorLeft - (titleViews[anchorPosition - 1].getLeft() - titleLeft + titleViews[anchorPosition - 1].getWidth() / 2 - indicatorLength / 2)) * offset*2);
                        }
                        indicatorLeft += offsetLength;
                    } else if (mode == MODE_SCROLL || mode == MODE_FIX_TITLE) {
                        if (mode == MODE_FIX_TITLE) {
                            if (mode == MODE_FIX_TITLE) {
                                if (anchorPosition - 1 >= 0) {
                                    indicatorLength = titleViews[anchorPosition].getWidth() + (int) ((titleViews[anchorPosition - 1].getWidth() - titleViews[anchorPosition].getWidth()) * (-offset));
                                }
                            }
                        }
                        int leftCurrent = titleCenter - indicatorLength / 2;
                        int offsetLength = 0;
                        if (anchorPosition - 1 >= 0) {
                            int leftNext = titleViews[anchorPosition - 1].getLeft() - titleLeft + titleViews[anchorPosition - 1].getWidth() / 2 - indicatorLength / 2;
                            offsetLength = (int) ((leftCurrent - leftNext) * offset);
                        }
                        indicatorLeft = leftCurrent + offsetLength;
                        indicatorRight = indicatorLeft + indicatorLength;
                    }
                }
            }
        }
        RectF r = new RectF();
        r.left = indicatorLeft;
        r.top = 0;
        r.right = indicatorRight;
        r.bottom = indicatorHeight;
        canvas.drawRoundRect(r, indicatorRadius, indicatorRadius, mPaint);
    }

    public void setMode(int mode) {
        this.mode = mode;
    }

    public IndicactorView setTitleViews(View... views) {
        this.titleViews = views;
        return this;
    }

    public IndicactorView setViewPager(ViewPager viewPager) {
        viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
            @Override
            public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
                Log.d("-------", "position:" + position + ", positionOffset:" + positionOffset + ", positionOffsetPixels:" + positionOffsetPixels);
                fromPositon = position;
                offset = positionOffset;
                if (!isJumpToNext) {
                    invalidate();
                }
            }

            @Override
            public void onPageSelected(int position) {
                Log.d("-------", "position:" + position);
                lastPosition = selectPosition;
                selectPosition = position;
                if (Math.abs(lastPosition - position) > 1) {
                    isJumpToNext = true;
                    animator = ValueAnimator.ofFloat(0, 1);
                    animator.setDuration(300);
                    animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                        @Override
                        public void onAnimationUpdate(ValueAnimator animation) {
                            jumpNextOffset = (float) animation.getAnimatedValue();
                            postInvalidate();
                        }
                    });
                    animator.start();
                }
            }

            @Override
            public void onPageScrollStateChanged(int state) {
                scrollState = state;
                if (scrollState == SCROLL_STATE_IDLE) {
                    isJumpToNext = false;
                    offset = 0;
                    invalidate();
                }
            }
        });
        return this;
    }
}

自定义属性:

  <declare-styleable name="IndicactorView">
        <attr name="indicator_length" format="dimension"/>
        <attr name="indicator_radius" format="dimension"/>
        <attr name="indicator_height" format="dimension"/>
        <attr name="indicator_color" format="reference|color"/>
        <attr name="indicator_mode">
            <enum name="MODE_FLOW" value="1"/>
            <enum name="MODE_SCROLL" value="2"/>
            <enum name="MODE_FIX_TITLE" value="3"/>
        </attr>
    </declare-styleable>

后面有时间的话,再写一个滚动标题栏的实例,现在只是有思路。

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

推荐阅读更多精彩内容

  • 专业考题类型管理运行工作负责人一般作业考题内容选项A选项B选项C选项D选项E选项F正确答案 变电单选GYSZ本规程...
    小白兔去钓鱼阅读 9,013评论 0 13
  • 站着女生的立场,我觉得男生应该不会喜欢女生这样几种朋友圈。 一、长得丑还自拍 因为现在各种自拍利器数不胜数,美颜滤...
    撑篙人JZ阅读 2,000评论 0 4
  • 她 骑着单车轻快的驶过,梳起的马尾活跃地跳着。阳光温柔的映着她精致的脸,微风吹起她的碎发,她像是一幅画,美的不像话...
    微陌_save阅读 423评论 0 0
  • 因为突然的大降温而无法调节的身体素质,我想我是真的很“柔弱”。曾经跑过半马的人,跑过多少个10公里的人,如今成了时...
    一念间_阅读 66评论 0 0
  • 一个人的天空很蓝, 蓝的有点忧郁; 一个人的时候很自由, 自由的有点落寞; 一个人的日子很轻松, 轻松的有点无聊;...
    桂桃儿阅读 303评论 3 9