自定义控件之-横线指示器

前言

其实指示器的自定义控件太多了,但是需求时刻在变,总有不满足的时候,所以就得自己来绘制
因为博主遇到了横线形式的指示器,所以特地分享一下,同时也教一下不会自定义的童鞋


效果图

这里写图片描述

可以看到可以和ViewPager一起联动,下面就写出实现的过程

首先我们需要弄明白几个点
1. 绘制每一个指示器通过canvas的绘制圆角矩形就行
2. 每一个指示器Item都需要一个Rect对象来描述绘制的位置
3. 和ViewPager联动需要监听ViewPager的滑动事件就能实现

大致实现流程
1. 测量出自身的宽高
2. 根据各个数值算出每一个指示器的Item的位置
3. 根据位置绘制出所有的指示器Item

代码实现

准备:绘制此控件需要的变量属性


    /**
     * 指示器的宽度,单位是dp
     */
    private int indicatorWidth = 10;

    /**
     * 指示器的高度,单位是dp
     */
    private int indicatorHeight = 4;

    /**
     * 被选中的指示器的宽度,单位是dp
     */
    private int selectedIndicatorWidth = 20;

    /**
     * 被选中的指示器的高度,单位是dp
     */
    private int selectedIndicatorHeight = 4;

    /**
     * 每一个指示器之间的间距,单位是dp
     */
    private int indicatorHorizontalSpace = 2;

    /**
     * 被选中的指示器的颜色
     */
    private int selectedIndicatorColor = Color.parseColor("#ED5C55");

    /**
     * 没被选中的指示器的颜色
     */
    private int unSelectedIndicatorColor = Color.WHITE;

    /**
     * 指示器的个数
     */
    private int indicatorCount = 0;

    /**
     * 当前选中的指示器下标
     */
    private int indicatorIndex = 0;

    /**
     * 偏移的比例,只有>0的,因为ViewPager中如果从0滑动到1,那么这个值就是这么变化的:
     * 0->0.999->0
     * 下标变化:
     * 0->0->1
     * 如果从1滑动到0,那么这个值就是这么变化的:
     * 0->0.999->0
     * 下标变化:
     * 1->0->0
     */
    private float offSet = 0f;

上述变量中都不需要解释,就最后一个offSet需要解释一下,因为我们肉眼看上去是和ViewPager联动的,但是对于此控件来说,是不断的绘制产生的效果,所以这个变量是记录从
index -> index + 1 或者 index -> index - 1 的时候的进度,所以才能在ViewPager联动的时候改变此值来改变计算出来的位置,下面会用到

2.测量自身的宽高


由于我们此控件基本全部都是包裹的效果,所以这里只处理包裹的效果,无论你写wrap_content或者match_parent都是包裹作用,是一样的,使用的时候注意了哦,支持内边距!

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        // calculate width of no selected index
        int width = indicatorCount * indicatorWidth + (indicatorCount - 1) * indicatorHorizontalSpace;

        // get the bigger height
        int height = Math.max(indicatorHeight, selectedIndicatorHeight);

        // if have selected indicator
        if (indicatorIndex >= 0 && indicatorIndex < indicatorCount) {
            width += selectedIndicatorWidth - indicatorWidth;
        }

        // add the padding
        width += getPaddingLeft() + getPaddingRight();
        height += getPaddingTop() + getPaddingBottom();

        // only have wrap

        // save the width and height
        setMeasuredDimension(width, height);

    }

测量自己的宽和高,因为允许没选中和选中的指示器有不同的高度,所以在高度方面是采取两者的较大值,因为在效果上我们可以看见选中的那个明显比较高
另外测量的时候,我们绘制的区域算出来之后,加上了内边距的值,这也就支持了内边距的设置效果

3.根据所有可影响的属性值计算出绘制的坐标


    /**
     * 记录所有指示器的位置信息,包括选中的那个
     */
    private void calculateRectFs() {

        if (rectFs == null || rectFs.length != indicatorCount) {
            rectFs = new RectF[indicatorCount];
        }

        int startLeft = getPaddingLeft();
        int startTop = getPaddingTop();
        int startBottom = getHeight() - getPaddingBottom();

        //除去内边距可绘制的区域的高度
        int drawHeight = getHeight() - getPaddingTop() - getPaddingBottom();

        /* 绘制的时候,因为绘制的指示器有高度不同的情况
         所以如果高度小一点的,那么就有一个绘制的top起点就有一个dy需要加上去
         才能保证绘制所有的指示器都在中间*/
        int dy;

        int currentIndicatorWidth = indicatorWidth;

        // 循环计算
        for (int i = 0; i < indicatorCount; i++) {

            //如果是选中的那个指示器
            if (i == indicatorIndex) {
                dy = (drawHeight - selectedIndicatorHeight) / 2;
                currentIndicatorWidth = selectedIndicatorWidth;
            } else {
                dy = (drawHeight - indicatorHeight) / 2;
                currentIndicatorWidth = indicatorWidth;
            }

            // 创建矩形,填入数据
            RectF rectF = new RectF(startLeft, startTop + dy,
                    startLeft + currentIndicatorWidth, startBottom - dy);

            startLeft += currentIndicatorWidth + indicatorHorizontalSpace;

            rectFs[i] = rectF;

        }

        // 这是就是根据偏移的百分比,在正常基础上左右偏移坐标
        // 但是这里不针对选择的下标是最后一个的情况

        if (indicatorIndex != indicatorCount - 1) {

            // 拿到选中那个位置信息和选中的下一个的位置信息
            RectF rectFSelected = rectFs[indicatorIndex];
            RectF next = rectFs[indicatorIndex + 1];

            // 计算两者的偏移量
            float selectedOffsetPx = (next.right - rectFSelected.right) * offSet;
            float nextOffsetPx = (rectFSelected.left - next.left) * offSet;

            // 在原来的基础上加上偏移量

            rectFSelected.left += selectedOffsetPx;
            rectFSelected.right += selectedOffsetPx;

            next.left += nextOffsetPx;
            next.right += nextOffsetPx;

        }

    }

