自定义控件——万能的圆形指示器Indictor

指示器在APP应用中最为常见,例如新手引导页,滑动页,轮播图等等,在这些界面的指示器中圆形指示器最为常见。圆形指示器在网上一搜也很多,但是很多用着都不太方便,耦合性太强,一些设置往往不符合要求,使用起来比较麻烦。在之前也写过一个自定义控件,也是指示器,这个指示器其也很优美,但是代码耦合性比较强,有兴趣的同学可以看看,点击打开链接鉴于此准备打造一个万能的轻量级圆形指示器,一行代码就可以实现指示器,同时也可以设置一些常用的属性来制定自己的需求。效果图如下:




使用

在使用的时候在xml中引入布局路径,在Activity中直接和ViewPager进行绑定,使用下面的一行代码即可实现上图的指示器功能:

((CircleIndicatorView)findViewById(R.id.indicator)).setUpWithViewPager(mViewPager);


实现原理

既然是自定义控件,那么离不开自定义控件的几个核心方法:onMeasure,onLayout,onDraw,指示器的自定义也是围绕着这些方法而展开的。首先自定义一些属性,来设置指示器的属性,如半径,间距,颜色等;其次自定义一个类,继承自View,在View中加载相关属性,进行初始化操作;然后我在重写一些View的核心方法,进行指示器参数设置和绘制;最后把View和当前的ViewPager进行绑定,使其联动。接下来介绍具体实现步骤。

一、自定义属性

在圆形指示器中常见的属性有默认颜色,选中颜色,半径,间距。因就定义这四大属性。在res/values文件夹下创建attrs.xml文件夹,创建CircleIndicatorView属性。

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="CircleIndicatorView">
        <attr name="indicatorRadius" format="dimension"/>
        <attr name="indicatorDistance" format="dimension"/>
        <attr name="indicatorColor" format="color"/>
        <attr name="indicatorSelectColor" format="color"/>
    </declare-styleable>
</resources>
二、View的初始化

1、写一个类CircleIndicatorView继承自View,重写他的构造方法:

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

    public CircleIndicatorView(Context context, AttributeSet attrs) {
        this(context, attrs, -1);
    }

    public CircleIndicatorView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        getAttr(context, attrs);
        init();
    }
2、初始化自定义属性

这里初始化属性需要借助于Context context, AttributeSet attrs两个参数,AttributeSet里面封装了自定义属性的一些内容。

    private void getAttr(Context context, AttributeSet attrs) {
        TypedArray attributes = context.obtainStyledAttributes(attrs, R.styleable.CircleIndicatorView);
        radius = attributes.getDimension(R.styleable.CircleIndicatorView_indicatorRadius, DisplayUtils.dpToPx(5));
        distance = attributes.getDimension(R.styleable.CircleIndicatorView_indicatorDistance, DisplayUtils.dpToPx(8));
        defaultColor = attributes.getColor(R.styleable.CircleIndicatorView_indicatorColor, defaultColor);
        defaultSelectColor = attributes.getColor(R.styleable.CircleIndicatorView_indicatorSelectColor, defaultSelectColor);
        attributes.recycle();  //进行变量缓存,每次加载从缓存数据里面读取
    }

obtainStyledAttributes:是用来加载自定义属性的布局文件,返回的是TypedArray类型的对象

getDimension:用来获取自定义的属性的大小尺寸,参数一为定义的属性名称,参数二为默认设置的尺寸大小

getColor:获取自定义颜色类型的数据,参数一为定义的属性名称,参数二为默认设置的颜色

3、初始化画笔

初始化自定义属性之后,接下来需要初始化画笔了,设置画笔的一些参数,用来绘制圆形指示器的每个小圆点

    private void init() {
        //创建画笔
        paint = new Paint();
        //设置抗锯齿
        paint.setAntiAlias(true);
        //设置绘制防抖
        paint.setDither(true);
        //设置绘制模式:内部填充:FILL:填充内部,FILL_AND_STROKE:填充内部和描边,STROKE:描边
        paint.setStyle(Paint.Style.FILL);
        //创建集合添加指示器
        indicatorList = new ArrayList<>();
    }

每个属性的含义都有详细的说明,这里不用多解释。有人可能会疑问问什么不在这里设置颜色呢?如果在这里设置颜色该设置成选中的还是默认的颜色?所以这里设置颜色并不合适,应该在后面选中的状态逻辑判断中,动态设置颜色。

三、onMeasure
onMeasuer方法是自定义指示器的核心方法之一,他的主要作用是测量指示器的每个小圆点的绘制位置,因此需要重写onMeasure方法:

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }
1、获取指示器的总宽度。
指示器的宽度是指:每个小圆点的宽度x小圆点的个数+两个小圆点之间距离x(小圆点的个数-1)

int width = (int) (radius * count * 2 + distance * (count - 1));

2、获取指示器的高度

指示器的高度:小圆点的半径x2

int height = (int) radius * 2;

3、设置绘制区域的大小

setMeasuredDimension是设置测量宽高数据的一个方法,接收两个参数分别是宽高。在父类的onMeasure里面可以看到他最终也是调用setMeasuredDimension方法来设置测量区域的大小的。

    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }
4、封装指示器小圆点的参数

确定好绘制区域的大小,接下来就需要绘制指示器的每个小圆点,每个小圆点绘制需要一个参考位置,因此封装一个circle对象,里面包含绘制点的宽高

    public static class circle {
        public float x; // 圆心x坐标
        public float y; // 圆心y 坐标
    }
5、遍历小圆点的个数,确定绘制位置
在遍历小圆点个数前需要清理掉indicatorList指示器集合,数据为空;在
遍历每个小圆点的位置之后,需要把每个小圆点参数对象添加到集合里面,以便于绘制小圆点时候每次从中取出。再遍历小圆点个数时候需要确定每个小圆点的坐标值。对于x方向上,随着小圆点的增加每次都是变化的,y轴方法,都是固定的,在y轴水平方向上不变。计算方式如下:

x轴:上一次绘制的距离+半径x2+小圆点之间的距离

x += radius * 2 + distance;

y轴:y周的值为固定的,不受每次x周小圆点的增加而变化,等于半径的长度

circle.y = getMeasuredHeight() / 2;

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int width = (int) (radius * count * 2 + distance * (count - 1));
        int height = (int) radius * 2;
        setMeasuredDimension(width, height);
        indicatorList.clear();
        float x = 0;
        for (int i = 0; i < count; i++) {
            circle circle = new circle();
            if (i == 0) {
                x = radius;
            } else {
                x += radius * 2 + distance;
            }
            circle.x = x;
            circle.y = getMeasuredHeight() / 2;
            indicatorList.add(circle);
        }
    }
四、onDraw

完成了测量之后,接下来进行绘制过程,和onMeasure过程一样,需要重写onDraw方法,进行指示器的绘制:

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
    }
在onDraw方法里面提供一个canvas类,我们可以借助于这个画布进行小圆点的绘制,绘制方法如下:

canvas.drawCircle(x, y, radius, paint);
x:表示x轴位置

y:表示y周位置

radius:表是绘制的半径

paint:表示绘制的颜色

这里我们只需要遍历上一步测量的小圆点的集合indicatorList,然后从中拿去每一个元素的x,y坐标值进行图像绘制。在初始化画笔的时候说到不能再那里设置颜色,现在可以在onDraw方法里面对画笔进行设置颜色值,这样就很灵活。

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        for (int i = 0; i < indicatorList.size(); i++) {
            circle circle = indicatorList.get(i);
            float x = circle.x;
            float y = circle.y;

            if (position == i) {
                paint.setColor(defaultSelectColor);
            } else {
                paint.setColor(defaultColor);
            }
            canvas.drawCircle(x, y, radius, paint);
        }
    }
五、指示器和ViewPager进行关联

经过以上四个步骤就可以定义出一个指示器了,但是它还是零散的,不能和ViewPager进行联动。如何做到联动?这里让定义的当前类CircleIndicatorView实现ViewPager.OnPageChangeListene接口。然后在setSelectPosition方法里面设置选中的位置,接着进行重绘,这样就会重新调用onDraw方法,在onDraw可以动态设置当前位置选中时候的颜色以及其他位置的颜色。

    @Override
    public void onPageSelected(int position) {
        setSelectPosition(position);
    }
    public void setSelectPosition(int selectPosition) {
        this.position = selectPosition;
        invalidate();
    }
还有最后一步就是对ViewPager进行初始化的操作:

    public void setUpWithViewPager(ViewPager viewPager) {
        if (viewPager == null) {
            return;
        }
        viewPager.removeOnPageChangeListener(this);
        viewPager.addOnPageChangeListener(this);
        int count = viewPager.getAdapter().getCount();
        setCount(count);
    }
    public void setCount(int count) {
        this.count = count;
    }

这样指示器Indictor就和ViewPager进行了完全的绑定。


六、其他属性设置

1、XML中属性配置

app:indicatorSelectColor="#f00";//设置选中颜色
app:indicatorColor="#fff" ; //设置默认颜色
app:indicatorDistance="8dp";//设置小圆点间间距
app:indicatorRadius="6dp" ; //设置小圆点半径

2、代码中属性配置

在刚刚开始时候自定义了一些属性,他可以直接写在XML布局文件中,这里为了灵活期间,在设定一些方法,用于在代码里面进行控制圆形指示器的属性,如下:

    /**
     * 设置小圆点数量
     * @param count
     */
    public void setCount(int count) {
        this.count = count;
    }

    /**
     * 设置选中指示器的颜色
     * @param selectColor
     */
    public void setSelectColor(int selectColor) {
        this.defaultSelectColor = selectColor;
    }

    /**
     * 设置指示器默认颜色
     * @param defaultColor
     */
    public void setDefaultColor(int defaultColor) {
        this.defaultColor = defaultColor;
    }

    /**
     * 设置选中的位置
     * @param selectPosition
     */
    public void setSelectPosition(int selectPosition) {
        this.position = selectPosition;
        invalidate();
    }

    /**
     * 设置Indicator 半径
     * @param radius
     */
    public void setRadius(int radius) {
        this.radius = radius;
    }

    /**
     * 设置小圆点之间的距离
     * @param distance
     */
    public void setDistance(int distance) {
        this.distance = distance;
    }


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值