diff --git a/README.md b/README.md
index 3bbbfbd..b933e5d 100644
--- a/README.md
+++ b/README.md
@@ -12,3 +12,15 @@ A magic circle (`・ω・´) ノ
[1]: http://weibo.com/1773655610/CzUai6Gid?type=comment#_rnd1442252060746
[2]: http://www.jianshu.com/p/791d3a791ec2#
+2016-12-29 23:49:56
+使用(伪)Builder模式,可链式调用对应方法设置动画属性,目前提供以下方法
+```java
+setInterpolator(Interpolator interpolator);
+setRepeatCount(int count);
+setRepeatMode(int mode);
+setDuration(int duration);
+setColor(int color);
+//颜色变化,需要两个都设置才有效
+setStartColor(int color);
+setEndColor(int color);
+```
\ No newline at end of file
diff --git a/app/app.iml b/app/app.iml
index 92dfbb7..67a484a 100644
--- a/app/app.iml
+++ b/app/app.iml
@@ -84,16 +84,23 @@
+
+
+
+
+
+
-
+
+
diff --git a/app/src/main/java/com/shadev/pierrebeziercircle/MagicCircle.java b/app/src/main/java/com/shadev/pierrebeziercircle/MagicCircle.java
index 20dd081..81c9ff0 100644
--- a/app/src/main/java/com/shadev/pierrebeziercircle/MagicCircle.java
+++ b/app/src/main/java/com/shadev/pierrebeziercircle/MagicCircle.java
@@ -1,234 +1,395 @@
package com.shadev.pierrebeziercircle;
+import android.animation.ArgbEvaluator;
+import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PointF;
+import android.support.annotation.NonNull;
import android.util.AttributeSet;
+import android.util.Log;
import android.view.View;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.Animation;
+import android.view.animation.Interpolator;
import android.view.animation.Transformation;
/**
- * 代码写的比较仓猝,以后再优化写法和补充那些数值的具体含义,求勿喷QAQ
+ * 作者相关教程
+ * http://www.jianshu.com/p/791d3a791ec2
+ *
*/
public class MagicCircle extends View {
-
- private Path mPath;
- private Paint mFillCirclePaint;
-
- /** View的宽度 **/
- private int width;
- /** View的高度,这里View应该是正方形,所以宽高是一样的 **/
- private int height;
- /** View的中心坐标x **/
- private int centerX;
- /** View的中心坐标y **/
- private int centerY;
-
- private float maxLength;
- private float mInterpolatedTime;
- private float stretchDistance;
- private float moveDistance;
- private float cDistance;
- private float radius;
- private float c;
- private float blackMagic = 0.551915024494f;
- private VPoint p2,p4;
- private HPoint p1,p3;
-
-
- public MagicCircle(Context context) {
- this(context, null, 0);
- }
-
- public MagicCircle(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public MagicCircle(Context context, AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
- init();
- }
-
- private void init() {
- mFillCirclePaint = new Paint();
- mFillCirclePaint.setColor(0xFFfe626d);
- mFillCirclePaint.setStyle(Paint.Style.FILL);
- mFillCirclePaint.setStrokeWidth(1);
- mFillCirclePaint.setAntiAlias(true);
- mPath = new Path();
- p2 = new VPoint();
- p4 = new VPoint();
-
- p1 = new HPoint();
- p3 = new HPoint();
- }
-
- @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- super.onLayout(changed, left, top, right, bottom);
- width = getWidth();
- height = getHeight();
- centerX = width / 2;
- centerY = height / 2;
- radius = 50;
- c = radius*blackMagic;
- stretchDistance = radius;
- moveDistance = radius*(3/5f);
- cDistance = c*0.45f;
- maxLength = width - radius -radius;
- }
-
- @Override protected void onDraw(Canvas canvas) {
- super.onDraw(canvas);
- mPath.reset();
- canvas.translate(radius, radius);
-
- if(mInterpolatedTime>=0&&mInterpolatedTime<=0.2){
- model1(mInterpolatedTime);
- }else if(mInterpolatedTime>0.2&&mInterpolatedTime<=0.5){
- model2(mInterpolatedTime);
- }else if(mInterpolatedTime>0.5&&mInterpolatedTime<=0.8){
- model3(mInterpolatedTime);
- }else if(mInterpolatedTime>0.8&&mInterpolatedTime<=0.9){
- model4(mInterpolatedTime);
- }else if(mInterpolatedTime>0.9&&mInterpolatedTime<=1){
- model5(mInterpolatedTime);
- }
-
- float offset = maxLength*(mInterpolatedTime-0.2f);
- offset = offset>0?offset:0;
- p1.adjustAllX(offset);
- p2.adjustAllX(offset);
- p3.adjustAllX(offset);
- p4.adjustAllX(offset);
-
- mPath.moveTo(p1.x,p1.y);
- mPath.cubicTo(p1.right.x, p1.right.y, p2.bottom.x, p2.bottom.y, p2.x,p2.y);
- mPath.cubicTo(p2.top.x, p2.top.y, p3.right.x, p3.right.y, p3.x,p3.y);
- mPath.cubicTo(p3.left.x, p3.left.y, p4.top.x, p4.top.y, p4.x,p4.y);
- mPath.cubicTo(p4.bottom.x,p4.bottom.y,p1.left.x,p1.left.y,p1.x,p1.y);
-
- canvas.drawPath(mPath,mFillCirclePaint);
-
- }
-
- private void model0(){
- p1.setY(radius);
- p3.setY(-radius);
- p3.x = p1.x = 0;
- p3.left.x = p1.left.x = -c;
- p3.right.x = p1.right.x = c;
-
- p2.setX(radius);
- p4.setX(-radius);
- p2.y = p4.y = 0;
- p2.top.y = p4.top.y = -c;
- p2.bottom.y = p4.bottom.y = c;
- }
-
- private void model1(float time){//0~0.2
- model0();
-
- p2.setX(radius+stretchDistance*time*5);
- }
-
- private void model2(float time){//0.2~0.5
- model1(0.2f);
- time = (time - 0.2f) * (10f / 3);
- p1.adjustAllX(stretchDistance/2 * time );
- p3.adjustAllX(stretchDistance/2 * time );
- p2.adjustY(cDistance * time);
- p4.adjustY(cDistance * time);
- }
-
- private void model3(float time){//0.5~0.8
- model2(0.5f);
- time = (time - 0.5f) * (10f / 3);
- p1.adjustAllX(stretchDistance / 2 * time);
- p3.adjustAllX(stretchDistance / 2 * time);
- p2.adjustY(-cDistance * time);
- p4.adjustY(-cDistance * time);
-
- p4.adjustAllX(stretchDistance / 2 * time);
-
- }
-
- private void model4(float time){//0.8~0.9
- model3(0.8f);
- time = (time - 0.8f) * 10;
- p4.adjustAllX(stretchDistance / 2 * time);
- }
-
- private void model5(float time){
- model4(0.9f);
- time = time - 0.9f;
- p4.adjustAllX((float) (Math.sin(Math.PI*time*10f)*(2/10f*radius)));
- }
-
- class VPoint{
- public float x;
- public float y;
- public PointF top = new PointF();
- public PointF bottom = new PointF();
-
- public void setX(float x){
- this.x = x;
- top.x = x;
- bottom.x = x;
- }
-
- public void adjustY(float offset){
- top.y -= offset;
- bottom.y += offset;
- }
-
- public void adjustAllX(float offset){
- this.x+= offset;
- top.x+= offset;
- bottom.x+=offset;
- }
- }
-
- class HPoint{
- public float x;
- public float y;
- public PointF left = new PointF();
- public PointF right = new PointF();
-
- public void setY(float y){
- this.y = y;
- left.y = y;
- right.y = y;
- }
-
- public void adjustAllX(float offset){
- this.x +=offset;
- left.x +=offset;
- right.x +=offset;
- }
- }
-
- private class MoveAnimation extends Animation {
-
- @Override
- protected void applyTransformation(float interpolatedTime, Transformation t) {
- super.applyTransformation(interpolatedTime, t);
- mInterpolatedTime = interpolatedTime;
- invalidate();
- }
- }
-
- public void startAnimation() {
- mPath.reset();
- mInterpolatedTime = 0;
- MoveAnimation move = new MoveAnimation();
- move.setDuration(1000);
- move.setInterpolator(new AccelerateDecelerateInterpolator());
- //move.setRepeatCount(Animation.INFINITE);
- //move.setRepeatMode(Animation.REVERSE);
- startAnimation(move);
- }
+ private static final String TAG = "MagicCircle";
+
+ //三阶贝塞尔绘制圆形需要用到的黑魔法
+ private static final float BLACK_MAGIC = 0.551915024494f;
+
+ private Path mPath;
+ private Paint mFillCirclePaint;
+
+ //View的尺寸,View应该是正方形,所以宽高是一样的
+ private int mWidth;
+ private int mHeight;
+
+ private float mMaxLength;
+ private float mInterpolatedTime;
+ private float mStretchDistance;
+ private float mDistance;
+ private float mRadius;
+ //控制点,需与BLACK_MAGIC配合
+ private float mMagicControl;
+
+ //上下两个点
+ private HPoint mPoint1, mPoint3;
+ //左右两个点
+ private VPoint mPoint2, mPoint4;
+
+ private int mColor;
+
+ //动画相关
+ private int mDuration;
+ private int mRepeatCount;
+ private int mRepeatMode;
+ private int mStartColor;
+ private int mEndColor;
+ private Interpolator mInterpolator;
+
+ public MagicCircle(Context context) {
+ this(context, null, 0);
+ }
+
+ public MagicCircle(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public MagicCircle(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ init();
+ }
+
+ private void init() {
+ mFillCirclePaint = new Paint();
+ mColor = 0xFFfe626d;
+ mFillCirclePaint.setColor(mColor);
+ mFillCirclePaint.setStyle(Paint.Style.FILL);
+ mFillCirclePaint.setAntiAlias(true);
+ mFillCirclePaint.setDither(true);
+
+ mPath = new Path();
+
+ mPoint2 = new VPoint();
+ mPoint4 = new VPoint();
+
+ mPoint1 = new HPoint();
+ mPoint3 = new HPoint();
+ }
+
+ @Override
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ super.onSizeChanged(w, h, oldw, oldh);
+ mWidth = w;
+ mHeight = h;
+ mRadius = 50;
+ mMagicControl = mRadius * BLACK_MAGIC;
+ mStretchDistance = mRadius;
+ mDistance = mMagicControl * 0.45f;
+ mMaxLength = mWidth - mRadius * 2;
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+ mPath.reset();
+ //将画布坐标轴中心移至初始圆心
+ canvas.translate(mRadius, mRadius);
+
+ //分阶段设置控制点和数据点坐标
+ setModelStatus(mInterpolatedTime);
+
+ //实时改变控制点的x坐标
+ float offset = mMaxLength * (mInterpolatedTime - 0.2f);
+ offset = offset > 0 ? offset : 0;
+ mPoint1.adjustAllX(offset);
+ mPoint2.adjustAllX(offset);
+ mPoint3.adjustAllX(offset);
+ mPoint4.adjustAllX(offset);
+
+ //设置路径,可参考教程
+ mPath.moveTo(mPoint1.x, mPoint1.y);
+ mPath.cubicTo(mPoint1.right.x, mPoint1.right.y, mPoint2.bottom.x, mPoint2.bottom.y, mPoint2.x, mPoint2.y);
+ mPath.cubicTo(mPoint2.top.x, mPoint2.top.y, mPoint3.right.x, mPoint3.right.y, mPoint3.x, mPoint3.y);
+ mPath.cubicTo(mPoint3.left.x, mPoint3.left.y, mPoint4.top.x, mPoint4.top.y, mPoint4.x, mPoint4.y);
+ mPath.cubicTo(mPoint4.bottom.x, mPoint4.bottom.y, mPoint1.left.x, mPoint1.left.y, mPoint1.x, mPoint1.y);
+
+ canvas.drawPath(mPath, mFillCirclePaint);
+ }
+
+ public void setModelStatus(float interpolatedTime) {
+ mInterpolatedTime = interpolatedTime;
+
+ if (mInterpolatedTime >= 0 && mInterpolatedTime <= 0.2) {
+ model1(mInterpolatedTime);
+ } else if (mInterpolatedTime > 0.2 && mInterpolatedTime <= 0.5) {
+ model2(mInterpolatedTime);
+ } else if (mInterpolatedTime > 0.5 && mInterpolatedTime <= 0.8) {
+ model3(mInterpolatedTime);
+ } else if (mInterpolatedTime > 0.8 && mInterpolatedTime <= 0.9) {
+ model4(mInterpolatedTime);
+ } else if (mInterpolatedTime > 0.9 && mInterpolatedTime <= 1) {
+ model5(mInterpolatedTime);
+ }
+
+ invalidate();
+ }
+
+ /**
+ * 设置初始状态路径
+ * 对应教程状态1
+ */
+ private void model0() {
+ mPoint1.setY(mRadius);
+ mPoint3.setY(-mRadius);
+ mPoint3.x = mPoint1.x = 0;
+ mPoint3.left.x = mPoint1.left.x = -mMagicControl;
+ mPoint3.right.x = mPoint1.right.x = mMagicControl;
+
+ mPoint2.setX(mRadius);
+ mPoint4.setX(-mRadius);
+ mPoint2.y = mPoint4.y = 0;
+ mPoint2.top.y = mPoint4.top.y = -mMagicControl;
+ mPoint2.bottom.y = mPoint4.bottom.y = mMagicControl;
+ }
+
+ /**
+ * 设置状态1路径,根据时间点不同,数据点的x坐标发生对应变化
+ * 对应教程状态2
+ *
+ * @param time 时间点
+ */
+ private void model1(float time) {//0~0.2
+ model0();
+
+ mPoint2.setX(mRadius + mStretchDistance * time * 5);
+ }
+
+ /**
+ * 设置状态2路径,根据时间点不同,数据点的x坐标发生对应变化
+ * 对应教程状态3
+ *
+ * @param time 时间点
+ */
+ private void model2(float time) {//0.2~0.5
+ model1(0.2f);
+ //将时间变化值调整为0-1,便于adjustAllX和adjustY根据比例计算偏移值
+ //后同
+ time = (time - 0.2f) * (10f / 3);
+
+ mPoint1.adjustAllX(mStretchDistance / 2 * time);
+ mPoint3.adjustAllX(mStretchDistance / 2 * time);
+
+ mPoint2.adjustY(mDistance * time);
+ mPoint4.adjustY(mDistance * time);
+ }
+
+ /**
+ * 设置状态3路径,根据时间点不同,数据点的x坐标发生对应变化
+ * 对应教程状态3
+ *
+ * @param time 时间点
+ */
+ private void model3(float time) {//0.5~0.8
+ model2(0.5f);
+ time = (time - 0.5f) * (10f / 3);
+
+ mPoint1.adjustAllX(mStretchDistance / 2 * time);
+ mPoint3.adjustAllX(mStretchDistance / 2 * time);
+
+ mPoint2.adjustY(-mDistance * time);
+ mPoint4.adjustY(-mDistance * time);
+
+ mPoint4.adjustAllX(mStretchDistance / 2 * time);
+ }
+
+ /**
+ * 设置状态4路径,根据时间点不同,数据点的x坐标发生对应变化
+ * 对应教程状态4
+ *
+ * @param time 时间点
+ */
+ private void model4(float time) {//0.8~0.9
+ model3(0.8f);
+ time = (time - 0.8f) * 10;
+
+ mPoint4.adjustAllX(mStretchDistance / 2 * time);
+ }
+
+ /**
+ * 设置状态5路径,根据时间点不同,数据点的x坐标发生对应变化
+ * 对应教程状态5,有回弹效果
+ *
+ * @param time 时间点
+ */
+ private void model5(float time) {
+ model4(0.9f);
+ time = time - 0.9f;
+
+ mPoint4.adjustAllX((float) (Math.sin(Math.PI * time * 10f) * (2 / 10f * mRadius)));
+ }
+
+ public void startAnimation() {
+ mPath.reset();
+ mInterpolatedTime = 0;
+ MoveAnimation move = new MoveAnimation();
+ move.setDuration(mDuration);
+ move.setInterpolator(mInterpolator);
+ if (mRepeatCount != 0) {
+ move.setRepeatCount(mRepeatCount);
+ }
+ if (mRepeatMode != 0) {
+ move.setRepeatMode(mRepeatMode);
+ }
+
+ startAnimation(move);
+ }
+
+ class VPoint {
+ public float x;
+ public float y;
+ public PointF top = new PointF();
+ public PointF bottom = new PointF();
+
+ public void setX(float x) {
+ this.x = x;
+ top.x = x;
+ bottom.x = x;
+ }
+
+ public void adjustY(float offset) {
+ top.y -= offset;
+ bottom.y += offset;
+ }
+
+ public void adjustAllX(float offset) {
+ this.x += offset;
+ top.x += offset;
+ bottom.x += offset;
+ }
+ }
+
+ class HPoint {
+ public float x;
+ public float y;
+
+ public PointF left = new PointF();
+ public PointF right = new PointF();
+
+ public void setY(float y) {
+ this.y = y;
+ left.y = y;
+ right.y = y;
+ }
+
+ public void adjustAllX(float offset) {
+ this.x += offset;
+ left.x += offset;
+ right.x += offset;
+ }
+ }
+
+ private class MoveAnimation extends Animation {
+ @Override
+ protected void applyTransformation(float interpolatedTime, Transformation t) {
+ super.applyTransformation(interpolatedTime, t);
+ mInterpolatedTime = interpolatedTime;
+ invalidate();
+ }
+ }
+
+ private void startColorAnim() {
+ //坑爹的ofArgb要api21以上才能用
+ ValueAnimator va = ValueAnimator.ofObject(new ArgbEvaluator(), mStartColor, mEndColor);
+ va.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ mFillCirclePaint.setColor((Integer) animation.getAnimatedValue());
+ }
+ });
+ if (mRepeatCount != 0) {
+ va.setRepeatCount(mRepeatCount);
+ }
+ if (mRepeatMode != 0) {
+ va.setRepeatMode(mRepeatMode);
+ }
+ va.setDuration(mDuration);
+ va.start();
+ }
+
+ static class Builder {
+ private static final int ERROR_VALUE = -1;
+ private MagicCircle mMagicCircle;
+
+ public Builder(@NonNull MagicCircle magicCircle) {
+ mMagicCircle = magicCircle;
+
+ //设置默认值
+ mMagicCircle.mDuration = 1000;
+ mMagicCircle.mRepeatCount = 0;
+ mMagicCircle.mRepeatMode = 0;
+ mMagicCircle.mStartColor = -1;
+ mMagicCircle.mEndColor = -1;
+ mMagicCircle.mInterpolator = new AccelerateDecelerateInterpolator();
+ }
+
+ public Builder setInterpolator(Interpolator interpolator) {
+ mMagicCircle.mInterpolator = interpolator;
+ return this;
+ }
+
+ public Builder setStartColor(int color) {
+ mMagicCircle.mStartColor = color;
+ return this;
+ }
+
+ public Builder setEndColor(int color) {
+ mMagicCircle.mEndColor = color;
+ return this;
+ }
+
+ public Builder setRepeatCount(int count) {
+ mMagicCircle.mRepeatCount = count;
+ return this;
+ }
+
+ public Builder setRepeatMode(int mode) {
+ mMagicCircle.mRepeatMode = mode;
+ return this;
+ }
+
+ public Builder setDuration(int duration) {
+ mMagicCircle.mDuration = duration;
+ return this;
+ }
+
+ public Builder setColor(int color) {
+ mMagicCircle.mFillCirclePaint.setColor(color);
+ return this;
+ }
+
+ public void start() {
+ if (mMagicCircle == null) {
+ Log.e(TAG, "View对象不能为空");
+ return;
+ }
+
+ mMagicCircle.startAnimation();
+
+ if (mMagicCircle.mStartColor != ERROR_VALUE
+ && mMagicCircle.mEndColor != ERROR_VALUE) {
+ mMagicCircle.startColorAnim();
+ }
+ }
+ }
}
diff --git a/app/src/main/java/com/shadev/pierrebeziercircle/MainActivity.java b/app/src/main/java/com/shadev/pierrebeziercircle/MainActivity.java
index d98a2e2..b03725e 100644
--- a/app/src/main/java/com/shadev/pierrebeziercircle/MainActivity.java
+++ b/app/src/main/java/com/shadev/pierrebeziercircle/MainActivity.java
@@ -1,24 +1,34 @@
package com.shadev.pierrebeziercircle;
+import android.graphics.Color;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
-import android.widget.Button;
+import android.view.animation.AccelerateDecelerateInterpolator;
+import android.view.animation.Animation;
public class MainActivity extends AppCompatActivity {
- private android.widget.Button btnstart;
- private MagicCircle circle3;
+ private MagicCircle mMagicCircle;
+ private MagicCircle.Builder builder;
- @Override protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- circle3 = (MagicCircle)findViewById(R.id.circle3);
- this.btnstart = (Button) findViewById(R.id.btn_start);
- btnstart.setOnClickListener(new View.OnClickListener() {
- @Override public void onClick(View v) {
- circle3.startAnimation();
- }
- });
- }
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_main);
+
+ mMagicCircle = (MagicCircle) findViewById(R.id.circle);
+ builder = new MagicCircle.Builder(mMagicCircle);
+ }
+
+ public void startCircleAnimation(View view) {
+ builder .setDuration(2000)
+// .setStartColor(Color.RED)
+// .setEndColor(Color.BLUE)
+ .setColor(Color.BLACK)
+ .setInterpolator(new AccelerateDecelerateInterpolator())
+ .setRepeatCount(Animation.INFINITE)
+ .setRepeatMode(Animation.REVERSE)
+ .start();
+ }
}
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
index be4efe1..7f19c11 100644
--- a/app/src/main/res/layout/activity_main.xml
+++ b/app/src/main/res/layout/activity_main.xml
@@ -1,23 +1,25 @@
-
-
+ android:onClick="startCircleAnimation"/>
+ android:layout_centerInParent="true"/>
diff --git a/build.gradle b/build.gradle
index b193299..f46d653 100644
--- a/build.gradle
+++ b/build.gradle
@@ -5,7 +5,7 @@ buildscript {
jcenter()
}
dependencies {
- classpath 'com.android.tools.build:gradle:2.2.0'
+ classpath 'com.android.tools.build:gradle:2.2.3'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index bcc2fe1..af9aab9 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,4 +1,4 @@
-#Fri Sep 11 18:23:36 CST 2015
+#Fri Dec 30 23:14:28 CST 2016
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME