├── .gitignore ├── .idea ├── .name ├── compiler.xml ├── copyright │ └── profiles_settings.xml ├── encodings.xml ├── gradle.xml ├── misc.xml ├── modules.xml └── runConfigurations.xml ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── legendmohe │ │ └── circleview │ │ └── ApplicationTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── legendmohe │ │ │ └── circleview │ │ │ ├── AnimationUtil.java │ │ │ ├── CircleView.java │ │ │ ├── ColorUtil.java │ │ │ ├── MainActivity.java │ │ │ └── model │ │ │ ├── Dot.java │ │ │ └── Oval.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 │ └── com │ └── legendmohe │ └── circleview │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── 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/.name: -------------------------------------------------------------------------------- 1 | CircleView -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | 23 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 19 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 46 | 47 | 48 | 49 | 50 | 1.8 51 | 52 | 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CircleViewDemo 2 | 3 | 效果图如下: 4 | 5 | ![效果图](https://cloud.githubusercontent.com/assets/1665771/15460644/c7cbd92c-20e5-11e6-8977-8233015c9387.gif) 6 | 7 | 模仿并实现了小米空气净化器的主页旋转UI,动画效果比小米的流畅。 8 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 23 5 | buildToolsVersion "23.0.3" 6 | 7 | defaultConfig { 8 | applicationId "com.legendmohe.circleview" 9 | minSdkVersion 16 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(dir: 'libs', include: ['*.jar']) 24 | testCompile 'junit:junit:4.12' 25 | compile 'com.android.support:appcompat-v7:23.4.0' 26 | compile 'com.jakewharton:butterknife:7.0.1' 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 /Users/legendmohe/Documents/adt-bundle-mac-x86_64-20140702/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/com/legendmohe/circleview/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package com.legendmohe.circleview; 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/com/legendmohe/circleview/AnimationUtil.java: -------------------------------------------------------------------------------- 1 | package com.legendmohe.circleview; 2 | 3 | import android.animation.ValueAnimator; 4 | import android.graphics.Color; 5 | 6 | /** 7 | * Created by legendmohe on 16/5/23. 8 | */ 9 | public class AnimationUtil { 10 | public static ValueAnimator animateColorChange( 11 | int fromColor, 12 | int toColor, 13 | int duration, 14 | final AnimateColorChangeListener listener) 15 | { 16 | final float[] from = new float[3]; 17 | final float[] to = new float[3]; 18 | 19 | Color.colorToHSV(fromColor, from); // from color 20 | Color.colorToHSV(toColor, to); // to color 21 | 22 | ValueAnimator anim = ValueAnimator.ofFloat(0, 1); // animate from 0 to 1 23 | anim.setDuration(duration); 24 | 25 | final float[] hsv = new float[3]; // transition color 26 | anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener(){ 27 | @Override public void onAnimationUpdate(ValueAnimator animation) { 28 | if (listener != null) { 29 | // Transition along each axis of HSV (hue, saturation, value) 30 | hsv[0] = from[0] + (to[0] - from[0])*animation.getAnimatedFraction(); 31 | hsv[1] = from[1] + (to[1] - from[1])*animation.getAnimatedFraction(); 32 | hsv[2] = from[2] + (to[2] - from[2])*animation.getAnimatedFraction(); 33 | listener.onColorChange(Color.HSVToColor(hsv)); 34 | } 35 | } 36 | }); 37 | 38 | return anim; 39 | } 40 | 41 | public interface AnimateColorChangeListener { 42 | void onColorChange(int color); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /app/src/main/java/com/legendmohe/circleview/CircleView.java: -------------------------------------------------------------------------------- 1 | package com.legendmohe.circleview; 2 | 3 | import android.animation.AnimatorSet; 4 | import android.animation.ValueAnimator; 5 | import android.annotation.TargetApi; 6 | import android.content.Context; 7 | import android.graphics.Canvas; 8 | import android.graphics.Color; 9 | import android.graphics.Paint; 10 | import android.graphics.RectF; 11 | import android.os.Build; 12 | import android.support.annotation.ColorInt; 13 | import android.util.AttributeSet; 14 | import android.view.View; 15 | import android.view.animation.LinearInterpolator; 16 | 17 | import com.legendmohe.circleview.model.Dot; 18 | import com.legendmohe.circleview.model.Oval; 19 | 20 | import java.util.Random; 21 | 22 | /** 23 | * Created by legendmohe on 16/5/22. 24 | */ 25 | public class CircleView extends View { 26 | private static final String TAG = "CircleView"; 27 | 28 | private Paint mOvalPaint; 29 | private Paint mDotPaint; 30 | private @ColorInt 31 | int mPaintColor; 32 | 33 | private ValueAnimator mOvalRotateAnimator; 34 | private ValueAnimator mOvalSizeAnimator; 35 | private ValueAnimator mDotMoveAnimator; 36 | private Random mRandom = new Random(); 37 | private AnimatorSet mAnimatorSet; 38 | 39 | private Oval[] mOvals; 40 | private Dot[] mDots; 41 | private int mCenterX, mCenterY; 42 | private float mRotateAngle = 0.0f; 43 | 44 | private int mNumOfDot = 30; 45 | private int mNumOfOval = 30; 46 | private int mOvalRadius = 400; 47 | private int mOvalRegionWidth = mOvalRadius*2 - 15; 48 | private int mOvalRegionHeight = mOvalRadius*2; 49 | private int mOvalRotateDuration = 3000; 50 | private float mOvalStrokeWidth = 3.0f; 51 | private int mOvalSizeDuration = 2000; 52 | private int mOvalSizeExpendWidth = 50; 53 | private int mDotMovingDuration = 1000; 54 | private int mOvalAlphaDelta = 20; 55 | private int mOvalMinAlpha = 20; 56 | private int mDotMaxVelocity = 4; 57 | private int mDotMinVelocity = 2; 58 | private int mDotMaxRadius = 8; 59 | private int mDotMinRadius = 2; 60 | private int mDotMinAlpha = 100; 61 | private int mDotMaxAplha = 255; 62 | private int mDotBornMargin = 30; 63 | 64 | public CircleView(Context context) { 65 | super(context); 66 | setup(); 67 | } 68 | 69 | public CircleView(Context context, AttributeSet attrs) { 70 | super(context, attrs); 71 | setup(); 72 | } 73 | 74 | public CircleView(Context context, AttributeSet attrs, int defStyleAttr) { 75 | super(context, attrs, defStyleAttr); 76 | setup(); 77 | } 78 | 79 | @TargetApi(Build.VERSION_CODES.LOLLIPOP) 80 | public CircleView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 81 | super(context, attrs, defStyleAttr, defStyleRes); 82 | setup(); 83 | } 84 | 85 | private void setup() { 86 | mPaintColor = Color.BLACK; 87 | 88 | mDotPaint = new Paint(); 89 | mDotPaint.setColor(mPaintColor); 90 | mDotPaint.setAntiAlias(true); 91 | mDotPaint.setStyle(Paint.Style.FILL); 92 | 93 | mOvalPaint = new Paint(); 94 | mOvalPaint.setColor(mPaintColor); 95 | mOvalPaint.setAntiAlias(true); 96 | mOvalPaint.setStrokeWidth(mOvalStrokeWidth); 97 | mOvalPaint.setStyle(Paint.Style.STROKE); 98 | 99 | mOvalRotateAnimator = new ValueAnimator(); 100 | mOvalRotateAnimator.setInterpolator(new LinearInterpolator()); 101 | mOvalRotateAnimator.setDuration(mOvalRotateDuration); 102 | mOvalRotateAnimator.setFloatValues(0, 360); 103 | mOvalRotateAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 104 | @Override 105 | public void onAnimationUpdate(ValueAnimator animation) { 106 | mRotateAngle = (float) animation.getAnimatedValue(); 107 | invalidate(); 108 | } 109 | }); 110 | mOvalRotateAnimator.setRepeatMode(ValueAnimator.RESTART); 111 | mOvalRotateAnimator.setRepeatCount(ValueAnimator.INFINITE); 112 | 113 | mOvalSizeAnimator = new ValueAnimator(); 114 | mOvalSizeAnimator.setInterpolator(new LinearInterpolator()); 115 | mOvalSizeAnimator.setDuration(mOvalSizeDuration); 116 | mOvalSizeAnimator.setIntValues(0, mOvalSizeExpendWidth); 117 | mOvalSizeAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 118 | @Override 119 | public void onAnimationUpdate(ValueAnimator animation) { 120 | int value = (int) animation.getAnimatedValue(); 121 | for (int i = 0; i < mNumOfOval; i++) { 122 | Oval oval = mOvals[i]; 123 | oval.mRectF.set( 124 | oval.mOriginalRectF.left + value, 125 | oval.mOriginalRectF.top, 126 | oval.mOriginalRectF.right - value, 127 | oval.mOriginalRectF.bottom); 128 | } 129 | invalidate(); 130 | } 131 | }); 132 | mOvalSizeAnimator.setRepeatMode(ValueAnimator.REVERSE); 133 | mOvalSizeAnimator.setRepeatCount(ValueAnimator.INFINITE); 134 | 135 | mDotMoveAnimator = new ValueAnimator(); 136 | mDotMoveAnimator.setInterpolator(new LinearInterpolator()); 137 | mDotMoveAnimator.setDuration(mDotMovingDuration); 138 | mDotMoveAnimator.setIntValues(0, mDotMovingDuration); 139 | mDotMoveAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 140 | @Override 141 | public void onAnimationUpdate(ValueAnimator animation) { 142 | for (int i = 0; i < mNumOfDot; i++) { 143 | Dot dot = mDots[i]; 144 | dot.distance -= dot.velocity; 145 | dot.alpha = (int) (dot.baseAlpha*((double)(dot.distance - mOvalRadius)/(dot.baseDistance - mOvalRadius))); 146 | if (dot.distance < mOvalRadius) { 147 | randomDot(dot); 148 | } 149 | } 150 | invalidate(); 151 | } 152 | }); 153 | mDotMoveAnimator.setRepeatMode(ValueAnimator.RESTART); 154 | mDotMoveAnimator.setRepeatCount(ValueAnimator.INFINITE); 155 | 156 | mAnimatorSet = new AnimatorSet(); 157 | mAnimatorSet.playTogether(mOvalRotateAnimator, mOvalSizeAnimator, mDotMoveAnimator); 158 | } 159 | 160 | @Override 161 | protected void onSizeChanged(int w, int h, int oldw, int oldh) { 162 | mCenterX = w/2; 163 | mCenterY = h/2; 164 | setupDrawables(w, h); 165 | if (mAnimatorSet.isStarted()) { 166 | mAnimatorSet.cancel(); 167 | } 168 | mAnimatorSet.start(); 169 | } 170 | 171 | private void setupDrawables(int w, int h) { 172 | mOvals = new Oval[mNumOfOval]; 173 | int alpha = 255; 174 | for (int i = 0; i < mNumOfOval; i++) { 175 | RectF rectF = new RectF( 176 | mCenterX - mOvalRegionWidth /2, 177 | mCenterY - mOvalRegionHeight /2, 178 | mCenterX + mOvalRegionWidth /2, 179 | mCenterY + mOvalRegionHeight /2); 180 | alpha = alpha - mOvalAlphaDelta; 181 | if (alpha < mOvalMinAlpha) 182 | alpha = mOvalMinAlpha; 183 | mOvals[i] = new Oval(rectF, 0, alpha); 184 | } 185 | 186 | mDots = new Dot[mNumOfDot]; 187 | for (int i = 0; i < mNumOfDot; i++) { 188 | mDots[i] = new Dot(mCenterX, mCenterY); 189 | randomDot(mDots[i]); 190 | } 191 | } 192 | 193 | private void randomDot(Dot dot) { 194 | int w = getWidth(); 195 | int h = getHeight(); 196 | 197 | int minDistance = mOvalRadius + mDotBornMargin; 198 | int alpha = mRandom.nextInt(mDotMaxAplha - mDotMinAlpha) + mDotMinAlpha; 199 | double angle = Math.toRadians(mRandom.nextInt(360)); 200 | int distance = mRandom.nextInt((int) Math.sqrt(w/2.0*w/2.0 + h/2.0*h/2.0) - minDistance) + minDistance; 201 | int velocity = mRandom.nextInt(mDotMaxVelocity - mDotMinVelocity) + mDotMinVelocity; 202 | int radius = mRandom.nextInt(mDotMaxRadius - mDotMinRadius) + mDotMinRadius; 203 | 204 | dot.setAlpha(alpha); 205 | dot.setBaseAlpha(alpha); 206 | dot.setAngle(angle); 207 | dot.setDistance(distance); 208 | dot.setBaseDistance(distance); 209 | dot.setVelocity(velocity); 210 | dot.setRadius(radius); 211 | } 212 | 213 | @Override 214 | protected void onDraw(Canvas canvas) { 215 | for (int i = 0; i < mNumOfDot; i++) { 216 | Dot dot = mDots[i]; 217 | mDotPaint.setAlpha(dot.alpha); 218 | canvas.drawCircle(dot.getCenterX(), dot.getCenterY(), dot.radius, mDotPaint); 219 | } 220 | 221 | canvas.rotate(mRotateAngle, mCenterX, mCenterY); 222 | for (int i = mNumOfOval - 1; i >= 0; i--) { 223 | Oval oval = mOvals[i]; 224 | mOvalPaint.setAlpha(oval.mAlpha); 225 | canvas.rotate(360/mNumOfOval, mCenterX, mCenterY); 226 | canvas.drawOval(oval.mRectF, mOvalPaint); 227 | } 228 | } 229 | 230 | public void setPaintColor(@ColorInt int color) { 231 | mPaintColor = color; 232 | mDotPaint.setColor(mPaintColor); 233 | mOvalPaint.setColor(mPaintColor); 234 | } 235 | 236 | public int getPaintColor() { 237 | return mPaintColor; 238 | } 239 | 240 | public void setNumOfDot(final int numOfDot) { 241 | postOnAnimation(new Runnable() { 242 | @Override 243 | public void run() { 244 | Dot[] copyDots = new Dot[numOfDot]; 245 | System.arraycopy(mDots, 0, copyDots, 0, Math.min(mDots.length, numOfDot)); 246 | 247 | int numberDelta = numOfDot - mNumOfDot; 248 | if (numberDelta > 0) { 249 | for (int i = 0; i < numberDelta; i++) { 250 | copyDots[i + mNumOfDot] = new Dot(mCenterX, mCenterY); 251 | randomDot(copyDots[i + mNumOfDot]); 252 | } 253 | } 254 | mNumOfDot = numOfDot; 255 | mDots = copyDots; 256 | } 257 | }); 258 | postInvalidateOnAnimation(); 259 | } 260 | 261 | public void setDotVelocity(final int maxVelocity, final int minVelocity) { 262 | postOnAnimation(new Runnable() { 263 | 264 | @Override 265 | public void run() { 266 | mDotMaxVelocity = maxVelocity; 267 | mDotMinVelocity = minVelocity; 268 | 269 | if (mDotMinVelocity <= 0) { 270 | mDotMinVelocity = 1; 271 | } 272 | mDotMaxVelocity = Math.max(mDotMaxVelocity, mDotMinVelocity); 273 | } 274 | }); 275 | postInvalidateOnAnimation(); 276 | } 277 | 278 | public void setOvalRotateDuration(final int ovalRotateDuration) { 279 | postOnAnimation(new Runnable() { 280 | 281 | @Override 282 | public void run() { 283 | mOvalRotateDuration = ovalRotateDuration; 284 | mOvalRotateDuration = Math.max(mOvalRotateDuration, 200); 285 | mOvalRotateAnimator.setDuration(mOvalRotateDuration); 286 | } 287 | }); 288 | postInvalidateOnAnimation(); 289 | } 290 | 291 | public void setOvalSizeExpendWidth(final int ovalSizeExpendWidth) { 292 | postOnAnimation(new Runnable() { 293 | 294 | @Override 295 | public void run() { 296 | mOvalSizeExpendWidth = ovalSizeExpendWidth; 297 | mOvalSizeExpendWidth = Math.max(mOvalSizeExpendWidth, 0); 298 | mOvalSizeAnimator.setIntValues(0, mOvalSizeExpendWidth); 299 | } 300 | }); 301 | postInvalidateOnAnimation(); 302 | } 303 | } 304 | -------------------------------------------------------------------------------- /app/src/main/java/com/legendmohe/circleview/ColorUtil.java: -------------------------------------------------------------------------------- 1 | package com.legendmohe.circleview; 2 | 3 | import android.graphics.Color; 4 | 5 | import java.util.Random; 6 | 7 | /** 8 | * Created by legendmohe on 16/5/23. 9 | */ 10 | public class ColorUtil { 11 | private static final Random sRandom = new Random(); 12 | 13 | public static int getContrastColor(int color) { 14 | double y = (299 * Color.red(color) + 587 * Color.green(color) + 114 * Color.blue(color)) / 1000; 15 | return y >= 128 ? Color.BLACK : Color.WHITE; 16 | } 17 | 18 | public static int getComplementaryColor(int colorToInvert) { 19 | float[] hsv = new float[3]; 20 | Color.RGBToHSV(Color.red(colorToInvert), Color.green(colorToInvert), 21 | Color.blue(colorToInvert), hsv); 22 | hsv[0] = (hsv[0] + 180) % 360; 23 | return Color.HSVToColor(hsv); 24 | } 25 | 26 | public static int getContrastVersionForColor(int color) { 27 | float[] hsv = new float[3]; 28 | Color.RGBToHSV(Color.red(color), Color.green(color), Color.blue(color), 29 | hsv); 30 | if (hsv[2] < 0.5) { 31 | hsv[2] = 0.7f; 32 | } else { 33 | hsv[2] = 0.3f; 34 | } 35 | hsv[1] = hsv[1] * 0.2f; 36 | return Color.HSVToColor(hsv); 37 | } 38 | 39 | public static int randomColor() { 40 | int color = Color.argb(255, sRandom.nextInt(256), sRandom.nextInt(256), sRandom.nextInt(256)); 41 | return color; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /app/src/main/java/com/legendmohe/circleview/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.legendmohe.circleview; 2 | 3 | import android.animation.AnimatorSet; 4 | import android.animation.ValueAnimator; 5 | import android.graphics.Color; 6 | import android.graphics.drawable.ColorDrawable; 7 | import android.support.v7.app.AppCompatActivity; 8 | import android.os.Bundle; 9 | import android.widget.RelativeLayout; 10 | import android.widget.SeekBar; 11 | import android.widget.TextView; 12 | 13 | import butterknife.Bind; 14 | import butterknife.ButterKnife; 15 | import butterknife.OnClick; 16 | 17 | public class MainActivity extends AppCompatActivity { 18 | 19 | @Bind(R.id.circle_view) 20 | CircleView mCircleView; 21 | @Bind(R.id.container_layout) 22 | RelativeLayout mContainerLayout; 23 | @Bind(R.id.textView1) 24 | TextView mTextView1; 25 | @Bind(R.id.textView2) 26 | TextView mTextView2; 27 | @Bind(R.id.textView3) 28 | TextView mTextView3; 29 | @Bind(R.id.textView4) 30 | TextView mTextView4; 31 | @Bind(R.id.dot_number_seekBar) 32 | SeekBar mDotNumberSeekBar; 33 | @Bind(R.id.dot_speed_seekBar) 34 | SeekBar mDotSpeedSeekBar; 35 | @Bind(R.id.circle_speed_seekBar) 36 | SeekBar mCircleSpeedSeekBar; 37 | @Bind(R.id.circle_expend_seekBar) 38 | SeekBar mCircleExpendSeekBar; 39 | @Bind(R.id.dot_number_seekBar_textView) 40 | TextView mDotNumberSeekBarTextView; 41 | @Bind(R.id.dot_speed_seekBar_textView) 42 | TextView mDotSpeedSeekBarTextView; 43 | @Bind(R.id.circle_speed_seekBar_textView) 44 | TextView mCircleSpeedSeekBarTextView; 45 | @Bind(R.id.circle_expend_seekBar_textView) 46 | TextView mCircleExpendSeekBarTextView; 47 | 48 | private AnimatorSet mColorAnimation; 49 | private int mColorAnimationDuration = 1000; 50 | 51 | private final int[] mColorList = new int[]{Color.BLACK, Color.WHITE, Color.GREEN, Color.CYAN, Color.YELLOW}; 52 | private int mCurrentColorIndex = 0; 53 | 54 | private AnimationUtil.AnimateColorChangeListener mContainerColorChangeListener = new AnimationUtil.AnimateColorChangeListener() { 55 | @Override 56 | public void onColorChange(int color) { 57 | mContainerLayout.setBackgroundColor(color); 58 | } 59 | }; 60 | 61 | ; 62 | private AnimationUtil.AnimateColorChangeListener mCircleColorChangeListener = new AnimationUtil.AnimateColorChangeListener() { 63 | @Override 64 | public void onColorChange(int color) { 65 | mCircleView.setPaintColor(color); 66 | mTextView1.setTextColor(color); 67 | mTextView2.setTextColor(color); 68 | mTextView3.setTextColor(color); 69 | mTextView4.setTextColor(color); 70 | } 71 | }; 72 | 73 | @Override 74 | protected void onCreate(Bundle savedInstanceState) { 75 | super.onCreate(savedInstanceState); 76 | setContentView(R.layout.activity_main); 77 | ButterKnife.bind(this); 78 | 79 | setupSeekBar(); 80 | } 81 | 82 | private void setupSeekBar() { 83 | mDotNumberSeekBar.setMax(100); 84 | mDotNumberSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { 85 | @Override 86 | public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { 87 | 88 | } 89 | 90 | @Override 91 | public void onStartTrackingTouch(SeekBar seekBar) { 92 | 93 | } 94 | 95 | @Override 96 | public void onStopTrackingTouch(SeekBar seekBar) { 97 | mCircleView.setNumOfDot(seekBar.getProgress()); 98 | mDotNumberSeekBarTextView.setText(String.valueOf(seekBar.getProgress())); 99 | } 100 | }); 101 | 102 | mDotSpeedSeekBar.setMax(20); 103 | mDotSpeedSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { 104 | @Override 105 | public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { 106 | 107 | } 108 | 109 | @Override 110 | public void onStartTrackingTouch(SeekBar seekBar) { 111 | 112 | } 113 | 114 | @Override 115 | public void onStopTrackingTouch(SeekBar seekBar) { 116 | mCircleView.setDotVelocity(seekBar.getProgress(), (int) (seekBar.getProgress() * 0.4)); 117 | mDotSpeedSeekBarTextView.setText(String.valueOf(seekBar.getProgress())); 118 | } 119 | }); 120 | mCircleSpeedSeekBar.setMax(6000); 121 | mCircleSpeedSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { 122 | @Override 123 | public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { 124 | 125 | } 126 | 127 | @Override 128 | public void onStartTrackingTouch(SeekBar seekBar) { 129 | 130 | } 131 | 132 | @Override 133 | public void onStopTrackingTouch(SeekBar seekBar) { 134 | int value = seekBar.getMax() - seekBar.getProgress(); 135 | mCircleView.setOvalRotateDuration(value); 136 | mCircleSpeedSeekBarTextView.setText(String.valueOf(value)); 137 | } 138 | }); 139 | mCircleExpendSeekBar.setMax(200); 140 | mCircleExpendSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { 141 | @Override 142 | public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { 143 | 144 | } 145 | 146 | @Override 147 | public void onStartTrackingTouch(SeekBar seekBar) { 148 | 149 | } 150 | 151 | @Override 152 | public void onStopTrackingTouch(SeekBar seekBar) { 153 | mCircleView.setOvalSizeExpendWidth(seekBar.getProgress()); 154 | mCircleExpendSeekBarTextView.setText(String.valueOf(seekBar.getProgress())); 155 | } 156 | }); 157 | } 158 | 159 | @OnClick(R.id.button4) 160 | public void onChangeStateClick() { 161 | // int color = mColorList[mCurrentColorIndex]; 162 | int color = ColorUtil.randomColor(); 163 | playColorChange(color, ColorUtil.getContrastColor(color)); 164 | // mCurrentColorIndex++; 165 | // if (mCurrentColorIndex >= mColorList.length) 166 | // mCurrentColorIndex = 0; 167 | } 168 | 169 | private void playColorChange(int backgroundColor, int circleColor) { 170 | if (mColorAnimation != null) { 171 | if (mColorAnimation.isRunning()) 172 | mColorAnimation.cancel(); 173 | } 174 | 175 | ValueAnimator backgroundColorAnimation = AnimationUtil.animateColorChange( 176 | ((ColorDrawable) mContainerLayout.getBackground()).getColor(), 177 | backgroundColor, 178 | mColorAnimationDuration, 179 | mContainerColorChangeListener); 180 | ValueAnimator circleColorAnimation = AnimationUtil.animateColorChange( 181 | mCircleView.getPaintColor(), 182 | circleColor, 183 | mColorAnimationDuration, 184 | mCircleColorChangeListener); 185 | mColorAnimation = new AnimatorSet(); 186 | mColorAnimation.playTogether(backgroundColorAnimation, circleColorAnimation); 187 | mColorAnimation.start(); 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /app/src/main/java/com/legendmohe/circleview/model/Dot.java: -------------------------------------------------------------------------------- 1 | package com.legendmohe.circleview.model; 2 | 3 | /** 4 | * Created by legendmohe on 16/5/22. 5 | */ 6 | public class Dot { 7 | public int alpha; 8 | public int baseAlpha; 9 | public int baseX; 10 | public int baseY; 11 | public int radius; 12 | public int velocity; 13 | public double angle; 14 | public int distance; 15 | public int baseDistance; 16 | 17 | public Dot(int baseY, int baseX) { 18 | this.baseY = baseY; 19 | this.baseX = baseX; 20 | } 21 | 22 | public int getCenterX() { 23 | return baseX + (int) (distance*Math.cos(angle)); 24 | } 25 | 26 | public int getCenterY() { 27 | return baseY + (int) (distance*Math.sin(angle)); 28 | } 29 | 30 | public int getAlpha() { 31 | return alpha; 32 | } 33 | 34 | public void setAlpha(int alpha) { 35 | this.alpha = alpha; 36 | } 37 | 38 | public int getRadius() { 39 | return radius; 40 | } 41 | 42 | public void setRadius(int radius) { 43 | this.radius = radius; 44 | } 45 | 46 | public int getVelocity() { 47 | return velocity; 48 | } 49 | 50 | public void setVelocity(int velocity) { 51 | this.velocity = velocity; 52 | } 53 | 54 | public double getAngle() { 55 | return angle; 56 | } 57 | 58 | public void setAngle(double angle) { 59 | this.angle = angle; 60 | } 61 | 62 | public int getDistance() { 63 | return distance; 64 | } 65 | 66 | public void setDistance(int distance) { 67 | this.distance = distance; 68 | } 69 | 70 | public int getBaseY() { 71 | return baseY; 72 | } 73 | 74 | public void setBaseY(int baseY) { 75 | this.baseY = baseY; 76 | } 77 | 78 | public int getBaseX() { 79 | return baseX; 80 | } 81 | 82 | public void setBaseX(int baseX) { 83 | this.baseX = baseX; 84 | } 85 | 86 | public int getBaseAlpha() { 87 | return baseAlpha; 88 | } 89 | 90 | public void setBaseAlpha(int baseAlpha) { 91 | this.baseAlpha = baseAlpha; 92 | } 93 | 94 | public int getBaseDistance() { 95 | return baseDistance; 96 | } 97 | 98 | public void setBaseDistance(int baseDistance) { 99 | this.baseDistance = baseDistance; 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /app/src/main/java/com/legendmohe/circleview/model/Oval.java: -------------------------------------------------------------------------------- 1 | package com.legendmohe.circleview.model; 2 | 3 | import android.graphics.RectF; 4 | 5 | /** 6 | * Created by legendmohe on 16/5/22. 7 | */ 8 | public class Oval { 9 | public RectF mRectF; 10 | public RectF mOriginalRectF; 11 | public float mRotateAngle; 12 | public int mAlpha; 13 | 14 | public Oval(RectF rectF, float rotateAngle, int alpha) { 15 | mRectF = rectF; 16 | mOriginalRectF = new RectF(rectF); 17 | mRotateAngle = rotateAngle; 18 | mAlpha = alpha; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 14 | 15 | 20 | 21 | 27 | 28 | 36 | 37 | 46 | 47 | 55 | 56 | 64 | 65 | 66 | 67 | 74 | 75 |