自定義view之可伸縮的圓弧與扇形

saka發表於2017-03-08

上一篇文章中講解了如何自定義一個帶有清除按鈕的Edittext,這次講解如何實現一個帶有動畫效果的圓弧及扇形圖。先簡單看一下效果:

自定義view之可伸縮的圓弧與扇形

簡單看一下這兩個圖形:

  1. 弧形是根據輸入的一個範圍在0-360範圍內的值,增加時會顯示一個逐漸增加的動畫,減少時也會有一個逐漸減少的動畫,這個動畫的插值器我設定的是先增速後減速。
  2. 扇形百分比動畫是一個每次都會從開始的位置從新生成的動畫,也可以做成類似於圓弧動畫的效果。

自定義圓弧類

這個圓弧類我們直接繼承自View,然後必然實現構造方法。

 private float value;//使用者設定的值
    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();
    }複製程式碼

在構造器中我們定義了一個init方法,這個方法主要是用來初始化一些東西,我只是初始化了畫筆,注意一點,不能將rectF在這時候設定範圍,因為我們是要根據使用者設定的大小來填充rectF。

private void init() {
        arcPaint = new Paint();
        arcPaint.setAntiAlias(true);//抗鋸齒
        arcPaint.setStyle(Paint.Style.STROKE);//只繪製圓弧邊界
        arcPaint.setColor(Color.parseColor("#2c2c2c"));
        arcPaint.setStrokeWidth(50);//50px的圓弧寬度
    }複製程式碼

畫筆大家應該用的都很熟練了,此處只說一點,如果不設定setStyle,預設是FILL,是填充效果。這個畫筆是在ondraw方法中用來繪製圓弧的。

onsizechange方法

還記得上節內容中將的view的初始化順序,

constructor->onmeasure->onDraw複製程式碼

現在引入一個新的方法,onSizeChanged(int w, int h, int oldw, int oldh)

這個方法是在控制元件的佈局引數發生變化時呼叫的,oldvalue在第一次載入時是0。

這個方法是在onmeasure之後ondraw之前呼叫。呼叫順序為:

constructor->onmeasure->onSizeChanged->onDraw複製程式碼

在這個方法中我們定義了rectF的範圍

rectF = new RectF(50, 50, getMeasuredHeight() - 50, getMeasuredHeight() - 50);複製程式碼

我們設定了一個邊界範圍是50px,目的是看的更清楚,關於stroke的繪製是在寬度外還是內的問題此處不做詳解。

ondraw方法

canvas.drawArc(rectF, 270, value, false, arcPaint);複製程式碼

方法只有一個最簡單的繪製圓弧,注意第二個引數是繪製的起始角度,第三個引數是繪製的角度,為不是結束角度,結束角度是起始角度+繪製角度。第三個引數設定為false,這時候繪製的圓弧而不是扇形。

設定值的介面

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();
    }複製程式碼

這個介面是要暴漏出來的,設定為public。

此處我們定義了一個ValueAnimator,用oldv接收設定的v,此處為了使用者體驗,當變化角度小於180時設定持續時間為500ms,當變化角度大於180度時設定持續時間為500ms。

定義的插值器是一個先加速後減速的插值器。

重要的是為animator中新增UpdateListener,這個函式會持續返回給我們一個ValueAnimator物件,這個物件包含了我們在插值器中設定的不同的時間段對應的值,相當於一個時間函式。回撥函式一直持續到動畫結束。

此處我們通過ValueAnimator取出對應的數值,然後通過呼叫invalidate方法來重新整理當前view,產生一個不斷在動的效果。

這個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));
            }
        });複製程式碼

這是呼叫程式碼,點選按鈕生成隨機的一個值。

同樣的方式我們可以用來設定扇形。基本原理相似。
在ondraw方法中要設定為如下

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的第三個引數要用true,才能繪製扇形。

設定值的方法如下:

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來包裹著三個動畫同時發生,當然也可以按順序發生,效果如下:

自定義view之可伸縮的圓弧與扇形

最後,關於動畫的使用

安卓Property Animator動畫詳解(一)-官方文件

安卓Property Animator動畫詳解(二)-自定義屬性

相關文章