android 之indicatorView的实现分析
使用一个软件常注意到刚使用软件时会有一个导览页,通过滑动页面来显示不同的图,底部有用于指示在哪一页的圆点,这就是我们将要实现的indicator(指示器),接来我们介绍在android 中如何实现?
indicator的实现
首先,我们需要了解indicator的组成,一般来说,它是由几个圆点组成,翻到对应页时,圆点颜色改变,有些indicator圆点还有字母或者数字,如图:
接下来,我们需要知道如何绘出这些圆和圆内的数字。要确定一个圆,需要知道圆的半径和圆心位置x,y,半径是由我们自行配置的,所以我们需要确定每个圆圆心的位置,而y是固定的,所以我们需要知道每个圆x的值,如图,我们可以观察到它的规律如下:第一个圆的x 就等于圆的半径,从第二个圆开始,当前圆的圆心x 坐标为 上一个圆的x 坐标 + (radius * 2 + mSpace)。 其中mSpace 是圆之间的间距。
(图源网络)
那么,接下来我们绘制这些圆,生成一个继承View的CircleIndicatorView,在这个View中定义相关的属性,如半径,圆点颜色等,我们需要定义一个indicator类,用来存放圆点数据:
public static class Indicator{
public float cx; // 圆心x坐标
public float cy; // 圆心y 坐标
}
接下来,计算每个圆的圆点圆心的位置,第一个圆心的x坐标为半径加上边界值,y的值取View的高度除二,取中间位置,上面已经介绍了它的计算规律,我们只需按规律算出相应的值保存在mIndicators(存放圆心的一个List)中:
private void measureIndicator(){
mIndicators.clear();//mIndicators为存放每个点的圆心的List
float cx = 0;
for(int i=0;i<mCount;i++){
Indicator indicator = new Indicator();
if( i== 0){
cx = mRadius + mStrokeWidth;//第一个点,mStrokeWidth为边界值
}else{
cx += (mRadius + mStrokeWidth) * 2 +mSpace;//第二、三......个
}
indicator.cx = cx;
indicator.cy = getMeasuredHeight() / 2;//getMeasuredHeight() 获取View的实际大小
mIndicators.add(indicator);
}
}
有了圆心的数据,我们就可以绘制圆了,我们需要定义画圆心和字符的Paint类(mCirclePaint、mTextPaint),Paint可以用于画几何图形,文本和bitmap,setStyle用于设置样式,样式有三种:
Paint.Style.FILL:填充内部
Paint.Style.FILL_AND_STROKE :填充内部和描边
Paint.Style.STROKE :描边
Paint还有其他属性:
setAntiAlias(boolean a) //设置抗锯齿,如果不设置,加载位图的时候可能会出现锯齿状的边界,如果设置,边界就会变的稍微有点模糊,锯齿就看不到了。
setDither(boolean dither) //设置是否抖动,如果不设置感觉就会有一些僵硬的线条,如果设置图像就会看的更柔和一些
protected void onDraw(Canvas canvas) {
for(int i=0;i<mIndicators.size();i++){
Indicator indicator = mIndicators.get(i);
float x = indicator.cx;
float y = indicator.cy;
if(mSelectPosition == i){//当前页
mCirclePaint.setStyle(Paint.Style.FILL);//填充
mCirclePaint.setColor(mSelectColor);//颜色可以再配置中自行设置
}else{
mCirclePaint.setColor(mDotNormalColor);
if(mFillMode != FillMode.NONE){//圆内非空
mCirclePaint.setStyle(Paint.Style.STROKE);//描边
}else{
mCirclePaint.setStyle(Paint.Style.FILL);
}
}
canvas.drawCircle(x,y, mRadius, mCirclePaint);
}
如果圆内有字母数字我们还需要再进行绘制,首先需要判断FillMode的值是什么,若为字母或数字则可进一步绘制,用Canvas来绘制文本需要给文本定位,我们借助Rect来获得文本的宽、高,Paint.getTextBounds用于获取文本的宽高,然后我们计算文本的开始位置,使文本居中:
// 绘制小圆点中的内容
if(mFillMode != FillMode.NONE){
String text = "";
if(mFillMode == FillMode.LETTER){//圆内为字母
if(i >= 0 && i<LETTER.length){
text = LETTER[i];
}
}else{
text = String.valueOf(i+1);
}
Rect bound = new Rect();
mTextPaint.getTextBounds(text,0,text.length(),bound);//用于获取文本的宽高
int textWidth = bound.width();
int textHeight = bound.height();
float textStartX = x - textWidth / 2;
float textStartY = y + textHeight / 2;
canvas.drawText(text,textStartX,textStartY, mTextPaint);
}
}
还有一个细节,我们在点击相应的小圆点时会跳转到相应页面,我们来实现它:
@Override
public boolean onTouchEvent(MotionEvent event) {
float xPoint = 0;
float yPoint = 0;
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
xPoint = event.getX();
yPoint = event.getY();
handleActionDown(xPoint,yPoint);
break;
}
return super.onTouchEvent(event);
}
private void handleActionDown(float xDis,float yDis){
for(int i=0;i<mIndicators.size();i++){
Indicator indicator = mIndicators.get(i);
if(xDis < (indicator.cx + mRadius+mStrokeWidth)
&& xDis >=(indicator.cx - (mRadius + mStrokeWidth))
&& yDis >= (yDis - (indicator.cy+mStrokeWidth))
&& yDis <(indicator.cy+mRadius+mStrokeWidth)){
// 找到了点击的Indicator
// 是否允许切换ViewPager
if(mIsEnableClickSwitch){
mViewPager.setCurrentItem(i,false);
}
// 回调
if(mOnIndicatorClickListener!=null){
mOnIndicatorClickListener.onSelected(i);
}
break;
}
}
}
我们需要与ViewPaper配合使用,所有还需要一个与ViewPaper关联的方法,并在其中设置ViewPaper监听该View:
public void setUpWithViewPager(ViewPager viewPager){
releaseViewPager();
if(viewPager == null){
return;
}
mViewPager = viewPager;
mViewPager.addOnPageChangeListener(this);
int count = mViewPager.getAdapter().getCount();
setCount(count);
}
基本的已实现,那么我们来应用它,在xml文件中,我们可以在这里设置很多属性,这些属性对应着attr.xml中的内容:
<resources>
<declare-styleable name="CircleIndicatorView">
<attr name="indicatorRadius" format="dimension"/>
<attr name="indicatorBorderWidth" format="dimension"/>
<attr name="indicatorSpace" format="dimension"/>
<attr name="indicatorTextColor" format="color"/>
<attr name="indicatorColor" format="color"/>
<attr name="indicatorSelectColor" format="color"/>
<attr name="enableIndicatorSwitch" format="boolean"/>
<attr name="fill_mode">
<enum name="letter" value="0"/>
<enum name="number" value="1"/>
<enum name="none" value="2"/>
</attr>
</declare-styleable>
</resources>
<com.zhouwei.indicatorview.CircleIndicatorView
android:id="@+id/indicator_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_marginBottom="50dp"
android:layout_centerHorizontal="true"
app:indicatorSelectColor="#00A882"
app:fill_mode="letter"
app:indicatorBorderWidth="2dp"
app:indicatorRadius="8dp"
app:indicatorColor="@color/colorAccent"
app:indicatorTextColor="@android:color/white"
/>
在activity中与ViewPaper关联:
mIndicatorView = (CircleIndicatorView) findViewById(R.id.indicator_view);
// 关联ViewPager
mIndicatorView.setUpWithViewPager(mViewPager);
实验效果:
到这里已经介绍完了,该控件已经有开源代码了,地址为:源码
我们可以在配置文件中设置即可直接使用:
在build.gradle(project)中添加:
allprojects {
repositories {
google()
jcenter()
maven { url 'https://jitpack.io' }
}
}
在build.gradle(app)中dependencies添加:
implementation 'com.github.pinguo-zhouwei:CircleIndicatorView:v1.0.0'
sync一下即可直接使用