自定义控件——万能的圆形指示器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;
    }


  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
一个轻量级的viewpager指示器 ,类似于nexus5 启动器的效果。它可以自定义指示器上小圆点的样式和动画效果。可以用于引导页。项目地址:https://github.com/ongakuer/CircleIndicator 效果图:如何使用1. xml布局中创建CircleIndicator是配合ViewPager使用的,一般如下布局:<RelativeLayout         android:layout_width="match_parent"         android:layout_height="match_parent">         <android.support.v4.view.ViewPager             android:id="@ id/viewpager_default"             android:layout_width="match_parent"             android:layout_height="match_parent"/>         <me.relex.circleindicator.CircleIndicator             android:id="@ id/indicator_default"             android:layout_centerInParent="true"             android:layout_width="fill_parent"             android:layout_height="40dp"/>     </RelativeLayout>2. java代码中// DEFAULT ViewPager defaultViewpager = (ViewPager) findViewById(R.id.viewpager_default); CircleIndicator defaultIndicator = (CircleIndicator) findViewById(R.id.indicator_default); DemoPagerAdapter defaultPagerAdapter = new DemoPagerAdapter(getSupportFragmentManager()); defaultViewpager.setAdapter(defaultPagerAdapter);//为ViewPager设置适配器 defaultIndicator.setViewPager(defaultViewpager);//将ViewPager添加到CircleIndicator中,以便监听ViewPager的滚动等事件DemoPagerAdapter是ViewPager的适配器,这个需要你去实现。属性说明属性名称类型说明ci_widthdimension小圆点的宽度ci_heightdimension小圆点的高度ci_margindimension小圆点间的间距ci_animatorreferenceViewPager页面切换时,给小圆点设置动画。设置当前的小圆点恢复到未选中时的状态的动画。例如:当前正从a切换到b,那么该属性设置的是a的动画。ci_animator_reversereference和"ci_animator"属性一样,只不过该属性设置的是b的动画。ci_drawablereference设置当前或选中的小圆点的样式,如颜色、圆角等。ci_drawable_unselectedreference和"ci_drawable"一样,只是该属性设置的是未选中的小圆点的样式。点击上面的"下载源码"下载完整的demo工程。demo简要说明xml布局:<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"     xmlns:app="http://schemas.android.com/apk/res-auto"     android:layout_width="match_parent"     android:layout_height="match_parent"     android:orientation="vertical">     <RelativeLayout         android:layout_width="match_parent"         android:layout_height="0dp"         android:layout_weight="1">         <android.support.v4.view.ViewPager             android:id="@ id/viewpager_default"             android:layout_width="match_parent"             android:layout_height="match_parent"/>         <me.relex.circleindicator.CircleIndicator             android:id="@ id/indicator_default"             android:layout_centerInParent="true"             android:layout_width="fill_parent"             android:layout_height="40dp"/>     </RelativeLayout>     <RelativeLayout         android:layout_width="match_parent"         android:layout_height="0dp"         android:layout_weight="1">         <android.support.v4.view.ViewPager             android:id="@ id/viewpager_custom"             android:layout_width="match_parent"             android:layout_height="match_parent"/>         <me.relex.circleindicator.CircleIndicator             android:id="@ id/indicator_custom"             app:ci_width="10dp"             app:ci_height="4dp"             app:ci_margin="6dp"             app:ci_animator="@animator/indicator_animator"             app:ci_animator_reverse="@animator/indicator_animator_reverse"             app:ci_drawable="@drawable/black_radius_square"             android:layout_centerInParent="true"             android:layout_width="fill_parent"             android:layout_height="40dp"/>     </RelativeLayout>     <RelativeLayout         android:layout_width="match_parent"         android:layout_height="0dp"         android:layout_weight="1">         <android.support.v4.view.ViewPager             android:id="@ id/viewpager_unselected_background"             android:layout_width="match_parent"             android:layout_height="match_parent"/>         <me.relex.circleindicator.CircleIndicator             android:id="@ id/indicator_unselected_background"             android:layout_centerInParent="true"             android:layout_width="fill_parent"             app:ci_width="6dp"             app:ci_height="6dp"             app:ci_animator="@animator/indicator_no_animator"             app:ci_drawable="@drawable/white_radius"             app:ci_drawable_unselected="@drawable/black_radius"             android:layout_height="40dp"             />     </RelativeLayout> </LinearLayout>定义了3组ViewPager和指示器。第一组是采用默认样式的,被选中的小圆点带有放大的动画。第2组比较全,既定义了动画又修改了样式。第3组是取消动画的同时使用了ci_drawable_unselected属性。适配器Adapter:public class DemoPagerAdapter extends FragmentPagerAdapter {         private int pagerCount = 5;         private Random random = new Random();         public DemoPagerAdapter(FragmentManager fm) {             super(fm);         }         @Override public Fragment getItem(int i) {             return ColorFragment.newInstance(0xff000000 | random.nextInt(0x00ffffff));         }         @Override public int getCount() {             return pagerCount;         }     }定义了5个页面或Fragment,通过ColorFragment生成Fragment。Fragment:public class ColorFragment extends Fragment {     private static final String ARG_COLOR = "color";     private int mColor;     public static ColorFragment newInstance(int param1) {         ColorFragment fragment = new ColorFragment();         Bundle args = new Bundle();         args.putInt(ARG_COLOR, param1);         fragment.setArguments(args);         return fragment;     }     public ColorFragment() {     }     @Override     public void onCreate(Bundle savedInstanceState) {         super.onCreate(savedInstanceState);         if (getArguments() != null) {             mColor = getArguments().getInt(ARG_COLOR);         }     }     @Override     public View onCreateView(LayoutInflater inflater, ViewGroup container,             Bundle savedInstanceState) {         View v = inflater.inflate(R.layout.color_fragment, container, false);         v.setBackgroundColor(mColor);         return v;     } }生成空的页面,没有任何控件,只是设置了页面的背景色。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值