今天我們來學習怎麼製作水平移動帶彈性的圓,先上效果圖:
下面講解一下具體的步驟,在瞭解之前大家先看一張圖,後面的代碼可以參考這張圖:
接下來上代碼,可以分成兩個部分,一個是易懂,一個是變形,註釋做的比較全,大家可以參考:
是不是很容易,理解,大家可以自己練習一下:
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PointF;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.Animation;
import android.view.animation.Transformation;
/**
* 代碼寫的比較倉猝,以後再優化寫法和補充那些數值的具體含義,求勿噴QAQ
*/
public class MagicCircle extends View {
private Path mPath;
private Paint mFillCirclePaint;
/**
* View的寬度
**/
private int width;
/**
* View的高度,這裏View應該是正方形,所以寬高是一樣的
**/
private int height;
private float maxLength;
private float mInterpolatedTime;
private float stretchDistance;
private float cDistance;
private float radius;
private float c;
private float blackMagic = 0.551915024494f; //這是三次貝塞爾式的常量,詳解請見:http://spencermortensen.com/articles/bezier-circle/
/*
* 下面定義了p1, p2, p3, p4,四個對象,人要想明明是四個控制點,爲什麼要定義四個對象呢,因爲這裏是爲了把這四個點和他們的輔助點聯繫在一起,
* 所以總共應該是四個控制點和八個輔助點
* */
private VPoint p2, p4; //定義了p2,p4兩個對象,即圓的上面兩個點,一個是右邊的點,一個是左邊的點
private HPoint p1, p3; //定義了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();
radius = 50;
c = radius * blackMagic;
stretchDistance = radius;
cDistance = c * 0.45f;
maxLength = width - radius / 8;
}
@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;
}
/*
* 初始化過後我們要做的第一件事就是移動p2這個點,將p2往右移,移動距離從0到stretchDistance * time * 5,也就是整個圓的半徑大小(此時達到圓的最大橫向長度,3倍圓半徑),
* 給人的感覺是右邊被拉了,其他不變,長度爲一個半徑
* */
private void model1(float time) {//0~0.2
model0();
p2.setX(radius + stretchDistance * time * 5);
}
/*
*這裏做了兩個處理,一個是p1,p3橫座標的移動,也就是往右移,然後就是p2,p4的四個輔助點的縱座標,這樣的話移動過程中就會有彎曲的感覺了,只要做稍稍調整即可,
* 觀察的效果給人感覺是圓形從左往右移,長度爲一個半徑,左右有向內凹的感覺
* */
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);
}
/*
* 這裏的處理和上面其實是差不多的,只不過全部反過來處理了,易懂,最後還移動p4的位置,保持圓的橫向長度不變(此時圓橫向長度應爲3倍圓半徑),
* 左右兩邊給人凸起來的感覺
* */
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);
}
/*
* 這裏將p4多往右邊移動了一點,目的是爲了等下的回彈,這樣的話給人的感覺是比較正常的
* */
private void model4(float time) {//0.8~0.9
model3(0.8f);
time = (time - 0.8f) * 10;
p4.adjustAllX(stretchDistance / 2 * time);
}
/*
* 這裏直接用函數回彈p4的座標
* */
private void model5(float time) {
model4(0.9f);
time = time - 0.9f;
p4.adjustAllX((float) (Math.sin(Math.PI * time * 10f) * (2 / 10f * radius)));
}
/*
* 這個類用來定義p2和p4,因爲只有這兩個點擁有top和Botton這樣的輔助點
* */
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;
}
}
/*
* 這個類用來定義p1和p2,因爲只有這兩個點擁有left和right這樣的輔助點
* */
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(10000); //用來設置動畫時長
move.setInterpolator(new AccelerateDecelerateInterpolator());
//move.setRepeatCount(Animation.INFINITE);
//move.setRepeatMode(Animation.REVERSE);
startAnimation(move);
}
}
最後給出項目代碼:
https://github.com/DevinShine/MagicCircle
參考文章:
http://www.jianshu.com/p/791d3a791ec2
http://blog.csdn.net/u013831257/article/details/51281136