原文地址: 自定義view之可伸縮的圓弧與扇形
上一篇文章中講解了如何自定義一個帶有清除按鈕的Edittext,這次講解如何實(shí)現(xiàn)一個帶有動畫效果的圓弧及扇形圖。先簡單看一下效果:

簡單看一下這兩個圖形:
- 弧形是根據(jù)輸入的一個范圍在0-360范圍內(nèi)的值,增加時會顯示一個逐漸增加的動畫,減少時也會有一個逐漸減少的動畫,這個動畫的插值器我設(shè)置的是先增速后減速。
- 扇形百分比動畫是一個每次都會從開始的位置從新生成的動畫,也可以做成類似于圓弧動畫的效果。
自定義圓弧類
這個圓弧類我們直接繼承自View,然后必然實(shí)現(xiàn)構(gòu)造方法。
private float value;//用戶設(shè)置的值
private Paint arcPaint;//要用到的畫筆
private RectF rectF;//繪制的范圍
private float oldValue;//過時的值
public ArcProgress(Context context) {
super(context);
init();
}
public ArcProgress(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
public ArcProgress(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
在構(gòu)造器中我們定義了一個init方法,這個方法主要是用來初始化一些東西,我只是初始化了畫筆,注意一點(diǎn),不能將rectF在這時候設(shè)置范圍,因?yàn)槲覀兪且鶕?jù)用戶設(shè)置的大小來填充rectF。
private void init() {
arcPaint = new Paint();
arcPaint.setAntiAlias(true);//抗鋸齒
arcPaint.setStyle(Paint.Style.STROKE);//只繪制圓弧邊界
arcPaint.setColor(Color.parseColor("#2c2c2c"));
arcPaint.setStrokeWidth(50);//50px的圓弧寬度
}
畫筆大家應(yīng)該用的都很熟練了,此處只說一點(diǎn),如果不設(shè)置setStyle,默認(rèn)是FILL,是填充效果。這個畫筆是在ondraw方法中用來繪制圓弧的。
onsizechange方法
還記得上節(jié)內(nèi)容中將的view的初始化順序,
constructor->onmeasure->onDraw
現(xiàn)在引入一個新的方法,onSizeChanged(int w, int h, int oldw, int oldh)
這個方法是在控件的布局參數(shù)發(fā)生變化時調(diào)用的,oldvalue在第一次加載時是0。
這個方法是在onmeasure之后ondraw之前調(diào)用。調(diào)用順序?yàn)椋?/p>
constructor->onmeasure->onSizeChanged->onDraw
在這個方法中我們定義了rectF的范圍
rectF = new RectF(50, 50, getMeasuredHeight() - 50, getMeasuredHeight() - 50);
我們設(shè)置了一個邊界范圍是50px,目的是看的更清楚,關(guān)于stroke的繪制是在寬度外還是內(nèi)的問題此處不做詳解。
ondraw方法
canvas.drawArc(rectF, 270, value, false, arcPaint);
方法只有一個最簡單的繪制圓弧,注意第二個參數(shù)是繪制的起始角度,第三個參數(shù)是繪制的角度,為不是結(jié)束角度,結(jié)束角度是起始角度+繪制角度。第三個參數(shù)設(shè)置為false,這時候繪制的圓弧而不是扇形。
設(shè)置值的接口
public void setValue(final float v) {
ValueAnimator animator = ValueAnimator.ofFloat(oldValue, v);
oldValue = v;
if (Math.abs(v - oldValue) > 180) {
animator.setDuration(1000);
} else {
animator.setDuration(500);
}
animator.setInterpolator(new AccelerateDecelerateInterpolator());
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
value = (float) animation.getAnimatedValue();
Log.d("change", "=" + animation.getAnimatedValue());
invalidate();
}
});
animator.start();
}
這個接口是要暴漏出來的,設(shè)置為public。
此處我們定義了一個ValueAnimator,用oldv接收設(shè)置的v,此處為了用戶體驗(yàn),當(dāng)變化角度小于180時設(shè)置持續(xù)時間為500ms,當(dāng)變化角度大于180度時設(shè)置持續(xù)時間為500ms。
定義的插值器是一個先加速后減速的插值器。
重要的是為animator中添加UpdateListener,這個函數(shù)會持續(xù)返回給我們一個ValueAnimator對象,這個對象包含了我們在插值器中設(shè)定的不同的時間段對應(yīng)的值,相當(dāng)于一個時間函數(shù)。回調(diào)函數(shù)一直持續(xù)到動畫結(jié)束。
此處我們通過ValueAnimator取出對應(yīng)的數(shù)值,然后通過調(diào)用invalidate方法來刷新當(dāng)前view,產(chǎn)生一個不斷在動的效果。
這個invalid會刷新所有的可見view,但是必須工作在ui線程。
這樣就完成了一個簡單的view
button = (Button) view.findViewById(R.id.btn_change);
circleProgress.setValue(300);
progress.setValue(10, 10, 10);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
circleProgress.setValue(new Random().nextInt(360));
progress.setValue(new Random().nextInt(20), new Random().nextInt(20),
new Random().nextInt(20));
}
});
這是調(diào)用代碼,點(diǎn)擊按鈕生成隨機(jī)的一個值。
同樣的方式我們可以用來設(shè)置扇形。基本原理相似。
在ondraw方法中要設(shè)置為如下
canvas.drawArc(rectF, 0, 360 * percentOne, true, circlePaint1);
canvas.drawArc(rectF, 360 * percentOne, 360 * percentTwo, true, circlePaint2);
canvas.drawArc(rectF, 360 * (percentOne + percentTwo), 360 * percentThree, true, circlePaint3);
drawArc的第三個參數(shù)要用true,才能繪制扇形。
設(shè)置值的方法如下:
public void setValue(int value1, int value2, int value3) {
int sum = value1 + value2 + value3;
percentOne = (float) value1 / sum;
newPercentOne = percentOne;
percentTwo = (float) value2 / sum;
newPercentTwo = percentTwo;
percentThree = (float) value3 / sum;
newPercentThree = percentThree;
ValueAnimator animator1 = ValueAnimator.ofFloat(oldPercentOne, newPercentOne);
ValueAnimator animator2 = ValueAnimator.ofFloat(oldPercentTwo, newPercentTwo);
ValueAnimator animator3 = ValueAnimator.ofFloat(oldPercentThree, newPercentThree);
animator1.setDuration(1000);
animator2.setDuration(1000);
animator3.setDuration(1000);
animator1.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
percentOne = (float) animation.getAnimatedValue();
}
});
animator2.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
percentTwo = (float) animation.getAnimatedValue();
}
});
animator3.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
percentThree= (float) animation.getAnimatedValue();
invalidate();
}
});
AnimatorSet animatorSet=new AnimatorSet();
animatorSet.playTogether(animator1,animator2,animator3);
animatorSet.start();
Log.d("rect :", "percentone=" + percentOne + ",percenttwo=" + percentTwo + ",percentthree=" + percentThree);
}
我們用一個animatorset來包裹著三個動畫同時發(fā)生,當(dāng)然也可以按順序發(fā)生,效果如下:

最后,關(guān)于動畫的使用