不知道大家有沒有使用過PostMan來調(diào)試接口辙培,剛打開postman的時候有有一個加載效果,覺得還不錯也不難邢锯,于是就趁著今天有時間練了一把手扬蕊。來看看效果圖吧!
這個效果很簡單丹擎,只要canvas的drawCircle方法和一個屬性動畫的知識即可完成厨相。照例先放個GitHub傳送門:PostManLoading
總體思路
1.畫圓,最里面的實(shí)心圓鸥鹉;
2.再畫圓,外面的三條空心圓庶骄;
3.畫三個移動的小球毁渗。
既然是練手項目,在項目中也不大可能會用到单刁,我就不寫的那么詳細(xì)了~
由于我們要做的是一個方形區(qū)域的效果灸异,所以重寫onMeasure方法把寬高重置為相等,這個不多說了羔飞。
1.畫圓肺樟,最里面的實(shí)心圓:
首先呢,先定義一個半徑和空心圓之間的間隔逻淌,和初始化一些畫筆的操作
private Paint mPaintCircle;
private Paint mPaintPoint;
private int mRandius = 40;
private int mInterval = 50;//空心圓之間的間隔
//初始化操作
mPaintCircle = new Paint();
mPaintCircle.setAntiAlias(true);
mPaintCircle.setStyle(Paint.Style.STROKE);
mPaintCircle.setStrokeWidth(3);
mPaintCircle.setColor(Color.RED);
mPaintPoint = new Paint();
mPaintPoint.setAntiAlias(true);
mPaintPoint.setStyle(Paint.Style.FILL);
mPaintPoint.setColor(Color.RED);
然后要干嘛來著~么伯,畫圓呀!
int centerX = getWidth() / 2;
int centerY = getHeight() / 2;
//繪制實(shí)心圓
canvas.drawCircle(centerX, centerY, mRandius, mPaintPoint);
2.再畫圓卡儒,外面的三條空心圓田柔;
//繪制空心圓俐巴,我們以相等的間隔來依次畫出
canvas.drawCircle(centerX, centerY, mRandius + mInterval, mPaintCircle);
canvas.drawCircle(centerX, centerY, mRandius + mInterval + mInterval, mPaintCircle);
canvas.drawCircle(centerX, centerY, mRandius + mInterval + mInterval + mInterval, mPaintCircle);
3.畫三個移動的小球。
這里是此次博客的核心~
首先硬爆,我們分析滾動小球的圓心是在空心圓的軌跡上不停地變化著欣舵,但是半徑不變,我們暫且定為20個像素缀磕。只要我們在屬性動畫中不停地獲取空心圓上軌跡的每一個坐標(biāo)點(diǎn)然后賦值給小球就可以了缘圈。開始了~我們先定義圓心坐標(biāo):
//第一個小圓圓心坐標(biāo)
private float X1;
private float Y1;
//第二個小圓圓心坐標(biāo)
private float X2;
private float Y2;
//第三個小圓圓心坐標(biāo)
private float X3;
private float Y3;
然后開始寫繪制代碼,至于代碼中的坐標(biāo)為什么要加centerX 和centerY 這個等你看完就知道了袜蚕,別急~
//繪制滾動的小圓球
canvas.drawCircle(centerX + X1, centerY + Y1, 20, mPaintPoint);
canvas.drawCircle(centerX + X2, centerY + Y2, 20, mPaintPoint);
canvas.drawCircle(centerX + X3, centerY + Y3, 20, mPaintPoint);
那么我們?nèi)绾蝸慝@取空心圓上的每一個點(diǎn)的坐標(biāo)呢糟把?不如畫個草圖吧~
看圖,我們要求的就是小紅球的圓心坐標(biāo)也就是空心圓的軌跡上的每一點(diǎn)的坐標(biāo)值廷没。
deg是對應(yīng)整個PostManLoadingView的中心的角度值糊饱,這個是從0~360°時刻變化著的,既然知道角度颠黎,大的空心圓半徑R也是已知另锋,那么要求途中的X和Y值就很簡單了,一個三角函數(shù)就可以:
好的狭归,到了這里應(yīng)該就有人會想到用Math類來求正弦函數(shù)和余弦函數(shù)了~
但是Math.sin(rad)和Math.cos(rad)方法中需要傳入的參數(shù)是指弧度rad而不是角度deg夭坪,所以這里需要把角度值轉(zhuǎn)化成弧度制再調(diào)用Math函數(shù)。這里再放一個轉(zhuǎn)化公式~具體詳細(xì)的解釋請看Android角度與弧度
接下來就容易了~
到了這里再來看我們上面留的那個問題就知道為什么了~求出X之后过椎,那么小紅球圓心坐標(biāo)最終的X值是多少呢室梅?很明顯就是centerX +X芥被。好的喜命,繼續(xù)往下走開始擼代碼!這里我用一個屬性動畫ValueAnimator傳入0-360代表角度稳强,進(jìn)而求出每一個度數(shù)時的X值和Y值敷待,不說了间涵,看代碼
ValueAnimator animator = ValueAnimator.ofFloat(0, 360);
animator.setDuration(mRandius * 30);
animator.setRepeatCount(ValueAnimator.INFINITE);
animator.setRepeatMode(ValueAnimator.RESTART);
animator.setInterpolator(new LinearInterpolator());
然后去監(jiān)聽這個動畫獲取每一個角度值~
animator1.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
float deg = (float) valueAnimator.getAnimatedValue();
float rad = (float) (Math.PI * deg / 180);
Y1 = (float) (Math.sin(rad) * (mRandius + mInterval));
X1 = (float) (Math.cos(rad) * (mRandius + mInterval));
}
});
好的,到了這里基本上就沒有什么東西了榜揖,由于考慮到三個小球轉(zhuǎn)動的速度不一樣勾哩,所以我們就把每隔小球所執(zhí)行動畫的時間設(shè)置不一樣就可以了~,好的Over举哟!
下面上全家福~
PostManLoadingView.java
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.LinearInterpolator;
/**
* Created by zhuyong on 2017/7/20.
*/
public class PostManLoadingView extends View {
private Paint mPaintCircle;
private Paint mPaintPoint;
private int mRandius = 40;
private int mInterval = 50;//空心圓之間的間隔
//第一個小圓圓心坐標(biāo)
private float X1;
private float Y1;
//第二個小圓圓心坐標(biāo)
private float X2;
private float Y2;
//第三個小圓圓心坐標(biāo)
private float X3;
private float Y3;
public PostManLoadingView(Context context) {
this(context, null);
}
public PostManLoadingView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public PostManLoadingView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mPaintCircle = new Paint();
mPaintCircle.setAntiAlias(true);
mPaintCircle.setStyle(Paint.Style.STROKE);
mPaintCircle.setStrokeWidth(3);
mPaintCircle.setColor(Color.RED);
mPaintPoint = new Paint();
mPaintPoint.setAntiAlias(true);
mPaintPoint.setStyle(Paint.Style.FILL);
mPaintPoint.setColor(Color.RED);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int mWidth = MeasureSpec.getSize(widthMeasureSpec);
setMeasuredDimension(mWidth, mWidth);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int centerX = getWidth() / 2;
int centerY = getHeight() / 2;
//繪制實(shí)心圓
canvas.drawCircle(centerX, centerY, mRandius, mPaintPoint);
//繪制空心圓
canvas.drawCircle(centerX, centerY, mRandius + mInterval, mPaintCircle);
canvas.drawCircle(centerX, centerY, mRandius + mInterval + mInterval, mPaintCircle);
canvas.drawCircle(centerX, centerY, mRandius + mInterval + mInterval + mInterval, mPaintCircle);
//繪制滾動的小圓球
canvas.drawCircle(centerX + X1, centerY + Y1, 20, mPaintPoint);
canvas.drawCircle(centerX + X2, centerY + Y2, 20, mPaintPoint);
canvas.drawCircle(centerX + X3, centerY + Y3, 20, mPaintPoint);
}
public void start() {
ValueAnimator animator1 = getValueAnimator(mRandius + mInterval);
animator1.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
float deg = (float) valueAnimator.getAnimatedValue();
float rad = (float) (Math.PI * deg / 180);
Y1 = (float) (Math.sin(rad) * (mRandius + mInterval));
X1 = (float) (Math.cos(rad) * (mRandius + mInterval));
}
});
animator1.start();
ValueAnimator animator2 = getValueAnimator(mRandius + mInterval + mInterval);
animator2.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
float deg = (float) valueAnimator.getAnimatedValue();
float rad = (float) (Math.PI * deg / 180);
Y2 = (float) (Math.sin(rad) * (mRandius + mInterval + mInterval));
X2 = (float) (Math.cos(rad) * (mRandius + mInterval + mInterval));
}
});
animator2.start();
ValueAnimator animator3 = getValueAnimator(mRandius + mInterval + mInterval + mInterval);
animator3.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
float deg = (float) valueAnimator.getAnimatedValue();
float rad = (float) (Math.PI * deg / 180);
Y3 = (float) (Math.sin(rad) * (mRandius + mInterval + mInterval + mInterval));
X3 = (float) (Math.cos(rad) * (mRandius + mInterval + mInterval + mInterval));
invalidate();
}
});
animator3.start();
}
public ValueAnimator getValueAnimator(final int mRandius) {
ValueAnimator animator = ValueAnimator.ofFloat(0, 360);
animator.setDuration(mRandius * 30);//這里動畫時間只是隨便寫的思劳,可以自己設(shè)置
animator.setRepeatCount(ValueAnimator.INFINITE);
animator.setRepeatMode(ValueAnimator.RESTART);
animator.setInterpolator(new LinearInterpolator());
return animator;
}
}
xml布局和Activity很簡單我就不放了,最后只要調(diào)start()方法就可以了~
GitHub傳送門:源碼