在测量的时候我们在自己绘制的宽高基础上加上了内边距
所以这里计算的时候,也需要考虑内边距的问题,所以绘制的时候起点不是0

int startLeft = getPaddingLeft();
int startTop = getPaddingTop();
int startBottom = getHeight() - getPaddingBottom();

也就是这三句代码,然后接下来的代码都写了注释应该是没问题的,总得一句话就是根据所有可影响的属性计算出每一个指示器Item的绘制区域

4.根据计算出来的坐标数据,绘制出效果


    @Override
    protected void onDraw(Canvas c) {
        super.onDraw(c);

        calculateRectFs();

        //绘制的起点

        Paint p = new Paint();
        p.setColor(Color.GREEN);
        p.setAntiAlias(true);// 设置画笔的锯齿效果

        if (rectFs == null) {
            return;
        }

        p.setColor(unSelectedIndicatorColor);

        for (int i = 0; i < rectFs.length; i++) {

            //不绘制选中的
            if (indicatorIndex == i) {
                continue;
            }

            //拿到位置信息
            RectF r = rectFs[i];

            //计算出半径
            float radius = Math.min(Math.abs((r.bottom - r.top) / 2), Math.abs((r.right - r.left) / 2));

            //绘制圆角矩形
            c.drawRoundRect(r, radius, radius, p);

        }

        if (indicatorIndex >= 0 && indicatorIndex < indicatorCount) {

            p.setColor(selectedIndicatorColor);

            //拿到位置信息
            RectF r = rectFs[indicatorIndex];

            //计算出半径
            float radius = Math.min(Math.abs((r.bottom - r.top) / 2), Math.abs((r.right - r.left) / 2));

            //绘制圆角矩形
            c.drawRoundRect(r, radius, radius, p);

        }
    }

这段代码就很简单了,在每次绘制之前都调用计算的方法,然后根据计算出来的数据直接循环绘制
这里有一点注意了,我们展示的效果中,选中的那个指示器Item是永远在上层的,这也就是绘制的时候必须在最后绘制,先绘制出其他非选中的那些Item

到这里,整个自定义控件就完工了,下面是和ViewPager的滑动进行绑定

这里写图片描述

这里写图片描述

这里的代码就是实现了ViewPager的滑动监听接口,然后在滑动的时候不断的改变下标和偏移的百分比,然后重新绘制,那么还差最后一点就是和ViewPager的绑定操作

提供一个方法出来设置ViewPager

    /**
     * 和ViewPager的滑动事件绑定
     * 此方法必须在ViewPager设置适配器之后
     *
     * @param vp
     */
    public void setUpViewPager(@NonNull ViewPager vp) {

        PagerAdapter adapter = vp.getAdapter();

        if (adapter != null) {
            indicatorCount = adapter.getCount();
        }

        indicatorIndex = vp.getCurrentItem();

        vp.removeOnPageChangeListener(this);
        vp.addOnPageChangeListener(this);

    }

代码很简单,就不解释了,这样就完成了所有的工作了

源码下载

如果想直接用,直接依赖

在主配置build文件中配置

allprojects {
    repositories {
        jcenter()
        //就是添加上这句
        maven { url 'https://jitpack.io' }
    }
}

//在项目模块中直接依赖就能使用了
compile ‘com.github.xiaojinzi123:widget:v1.1.3.2’

就可以使用了,贴出一个完整用法

<com.move.widget.XIndicator
        android:id="@+id/indicator"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignBottom="@+id/vp"
        android:layout_centerHorizontal="true"
        android:layout_marginBottom="10dp"
        app:indicatorHeight="6dp"
        app:indicatorSpace="6dp"
        app:indicatorWidth="20dp"
        app:index="1"
        app:count="4"
        app:selectedIndicatorHeight="8dp"
        app:selectedIndicatorWidth="32dp" />
  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值