自定義View_一個加特效的圓形加載條

前幾天在朋友圈看到了一個圓形的循環的加載效果,于是嘗試著實現了一下,照例先放個GitHub傳送門:https://github.com/SuperKotlin/CircleProgressBar
先來看一下效果圖(fuck!在真機上是很流暢的,切成gif有點掉幀?。?。

circleprogressbar.gif

看到這樣的一個效果,該如何去實現呢?

總體思路


1.自定義屬性:圓弧的顏色和寬度,一開始加載進度的位置;
2.畫出需要的效果:設置不同的角度,使用畫筆paint在canvas上繪制圓??;

好的,接下來我們就按照這個思路一步一步的擼:

1.自定義我們需要的屬性:


在values文件夾下新建文件attrs.xml

      <declare-styleable name="CircleProgressView">
        <!--畫筆寬度-->
        <attr name="progress_paint_width" format="dimension" />
        <!--畫筆顏色-->
        <attr name="progress_paint_color" format="color" />
        <!--加載進度的開始位置-->
        <attr name="progress_location" format="enum">
            <enum name="left" value="1" />
            <enum name="top" value="2" />
            <enum name="right" value="3" />
            <enum name="bottom" value="4" />
        </attr>
    </declare-styleable>

然后在自定義View中獲取并設置這些屬性:
首先,來聲明我們的屬性類型:

    private Paint mPaintCurrent;//圓弧畫筆
    private float mPaintWidth;//畫筆寬度
    private int mPaintColor = Color.RED;//畫筆顏色
    private int location;//從哪個位置開始
    private float startAngle;//開始角度

獲取自定義屬性值:

//獲取屬性值
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.CircleProgressView);
location = array.getInt(R.styleable.CircleProgressView_progress_location, 2);
mPaintWidth = array.getDimension(R.styleable.CircleProgressView_progress_paint_width, dip2px(context, 4));//默認4dp
mPaintColor = array.getColor(R.styleable.CircleProgressView_progress_paint_color, mPaintColor);
array.recycle();

初始化和賦值:

//畫筆->進度圓弧
mPaintCurrent = new Paint();
mPaintCurrent.setAntiAlias(true);
mPaintCurrent.setStrokeWidth(mPaintWidth);
mPaintCurrent.setStyle(Paint.Style.STROKE);
mPaintCurrent.setColor(mPaintColor);
mPaintCurrent.setStrokeCap(Paint.Cap.ROUND);
if (location == 1) {
    startAngle = -180;
} else if (location == 2) {//默認從上側開始
    startAngle = -90;
} else if (location == 3) {
    startAngle = 0;
} else if (location == 4) {
    startAngle = 90;
}

2.畫出需要的效果:設置不同的角度,使用畫筆paint在canvas上繪制圓弧:


注意:繪制操作是在onDraw(Canvas canvas)方法中。

我們使用cancas的drawArc()方法來繪制圓弧,不清楚這個方法怎么使用以及里面的參數是什么意思的請看:自定義View_手把手教你擼出一個圓形加載進度條里面有詳細介紹!
首先來分析一下繪制這個圓弧的過程,我們分為兩步,分別如圖:

步驟1.png
步驟2.png

整個的效果可以拆分為這兩個,第一個是從0°繪制到360°的位置,剛好是一個完整的圓形。第二個是從一個完整的圓形360°慢慢的減少到0°。至于如何讓他循環不停地完成這兩個過程,我們寫個定時器不停地調用invalidate()就OK了。

第一個過程:我們只需要使drawArc方法中的sweepAngle(掃過的角度)從0增大到360就OK了,可以理解為按照順時針方向一點一點的畫出圓弧;

第二個過程:可以理解為按照順時針方向一點一點的擦除圓弧,那么它的sweepAngle(掃過的角度)從360減小到0就OK了?錯?。。。驗槲覀兪琼槙r針擦除,所以應該加個負號,即從-360減小到-0就OK了。
OK,分析結束開始擼代碼:我們定義一個變量sweepAngle=0,只需要在定時器里判斷:

sweepAngle++;
    if (sweepAngle== 360) {
         sweepAngle= -360;
    }
invalidate();

然后繪制:

canvas.drawArc(rectF, startAngle, sweepAngle, false, mPaintCurrent);

到此算是完成了整個效果,但是你會發現繪制的很慢,因為定時器就算每隔1毫秒繪制一次那么1毫秒也就僅僅繪制了1°而已,所以我們來設置一個比例參數mCurrent=0。

