2017年5月2日 星期二

Android: CircleProgress的實作

前言

這次練習做了一個簡單的Circle Progress,產生幾個圓形,再利用Animator物件幫我們達到持續旋轉的效果,效果如下。



以下我歸納為幾個實作步驟:
  1. 初始化
  2. 於量測階段(onMeasure)設定大小
  3. 於大小確定階段設置基本資源和動畫
  4. 於繪製階段繪製所需圓形

初始化

初始化就如同先前的篇章提到的方式,若你希望提供更多的屬性來客製化,就設定自己所需要的屬性資源即可,這邊我直接使用簡單的方式,直接設定單一顏色即可。

private void init() {
        this.viewWidth = 150;
        this.viewHeight = 150;
        this.color = Color.parseColor("#FF4081");
        this.numberOfCircle = 6;
        this.rotates = new float[this.numberOfCircle];
}

CircleProgress的寬高就直接設置150、顏色固定為粉紅色,本身的圓圈和後面跟隨的小圓圈共有六個,當然你可以提供客製化需求,自定義attrs的設定即可。

於量測階段(onMeasure)設定大小

onMeasure這個方法是View的生命週期(LifeCycle)之一,其實相似於Activity或Fragment的生命週期,就是一個View從無到繪製出來的一個過程,然而onMeasure是在View初始化時,需要先對我們的佈局及當中的子視圖進行量測的一個階段,我們在這個階段可以取得量測的大小,或是直接設定我們所要的尺寸,以下我直接將viewWidth=150和viewHeight=150透過setMeasuredDimension方法存取,程式碼如下。

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        final int measuredWidth = resolveSize(this.viewWidth, widthMeasureSpec);
        final int measuredHeight = resolveSize(this.viewHeight, heightMeasureSpec);
        this.setMeasuredDimension(measuredWidth, measuredHeight);
}

於大小確定階段設置基本資源和動畫

onLayout階段則是佈局與子視圖都已經確定其大小和位置的階段,這時我們就可以進行我們圓形繪製的前置準備,程式碼如下。

@Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        this.initLayoutSize();
        this.initCircles();
        this.prepareAnim();
}

private void initLayoutSize() {
        this.onLayoutWidth = this.getWidth();
        this.onLayoutHeight = this.getHeight();
        this.center = new PointF(this.onLayoutWidth / 2.0f, this.onLayoutHeight / 2.0f);
}

private void initCircles() {
        final float size = Math.min(this.onLayoutWidth, this.onLayoutHeight);
        final float circleRadius = size / 10.0f;
        this.circles = new Circle[this.numberOfCircle];

        for (int i = 0; i < this.numberOfCircle; i++) {
            this.circles[i] = new Circle();
            this.circles[i].setCenter(this.center.x, circleRadius);
            this.circles[i].setColor(this.color);
            this.circles[i].setRadius(circleRadius - circleRadius * i / 6);
        }
}

private void prepareAnim() {
        for (int i = 0; i < this.numberOfCircle; i++) {
            final int index = i;

            ValueAnimator fadeAnimator = ValueAnimator.ofFloat(360, 0, 0, 360);
            fadeAnimator.setRepeatCount(ValueAnimator.INFINITE);
            fadeAnimator.setDuration(2500);
            fadeAnimator.setStartDelay(index * 100);
            fadeAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    rotates[index] = (float) animation.getAnimatedValue();
                    reDraw();
                }
            });

            fadeAnimator.start();
        }
}

private void reDraw() {
        this.invalidate();
}

於繪製階段繪製所需圓形

最後一步則是於onDraw階段繪製我們所有的圓形即可,程式碼如下。

private void drawGraphic(Canvas canvas) {
        for (int i = 0; i < this.numberOfCircle; i++) {
            canvas.save();
            canvas.rotate(this.rotates[i], this.center.x, this.center.y);
            this.circles[i].draw(canvas);
            canvas.restore();
        }
}

Source Code