├── .gitignore ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── cn │ │ └── modificator │ │ └── sample │ │ └── ApplicationTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── cn │ │ │ └── modificator │ │ │ └── sample │ │ │ └── MainActivity.java │ └── res │ │ ├── layout │ │ └── activity_main.xml │ │ ├── mipmap-hdpi │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxxhdpi │ │ └── ic_launcher.png │ │ ├── values-w820dp │ │ └── dimens.xml │ │ └── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── cn │ └── modificator │ └── sample │ └── ExampleUnitTest.java ├── build.gradle ├── circleindicator ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── cn │ │ └── modificator │ │ └── circleindicator │ │ └── ApplicationTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── cn │ │ │ └── modificator │ │ │ └── circleindicator │ │ │ └── CircleIndicator.java │ └── res │ │ └── values │ │ └── values.xml │ └── test │ └── java │ └── cn │ └── modificator │ └── circleindicator │ └── ExampleUnitTest.java ├── picture.gif └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | /.idea 10 | /gradle.properties 11 | /gradle 12 | /gradlew 13 | /gradlew.bat 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Modificator 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## CircleIndicator 2 | ========== 3 | Indicator for ViewPager 4 | 5 | layout 6 | ```xml 7 | 14 | 15 | 16 | 24 | ``` 25 | Java 26 | ```java 27 | //after viewpager setAdapter 28 | CircleIndicator.setViewPager() 29 | ``` 30 | 31 | ##### Enjoy it! 32 | ![picture.gif](picture.gif "") -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 23 5 | buildToolsVersion "23.0.2" 6 | 7 | defaultConfig { 8 | applicationId "cn.modificator.sample" 9 | minSdkVersion 14 10 | targetSdkVersion 23 11 | versionCode 1 12 | versionName "1.0" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | } 21 | 22 | dependencies { 23 | compile fileTree(include: ['*.jar'], dir: 'libs') 24 | testCompile 'junit:junit:4.12' 25 | compile 'com.android.support:appcompat-v7:23.3.0' 26 | compile project(':circleindicator') 27 | } 28 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /home/mod/Android/Sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /app/src/androidTest/java/cn/modificator/sample/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package cn.modificator.sample; 2 | 3 | import android.app.Application; 4 | import android.test.ApplicationTestCase; 5 | 6 | /** 7 | * Testing Fundamentals 8 | */ 9 | public class ApplicationTest extends ApplicationTestCase { 10 | public ApplicationTest() { 11 | super(Application.class); 12 | } 13 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /app/src/main/java/cn/modificator/sample/MainActivity.java: -------------------------------------------------------------------------------- 1 | package cn.modificator.sample; 2 | 3 | import android.os.Bundle; 4 | import android.support.v4.view.PagerAdapter; 5 | import android.support.v4.view.ViewPager; 6 | import android.support.v7.app.AppCompatActivity; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | import android.widget.TextView; 10 | 11 | import cn.modificator.circleindicator.CircleIndicator; 12 | 13 | public class MainActivity extends AppCompatActivity { 14 | ViewPager mViewPager; 15 | CircleIndicator mTabView; 16 | 17 | @Override 18 | protected void onCreate(Bundle savedInstanceState) { 19 | super.onCreate(savedInstanceState); 20 | setContentView(R.layout.activity_main); 21 | 22 | mViewPager = (ViewPager) findViewById(R.id.mViewPager); 23 | mTabView = (CircleIndicator) findViewById(R.id.mTabView); 24 | 25 | mViewPager.setAdapter(new PagerAdapter() { 26 | @Override 27 | public int getCount() { 28 | return 4; 29 | } 30 | 31 | @Override 32 | public boolean isViewFromObject(View view, Object object) { 33 | return view == object; 34 | } 35 | 36 | @Override 37 | public void destroyItem(ViewGroup container, int position, Object object) { 38 | // super.destroyItem(container, position, object); 39 | container.removeView((View) object); 40 | } 41 | 42 | @Override 43 | public Object instantiateItem(ViewGroup container, int position) { 44 | TextView textView = new TextView(container.getContext()); 45 | textView.setText("page " + position); 46 | container.addView(textView); 47 | return textView; 48 | } 49 | }); 50 | mTabView.setViewPager(mViewPager); 51 | 52 | ((CircleIndicator) findViewById(R.id.indicator1)).setViewPager(mViewPager); 53 | ((CircleIndicator) findViewById(R.id.indicator2)).setViewPager(mViewPager); 54 | ((CircleIndicator) findViewById(R.id.indicator3)).setViewPager(mViewPager); 55 | ((CircleIndicator) findViewById(R.id.indicator4)).setViewPager(mViewPager); 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 15 | 16 | 27 | 38 | 49 | 60 | 61 | 65 | 66 | 69 | 70 | 74 | 75 | 81 | 82 | 86 | 87 | 92 | 93 | 94 | 100 | 101 | 105 | 106 | 111 | 112 | 113 | 119 | 120 | 124 | 125 | 130 | 131 | 132 | 138 | 139 | 143 | 144 | 149 | 150 | 151 | 152 | 153 | 164 | 165 | 166 | 167 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Modificator/CircleIndicator/0965329822e466d1957a96494418bd9570cef05e/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Modificator/CircleIndicator/0965329822e466d1957a96494418bd9570cef05e/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Modificator/CircleIndicator/0965329822e466d1957a96494418bd9570cef05e/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Modificator/CircleIndicator/0965329822e466d1957a96494418bd9570cef05e/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Modificator/CircleIndicator/0965329822e466d1957a96494418bd9570cef05e/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/values-w820dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 64dp 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 16dp 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Sample 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/test/java/cn/modificator/sample/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package cn.modificator.sample; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * To work on unit tests, switch the Test Artifact in the Build Variants view. 9 | */ 10 | public class ExampleUnitTest { 11 | @Test 12 | public void addition_isCorrect() throws Exception { 13 | assertEquals(4, 2 + 2); 14 | } 15 | } -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | repositories { 5 | jcenter() 6 | } 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:2.1.0' 9 | 10 | // NOTE: Do not place your application dependencies here; they belong 11 | // in the individual module build.gradle files 12 | } 13 | } 14 | 15 | allprojects { 16 | repositories { 17 | jcenter() 18 | } 19 | } 20 | 21 | task clean(type: Delete) { 22 | delete rootProject.buildDir 23 | } 24 | -------------------------------------------------------------------------------- /circleindicator/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /circleindicator/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion 23 5 | buildToolsVersion "23.0.2" 6 | 7 | defaultConfig { 8 | minSdkVersion 14 9 | targetSdkVersion 23 10 | versionCode 1 11 | versionName "1.0" 12 | } 13 | buildTypes { 14 | release { 15 | minifyEnabled false 16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 17 | } 18 | } 19 | } 20 | 21 | dependencies { 22 | compile fileTree(dir: 'libs', include: ['*.jar']) 23 | provided 'com.android.support:support-v4:23.3.0' 24 | } 25 | -------------------------------------------------------------------------------- /circleindicator/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /home/mod/Android/Sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /circleindicator/src/androidTest/java/cn/modificator/circleindicator/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package cn.modificator.circleindicator; 2 | 3 | import android.app.Application; 4 | import android.test.ApplicationTestCase; 5 | 6 | /** 7 | * Testing Fundamentals 8 | */ 9 | public class ApplicationTest extends ApplicationTestCase { 10 | public ApplicationTest() { 11 | super(Application.class); 12 | } 13 | } -------------------------------------------------------------------------------- /circleindicator/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /circleindicator/src/main/java/cn/modificator/circleindicator/CircleIndicator.java: -------------------------------------------------------------------------------- 1 | package cn.modificator.circleindicator; 2 | 3 | import android.content.Context; 4 | import android.content.res.TypedArray; 5 | import android.database.DataSetObserver; 6 | import android.graphics.Canvas; 7 | import android.graphics.Paint; 8 | import android.graphics.Path; 9 | import android.graphics.RectF; 10 | import android.support.annotation.ColorInt; 11 | import android.support.annotation.FloatRange; 12 | import android.support.annotation.IntDef; 13 | import android.support.annotation.IntRange; 14 | import android.support.v4.view.ViewPager; 15 | import android.util.AttributeSet; 16 | import android.view.View; 17 | 18 | import java.lang.annotation.Retention; 19 | import java.lang.annotation.RetentionPolicy; 20 | 21 | /** 22 | * Created by modificator on 16-5-11. 23 | */ 24 | public class CircleIndicator extends View implements ViewPager.OnPageChangeListener { 25 | 26 | public static final int GRAVITY_FILL = 0; 27 | public static final int GRAVITY_CENTER = 1; 28 | public static final int GRAVITY_LEFT = 2; 29 | public static final int GRAVITY_RIGHT = 3; 30 | 31 | private ViewPager mViewPager; 32 | //point 对齐方式 33 | // @PointGravity 34 | private int pointGravity = 0; 35 | //圆环宽度百分比 36 | private float ringWidth = 0.2f; 37 | //点的背景色 38 | private int pointBgColor = 0xffaaaaaa; 39 | //圆环前景色 40 | private int ringColor = 0xff000000; 41 | //viewpager的页码 42 | private int position = 0; 43 | //Value from [0, 1) indicating the offset from the page at position. 44 | float positionOffset = 0; 45 | //画笔 46 | private Paint paint; 47 | //观察viewpager 页面数变化 48 | DataSetObserver dataSetObserver; 49 | 50 | @IntDef(flag = false, value = {GRAVITY_FILL, GRAVITY_CENTER, GRAVITY_LEFT, GRAVITY_RIGHT}) 51 | @Retention(RetentionPolicy.SOURCE) 52 | public @interface PointGravity { 53 | } 54 | 55 | public CircleIndicator(Context context) { 56 | this(context, null); 57 | } 58 | 59 | public CircleIndicator(Context context, AttributeSet attrs) { 60 | this(context, attrs, 0); 61 | } 62 | 63 | public CircleIndicator(Context context, AttributeSet attrs, int defStyleAttr) { 64 | super(context, attrs, defStyleAttr); 65 | //获取属性 66 | TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.indicator, defStyleAttr, 0); 67 | pointGravity = ta.getInt(R.styleable.indicator_pointGravity, pointGravity); 68 | ringWidth = ta.getFraction(R.styleable.indicator_ringWidth, 1, 1, ringWidth); 69 | pointBgColor = ta.getColor(R.styleable.indicator_pointBgColor, pointBgColor); 70 | ringColor = ta.getColor(R.styleable.indicator_ringColor, ringColor); 71 | ta.recycle(); 72 | init(); 73 | } 74 | 75 | /** 76 | * 初始化画笔 77 | */ 78 | private void init() { 79 | paint = new Paint(); 80 | paint.setStyle(Paint.Style.STROKE); 81 | //当viewpager 页面删除或增加的时候实时改变指示器 82 | dataSetObserver = new DataSetObserver() { 83 | @Override 84 | public void onChanged() { 85 | super.onChanged(); 86 | invalidate(); 87 | } 88 | 89 | @Override 90 | public void onInvalidated() { 91 | super.onInvalidated(); 92 | invalidate(); 93 | } 94 | }; 95 | } 96 | 97 | @Override 98 | protected void onDraw(Canvas canvas) { 99 | super.onDraw(canvas); 100 | if (mViewPager == null || mViewPager.getAdapter() == null || mViewPager.getAdapter().getCount() == 0) 101 | return; 102 | //绘制个数 103 | int pointCount = mViewPager.getAdapter().getCount(); 104 | //获取直径 105 | float pointSize = Math.min(getWidth() * 1f / pointCount, getHeight()); 106 | //设置画笔大小 107 | paint.setStrokeWidth(pointSize * ringWidth / 2f); 108 | //计算绘制矩形宽度,如果大于View宽度则设置为填充 pointCount + 1 是为两边留出空位 109 | if ((pointCount * pointSize + (pointCount + 1) * pointSize / 3) > getWidth()) 110 | pointGravity = GRAVITY_FILL; 111 | 112 | drawBgPoint(canvas, pointCount, pointSize); 113 | drawRingLine(canvas, pointCount, pointSize); 114 | } 115 | 116 | /** 117 | * 绘制背景 118 | * 119 | * @param canvas 120 | * @param pointCount 121 | * @param pointSize 122 | */ 123 | private void drawBgPoint(Canvas canvas, int pointCount, float pointSize) { 124 | //设置画笔样式为填充 125 | paint.setStyle(Paint.Style.FILL); 126 | //设置背景色 127 | paint.setColor(pointBgColor); 128 | if (pointGravity == GRAVITY_FILL) { 129 | float boxWidth = getWidth() * 1f / pointCount; 130 | for (int i = 0; i < pointCount; i++) { 131 | float circleCenterX = boxWidth * i + (boxWidth / 2f); 132 | float circleCenterY = getHeight() / 2f; 133 | canvas.drawCircle(circleCenterX, circleCenterY, pointSize / 2f, paint); 134 | } 135 | } else { 136 | //这个变量现在的值是 第一个点到view左边的距离 137 | float drawLeft = getWidth() - (pointCount * pointSize + (pointCount - 1) * pointSize / 3); 138 | if (pointGravity == GRAVITY_CENTER) { 139 | //居中,so 一边一半 140 | drawLeft = drawLeft / 2; 141 | } else if (pointGravity == GRAVITY_RIGHT) { 142 | //右对齐,最右边留下点距离 143 | drawLeft = drawLeft - pointSize / 3; 144 | } else { 145 | //最对齐, 146 | drawLeft = pointSize / 3; 147 | } 148 | 149 | //背景圆的中心点 150 | float circleCenterY = getHeight() / 2f; 151 | float circleCenterX = 0; 152 | for (int i = 0; i < pointCount; i++) { 153 | circleCenterX = drawLeft + pointSize / 2;// + pointSize * 13 / 10 * (i - 1);//* i + pointSize / 3 * (i - 1) + pointSize * i; 154 | drawLeft = drawLeft + pointSize * 13 / 10; 155 | canvas.drawCircle(circleCenterX, circleCenterY, pointSize / 2f, paint); 156 | } 157 | } 158 | } 159 | 160 | private void drawRingLine(Canvas canvas, int pointCount, float pointSize) { 161 | paint.setStyle(Paint.Style.STROKE); 162 | paint.setColor(ringColor); 163 | //计算运动路径长度 164 | double pathLength = pointSize * Math.PI + (pointGravity == GRAVITY_FILL ? (getWidth() * 1f / pointCount) : (pointSize * 13 / 10)); 165 | //计算最第一个点左侧x坐标 166 | float drawLeft = getWidth() - (pointCount * pointSize + (pointCount - 1) * pointSize / 3); 167 | 168 | //position * pointSize * 13 / 10 这个是点的宽度和中间间距的长度 169 | if (pointGravity == GRAVITY_CENTER) { 170 | drawLeft = drawLeft / 2 + position * pointSize * 13 / 10; 171 | } else if (pointGravity == GRAVITY_RIGHT) { 172 | drawLeft = drawLeft - pointSize / 3 + position * pointSize * 13 / 10; 173 | } else { 174 | drawLeft = pointSize / 3 + position * pointSize * 13 / 10; 175 | } 176 | //fill的时候确定中心点 177 | float boxWidth = getWidth() * 1f / pointCount; 178 | //画笔宽度恩,要把圈环画到背景里面需要用到 179 | float paintWidth = paint.getStrokeWidth(); 180 | 181 | //计算第一个圆环的绘制范围 182 | RectF lastRectF = new RectF(); 183 | float lastCenterX = pointGravity == GRAVITY_FILL ? (boxWidth * position + (boxWidth / 2)) : (drawLeft + pointSize / 2); 184 | float lastCenterY = getHeight() / 2f; 185 | lastRectF.left = lastCenterX - pointSize / 2 + paintWidth / 2; 186 | lastRectF.top = lastCenterY - pointSize / 2 + paintWidth / 2; 187 | lastRectF.right = lastCenterX + pointSize / 2 - paintWidth / 2; 188 | lastRectF.bottom = lastCenterY + pointSize / 2 - paintWidth / 2; 189 | //计算第一个圆环绘制角度,保证第二个圆环和下面的线联动 190 | float lastAngle = (float) (360 * ((pointSize * Math.PI / pathLength - positionOffset) / (pointSize * Math.PI / pathLength))); 191 | //避免圆环继续往回画 192 | lastAngle = lastAngle > 0 ? lastAngle : 0; 193 | canvas.drawArc(lastRectF, 90.0f, lastAngle, false, paint); 194 | 195 | //计算第二个圆环的绘制范围 196 | RectF nextRectF = new RectF(); 197 | float nextCenterX = pointGravity == GRAVITY_FILL ? (boxWidth * (position + 1) + (boxWidth / 2)) : (drawLeft + pointSize / 2 + pointSize * 13 / 10); 198 | float nextCenterY = getHeight() / 2f; 199 | nextRectF.left = nextCenterX - pointSize / 2 + paintWidth / 2; 200 | nextRectF.top = nextCenterY - pointSize / 2 + paintWidth / 2; 201 | nextRectF.right = nextCenterX + pointSize / 2 - paintWidth / 2; 202 | nextRectF.bottom = nextCenterY + pointSize / 2 - paintWidth / 2; 203 | float nextAngle = (float) (360 * (((1 - positionOffset) - pointSize * Math.PI / pathLength) / (pointSize * Math.PI / pathLength))); 204 | nextAngle = nextAngle < 0 ? nextAngle : 0; 205 | canvas.drawArc(nextRectF, 90.0f, nextAngle, false, paint); 206 | 207 | //计算底下的线的绘制范围 208 | float lineTop = nextRectF.bottom - paint.getStrokeWidth() / 2; 209 | float lineRight = Double.valueOf(lastCenterX + pathLength * positionOffset).floatValue(); 210 | float lineLeft = Double.valueOf(lineRight - pointSize * Math.PI).floatValue(); 211 | float lineBottom = lineTop + paint.getStrokeWidth(); 212 | //避免绘制超出两个圆心 213 | lineLeft = lineLeft > nextCenterX ? nextCenterX : lineLeft < lastCenterX ? lastCenterX : lineLeft; 214 | lineRight = lineRight > nextCenterX ? nextCenterX : lineRight; 215 | 216 | //别问我为啥用path , line根本满足不了好伐 217 | Path linePath = new Path(); 218 | linePath.moveTo(lineLeft, lineTop); 219 | linePath.lineTo(lineRight, lineTop); 220 | linePath.lineTo(lineRight, lineBottom); 221 | linePath.lineTo(lineLeft, lineBottom); 222 | 223 | //将画笔影响降到最低,不然会超出,虽然还是会超出1像素 224 | paint.setStrokeWidth(1); 225 | paint.setStyle(Paint.Style.FILL); 226 | canvas.drawPath(linePath, paint); 227 | } 228 | 229 | /** 230 | * 设置viewpager滑动监听 231 | * @param viewpager 232 | */ 233 | public void setViewPager(ViewPager viewpager) { 234 | this.mViewPager = viewpager; 235 | mViewPager.addOnPageChangeListener(this); 236 | mViewPager.getAdapter().registerDataSetObserver(dataSetObserver); 237 | } 238 | 239 | @Override 240 | protected void onVisibilityChanged(View changedView, int visibility) { 241 | super.onVisibilityChanged(changedView, visibility); 242 | if (mViewPager != null && mViewPager.getAdapter() != null) 243 | if (visibility == VISIBLE) { 244 | mViewPager.addOnPageChangeListener(this); 245 | try {//上面设置观察者后在设置会挂掉 246 | mViewPager.getAdapter().registerDataSetObserver(dataSetObserver); 247 | } catch (Exception e) { 248 | } 249 | } else { 250 | mViewPager.removeOnPageChangeListener(this); 251 | mViewPager.getAdapter().unregisterDataSetObserver(dataSetObserver); 252 | } 253 | } 254 | 255 | /** 256 | * 画笔宽度百分比 257 | * 258 | * @param ringWidth 259 | */ 260 | public void setRingWidth(@FloatRange(from = 0, to = 1) 261 | float ringWidth) { 262 | this.ringWidth = ringWidth; 263 | invalidate(); 264 | } 265 | 266 | /** 267 | * 设置对齐方式 268 | * 269 | * @param pointGravity 270 | */ 271 | public void setPointGravity(@PointGravity 272 | int pointGravity) { 273 | this.pointGravity = pointGravity; 274 | invalidate(); 275 | } 276 | 277 | /** 278 | * 设置圆环颜色 279 | * 280 | * @param ringColor 0x00000000 - 0xffffffff 281 | */ 282 | public void setRingColor(@ColorInt 283 | @IntRange(from = 0x00000000, to = 0xffffffff) 284 | int ringColor) { 285 | this.ringColor = ringColor; 286 | } 287 | 288 | /** 289 | * 设置point 背景色 290 | * 291 | * @param pointBgColor 0x00000000 - 0xffffffff 292 | */ 293 | public void setPointBgColor(@ColorInt 294 | @IntRange(from = 0x00000000, to = 0xffffffff) 295 | int pointBgColor) { 296 | this.pointBgColor = pointBgColor; 297 | } 298 | 299 | public float getRingWidth() { 300 | return ringWidth; 301 | } 302 | 303 | public int getPointGravity() { 304 | return pointGravity; 305 | } 306 | 307 | public int getRingColor() { 308 | return ringColor; 309 | } 310 | 311 | public int getPointBgColor() { 312 | return pointBgColor; 313 | } 314 | 315 | @Override 316 | public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { 317 | this.position = position; 318 | this.positionOffset = positionOffset; 319 | this.invalidate(); 320 | } 321 | 322 | @Override 323 | public void onPageSelected(int position) { 324 | 325 | } 326 | 327 | @Override 328 | public void onPageScrollStateChanged(int state) { 329 | 330 | } 331 | } 332 | -------------------------------------------------------------------------------- /circleindicator/src/main/res/values/values.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | CircleIndicator 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /circleindicator/src/test/java/cn/modificator/circleindicator/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package cn.modificator.circleindicator; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * To work on unit tests, switch the Test Artifact in the Build Variants view. 9 | */ 10 | public class ExampleUnitTest { 11 | @Test 12 | public void addition_isCorrect() throws Exception { 13 | assertEquals(4, 2 + 2); 14 | } 15 | } -------------------------------------------------------------------------------- /picture.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Modificator/CircleIndicator/0965329822e466d1957a96494418bd9570cef05e/picture.gif -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', ':circleindicator' 2 | --------------------------------------------------------------------------------