博主聲明:
轉(zhuǎn)載請(qǐng)?jiān)陂_(kāi)頭附加本文鏈接及作者信息币喧,并標(biāo)記為轉(zhuǎn)載变汪。本文由博主 威威喵 原創(chuàng),請(qǐng)多支持與指教催首。
本文首發(fā)于此 博主:威威喵 | 博客主頁(yè):https://blog.csdn.net/smile_running
今天在打開(kāi) QQ 的時(shí)候扶踊,我本人一般都是不怎么去看消息的,除了各大群里面的消息刷屏翅帜,也很少聊天姻檀。其實(shí),我個(gè)人不太喜歡在網(wǎng)絡(luò)上聊天涝滴,除了有什么事情要說(shuō)绣版,那倒是情有可原胶台。如果就閑聊、扯淡的話(huà)杂抽,那等到有時(shí)間诈唬,幾個(gè)人坐下來(lái)喝喝茶,聊聊天不是更好嘛缩麸。雖然我高中也喜歡聊天铸磅,到大學(xué)了,就真的很少和別人在網(wǎng)絡(luò)上聊天了杭朱。也許是年齡越來(lái)越大了阅仔,融入不了小年輕的世界啦,哈哈弧械,我 TM 才 21 歲啊八酒,老了老了。刃唐。羞迷。
好了,瞎扯淡的話(huà)就到這里画饥。也許是我今天走了挺多路程的衔瓮,不知咋地了,QQ 運(yùn)動(dòng)忽然對(duì)我說(shuō) “今天狀態(tài)爆表”抖甘,我趕緊點(diǎn)進(jìn)去看了一下热鞍,不知不覺(jué)今天都走了 2W 多步了,不過(guò)就是腳有點(diǎn)酸衔彻。我又點(diǎn)進(jìn)去看了一下碍现,咦,這個(gè)步數(shù)的動(dòng)畫(huà)效果很不錯(cuò)啊米奸。之前雖然也看過(guò)了,但都沒(méi)在意爽篷,今天突然來(lái)興趣了悴晰,總想試試如何實(shí)現(xiàn)這個(gè)效果。那么逐工,說(shuō)干就干吧铡溪。
首先呢,QQ 步數(shù)動(dòng)畫(huà)是這樣一個(gè)效果泪喊,它的外圈是一條弧線(xiàn)棕硫,中間是步數(shù)的值,隨著這個(gè)值的增加袒啼,那么弧線(xiàn)的弧度也會(huì)隨之遞增哈扮,直接來(lái)看我實(shí)現(xiàn)的效果圖吧纬纪,更加形象:
好了,就是這樣的一個(gè)效果滑肉,在很多 app 里面都很常見(jiàn)的包各,好像支付寶的錢(qián)錢(qián)動(dòng)畫(huà)也是類(lèi)似的效果,一個(gè)值會(huì)在變化靶庙,直到值為最終結(jié)果為止问畅。那么我們接下來(lái)看看如何實(shí)現(xiàn)這個(gè)效果吧。
首先呢六荒,這肯定是一個(gè)自定義 View护姆,我們需要獲取有它的一個(gè)步數(shù)值、最大步數(shù)值掏击、弧線(xiàn)卵皂、弧線(xiàn)的寬度、不同的顏色值等這幾個(gè)屬性铐料,那我們就可以將它定義為一個(gè) attrs.xml 文件渐裂,在 View 標(biāo)簽中用屬性來(lái)設(shè)置值。第一步钠惩,在 res — value 下新建一個(gè) attrs.xml 文件柒凉,其中代碼如下:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="QQStepsView">
<!-- 步數(shù)文本的顏色 -->
<attr name="step_text_color" format="color" />
<!-- 步數(shù)文本字體的大小 -->
<attr name="step_text_size" format="dimension" />
<!-- 步數(shù)文本字體的大小 -->
<attr name="step_text_spacing" format="float" />
<!-- 步數(shù)的最大值 -->
<attr name="step_max_value" format="float" />
<!-- 弧的顏色 -->
<attr name="step_arc_color" format="color" />
<!-- 進(jìn)度的顏色 -->
<attr name="step_progress_color" format="color" />
<!-- 弧的寬度 -->
<attr name="step_arc_width" format="dimension" />
<!-- 弧開(kāi)始的角度 -->
<attr name="arc_start_angle" format="float" />
<!-- 弧開(kāi)始的角度 -->
<attr name="arc_sweep_angle" format="float" />
</declare-styleable>
</resources>
上面代碼我都標(biāo)了屬性作用,也就不要我再解釋了吧篓跛。然后要在哪里獲取這些自定義屬性呢膝捞?肯定是在我們的自定義 View 里面,我們繼承 View 時(shí)都會(huì)默認(rèn)添加三個(gè)構(gòu)造函數(shù)愧沟,就是在構(gòu)造函數(shù)里面去獲取這些值的蔬咬。
public QQStepsView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initAttrs(context, attrs);
init();
}
private void initAttrs(Context context, AttributeSet attrs) {
if (attrs != null) {
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.QQStepsView);
mArcColor = array.getColor(R.styleable.QQStepsView_step_arc_color, Color.LTGRAY);
mStepsColor = array.getColor(R.styleable.QQStepsView_step_progress_color, Color.RED);
mTextColor = array.getColor(R.styleable.QQStepsView_step_text_color, Color.RED);
mStartAngle = array.getFloat(R.styleable.QQStepsView_arc_start_angle, -225f);
mSweepAngle = array.getFloat(R.styleable.QQStepsView_arc_start_angle, 270f);
mStepsWidth = array.getFloat(R.styleable.QQStepsView_step_arc_width, dp2px(10f));
mTextSize = array.getDimension(R.styleable.QQStepsView_step_text_size, dp2px(28f));
mTextSpacing = array.getFloat(R.styleable.QQStepsView_step_text_spacing, dp2px(0.02f));
mMaxSteps = array.getFloat(R.styleable.QQStepsView_step_max_value, 20000f);
array.recycle();
}
}
下面的話(huà),就要去測(cè)量 View 的寬和高了沐寺。根據(jù)這種情況林艘,我們 View 的寬和高要一致才行,不然的話(huà)圓弧會(huì)變成橢圓的弧混坞,就不好看了狐援,代碼如下:
@SuppressLint("DrawAllocation")
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.AT_MOST || MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.AT_MOST) {
mWidth = mHeight = 200;
} else {
mWidth = MeasureSpec.getSize(widthMeasureSpec);
mHeight = MeasureSpec.getSize(heightMeasureSpec);
if (mWidth != mHeight) {
mHeight = mWidth = Math.min(mWidth, mHeight);
}
}
mCenterX = mWidth / 2;
mCenterY = mHeight / 2;
float paintSize = mArcPaint.getStrokeWidth() / 2;
mRect = new RectF(paintSize, paintSize, mWidth - paintSize, mHeight - paintSize);
}
最后,就是繪制圖像了究孕。一個(gè)是弧線(xiàn)的繪制啥酱,一個(gè)是步數(shù)文本的繪制,這部分沒(méi)啥好解釋的厨诸,就是計(jì)算一下坐標(biāo)而已镶殷,代碼如下:
private void drawSteps(Canvas canvas) {
// 繪制空的弧
canvas.drawArc(mRect, mStartAngle, mSweepAngle, false, mArcPaint);
//繪制步數(shù)的弧
mStepScale = mSteps / mMaxSteps;
canvas.drawArc(mRect, mStartAngle, mStepScale * mSweepAngle, false, mStepsPaint);
}
private void drawStepsText(Canvas canvas) {
String steps = String.valueOf(mSteps);
mBounds = new Rect();
mStepsTextPaint.getTextBounds(steps, 0, steps.length(), mBounds);
Paint.FontMetrics metrics = mStepsTextPaint.getFontMetrics();
canvas.drawText(steps, mCenterX - mBounds.width() / 2, mCenterY - (metrics.top + metrics.bottom) / 2, mStepsTextPaint);
}
這樣的話(huà),這 QQ 步數(shù)效果所需的關(guān)鍵性代碼都寫(xiě)完了微酬,那我直接放出所以代碼:
package nd.no.xww.qqmessagedragview;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Typeface;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.View;
/**
* @author xww
* @desciption : QQ 步數(shù)動(dòng)畫(huà)效果
* @date 2018/7/26
* @time 16:48
* @威威喵
*/
public class QQStepsView extends View {
private Paint mArcPaint;
private Paint mStepsPaint;
private Paint mStepsTextPaint;
private float mStepsWidth;
private float mStartAngle;
private float mSweepAngle;
private int mTextColor;
private int mArcColor;
private int mStepsColor;
private float mTextSize;
private float mTextSpacing;
private int mSteps;
private float mMaxSteps;
private float mStepScale;
private RectF mRect;
private Rect mBounds;
private float mWidth;
private float mHeight;
private float mCenterX;
private float mCenterY;
private void init() {
initPaint();
}
private void initAttrs(Context context, AttributeSet attrs) {
if (attrs != null) {
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.QQStepsView);
mArcColor = array.getColor(R.styleable.QQStepsView_step_arc_color, Color.LTGRAY);
mStepsColor = array.getColor(R.styleable.QQStepsView_step_progress_color, Color.RED);
mTextColor = array.getColor(R.styleable.QQStepsView_step_text_color, Color.RED);
mStartAngle = array.getFloat(R.styleable.QQStepsView_arc_start_angle, -225f);
mSweepAngle = array.getFloat(R.styleable.QQStepsView_arc_start_angle, 270f);
mStepsWidth = array.getDimension(R.styleable.QQStepsView_step_arc_width, dp2px(10f));
mTextSize = array.getDimension(R.styleable.QQStepsView_step_text_size, dp2px(28f));
mTextSpacing = array.getFloat(R.styleable.QQStepsView_step_text_spacing, dp2px(0.02f));
mMaxSteps = array.getFloat(R.styleable.QQStepsView_step_max_value, 20000f);
array.recycle();
}
}
private void initPaint() {
mArcPaint = getPaint(mArcColor);
mStepsPaint = getPaint(mStepsColor);
mStepsTextPaint = getPaint(mTextColor);
mStepsTextPaint.setStyle(Paint.Style.FILL);
mStepsTextPaint.setTextSize(mTextSize);
mStepsTextPaint.setLetterSpacing(mTextSpacing);
mStepsTextPaint.setTypeface(Typeface.MONOSPACE);
}
private Paint getPaint(int color) {
Paint paint = new Paint();
paint.setDither(true);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(mStepsWidth);
paint.setAntiAlias(true);
paint.setColor(color);
//設(shè)置弧度兩端為圓滑
paint.setStrokeCap(Paint.Cap.ROUND);
return paint;
}
public QQStepsView(Context context) {
this(context, null);
}
public QQStepsView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public QQStepsView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initAttrs(context, attrs);
init();
}
@SuppressLint("DrawAllocation")
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.AT_MOST || MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.AT_MOST) {
mWidth = mHeight = 200;
} else {
mWidth = MeasureSpec.getSize(widthMeasureSpec);
mHeight = MeasureSpec.getSize(heightMeasureSpec);
if (mWidth != mHeight) {
mHeight = mWidth = Math.min(mWidth, mHeight);
}
}
mCenterX = mWidth / 2;
mCenterY = mHeight / 2;
float paintSize = mArcPaint.getStrokeWidth() / 2;
mRect = new RectF(paintSize, paintSize, mWidth - paintSize, mHeight - paintSize);
}
@Override
protected void onDraw(Canvas canvas) {
drawSteps(canvas);
drawStepsText(canvas);
}
private void drawSteps(Canvas canvas) {
// 繪制空的弧
canvas.drawArc(mRect, mStartAngle, mSweepAngle, false, mArcPaint);
//繪制步數(shù)的弧
mStepScale = mSteps / mMaxSteps;
canvas.drawArc(mRect, mStartAngle, mStepScale * mSweepAngle, false, mStepsPaint);
}
private void drawStepsText(Canvas canvas) {
String steps = String.valueOf(mSteps);
mBounds = new Rect();
mStepsTextPaint.getTextBounds(steps, 0, steps.length(), mBounds);
Paint.FontMetrics metrics = mStepsTextPaint.getFontMetrics();
canvas.drawText(steps, mCenterX - mBounds.width() / 2, mCenterY - (metrics.top + metrics.bottom) / 2, mStepsTextPaint);
}
public void setSteps(int steps) {
this.mSteps = steps;
invalidate();
}
public void setMaxSteps(float maxSteps) {
this.mMaxSteps = maxSteps;
}
private float dp2px(float dp) {
return dp * getResources().getDisplayMetrics().density;
}
}
上面就是完整代碼绘趋,那使用起來(lái)也非常簡(jiǎn)單颤陶,比如你可以這樣:
<nd.no.xww.qqmessagedragview.QQStepsView
android:id="@+id/qqstepsview"
android:layout_width="200dp"
app:step_arc_color="#D15FEE"
app:step_max_value="40000"
app:step_progress_color="#FF83FA"
app:step_text_color="#B23AEE"
app:step_text_size="25sp"
android:layout_height="200dp"
android:layout_gravity="center" />
這里要記得添加一下 app 的一個(gè)命名空間,否則是出現(xiàn)不了我們自定義的屬性的埋心。這個(gè)命名空間的話(huà)指郁,在上一層 View 下打入 app 就會(huì)跑出來(lái)啦。最后拷呆,也是至關(guān)重要的一步闲坎,動(dòng)畫(huà)效果的設(shè)定,上面僅僅是一個(gè)靜態(tài)的 View茬斧,要想讓它動(dòng)起來(lái)腰懂,還需要加入以下代碼方可呈現(xiàn)動(dòng)畫(huà):
qqStepsView = findViewById(R.id.qqstepsview);
ValueAnimator valueAnimator = ObjectAnimator.ofInt(0, 26666);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
qqStepsView.setSteps((int) animation.getAnimatedValue());
}
});
valueAnimator.setDuration(2000);
valueAnimator.setInterpolator(new AccelerateInterpolator());
valueAnimator.start();
解釋一下上面的代碼,在 MainActivity 中綁定一下 id 拿到實(shí)例项秉,然后給它設(shè)置一個(gè)值動(dòng)畫(huà)绣溜,給這個(gè)動(dòng)畫(huà)添加一個(gè)值的監(jiān)聽(tīng),它會(huì)在上面設(shè)置的 Duration(2S)內(nèi)從 0 變化到 26666 的值娄蔼,然后我們?cè)O(shè)置一個(gè)步數(shù)就好了怖喻,那么 View 就會(huì)一直跟隨著它的變化直到動(dòng)畫(huà)結(jié)束。
切換一下顏色和字體大小等岁诉,都非常的簡(jiǎn)單锚沸,像使用 TextView 一樣,看看效果
好了涕癣,這樣就可以完成一個(gè)效果很不錯(cuò)的動(dòng)畫(huà)了哗蜈,所以你在學(xué)會(huì)了這種值變化動(dòng)畫(huà)后,對(duì)于那些支付寶金額坠韩、QQ步數(shù)等一些數(shù)值變化的動(dòng)畫(huà)還不是手到擒來(lái)距潘。