本文的合集已經(jīng)編著成書,高級Android開發(fā)強(qiáng)化實(shí)戰(zhàn),歡迎各位讀友的建議和指導(dǎo)。在京東即可購買:https://item.jd.com/12385680.html
在Android中, 屬性動畫是非常有意思的功能, 控制參數(shù)變換動畫效果.
與使用gif圖片相比, 動畫控件要節(jié)約空間和增加響應(yīng)速度.
要點(diǎn):
(1) 使用PercentLayout設(shè)置自定義控件的大小.
(2) 屬性動畫的兩個(gè)重要函數(shù), 中值和映射.
(3) 擦除畫筆(PorterDuff.Mode.CLEAR)的使用方法.
(4) 使用顏色估值器(ArgbEvaluator)控制顏色變換.
(5) 自定義控件的延遲重繪(postInvalidate)方法.
(6) 屬性動畫(Property<View, Float>
)的設(shè)置和使用.
(7) 使用映射函數(shù), 控制變換節(jié)奏.
(8) 插值器(Interpolator)的使用方法.
(9) 動畫集合(AnimatorSet)的使用方法.
(10) 點(diǎn)擊事件(onTouchEvent)的設(shè)置.
掌握這些之后, 我們就可以使用屬性動畫完成一些自定義控件, 讓我們來看看是如何實(shí)現(xiàn)的.
Github下載地址
1. 主頁
主頁加載自定義的星型控件, 所有邏輯都在控件中實(shí)現(xiàn).
邏輯
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
布局
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="clwang.chunyu.me.wcl_like_anim_demo.MainActivity">
<clwang.chunyu.me.wcl_like_anim_demo.LikeButtonView
android:layout_width="200dp"
android:layout_height="200dp"
android:layout_gravity="center"/>
</FrameLayout>
2. 星型控件
控件主要分為三個(gè)部分:
(1) 圓環(huán)的爆炸效果.
(2) 點(diǎn)狀的散射效果.
(3) 星星的明暗變化.
布局, 使用PercentLayout, 圓環(huán)和星星占控件寬度的40%, 高度與寬度相同.
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<clwang.chunyu.me.wcl_like_anim_demo.DotsView
android:id="@+id/like_button_dv_dots"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"/>
<android.support.percent.PercentRelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center">
<clwang.chunyu.me.wcl_like_anim_demo.CircleView
android:id="@+id/like_button_cv_circle"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_aspectRatio="100%"
app:layout_widthPercent="40%"/>
</android.support.percent.PercentRelativeLayout>
<android.support.percent.PercentRelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center">
<ImageView
android:id="@+id/like_button_iv_star"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_gravity="center"
android:contentDescription="@null"
android:src="@drawable/ic_star_rate_off"
app:layout_aspectRatio="100%"
app:layout_widthPercent="40%"/>
</android.support.percent.PercentRelativeLayout>
</FrameLayout>
屬性動畫常用工具類, 映射函數(shù)和中值函數(shù).
/**
* 工具類
* <p>
* Created by wangchenlong on 16/1/5.
*/
public class Utils {
// 映射到下一個(gè)域
public static double mapValueFromRangeToRange(double value, double fromLow, double fromHigh, double toLow, double toHigh) {
return toLow + ((value - fromLow) / (fromHigh - fromLow) * (toHigh - toLow));
}
// 中間值, value<low, 返回low, value>high, 返回high.
public static double clamp(double value, double low, double high) {
return Math.min(Math.max(value, low), high);
}
}
3. 圓環(huán)
圓環(huán)要實(shí)現(xiàn)爆炸效果. 外圓是實(shí)心圓圈, 顏色漸變; 內(nèi)圓是擦除效果.
邏輯
/**
* 圓形視圖, 外圓是實(shí)心圓圈, 顏色漸變; 內(nèi)圓是擦除效果.
* <p>
* Created by wangchenlong on 16/1/5.
*/
public class CircleView extends View {
private static final int START_COLOR = 0xFFFF5722;
private static final int END_COLOR = 0xFFFFC107;
private ArgbEvaluator mArgbEvaluator; // Argb估計(jì)器
private Paint mCirclePaint; // 圓形視圖
private Paint mMaskPaint; // 掩蓋視圖
private Canvas mTempCanvas; // 中間畫布
private Bitmap mTempBitmap; // 中間圖畫
private int mMaxCircleSize; // 最大圓環(huán)大小
private float mOuterCircleRadiusProgress; // 外圈圓的控制器
private float mInnerCircleRadiusProgress; // 內(nèi)圈圓的控制器
public CircleView(Context context) {
super(context);
init();
}
public CircleView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public CircleView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public CircleView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
// 初始化
private void init() {
mCirclePaint = new Paint();
mCirclePaint.setStyle(Paint.Style.FILL);
mMaskPaint = new Paint(); // 消失的效果
mMaskPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
mArgbEvaluator = new ArgbEvaluator();
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mMaxCircleSize = w / 2; // 當(dāng)前畫布的一半.
mTempBitmap = Bitmap.createBitmap(getWidth(), getWidth(), Bitmap.Config.ARGB_8888);
mTempCanvas = new Canvas(mTempBitmap); // 初始化畫布
}
@Override protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mTempCanvas.drawColor(0xffffff, PorterDuff.Mode.CLEAR); // 清除顏色, 設(shè)置為白色
// 在控件的中心畫圓, 寬度是當(dāng)前視圖寬度的一半(會隨著控件變化), 半徑會越來越大.
mTempCanvas.drawCircle(getWidth() / 2, getHeight() / 2, mOuterCircleRadiusProgress * mMaxCircleSize, mCirclePaint);
mTempCanvas.drawCircle(getWidth() / 2, getHeight() / 2, mInnerCircleRadiusProgress * mMaxCircleSize, mMaskPaint);
canvas.drawBitmap(mTempBitmap, 0, 0, null); // 畫布繪制兩個(gè)圓.
}
public float getOuterCircleRadiusProgress() {
return mOuterCircleRadiusProgress;
}
public void setOuterCircleRadiusProgress(float outerCircleRadiusProgress) {
mOuterCircleRadiusProgress = outerCircleRadiusProgress;
updateCircleColor();
postInvalidate(); // 延遲重繪
}
// 更新圓圈的顏色變化
private void updateCircleColor() {
// 0.5到1顏色漸變
float colorProgress = (float) Utils.clamp(mOuterCircleRadiusProgress, 0.5, 1);
// 轉(zhuǎn)換映射控件
colorProgress = (float) Utils.mapValueFromRangeToRange(colorProgress, 0.5f, 1f, 0f, 1f);
mCirclePaint.setColor((Integer) mArgbEvaluator.evaluate(colorProgress, START_COLOR, END_COLOR));
}
public float getInnerCircleRadiusProgress() {
return mInnerCircleRadiusProgress;
}
public void setInnerCircleRadiusProgress(float innerCircleRadiusProgress) {
mInnerCircleRadiusProgress = innerCircleRadiusProgress;
postInvalidate(); // 延遲重繪
}
// 內(nèi)部圓圈處理
public static final Property<CircleView, Float> INNER_CIRCLE_RADIUS_PROGRESS =
new Property<CircleView, Float>(Float.class, "innerCircleRadiusProgress") {
@Override
public Float get(CircleView object) {
return object.getInnerCircleRadiusProgress();
}
@Override
public void set(CircleView object, Float value) {
object.setInnerCircleRadiusProgress(value);
}
};
// 外部圓圈處理
public static final Property<CircleView, Float> OUTER_CIRCLE_RADIUS_PROGRESS =
new Property<CircleView, Float>(Float.class, "outerCircleRadiusProgress") {
@Override
public Float get(CircleView object) {
return object.getOuterCircleRadiusProgress();
}
@Override
public void set(CircleView object, Float value) {
object.setOuterCircleRadiusProgress(value);
}
};
}
初始化, 外圓使用實(shí)心畫筆(Paint), 內(nèi)圓使用擦除畫筆. ArgbEvaluator控制顏色變換.
// 初始化
private void init() {
mCirclePaint = new Paint(); // 實(shí)心圓圈
mCirclePaint.setStyle(Paint.Style.FILL);
mMaskPaint = new Paint(); // 消失效果
mMaskPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
mArgbEvaluator = new ArgbEvaluator();
}
圓圈, 內(nèi)圓和外圓繪制在畫布的中心, 半徑是畫布當(dāng)前寬度的一半.
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mMaxCircleSize = w / 2; // 當(dāng)前畫布的一半.
mTempBitmap = Bitmap.createBitmap(getWidth(), getWidth(), Bitmap.Config.ARGB_8888);
mTempCanvas = new Canvas(mTempBitmap); // 初始化畫布
}
@Override protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mTempCanvas.drawColor(0xffffff, PorterDuff.Mode.CLEAR); // 清除顏色, 設(shè)置為白色
// 在控件的中心畫圓, 寬度是當(dāng)前視圖寬度的一半(會隨著控件變化).
mTempCanvas.drawCircle(getWidth() / 2, getHeight() / 2, mOuterCircleRadiusProgress * mMaxCircleSize, mCirclePaint);
mTempCanvas.drawCircle(getWidth() / 2, getHeight() / 2, mInnerCircleRadiusProgress * mMaxCircleSize, mMaskPaint);
canvas.drawBitmap(mTempBitmap, 0, 0, null); // 畫布繪制兩個(gè)圓.
}
通過控制Progress控制內(nèi)外圓圈的半徑.
public float getOuterCircleRadiusProgress() {
return mOuterCircleRadiusProgress;
}
public void setOuterCircleRadiusProgress(float outerCircleRadiusProgress) {
mOuterCircleRadiusProgress = outerCircleRadiusProgress;
updateCircleColor();
postInvalidate(); // 延遲重繪
}
// 更新圓圈
private void updateCircleColor() {
// 0.5到1顏色漸變
float colorProgress = (float) Utils.clamp(mOuterCircleRadiusProgress, 0.5, 1);
// 轉(zhuǎn)換映射控件
colorProgress = (float) Utils.mapValueFromRangeToRange(colorProgress, 0.5f, 1f, 0f, 1f);
mCirclePaint.setColor((Integer) mArgbEvaluator.evaluate(colorProgress, START_COLOR, END_COLOR));
}
public float getInnerCircleRadiusProgress() {
return mInnerCircleRadiusProgress;
}
public void setInnerCircleRadiusProgress(float innerCircleRadiusProgress) {
mInnerCircleRadiusProgress = innerCircleRadiusProgress;
postInvalidate(); // 延遲重繪
}
postInvalidate(), 延遲重繪, 不會阻礙UI線程.
設(shè)置動畫屬性, 通過設(shè)置progress的值, 改變圓圈的大小和顏色.
// 內(nèi)部圓圈處理
public static final Property<CircleView, Float> INNER_CIRCLE_RADIUS_PROGRESS =
new Property<CircleView, Float>(Float.class, "innerCircleRadiusProgress") {
@Override
public Float get(CircleView object) {
return object.getInnerCircleRadiusProgress();
}
@Override
public void set(CircleView object, Float value) {
object.setInnerCircleRadiusProgress(value);
}
};
// 外部圓圈處理
public static final Property<CircleView, Float> OUTER_CIRCLE_RADIUS_PROGRESS =
new Property<CircleView, Float>(Float.class, "outerCircleRadiusProgress") {
@Override
public Float get(CircleView object) {
return object.getOuterCircleRadiusProgress();
}
@Override
public void set(CircleView object, Float value) {
object.setOuterCircleRadiusProgress(value);
}
};
4. 散射點(diǎn)
散射點(diǎn), 由大點(diǎn)小點(diǎn)組成, 兩類點(diǎn)排列和顏色均錯(cuò)開, 速度先慢后快向外發(fā)射.
/**
* 發(fā)散的點(diǎn), 由大點(diǎn)小點(diǎn)組成, 兩類點(diǎn)排列和顏色均錯(cuò)開, 速度先慢后快向外發(fā)射.
* <p>
* Created by wangchenlong on 16/1/6.
*/
public class DotsView extends View {
private static final int DOTS_COUNT = 7; // 7個(gè)點(diǎn)陣
private static final int OUTER_DOTS_POSITION_ANGLE = 51; // 每個(gè)原點(diǎn)51度
private static final int COLOR_1 = 0xFFFFC107;
private static final int COLOR_2 = 0xFFFF9800;
private static final int COLOR_3 = 0xFFFF5722;
private static final int COLOR_4 = 0xFFF44336;
private final Paint[] mCirclePaints = new Paint[4]; // 4種類型的圓圈
// 圖像的中心位置
private int mCenterX;
private int mCenterY;
private float mMaxOuterDotsRadius; // 最大外圈的半徑
private float mMaxInnerDotsRadius; // 最大內(nèi)圈的半徑
private float mMaxDotSize; // 圓圈的最大尺寸
private float mCurrentProgress = 0; // 當(dāng)前進(jìn)度, 核心參數(shù)
private float mCurrentRadius1 = 0; // 外圈點(diǎn)的半徑
private float mCurrentDotSize1 = 0; // 外圈點(diǎn)的大小
private float mCurrentDotSize2 = 0; // 內(nèi)圈點(diǎn)的半徑
private float mCurrentRadius2 = 0; // 內(nèi)圈點(diǎn)的大小
private ArgbEvaluator argbEvaluator = new ArgbEvaluator();
public DotsView(Context context) {
super(context);
init();
}
public DotsView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public DotsView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public DotsView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init();
}
private void init() {
// 填充圓圈
for (int i = 0; i < mCirclePaints.length; i++) {
mCirclePaints[i] = new Paint();
mCirclePaints[i].setStyle(Paint.Style.FILL);
}
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mCenterX = w / 2;
mCenterY = h / 2;
mMaxDotSize = 20; // 點(diǎn)的大小
mMaxOuterDotsRadius = w / 2 - mMaxDotSize * 2; // 最大外圈
mMaxInnerDotsRadius = 0.8f * mMaxOuterDotsRadius; // 最大內(nèi)圈
}
@Override
protected void onDraw(Canvas canvas) {
drawOuterDotsFrame(canvas);
drawInnerDotsFrame(canvas);
}
// 外圈點(diǎn), 畫若干點(diǎn), 使用不同顏色, 中心位置CurrentRadius和點(diǎn)大小CurrentDotSize是變量.
private void drawOuterDotsFrame(Canvas canvas) {
for (int i = 0; i < DOTS_COUNT; i++) {
int cX = (int) (mCenterX + mCurrentRadius1 * Math.cos(i * OUTER_DOTS_POSITION_ANGLE * Math.PI / 180));
int cY = (int) (mCenterY + mCurrentRadius1 * Math.sin(i * OUTER_DOTS_POSITION_ANGLE * Math.PI / 180));
canvas.drawCircle(cX, cY, mCurrentDotSize1, mCirclePaints[i % mCirclePaints.length]);
}
}
// 內(nèi)圈點(diǎn), 與外圈點(diǎn)錯(cuò)開10, 顏色也與外圈點(diǎn)開, 中心位置CurrentRadius和點(diǎn)大小CurrentDotSize是變量.
private void drawInnerDotsFrame(Canvas canvas) {
for (int i = 0; i < DOTS_COUNT; i++) {
int cX = (int) (mCenterX + mCurrentRadius2 * Math.cos((i * OUTER_DOTS_POSITION_ANGLE - 10) * Math.PI / 180));
int cY = (int) (mCenterY + mCurrentRadius2 * Math.sin((i * OUTER_DOTS_POSITION_ANGLE - 10) * Math.PI / 180));
// i+1確保顏色不同
canvas.drawCircle(cX, cY, mCurrentDotSize2, mCirclePaints[(i + 1) % mCirclePaints.length]);
}
}
// 設(shè)置當(dāng)前進(jìn)度, 會更新大小和軌跡
public void setCurrentProgress(float currentProgress) {
mCurrentProgress = currentProgress;
// 更新位置
updateInnerDotsPosition();
updateOuterDotsPosition();
updateDotsPaints(); // 更新顏色
updateDotsAlpha(); // 更新透明度
postInvalidate(); // 每次設(shè)置都會延遲重繪
}
public float getCurrentProgress() {
return mCurrentProgress;
}
// 更新內(nèi)部點(diǎn)
private void updateInnerDotsPosition() {
// 0.3以上不動
if (mCurrentProgress < 0.3f) {
this.mCurrentRadius2 = (float) Utils.mapValueFromRangeToRange(mCurrentProgress, 0, 0.3f, 0.f, mMaxInnerDotsRadius);
} else {
this.mCurrentRadius2 = mMaxInnerDotsRadius;
}
// 點(diǎn)的縮小速度
if (mCurrentProgress < 0.2) {
this.mCurrentDotSize2 = mMaxDotSize;
} else if (mCurrentProgress < 0.5) {
this.mCurrentDotSize2 = (float) Utils.mapValueFromRangeToRange(mCurrentProgress, 0.2f, 0.5f, mMaxDotSize, 0.3 * mMaxDotSize);
} else {
this.mCurrentDotSize2 = (float) Utils.mapValueFromRangeToRange(mCurrentProgress, 0.5f, 1f, mMaxDotSize * 0.3f, 0);
}
}
// 變換外層點(diǎn)的位置
private void updateOuterDotsPosition() {
// 半徑先走的快, 后走的慢
if (mCurrentProgress < 0.3f) {
mCurrentRadius1 = (float) Utils.mapValueFromRangeToRange(mCurrentProgress, 0.0f, 0.3f, 0, mMaxOuterDotsRadius * 0.8f);
} else {
mCurrentRadius1 = (float) Utils.mapValueFromRangeToRange(mCurrentProgress, 0.3f, 1f, 0.8f * mMaxOuterDotsRadius, mMaxOuterDotsRadius);
}
// 點(diǎn)的大小, 小于0.7是最大點(diǎn), 大于0.7逐漸為0.
if (mCurrentProgress < 0.7f) {
mCurrentDotSize1 = mMaxDotSize;
} else {
mCurrentDotSize1 = (float) Utils.mapValueFromRangeToRange(mCurrentProgress, 0.7f, 1f, mMaxDotSize, 0);
}
}
// 變化顏色
private void updateDotsPaints() {
if (mCurrentProgress < 0.5f) {
float progress = (float) Utils.mapValueFromRangeToRange(mCurrentProgress, 0f, 0.5f, 0, 1f);
mCirclePaints[0].setColor((Integer) argbEvaluator.evaluate(progress, COLOR_1, COLOR_2));
mCirclePaints[1].setColor((Integer) argbEvaluator.evaluate(progress, COLOR_2, COLOR_3));
mCirclePaints[2].setColor((Integer) argbEvaluator.evaluate(progress, COLOR_3, COLOR_4));
mCirclePaints[3].setColor((Integer) argbEvaluator.evaluate(progress, COLOR_4, COLOR_1));
} else {
float progress = (float) Utils.mapValueFromRangeToRange(mCurrentProgress, 0.5f, 1f, 0, 1f);
mCirclePaints[0].setColor((Integer) argbEvaluator.evaluate(progress, COLOR_2, COLOR_3));
mCirclePaints[1].setColor((Integer) argbEvaluator.evaluate(progress, COLOR_3, COLOR_4));
mCirclePaints[2].setColor((Integer) argbEvaluator.evaluate(progress, COLOR_4, COLOR_1));
mCirclePaints[3].setColor((Integer) argbEvaluator.evaluate(progress, COLOR_1, COLOR_2));
}
}
// 變化透明度
private void updateDotsAlpha() {
float progress = (float) Utils.clamp(mCurrentProgress, 0.6f, 1f); // 最小0.6, 最大1
int alpha = (int) Utils.mapValueFromRangeToRange(progress, 0.6f, 1f, 255, 0); // 直至消失
mCirclePaints[0].setAlpha(alpha);
mCirclePaints[1].setAlpha(alpha);
mCirclePaints[2].setAlpha(alpha);
mCirclePaints[3].setAlpha(alpha);
}
public static final Property<DotsView, Float> DOTS_PROGRESS = new Property<DotsView, Float>(Float.class, "dotsProgress") {
@Override
public Float get(DotsView object) {
return object.getCurrentProgress();
}
@Override
public void set(DotsView object, Float value) {
object.setCurrentProgress(value);
}
};
}
初始化圓圈, 設(shè)置點(diǎn)的大小和半徑, 大小點(diǎn)錯(cuò)開排列.
private void init() {
// 填充圓圈
for (int i = 0; i < mCirclePaints.length; i++) {
mCirclePaints[i] = new Paint();
mCirclePaints[i].setStyle(Paint.Style.FILL);
}
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mCenterX = w / 2;
mCenterY = h / 2;
mMaxDotSize = 20; // 點(diǎn)的大小
mMaxOuterDotsRadius = w / 2 - mMaxDotSize * 2; // 最大外圈
mMaxInnerDotsRadius = 0.8f * mMaxOuterDotsRadius; // 最大內(nèi)圈
}
@Override
protected void onDraw(Canvas canvas) {
drawOuterDotsFrame(canvas);
drawInnerDotsFrame(canvas);
}
// 外圈點(diǎn), 畫若干點(diǎn), 使用不同顏色, 中心位置CurrentRadius和點(diǎn)大小CurrentDotSize是變量.
private void drawOuterDotsFrame(Canvas canvas) {
for (int i = 0; i < DOTS_COUNT; i++) {
int cX = (int) (mCenterX + mCurrentRadius1 * Math.cos(i * OUTER_DOTS_POSITION_ANGLE * Math.PI / 180));
int cY = (int) (mCenterY + mCurrentRadius1 * Math.sin(i * OUTER_DOTS_POSITION_ANGLE * Math.PI / 180));
canvas.drawCircle(cX, cY, mCurrentDotSize1, mCirclePaints[i % mCirclePaints.length]);
}
}
// 內(nèi)圈點(diǎn), 與外圈點(diǎn)錯(cuò)開10, 顏色也與外圈點(diǎn)開, 中心位置CurrentRadius和點(diǎn)大小CurrentDotSize是變量.
private void drawInnerDotsFrame(Canvas canvas) {
for (int i = 0; i < DOTS_COUNT; i++) {
int cX = (int) (mCenterX + mCurrentRadius2 * Math.cos((i * OUTER_DOTS_POSITION_ANGLE - 10) * Math.PI / 180));
int cY = (int) (mCenterY + mCurrentRadius2 * Math.sin((i * OUTER_DOTS_POSITION_ANGLE - 10) * Math.PI / 180));
// i+1確保顏色不同
canvas.drawCircle(cX, cY, mCurrentDotSize2, mCirclePaints[(i + 1) % mCirclePaints.length]);
}
}
設(shè)置點(diǎn)的大小和半徑, 顏色變化, 透明度, 使用映射函數(shù)控制速度變化.
// 設(shè)置當(dāng)前進(jìn)度, 會更新大小和軌跡
public void setCurrentProgress(float currentProgress) {
mCurrentProgress = currentProgress;
// 更新位置
updateInnerDotsPosition();
updateOuterDotsPosition();
updateDotsPaints(); // 更新顏色
updateDotsAlpha(); // 更新透明度
postInvalidate(); // 每次設(shè)置都會延遲重繪
}
public float getCurrentProgress() {
return mCurrentProgress;
}
// 更新內(nèi)部點(diǎn)
private void updateInnerDotsPosition() {
// 0.3以上不動
if (mCurrentProgress < 0.3f) {
this.mCurrentRadius2 = (float) Utils.mapValueFromRangeToRange(mCurrentProgress, 0, 0.3f, 0.f, mMaxInnerDotsRadius);
} else {
this.mCurrentRadius2 = mMaxInnerDotsRadius;
}
// 點(diǎn)的縮小速度
if (mCurrentProgress < 0.2) {
this.mCurrentDotSize2 = mMaxDotSize;
} else if (mCurrentProgress < 0.5) {
this.mCurrentDotSize2 = (float) Utils.mapValueFromRangeToRange(mCurrentProgress, 0.2f, 0.5f, mMaxDotSize, 0.3 * mMaxDotSize);
} else {
this.mCurrentDotSize2 = (float) Utils.mapValueFromRangeToRange(mCurrentProgress, 0.5f, 1f, mMaxDotSize * 0.3f, 0);
}
}
// 變換外層點(diǎn)的位置
private void updateOuterDotsPosition() {
// 半徑先走的快, 后走的慢
if (mCurrentProgress < 0.3f) {
mCurrentRadius1 = (float) Utils.mapValueFromRangeToRange(mCurrentProgress, 0.0f, 0.3f, 0, mMaxOuterDotsRadius * 0.8f);
} else {
mCurrentRadius1 = (float) Utils.mapValueFromRangeToRange(mCurrentProgress, 0.3f, 1f, 0.8f * mMaxOuterDotsRadius, mMaxOuterDotsRadius);
}
// 點(diǎn)的大小, 小于0.7是最大點(diǎn), 大于0.7逐漸為0.
if (mCurrentProgress < 0.7f) {
mCurrentDotSize1 = mMaxDotSize;
} else {
mCurrentDotSize1 = (float) Utils.mapValueFromRangeToRange(mCurrentProgress, 0.7f, 1f, mMaxDotSize, 0);
}
}
// 變化顏色
private void updateDotsPaints() {
if (mCurrentProgress < 0.5f) {
float progress = (float) Utils.mapValueFromRangeToRange(mCurrentProgress, 0f, 0.5f, 0, 1f);
mCirclePaints[0].setColor((Integer) argbEvaluator.evaluate(progress, COLOR_1, COLOR_2));
mCirclePaints[1].setColor((Integer) argbEvaluator.evaluate(progress, COLOR_2, COLOR_3));
mCirclePaints[2].setColor((Integer) argbEvaluator.evaluate(progress, COLOR_3, COLOR_4));
mCirclePaints[3].setColor((Integer) argbEvaluator.evaluate(progress, COLOR_4, COLOR_1));
} else {
float progress = (float) Utils.mapValueFromRangeToRange(mCurrentProgress, 0.5f, 1f, 0, 1f);
mCirclePaints[0].setColor((Integer) argbEvaluator.evaluate(progress, COLOR_2, COLOR_3));
mCirclePaints[1].setColor((Integer) argbEvaluator.evaluate(progress, COLOR_3, COLOR_4));
mCirclePaints[2].setColor((Integer) argbEvaluator.evaluate(progress, COLOR_4, COLOR_1));
mCirclePaints[3].setColor((Integer) argbEvaluator.evaluate(progress, COLOR_1, COLOR_2));
}
}
// 變化透明度
private void updateDotsAlpha() {
float progress = (float) Utils.clamp(mCurrentProgress, 0.6f, 1f); // 最小0.6, 最大1
int alpha = (int) Utils.mapValueFromRangeToRange(progress, 0.6f, 1f, 255, 0); // 直至消失
mCirclePaints[0].setAlpha(alpha);
mCirclePaints[1].setAlpha(alpha);
mCirclePaints[2].setAlpha(alpha);
mCirclePaints[3].setAlpha(alpha);
}
添加屬性
public static final Property<DotsView, Float> DOTS_PROGRESS = new Property<DotsView, Float>(Float.class, "dotsProgress") {
@Override
public Float get(DotsView object) {
return object.getCurrentProgress();
}
@Override
public void set(DotsView object, Float value) {
object.setCurrentProgress(value);
}
};
5. 控件邏輯
喜歡按鈕, 通過屬性值變化(0-1), 設(shè)置子控件狀態(tài), 使用插值器控制速度變化.
/**
* 喜歡按鈕, 通過屬性值變化(0-1)設(shè)置子控件狀態(tài), 使用不同的插值器控制速度.
* <p>
* Created by wangchenlong on 16/1/5.
*/
public class LikeButtonView extends FrameLayout {
@Bind(R.id.like_button_cv_circle) CircleView mCvCircle; // 圓形
@Bind(R.id.like_button_iv_star) ImageView mIvStar; // 星星
@Bind(R.id.like_button_dv_dots) DotsView mDvDots; // 圓圈
private DecelerateInterpolator mDecelerate; // 減速插值
private OvershootInterpolator mOvershoot; // 超出插值
private AccelerateDecelerateInterpolator mAccelerateDecelerate; // 加速度減速插值
private AnimatorSet mAnimatorSet; // 動畫集合
private boolean mIsChecked; // 點(diǎn)擊狀態(tài)
public LikeButtonView(Context context) {
super(context);
init();
}
public LikeButtonView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public LikeButtonView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public LikeButtonView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init();
}
// 初始化視圖
private void init() {
isInEditMode();
LayoutInflater.from(getContext()).inflate(R.layout.view_like_button, this, true);
ButterKnife.bind(this);
mDecelerate = new DecelerateInterpolator(); // 減速插值器
mOvershoot = new OvershootInterpolator(4); // 超出插值器
mAccelerateDecelerate = new AccelerateDecelerateInterpolator(); // 加速再減速插值器
setOnClickListener(this::clickView);
}
// 點(diǎn)擊視圖
private void clickView(View view) {
mIsChecked = !mIsChecked;
mIvStar.setImageResource(mIsChecked ? R.drawable.ic_star_rate_on : R.drawable.ic_star_rate_off);
if (mAnimatorSet != null) {
mAnimatorSet.cancel();
}
if (mIsChecked) {
mIvStar.animate().cancel();
mIvStar.setScaleX(0);
mIvStar.setScaleY(0);
mCvCircle.setInnerCircleRadiusProgress(0);
mCvCircle.setOuterCircleRadiusProgress(0);
mDvDots.setCurrentProgress(0);
mAnimatorSet = new AnimatorSet();
ObjectAnimator outerCircleAnimator = ObjectAnimator.ofFloat(mCvCircle, CircleView.OUTER_CIRCLE_RADIUS_PROGRESS, 0.1f, 1f);
outerCircleAnimator.setDuration(250);
outerCircleAnimator.setInterpolator(mDecelerate);
ObjectAnimator innerCircleAnimator = ObjectAnimator.ofFloat(mCvCircle, CircleView.INNER_CIRCLE_RADIUS_PROGRESS, 0.1f, 1f);
innerCircleAnimator.setDuration(200);
innerCircleAnimator.setStartDelay(200);
innerCircleAnimator.setInterpolator(mDecelerate);
ObjectAnimator starScaleYAnimator = ObjectAnimator.ofFloat(mIvStar, ImageView.SCALE_Y, 0.2f, 1f);
starScaleYAnimator.setDuration(350);
starScaleYAnimator.setStartDelay(250);
starScaleYAnimator.setInterpolator(mOvershoot);
ObjectAnimator starScaleXAnimator = ObjectAnimator.ofFloat(mIvStar, ImageView.SCALE_X, 0.2f, 1f);
starScaleXAnimator.setDuration(350);
starScaleXAnimator.setStartDelay(250);
starScaleXAnimator.setInterpolator(mOvershoot);
ObjectAnimator dotsAnimator = ObjectAnimator.ofFloat(mDvDots, DotsView.DOTS_PROGRESS, 0, 1f);
dotsAnimator.setDuration(900);
dotsAnimator.setStartDelay(50);
dotsAnimator.setInterpolator(mAccelerateDecelerate);
mAnimatorSet.playTogether(
outerCircleAnimator,
innerCircleAnimator,
starScaleYAnimator,
starScaleXAnimator,
dotsAnimator
);
mAnimatorSet.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationCancel(Animator animation) {
mIvStar.setScaleX(1);
mIvStar.setScaleY(1);
mCvCircle.setInnerCircleRadiusProgress(0);
mCvCircle.setOuterCircleRadiusProgress(0);
mDvDots.setCurrentProgress(0);
}
});
mAnimatorSet.start();
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mIvStar.animate().scaleX(0.7f).scaleY(0.7f).setDuration(150).setInterpolator(mDecelerate);
setPressed(true);
break;
case MotionEvent.ACTION_MOVE:
float x = event.getX();
float y = event.getY();
boolean isInside = (x > 0 && x < getWidth() && y > 0 && y < getHeight());
if (isPressed() != isInside) {
setPressed(isInside);
}
break;
case MotionEvent.ACTION_UP:
mIvStar.animate().scaleX(1).scaleY(1).setInterpolator(mDecelerate);
if (isPressed()) {
performClick();
setPressed(false);
}
break;
}
return true;
}
}
初始化, 減速插值器用于圓環(huán)控件, 超出插值器用于星星圖片, 加速再減速插值器用于散射點(diǎn).
// 初始化視圖
private void init() {
isInEditMode();
LayoutInflater.from(getContext()).inflate(R.layout.view_like_button, this, true);
ButterKnife.bind(this);
mDecelerate = new DecelerateInterpolator(); // 減速插值器
mOvershoot = new OvershootInterpolator(4); // 超出插值器, 圖片放大和縮小
mAccelerateDecelerate = new AccelerateDecelerateInterpolator(); // 加速再減速插值器
setOnClickListener(this::clickView);
}
依次設(shè)計(jì)三個(gè)控件的動畫, 放入動畫集合, 設(shè)置初始狀態(tài), 啟動.
// 點(diǎn)擊視圖
private void clickView(View view) {
mIsChecked = !mIsChecked;
mIvStar.setImageResource(mIsChecked ? R.drawable.ic_star_rate_on : R.drawable.ic_star_rate_off);
// 情況狀態(tài)
if (mAnimatorSet != null) {
mAnimatorSet.cancel();
}
if (mIsChecked) {
mIvStar.animate().cancel();
mIvStar.setScaleX(0);
mIvStar.setScaleY(0);
mCvCircle.setInnerCircleRadiusProgress(0);
mCvCircle.setOuterCircleRadiusProgress(0);
mDvDots.setCurrentProgress(0);
mAnimatorSet = new AnimatorSet();
ObjectAnimator outerCircleAnimator = ObjectAnimator.ofFloat(mCvCircle, CircleView.OUTER_CIRCLE_RADIUS_PROGRESS, 0.1f, 1f);
outerCircleAnimator.setDuration(250);
outerCircleAnimator.setInterpolator(mDecelerate);
// 延遲擦除
ObjectAnimator innerCircleAnimator = ObjectAnimator.ofFloat(mCvCircle, CircleView.INNER_CIRCLE_RADIUS_PROGRESS, 0.1f, 1f);
innerCircleAnimator.setDuration(200);
innerCircleAnimator.setStartDelay(200);
innerCircleAnimator.setInterpolator(mDecelerate);
// 豎直水平放大和縮小
ObjectAnimator starScaleYAnimator = ObjectAnimator.ofFloat(mIvStar, ImageView.SCALE_Y, 0.2f, 1f);
starScaleYAnimator.setDuration(350);
starScaleYAnimator.setStartDelay(250);
starScaleYAnimator.setInterpolator(mOvershoot);
ObjectAnimator starScaleXAnimator = ObjectAnimator.ofFloat(mIvStar, ImageView.SCALE_X, 0.2f, 1f);
starScaleXAnimator.setDuration(350);
starScaleXAnimator.setStartDelay(250);
starScaleXAnimator.setInterpolator(mOvershoot);
// 先快后慢.
ObjectAnimator dotsAnimator = ObjectAnimator.ofFloat(mDvDots, DotsView.DOTS_PROGRESS, 0, 1f);
dotsAnimator.setDuration(900);
dotsAnimator.setStartDelay(50);
dotsAnimator.setInterpolator(mAccelerateDecelerate);
// 放入動畫集合
mAnimatorSet.playTogether(
outerCircleAnimator,
innerCircleAnimator,
starScaleYAnimator,
starScaleXAnimator,
dotsAnimator
);
// 動畫集合監(jiān)聽
mAnimatorSet.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationCancel(Animator animation) {
// 初始值
mIvStar.setScaleX(1);
mIvStar.setScaleY(1);
mCvCircle.setInnerCircleRadiusProgress(0);
mCvCircle.setOuterCircleRadiusProgress(0);
mDvDots.setCurrentProgress(0);
}
});
mAnimatorSet.start();
}
}
點(diǎn)擊事件
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mIvStar.animate().scaleX(0.7f).scaleY(0.7f).setDuration(150).setInterpolator(mDecelerate);
setPressed(true);
break;
// 在控件內(nèi)移動, 判斷為點(diǎn)擊.
case MotionEvent.ACTION_MOVE:
float x = event.getX();
float y = event.getY();
boolean isInside = (x > 0 && x < getWidth() && y > 0 && y < getHeight());
if (isPressed() != isInside) {
setPressed(isInside);
}
break;
case MotionEvent.ACTION_UP:
mIvStar.animate().scaleX(1).scaleY(1).setInterpolator(mDecelerate);
if (isPressed()) {
performClick();
setPressed(false);
}
break;
}
return true;
}
屬性動畫雖然有些復(fù)雜, 但是掌握其中原理, 特別有意思, 類似于數(shù)學(xué)公式, 及其鍛煉智商.
OK, that's all! Enjoy it!