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

来源:互联网 发布:php提权 编辑:程序博客网 时间:2024/06/01 09:42

前言

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


效果图

这里写图片描述

可以看到可以和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 0
原创粉丝点击