本來想找一個(gè)ios的菊花加載圖旋轉(zhuǎn)一下搞定這個(gè)的沼瘫,但是發(fā)現(xiàn)使用圖片后由于圖片的不對(duì)稱導(dǎo)致動(dòng)畫時(shí)有抖動(dòng)現(xiàn)象抬纸,不能忍,于是搜了下自定義菊花圖耿戚,額湿故,然后就沒有然后了阿趁,都不是想要的,于是開始了苦逼的自定義View...
先來張效果圖:
再來張動(dòng)畫效果圖:
看圖說話:
1坛猪、12條顏色線脖阵,帶圓角
2、動(dòng)畫起來后12個(gè)顏色跟著變化
需要解決的問題:
1墅茉、12個(gè)顏色命黔,也太多了吧!>徒铩:纺肌!我可不想吸這么多次洋机,有毒的W寡纭(嗯,我為吸管代言)
于是愉快的決定了只是用兩種顏色绷旗,即開始顏色和結(jié)束顏色喜鼓,其余使用漸變獲取。(額衔肢,好吧颠通,我是懶了!)
2膀懈、動(dòng)畫
什么顿锰,動(dòng)畫是問題嗎?一個(gè)旋轉(zhuǎn)搞定了启搂!嗯硼控,你大佬,你來胳赌,反正我做不來@魏场!
由于旋轉(zhuǎn)動(dòng)畫是轉(zhuǎn)動(dòng)的疑苫,難以實(shí)現(xiàn)顏色跟著變化的效果熏版,所以我使用了ValueAnimator,
嗯捍掺,是的撼短,你猜對(duì)了,既然使用了ValueAnimator挺勿,就要調(diào)用 invalidate(); 或 postInvalidate(); 來實(shí)現(xiàn)重繪曲横。
嗯,看起來還是挺簡(jiǎn)單的,來試試吧:
先定義一下屬性:
/**
* 線圓角及寬度
*/
private int mLineBold;
/**
* 線條開始顏色 默認(rèn)白色
*/
private int mStartColor = Color.parseColor("#FFFFFF");
/**
* 線條結(jié)束顏色 默認(rèn)灰色
*/
private int mEndColor = Color.parseColor("#9B9B9B");
/**
* view的寬度 高度
*/
private int mWidth;
/**
* view的高度
*/
private int mHeight;
/**
* 線條長度
*/
private int mLineLength;
/**
* 線條個(gè)數(shù) 默認(rèn)12條
*/
private int mLineCount = 12;
/**
* 背景畫筆
*/
private Paint mBgPaint;
/**
* 漸變顏色
*/
private int[] mColors;
/**
* 動(dòng)畫是否已開啟
*/
private boolean isAnimationStart;
/**
* 開始index
*/
private int mStartIndex;
/**
* 動(dòng)畫
*/
private ValueAnimator mValueAnimator;
>>>>>>>>>>>>>>> 然后繼續(xù) >>>>>>>>>>>>>>>>>
1.第一步:
自定義屬性:
// 由于考慮到后續(xù)可能使用更多數(shù)量的線禾嫉,所以索性加了個(gè)線個(gè)數(shù)的屬性
<!--菊花圖-->
<declare-styleable name="ChrysanthemumView">
<!--線條個(gè)數(shù)-->
<attr name="lineCount" format="integer" />
<!--菊花開始顏色-->
<attr name="startColor" format="reference|color" />
<!--菊花結(jié)束顏色-->
<attr name="endColor" format="reference|color" />
</declare-styleable>
2.第二步灾杰,初始化:
public ChrysanthemumView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
loadAttrs(context, attrs);
initPaint();
initColor();
}
/**
* 加載自定義的屬性
*/
private void loadAttrs(Context context, AttributeSet attrs) {
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.ChrysanthemumView);
mStartColor = array.getColor(R.styleable.ChrysanthemumView_startColor, mStartColor);
mEndColor = array.getColor(R.styleable.ChrysanthemumView_endColor, mEndColor);
mLineCount = array.getInt(R.styleable.ChrysanthemumView_lineCount, mLineCount);
//TypedArray回收
array.recycle();
}
/**
* 初始化畫筆
*/
private void initPaint() {
mBgPaint = new Paint();
//使得畫筆更加圓滑
mBgPaint.setAntiAlias(true);
mBgPaint.setStrokeJoin(Paint.Join.ROUND);
mBgPaint.setStrokeCap(Paint.Cap.ROUND);
}
3.第三步,解決顏色的問題:
/**
* 初始化顏色
*/
private void initColor() {
// 漸變色計(jì)算類
ArgbEvaluator argbEvaluator = new ArgbEvaluator();
// 初始化對(duì)應(yīng)空間
mColors = new int[mLineCount];
// 獲取對(duì)應(yīng)的線顏色 此處由于是白色起頭 黑色結(jié)尾所以需要反過來計(jì)算 即線的數(shù)量到0的數(shù)量遞減 對(duì)應(yīng)的ValueAnimator 是從0到線的數(shù)量-1遞增
for (int i = mLineCount; i > 0; i--) {
float alpha = (float) i / mLineCount;
mColors[mLineCount - i] = (int) argbEvaluator.evaluate(alpha, mStartColor, mEndColor);
}
}
4.第四步熙参,解決尺寸的問題:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// 獲取view的寬度 默認(rèn)40dp
mWidth = getViewSize(dp2px(getContext(), 40), widthMeasureSpec);
// 獲取view的高度 默認(rèn)40dp
mHeight = getViewSize(dp2px(getContext(), 40), heightMeasureSpec);
// 使寬高保持一致
mHeight = mWidth = Math.min(mWidth, mHeight);
// 獲取線的長度
mLineLength = mWidth / 6;
// 獲取線圓角及寬度
mLineBold = mWidth / mLineCount;
// 設(shè)置線的圓角及寬度
mBgPaint.setStrokeWidth(mLineBold);
setMeasuredDimension(mWidth,mHeight);
}
/**
* 測(cè)量模式 表示意思
* UNSPECIFIED 父容器沒有對(duì)當(dāng)前View有任何限制艳吠,當(dāng)前View可以任意取尺寸
* EXACTLY 當(dāng)前的尺寸就是當(dāng)前View應(yīng)該取的尺寸
* AT_MOST 當(dāng)前尺寸是當(dāng)前View能取的最大尺寸
*
* @param defaultSize 默認(rèn)大小
* @param measureSpec 包含測(cè)量模式和寬高信息
* @return 返回View的寬高大小
*/
private int getViewSize(int defaultSize, int measureSpec) {
int viewSize = defaultSize;
//獲取測(cè)量模式
int mode = MeasureSpec.getMode(measureSpec);
//獲取大小
int size = MeasureSpec.getSize(measureSpec);
switch (mode) {
case MeasureSpec.UNSPECIFIED:
//如果沒有指定大小,就設(shè)置為默認(rèn)大小
viewSize = defaultSize;
break;
case MeasureSpec.AT_MOST:
//如果測(cè)量模式是最大取值為size
//我們將大小取最大值,你也可以取其他值
viewSize = size;
break;
case MeasureSpec.EXACTLY:
//如果是固定的大小孽椰,那就不要去改變它
viewSize = size;
break;
default:
}
return viewSize;
}
5.第五步昭娩,繪制:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 獲取半徑
int r = mWidth / 2;
// 繪制前先旋轉(zhuǎn)一個(gè)角度,使最頂上開始位置顏色與開始顏色匹配
canvas.rotate(360f / mLineCount, r, r);
for (int i = 0; i < mLineCount; i++) {
// 獲取顏色下標(biāo)
int index = (mStartIndex + i) % mLineCount;
// 設(shè)置顏色
mBgPaint.setColor(mColors[index]);
// 繪制線條 mLineBold >> 1 == mLineBold / 2 使居中顯示
canvas.drawLine(r, mLineBold >> 1, r, (mLineBold >> 1) + mLineLength, mBgPaint);
// 旋轉(zhuǎn)角度
canvas.rotate(360f / mLineCount, r, r);
}
}
6.第六步弄屡,動(dòng)起來L赓鳌!0蚪荨B踵凇:
/**
* 開始動(dòng)畫
*
* @param duration 動(dòng)畫時(shí)間
*/
public void startAnimation(int duration) {
Log.d(TAG, "startAnimation: " + mStartIndex);
if (mValueAnimator == null) {
mValueAnimator = ValueAnimator.ofInt(mLineCount, 0);
mValueAnimator.setDuration(duration);
mValueAnimator.setTarget(0);
mValueAnimator.setRepeatCount(-1);
mValueAnimator.setInterpolator(new LinearInterpolator());
mValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
// 此處會(huì)回調(diào)3次 需要去除后面的兩次回調(diào)
if (mStartIndex != (int) animation.getAnimatedValue()) {
mStartIndex = (int) animation.getAnimatedValue();
Log.d(TAG, "onAnimationUpdate: " + mStartIndex);
invalidate();
}
}
});
}
mValueAnimator.start();
isAnimationStart = true;
}
完成了!H埂秀仲!
額,少了東西了壶笼。
/**
* 結(jié)束動(dòng)畫
*/
public void stopAnimation() {
Log.d(TAG, "stopAnimation: " + mStartIndex);
if (mValueAnimator != null) {
mValueAnimator.cancel();
isAnimationStart = false;
}
}
不行神僵,還不夠!
/**
* 防止內(nèi)存溢出 未結(jié)束動(dòng)畫并退出頁面時(shí)覆劈,需使用此函數(shù)保礼,或手動(dòng)釋放此view
*/
public void detachView() {
if (mValueAnimator != null) {
mValueAnimator.cancel();
mValueAnimator = null;
isAnimationStart = false;
}
}
所有源碼:
<!--菊花圖 自定義屬性-->
<declare-styleable name="ChrysanthemumView">
<!--線條個(gè)數(shù)-->
<attr name="lineCount" format="integer" />
<!--菊花開始顏色-->
<attr name="startColor" format="reference|color" />
<!--菊花結(jié)束顏色-->
<attr name="endColor" format="reference|color" />
</declare-styleable>
/**
* @author : Kingsley
* info: 菊花圖
* @date : 2019/4/30 10:12
*/
public class ChrysanthemumView extends View {
private static final String TAG = "ChrysanthemumView";
/**
* 線圓角及寬度
*/
private int mLineBold;
/**
* 線條開始顏色 默認(rèn)白色
*/
private int mStartColor = Color.parseColor("#FFFFFF");
/**
* 線條結(jié)束顏色 默認(rèn)灰色
*/
private int mEndColor = Color.parseColor("#9B9B9B");
/**
* view的寬度 高度
*/
private int mWidth;
/**
* view的高度
*/
private int mHeight;
/**
* 線條長度
*/
private int mLineLength;
/**
* 線條個(gè)數(shù) 默認(rèn)12條
*/
private int mLineCount = 12;
/**
* 背景畫筆
*/
private Paint mBgPaint;
/**
* 漸變顏色
*/
private int[] mColors;
/**
* 動(dòng)畫是否已開啟
*/
private boolean isAnimationStart;
/**
* 開始index
*/
private int mStartIndex;
/**
* 動(dòng)畫
*/
private ValueAnimator mValueAnimator;
public ChrysanthemumView(Context context) {
this(context, null);
}
public ChrysanthemumView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public ChrysanthemumView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
loadAttrs(context, attrs);
initPaint();
initColor();
}
/**
* 初始化顏色
*/
private void initColor() {
// 漸變色計(jì)算類
ArgbEvaluator argbEvaluator = new ArgbEvaluator();
// 初始化對(duì)應(yīng)空間
mColors = new int[mLineCount];
// 獲取對(duì)應(yīng)的線顏色 此處由于是白色起頭 黑色結(jié)尾所以需要反過來計(jì)算 即線的數(shù)量到0的數(shù)量遞減 對(duì)應(yīng)的ValueAnimator 是從0到線的數(shù)量-1遞增
for (int i = mLineCount; i > 0; i--) {
float alpha = (float) i / mLineCount;
mColors[mLineCount - i] = (int) argbEvaluator.evaluate(alpha, mStartColor, mEndColor);
}
}
/**
* 加載自定義的屬性
*/
private void loadAttrs(Context context, AttributeSet attrs) {
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.ChrysanthemumView);
mStartColor = array.getColor(R.styleable.ChrysanthemumView_startColor, mStartColor);
mEndColor = array.getColor(R.styleable.ChrysanthemumView_endColor, mEndColor);
mLineCount = array.getInt(R.styleable.ChrysanthemumView_lineCount, mLineCount);
//TypedArray回收
array.recycle();
}
/**
* 初始化畫筆
*/
private void initPaint() {
mBgPaint = new Paint();
//使得畫筆更加圓滑
mBgPaint.setAntiAlias(true);
mBgPaint.setStrokeJoin(Paint.Join.ROUND);
mBgPaint.setStrokeCap(Paint.Cap.ROUND);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// 獲取view的寬度 默認(rèn)40dp
mWidth = getViewSize(dp2px(getContext(), 40), widthMeasureSpec);
// 獲取view的高度 默認(rèn)40dp
mHeight = getViewSize(dp2px(getContext(), 40), heightMeasureSpec);
// 使寬高保持一致
mHeight = mWidth = Math.min(mWidth, mHeight);
// 獲取線的長度
mLineLength = mWidth / 6;
// 獲取線圓角及寬度
mLineBold = mWidth / mLineCount;
// 設(shè)置線的圓角及寬度
mBgPaint.setStrokeWidth(mLineBold);
setMeasuredDimension(mWidth,mHeight);
}
/**
* 測(cè)量模式 表示意思
* UNSPECIFIED 父容器沒有對(duì)當(dāng)前View有任何限制,當(dāng)前View可以任意取尺寸
* EXACTLY 當(dāng)前的尺寸就是當(dāng)前View應(yīng)該取的尺寸
* AT_MOST 當(dāng)前尺寸是當(dāng)前View能取的最大尺寸
*
* @param defaultSize 默認(rèn)大小
* @param measureSpec 包含測(cè)量模式和寬高信息
* @return 返回View的寬高大小
*/
private int getViewSize(int defaultSize, int measureSpec) {
int viewSize = defaultSize;
//獲取測(cè)量模式
int mode = MeasureSpec.getMode(measureSpec);
//獲取大小
int size = MeasureSpec.getSize(measureSpec);
switch (mode) {
case MeasureSpec.UNSPECIFIED:
//如果沒有指定大小责语,就設(shè)置為默認(rèn)大小
viewSize = defaultSize;
break;
case MeasureSpec.AT_MOST:
//如果測(cè)量模式是最大取值為size
//我們將大小取最大值,你也可以取其他值
viewSize = size;
break;
case MeasureSpec.EXACTLY:
//如果是固定的大小炮障,那就不要去改變它
viewSize = size;
break;
default:
}
return viewSize;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 獲取半徑
int r = mWidth / 2;
// 繪制前先旋轉(zhuǎn)一個(gè)角度,使最頂上開始位置顏色與開始顏色匹配
canvas.rotate(360f / mLineCount, r, r);
for (int i = 0; i < mLineCount; i++) {
// 獲取顏色下標(biāo)
int index = (mStartIndex + i) % mLineCount;
// 設(shè)置顏色
mBgPaint.setColor(mColors[index]);
// 繪制線條 mLineBold >> 1 == mLineBold / 2 使居中顯示
canvas.drawLine(r, mLineBold >> 1, r, (mLineBold >> 1) + mLineLength, mBgPaint);
// 旋轉(zhuǎn)角度
canvas.rotate(360f / mLineCount, r, r);
}
}
/**
* 開始動(dòng)畫
*
* @param duration 動(dòng)畫時(shí)間
*/
public void startAnimation(int duration) {
Log.d(TAG, "startAnimation: " + mStartIndex);
if (mValueAnimator == null) {
mValueAnimator = ValueAnimator.ofInt(mLineCount, 0);
mValueAnimator.setDuration(duration);
mValueAnimator.setTarget(0);
mValueAnimator.setRepeatCount(-1);
mValueAnimator.setInterpolator(new LinearInterpolator());
mValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
// 此處會(huì)回調(diào)3次 需要去除后面的兩次回調(diào)
if (mStartIndex != (int) animation.getAnimatedValue()) {
mStartIndex = (int) animation.getAnimatedValue();
Log.d(TAG, "onAnimationUpdate: " + mStartIndex);
invalidate();
}
}
});
}
mValueAnimator.start();
isAnimationStart = true;
}
/**
* 開始動(dòng)畫 時(shí)間為1800毫秒一次
*/
public void startAnimation() {
startAnimation(1800);
}
/**
* 結(jié)束動(dòng)畫
*/
public void stopAnimation() {
Log.d(TAG, "stopAnimation: " + mStartIndex);
if (mValueAnimator != null) {
mValueAnimator.cancel();
isAnimationStart = false;
}
}
/**
* 是否在動(dòng)畫中
*
* @return 是為 true 否則 false
*/
public boolean isAnimationStart() {
return isAnimationStart;
}
/**
* dp轉(zhuǎn)px
*
* @param context context
* @param dpVal dpVal
* @return px
*/
public static int dp2px(Context context, float dpVal) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
dpVal, context.getResources().getDisplayMetrics());
}
/**
* 防止內(nèi)存溢出 未結(jié)束動(dòng)畫并退出頁面時(shí)坤候,需使用此函數(shù)胁赢,或手動(dòng)釋放此view
*/
public void detachView() {
if (mValueAnimator != null) {
mValueAnimator.cancel();
mValueAnimator = null;
isAnimationStart = false;
}
}
}