sweepAngle++;
    if (sweepAngle== 100) {
         sweepAngle= -100;
    }
invalidate();

然后繪制:

float sweepAngle = 360 * mCurrent / 100;
canvas.drawArc(rectF, startAngle, sweepAngle, false, mPaintCurrent);

下面我把全部代碼貼上來:

attrs.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="CircleProgressBar">
        <!--畫筆寬度-->
        <attr name="progress_paint_width" format="dimension" />
        <!--畫筆顏色-->
        <attr name="progress_paint_color" format="color" />
        <!--加載進度的開始位置-->
        <attr name="progress_location" format="enum">
            <enum name="left" value="1" />
            <enum name="top" value="2" />
            <enum name="right" value="3" />
            <enum name="bottom" value="4" />
        </attr>
    </declare-styleable>
</resources>

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:orientation="vertical">
    <com.example.zhuyong.circleprogressbar.CircleProgressBar
        android:id="@+id/circle_view"
        android:layout_width="150dp"
        android:layout_height="150dp"
        app:progress_location="top"
        app:progress_paint_color="#FF4081"
        app:progress_paint_width="4dp" />
</LinearLayout>

CircleProgressView.java

package com.example.zhuyong.circleprogressbar;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.os.Handler;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.View;

/**
 * Created by zhuyong on 2017/7/2.
 */

public class CircleProgressBar extends View {

    private int mCurrent = 0;//當前進度
    private Paint mPaintCurrent;
    private float mPaintWidth;//畫筆寬度
    private int mPaintColor = Color.RED;//畫筆顏色
    private int location;//從哪個位置開始
    private float startAngle;//開始角度
    private int TIME = 10;//默認每隔10ms重繪一次
    Handler handler = new Handler();
    Runnable runnable = new Runnable() {

        @Override
        public void run() {
            try {
                handler.postDelayed(this, TIME);
                mCurrent++;
                if (mCurrent == 100) {
                    mCurrent = -100;
                }
                invalidate();
            } catch (Exception e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    };

    public CircleProgressBar(Context context) {
        this(context, null);
    }

    public CircleProgressBar(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public CircleProgressBar(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        //獲取屬性值
        TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.CircleProgressBar);
        location = array.getInt(R.styleable.CircleProgressBar_progress_location, 2);
        mPaintWidth = array.getDimension(R.styleable.CircleProgressBar_progress_paint_width, dip2px(context, 4));//默認4dp
        mPaintColor = array.getColor(R.styleable.CircleProgressBar_progress_paint_color, mPaintColor);
        array.recycle();
        initPaint();
    }


    private void initPaint() {
        //畫筆->進度圓弧
        mPaintCurrent = new Paint();
        mPaintCurrent.setAntiAlias(true);
        mPaintCurrent.setStrokeWidth(mPaintWidth);
        mPaintCurrent.setStyle(Paint.Style.STROKE);
        mPaintCurrent.setColor(mPaintColor);
        mPaintCurrent.setStrokeCap(Paint.Cap.ROUND);

        if (location == 1) {
            startAngle = -180;
        } else if (location == 2) {//默認從上側開始
            startAngle = -90;
        } else if (location == 3) {
            startAngle = 0;
        } else if (location == 4) {
            startAngle = 90;
        }
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int width = MeasureSpec.getSize(widthMeasureSpec);
        int height = MeasureSpec.getSize(heightMeasureSpec);
        int size = width > height ? height : width;
        setMeasuredDimension(size, size);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //繪制當前進度
        RectF rectF = new RectF(mPaintWidth / 2, mPaintWidth / 2, getWidth() - mPaintWidth / 2, getHeight() - mPaintWidth / 2);
        float sweepAngle = 360 * mCurrent / 100;
        canvas.drawArc(rectF, startAngle, sweepAngle, false, mPaintCurrent);
    }

    /**
     * 開始
     */
    public void start() {
        handler.postDelayed(runnable, TIME); //每隔TIMEms執行
    }

    /**
     * 根據手機的分辨率從 dp 的單位 轉成為 px(像素)
     */
    public static int dip2px(Context context, float dpValue) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (dpValue * scale + 0.5f);
    }

}


MainActivity.java

package com.example.zhuyong.circleprogressbar;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

public class MainActivity extends AppCompatActivity {
    
    private CircleProgressBar mCircleProgressView;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mCircleProgressView = (CircleProgressBar) findViewById(R.id.circle_view);
        mCircleProgressView.start();
    }
}

GitHub傳送門:源碼

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容