官方的:canvas
咱們上個版本的:app
能夠看出谷歌的是有位移效果,而咱們的是原地擴散的效果,固然動畫速度這個與PS的設置有關,不作比較,實際速度比上面的略快。ide
下面我們就來試試作作位移的特效,先畫個圖給你們看看:動畫
相信你們都能看懂,第一種就是以前的實現方式,只是在原地擴散,第二種就是新的,將在擴散的同時向中心靠攏,且爲了達到更加好的視覺效果,靠攏中心的XY軸速度並非同樣的,X軸的靠攏時間=整個擴散時間,向Y軸靠攏的時間~=整個擴散時間*0.3(且都是先快後慢),如今來看當作品效果:this
點擊中間的時候與第一種差距不大,可是點擊兩邊的時候將會有明顯的差距,能感受到向中心靠攏的觸覺。是否是和谷歌的相比起來又近了一些了?lua
說了這個多的理論與演示,下面來講說整個的實現:spa
首先咱們抽取上一篇文章的成果做爲這篇的開頭,具體怎麼新建控件就再也不作介紹了,先看看上一篇的代碼成果(該代碼進行了必定的修改):rest
public class MaterialButton extends Button { orm
private static final Interpolator ANIMATION_INTERPOLATOR = new DecelerateInterpolator(); get
private static final long ANIMATION_TIME = 600;
private Paint backgroundPaint;
private static ArgbEvaluator argbEvaluator = new ArgbEvaluator();
private float paintX, paintY, radius;
public MaterialButton(Context context) {
super(context);
init(null, 0);
}
public MaterialButton(Context context, AttributeSet attrs) {
super(context, attrs);
init(attrs, 0);
}
public MaterialButton(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(attrs, defStyle);
}
@SuppressWarnings("deprecation")
private void init(AttributeSet attrs, int defStyle) {
...
}
@SuppressWarnings("NullableProblems")
@Override
protected void onDraw(Canvas canvas) {
canvas.save();
canvas.drawCircle(paintX, paintY, radius, backgroundPaint);
canvas.restore();
super.onDraw(canvas);
}
@SuppressWarnings("NullableProblems")
@Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
paintX = event.getX();
paintY = event.getY();
startRoundAnimator();
}
return super.onTouchEvent(event);
}
/**
* =============================================================================================
* The Animator methods
* =============================================================================================
*/
/**
* Start Round Animator
*/
private void startRoundAnimator() {
float start, end, height, width;
long time = (long) (ANIMATION_TIME * 1.85);
//Height Width
height = getHeight();
width = getWidth();
//Start End
if (height < width) {
start = height;
end = width;
} else {
start = width;
end = height;
}
float startRadius = (start / 2 > paintY ? start - paintY : paintY) * 1.15f;
float endRadius = (end / 2 > paintX ? end - paintX : paintX) * 0.85f;
//If The approximate square approximate square
if (startRadius > endRadius) {
startRadius = endRadius * 0.6f;
endRadius = endRadius / 0.8f;
time = (long) (time * 0.5);
}
AnimatorSet set = new AnimatorSet();
set.playTogether(
ObjectAnimator.ofFloat(this, mRadiusProperty, startRadius, endRadius),
ObjectAnimator.ofObject(this, mBackgroundColorProperty, argbEvaluator, attributes.getColor(1), attributes.getColor(2))
);
// set Time
set.setDuration((long) (time / end * endRadius));
set.setInterpolator(ANIMATION_INTERPOLATOR);
set.start();
}
/**
* =============================================================================================
* The custom properties
* =============================================================================================
*/
private Property<MaterialButton, Float> mRadiusProperty = new Property<MaterialButton, Float>(Float.class, "radius") {
@Override
public Float get(MaterialButton object) {
return object.radius;
}
@Override
public void set(MaterialButton object, Float value) {
object.radius = value;
invalidate();
}
};
private Property<MaterialButton, Integer> mBackgroundColorProperty = new Property<MaterialButton, Integer>(Integer.class, "bg_color") {
@Override
public Integer get(MaterialButton object) {
return object.backgroundPaint.getColor();
}
@Override
public void set(MaterialButton object, Integer value) {
object.backgroundPaint.setColor(value);
}
};
}
在上述代碼中咱們實現了點擊時進行擴散的效果,初始化控件部分因爲我加入了許多的代碼這裏刪除了,具體能夠看看個人項目實現,最後會給出地址。
如今基於此開工!
首先咱們創建 兩個新的屬性 分別X座標與Y座標屬性:
private Property<MaterialButton, Float> mPaintXProperty = new Property<MaterialButton, Float>(Float.class, "paintX") {
@Override
public Float get(MaterialButton object) {
return object.paintX;
}
@Override
public void set(MaterialButton object, Float value) {
object.paintX = value;
}
};
private Property<MaterialButton, Float> mPaintYProperty = new Property<MaterialButton, Float>(Float.class, "paintY") {
@Override
public Float get(MaterialButton object) {
return object.paintY;
}
@Override
public void set(MaterialButton object, Float value) {
object.paintY = value;
}
};
在這兩個屬性中並未調用第一篇所說的 「 invalidate();」方法進行界面刷新,由於該方法應該放在持續時間最長的半徑屬性中調用。
以後咱們獲取到高寬 以及根據高和寬 計算出對應的 開始半徑與結束半徑:
<span style="white-space:pre"> </span>float start, end, height, width, speed = 0.3f;
long time = ANIMATION_TIME;
//Height Width
height = getHeight();
width = getWidth();
//Start End
if (height < width) {
start = height;
end = width;
} else {
start = width;
end = height;
}
start = start / 2 > paintY ? start - paintY : paintY;
end = end * 0.8f / 2f;
//If The approximate square approximate square
if (start > end) {
start = end * 0.6f;
end = end / 0.8f;
time = (long) (time * 0.65);
speed = 1f;
}
咱們首先比較了高與寬的長度 把短的賦予爲開始半徑 長的賦予爲結束半徑。
第二步,咱們把開始長度除以2 得出其一半的長度 而後與 點擊時的Y軸座標比較,若是Y軸較長則取Y,若是不夠則取其相減結果。這樣能保證點擊開始時的半徑能恰好大於其高或者寬(短的一邊),這樣就不會出現小圓擴散的效果,看起來將會由橢圓的效果(固然之後將會直接畫出橢圓)
第三步,咱們運算出結束半徑,同時保證結束半徑爲長的一邊的一半的8/10 這樣的效果是不會出現佈滿整個控件的狀況。8/10 的空間恰好是個不錯的選擇。
第四步,判斷開始長度是否大於結束長度,若是是(近似正方形狀況),進行必定規則的從新運算,保證其開始半徑能恰好與控件長度差很少(0.48左右),結束半徑能剛剛佈滿控件,同時減小動畫時間
固然,我如今才發現了一個BUG,在第二步的地方的BUG,你們看看,但願能提出是哪裏的BUG;就當是一個互動!該BUG將會在下個版本修復。
以後咱們創建每一個屬性的動畫,並給每一個屬性動畫設置對應的時間:
//PaintX
ObjectAnimator aPaintX = ObjectAnimator.ofFloat(this, mPaintXProperty, paintX, width / 2);
aPaintX.setDuration(time);
//PaintY
ObjectAnimator aPaintY = ObjectAnimator.ofFloat(this, mPaintYProperty, paintY, height / 2);
aPaintY.setDuration((long) (time * speed));
//Radius
ObjectAnimator aRadius = ObjectAnimator.ofFloat(this, mRadiusProperty, start, end);
aRadius.setDuration(time);
//Background
ObjectAnimator aBackground = ObjectAnimator.ofObject(this, mBackgroundColorProperty, argbEvaluator, attributes.getColor(1), attributes.getColor(2));
aBackground.setDuration(time);
能夠看見Y軸的時間乘以了一個speed變量,該變量默認是0.3 若是是近似正方形將初始化爲1以便能同時對齊到中心位置,在上一步中有對應變量。
而後我們把全部的屬性動畫添加到一個動畫集並設置其速度方式爲:先快後慢。最後啓動該動畫集。
//AnimatorSet
AnimatorSet set = new AnimatorSet();
set.playTogether(aPaintX, aPaintY, aRadius, aBackground);
set.setInterpolator(ANIMATION_INTERPOLATOR);
set.start();
以上就是最新的動畫效果的實現原理及代碼了,固然咱們能夠將其合併到第一篇的代碼中,並使用一個 Bool 屬性來控制使用哪種動畫:
public class MaterialButton extends Button {
private static final Interpolator ANIMATION_INTERPOLATOR = new DecelerateInterpolator();
private static final long ANIMATION_TIME = 600;
private Paint backgroundPaint;
private static ArgbEvaluator argbEvaluator = new ArgbEvaluator();
private float paintX, paintY, radius;
private Attributes attributes;
public MaterialButton(Context context) {
super(context);
init(null, 0);
}
public MaterialButton(Context context, AttributeSet attrs) {
super(context, attrs);
init(attrs, 0);
}
public MaterialButton(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(attrs, defStyle);
}
@SuppressWarnings("deprecation")
private void init(AttributeSet attrs, int defStyle) {
...
}
@SuppressWarnings("NullableProblems")
@Override
protected void onDraw(Canvas canvas) {
canvas.save();
canvas.drawCircle(paintX, paintY, radius, backgroundPaint);
canvas.restore();
super.onDraw(canvas);
}
@SuppressWarnings("NullableProblems")
@Override
public boolean onTouchEvent(MotionEvent event) {
if (attributes.isMaterial() && event.getAction() == MotionEvent.ACTION_DOWN) {
paintX = event.getX();
paintY = event.getY();
if (attributes.isAutoMove())
startMoveRoundAnimator();
else
startRoundAnimator();
}
return super.onTouchEvent(event);
}
/**
* =============================================================================================
* The Animator methods
* =============================================================================================
*/
/**
* Start Round Animator
*/
private void startRoundAnimator() {
float start, end, height, width;
long time = (long) (ANIMATION_TIME * 1.85);
//Height Width
height = getHeight();
width = getWidth();
//Start End
if (height < width) {
start = height;
end = width;
} else {
start = width;
end = height;
}
float startRadius = (start / 2 > paintY ? start - paintY : paintY) * 1.15f;
float endRadius = (end / 2 > paintX ? end - paintX : paintX) * 0.85f;
//If The approximate square approximate square
if (startRadius > endRadius) {
startRadius = endRadius * 0.6f;
endRadius = endRadius / 0.8f;
time = (long) (time * 0.5);
}
AnimatorSet set = new AnimatorSet();
set.playTogether(
ObjectAnimator.ofFloat(this, mRadiusProperty, startRadius, endRadius),
ObjectAnimator.ofObject(this, mBackgroundColorProperty, argbEvaluator, attributes.getColor(1), attributes.getColor(2))
);
// set Time
set.setDuration((long) (time / end * endRadius));
set.setInterpolator(ANIMATION_INTERPOLATOR);
set.start();
}
/**
* Start Move Round Animator
*/
private void startMoveRoundAnimator() {
float start, end, height, width, speed = 0.3f;
long time = ANIMATION_TIME;
//Height Width
height = getHeight();
width = getWidth();
//Start End
if (height < width) {
start = height;
end = width;
} else {
start = width;
end = height;
}
start = start / 2 > paintY ? start - paintY : paintY;
end = end * 0.8f / 2f;
//If The approximate square approximate square
if (start > end) {
start = end * 0.6f;
end = end / 0.8f;
time = (long) (time * 0.65);
speed = 1f;
}
//PaintX
ObjectAnimator aPaintX = ObjectAnimator.ofFloat(this, mPaintXProperty, paintX, width / 2);
aPaintX.setDuration(time);
//PaintY
ObjectAnimator aPaintY = ObjectAnimator.ofFloat(this, mPaintYProperty, paintY, height / 2);
aPaintY.setDuration((long) (time * speed));
//Radius
ObjectAnimator aRadius = ObjectAnimator.ofFloat(this, mRadiusProperty, start, end);
aRadius.setDuration(time);
//Background
ObjectAnimator aBackground = ObjectAnimator.ofObject(this, mBackgroundColorProperty, argbEvaluator, attributes.getColor(1), attributes.getColor(2));
aBackground.setDuration(time);
//AnimatorSet
AnimatorSet set = new AnimatorSet();
set.playTogether(aPaintX, aPaintY, aRadius, aBackground);
set.setInterpolator(ANIMATION_INTERPOLATOR);
set.start();
}
/**
* =============================================================================================
* The custom properties
* =============================================================================================
*/
private Property<MaterialButton, Float> mPaintXProperty = new Property<MaterialButton, Float>(Float.class, "paintX") {
@Override
public Float get(MaterialButton object) {
return object.paintX;
}
@Override
public void set(MaterialButton object, Float value) {
object.paintX = value;
}
};
private Property<MaterialButton, Float> mPaintYProperty = new Property<MaterialButton, Float>(Float.class, "paintY") {
@Override
public Float get(MaterialButton object) {
return object.paintY;
}
@Override
public void set(MaterialButton object, Float value) {
object.paintY = value;
}
};
private Property<MaterialButton, Float> mRadiusProperty = new Property<MaterialButton, Float>(Float.class, "radius") {
@Override
public Float get(MaterialButton object) {
return object.radius;
}
@Override
public void set(MaterialButton object, Float value) {
object.radius = value;
invalidate();
}
};
private Property<MaterialButton, Integer> mBackgroundColorProperty = new Property<MaterialButton, Integer>(Integer.class, "bg_color") {
@Override
public Integer get(MaterialButton object) {
return object.backgroundPaint.getColor();
}
@Override
public void set(MaterialButton object, Integer value) {
object.backgroundPaint.setColor(value);
}
};
}
在最後附上兩種方式運行後的效果對比圖: