Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android自定義View之可隨時暫停、開啟的圓形下載進度條

Android自定義View之可隨時暫停、開啟的圓形下載進度條

編輯:關於Android編程

這是一個一言不合就手撸一個自定義View的任性時代,因此最近一段時間一直在學習自定義View相關的知識,也看了很多與此相關的博客,有句話叫做不要重復造輪子,別人寫好的直接拿過來改吧改吧,能用就行,但是,要想像那些任性的大牛一樣,分分鐘撸一個自定義View,就得不斷的重復造輪子,學習大神們的設計思路, 站在牛人的肩膀上不斷前行,每篇開篇之前都要啰嗦半天,急性子的童鞋可以直接跳過。看到yissan大牛寫了一篇自定義圓形進度條,思路很清晰,就照著也撸了一遍,果然是酸爽啊,在這裡非常感謝yissan大牛,哈哈。。。為了讓大家能一遍就看懂,我會把注釋寫的非常非常詳細,秒懂哦,,什麼??你不能秒懂。。注釋都寫的辣麼詳細了,面壁思過去。。哈哈

下面看下效果圖:
這裡寫圖片描述

1、首先創建View

(1)設置自定義View屬性,通常做法是在res/values裡面創建一個attrs文件夾,來寫我們的自定義屬性,一般我們設置屬性的name時,一般習慣性的將我們自定義的類名作為name



    
    
        
        
        
        
        
        
        
        
        
        
    

(2)設置完了自定義屬性,下一步當然是在我們的自定義View類中去獲取。(我們都習慣在參數多的構造方法中去獲取自定義屬性,其他構造方法則去通過this去調用,注意這裡是this而不是super,super的話則指向的是父類,這裡我犯了一個常識性錯誤,一鍵生成幾個構造方法,忘了將super改成this,導致獲取屬性的方法沒有被調用執行,大家在調用的時候可以打斷點試試)

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

    public CustomCircleProgress(Context context, AttributeSet attrs) {
        this(context, attrs,0);
    }

    public CustomCircleProgress(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        //獲取自定義屬性的值
        TypedArray array = getContext().obtainStyledAttributes(attrs, R.styleable.CustomCircleProgress);
        //默認圓的顏色
        mDefaultColor = array.getColor(R.styleable.CustomCircleProgress_progress_default_color, PROGRESS_DEFAULT_COLOR);
        //進度條的顏色
        mReachedColor = array.getColor(R.styleable.CustomCircleProgress_progress_reached_color, PROGRESS_REACHED_COLOR);
        //默認圓的高度
        mDefaultHeight = (int) array.getDimension(R.styleable.CustomCircleProgress_progress_default_height, mDefaultHeight);
        //進度條的高度
        mReachedHeight = (int) array.getDimension(R.styleable.CustomCircleProgress_progress_reached_height, mReachedHeight);
        //圓的半徑
        mRadius = (int) array.getDimension(R.styleable.CustomCircleProgress_circle_radius, mRadius);
        //最後不要忘了回收 TypedArray
        array.recycle();

        //設置畫筆(new畫筆的操作一般不要放在onDraw方法中,因為在繪制的過程中onDraw方法會被多次調用)
        setPaint();

我們在new我們的畫筆時一般不要在onDraw()方法中去new,因為view在不斷的繪制過程中onDraw()方法會不斷的被調用,這樣就會造成不停的new我們的畫筆實例。

//設置畫筆
private void setPaint() {
        mPaint = new Paint();
        //下面是設置畫筆的一些屬性
        mPaint.setAntiAlias(true);//抗鋸齒
        mPaint.setDither(true);//防抖動,繪制出來的圖要更加柔和清晰
        mPaint.setStyle(Paint.Style.STROKE);//設置填充樣式
        /**
         *  Paint.Style.FILL    :填充內部
         *  Paint.Style.FILL_AND_STROKE  :填充內部和描邊
         *  Paint.Style.STROKE  :僅描邊
         */
        mPaint.setStrokeCap(Paint.Cap.ROUND);//設置畫筆筆刷類型


    }

2、處理View的布局,即測量onMeasure( )

當我們在xml文件中給這個view設置android:layout_width=”“android:layout_height=”“屬性為固定值、wrap_parent、match_parent 時,表明開發者向ViewGroup溝通表明我需要的空間。ViewGroup收到了開發者對View大小的說明,然後ViewGroup會綜合考慮自己的空間大小以及開發者的請求,然後生成兩個MeasureSpec對象(width與height)傳給View,這兩個對象是ViewGroup向子View提出的要求,就相當於告訴子View:“我已經與你的使用者(開發者)商量過了,現在把我們商量確定的結果告訴你,你的寬度不能違反width MeasureSpec對象的要求,你的高度不能違反height MeasureSpec對象的要求,現在,你趕緊根據這個要求確定下自己要多大空間,只許少,不許多哦。”對於超過ViewGroup為我們分配的空間時,就需要進行測量處理,然後再將處理後的結果反饋給ViewGroup,如果不是很了解的話可以點擊查看上一篇博客,有詳細的說明

/**
     * 使用onMeasure方法是因為我們的自定義圓形View的一些屬性(如:進度條寬度等)都交給用戶自己去自定義了,所以我們需要去測量下
     * 看是否符合要求
     * @param widthMeasureSpec
     * @param heightMeasureSpec
     */
    @Override
    protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);

        int paintHeight = Math.max(mReachedHeight, mDefaultHeight);//比較兩數,取最大值

        if(heightMode != MeasureSpec.EXACTLY){
            //如果用戶沒有精確指出寬高時,我們就要測量整個View所需要分配的高度了,測量自定義圓形View設置的上下內邊距+圓形view的直徑+圓形描邊邊框的高度
            int exceptHeight = getPaddingTop() + getPaddingBottom() + mRadius*2 + paintHeight;
            //然後再將測量後的值作為精確值傳給父類,告訴他我需要這麼大的空間,你給我分配吧
            heightMeasureSpec = MeasureSpec.makeMeasureSpec(exceptHeight, MeasureSpec.EXACTLY);
        }
        if(widthMode != MeasureSpec.EXACTLY){
            //這裡在自定義屬性中沒有設置圓形邊框的寬度,所以這裡直接用高度代替
            int exceptWidth = getPaddingLeft() + getPaddingRight() + mRadius*2 + paintHeight;
            widthMeasureSpec = MeasureSpec.makeMeasureSpec(exceptWidth, MeasureSpec.EXACTLY);
        }

        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

我們需要考慮開發者有時會給View設置一些padding屬性

3、繪制View,即onDraw()

(1)這裡我們需要繪制默認的內部圓以及表示進度的外層圓弧,根據進度值的變化來繪制圓弧。在繪制外層表示進度的圓弧時,需要首先確定圓弧的外接矩形(進度也就成了內切圓)的坐標,如下圖所示
這裡寫圖片描述

@Override
    protected synchronized void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        /**
         * 這裡canvas.save();和canvas.restore();是兩個相互匹配出現的,作用是用來保存畫布的狀態和取出保存的狀態的
         * 當我們對畫布進行旋轉,縮放,平移等操作的時候其實我們是想對特定的元素進行操作,但是當你用canvas的方法來進行這些操作的時候,其實是對整個畫布進行了操作,
         * 那麼之後在畫布上的元素都會受到影響,所以我們在操作之前調用canvas.save()來保存畫布當前的狀態,當操作之後取出之前保存過的狀態,
         * (比如:前面元素設置了平移或旋轉的操作後,下一個元素在進行繪制之前執行了canvas.save();和canvas.restore()操作)這樣後面的元素就不會受到(平移或旋轉的)影響
         */
        canvas.save();
        //為了保證最外層的圓弧全部顯示,我們通常會設置自定義view的padding屬性,這樣就有了內邊距,所以畫筆應該平移到內邊距的位置,這樣畫筆才會剛好在最外層的圓弧上
        //畫筆平移到指定paddingLeft, getPaddingTop()位置
        canvas.translate(getPaddingLeft(),getPaddingTop());
        mPaint.setStyle(Paint.Style.STROKE);
        //畫默認圓(邊框)的一些設置
        mPaint.setColor(mDefaultColor);
        mPaint.setStrokeWidth(mDefaultHeight);
        canvas.drawCircle(mRadius,mRadius,mRadius,mPaint);

        //畫進度條的一些設置
        mPaint.setColor(mReachedColor);
        mPaint.setStrokeWidth(mReachedHeight);
        //根據進度繪制圓弧
        float sweepAngle = getProgress() * 1.0f / getMax() * 360;
        canvas.drawArc(new RectF(0, 0, mRadius * 2, mRadius *2), 0, sweepAngle, false, mPaint);//drawArc:繪制圓弧

        canvas.restore();

    }

我們做個定時器,讓進度條動起來

public class MainActivity extends AppCompatActivity {

    private CustomCircleProgress circleProgress;
    private int progress;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        circleProgress = (CustomCircleProgress) findViewById(R.id.circleProgress);

        Timer timer = new Timer();
         TimerTask task = new TimerTask() {
            @Override
            public void run() {
                if(progress >= 100){
                    progress = 0;
                    circleProgress.setProgress(0);
                }else{
                    progress = circleProgress.getProgress();
                    circleProgress.setProgress(++progress);
                }
            }
        };
        timer.schedule(task,0,100);

    }
}

這樣得到的效果圖是這樣的
這裡寫圖片描述vc/yoaPV4sDvztLDx9a70Oi9q9Syu6G1xMbwyrzOu9bDyejWw7PJLTkwtsi8tL/Jo6xjYW52YXMuZHJhd0FyYyhuZXcgUmVjdEYoMCwgMCwgbVJhZGl1cyA8L2VtPiAyLCBtUmFkaXVzICoyKSwgLTkwLCBzd2VlcEFuZ2xlLCBmYWxzZSwgbVBhaW50KaO7ztLDx9TZwLS/tM/C0Ke5+828PGJyIC8+DQo8aW1nIGFsdD0="這裡寫圖片描述" src="/uploadfile/Collfiles/20160831/201608310918321627.gif" title="\" />
完美,,哈哈,,,,

//繪制圓
public void drawCircle (float cx, float cy, float radius, Paint paint)
//參數說明
/**
* cx:圓心的x坐標。
  cy:圓心的y坐標。
  radius:圓的半徑。
  paint:繪制時所使用的畫筆。
*/

(2)接下來,我們開始繪制裡面的暫停(完成)狀態時的三角形,以及開啟狀態時的兩條豎線,首先我們通過枚舉的方式定義這兩種狀態,並提供set/get方法供外界調用。首先我們需要Path mPath = new Path();然後通過mPath.moveTo()確定三角形的第一個點的坐標,然後通過mPath.lineTo()鏈接其他幾個點的坐標,如果當我們設置畫筆的樣式為mPaint.setStyle(Paint.Style.STROKE);則我們需要執行close形成封閉的三角形,或者你也可以直接再來一條mPath.lineTo()再將第一個點的坐標給連接起來,這樣也形成了一個封閉的三角形。

//通過path路徑繪制三角形
        mPath = new Path();
        //讓三角形的長度等於圓的半徑(等邊三角形)
        triangleLength = mRadius;
        //繪制三角形,首先我們需要確定三個點的坐標
        float firstX = (float) ((mRadius*2 - Math.sqrt(3.0) / 2 * mRadius) / 2);//左上角第一個點的橫坐標,根據勾三股四弦五定律,Math.sqrt(3.0)表示開方
        //為了顯示的好看些,這裡微調下firstX橫坐標
        float mFirstX = (float)(firstX + firstX*0.2);
        float firstY = mRadius - triangleLength / 2;
        //同理,依次可得出第二個點(左下角)第三個點的坐標
        float secondX = mFirstX;
        float secondY = (float) (mRadius + triangleLength / 2);
        float thirdX = (float) (mFirstX + Math.sqrt(3.0) / 2 * mRadius);
        float thirdY =  mRadius;
        mPath.moveTo(mFirstX,firstY);
        mPath.lineTo(secondX,secondY);
        mPath.lineTo(thirdX,thirdY);
        mPath.lineTo(mFirstX,firstY);

然後我們在onDraw()方法中去判斷繪制不同狀態下的view

//有了path之後就可以在onDraw中繪制三角形的End和Starting狀態了
        if(mStatus == Status.End){//未開始狀態,畫筆填充三角形
            mPaint.setStyle(Paint.Style.FILL);
            //設置顏色
            mPaint.setColor(Color.parseColor("#01A1EB"));
            //畫三角形
            canvas.drawPath(mPath,mPaint);
        }else{//正在進行狀態,畫兩條豎線
            mPaint.setStyle(Paint.Style.STROKE);
            mPaint.setStrokeWidth(dp2px(5));
            mPaint.setColor(Color.parseColor("#01A1EB"));
            canvas.drawLine(mRadius*2/3, mRadius*2/3, mRadius*2/3, 2*mRadius*2/3, mPaint);
            canvas.drawLine(2*mRadius - (mRadius*2/3), mRadius*2/3, 2*mRadius - (mRadius*2/3), 2*mRadius*2/3, mPaint);
        }

4、處理與用戶的交互




    

最後是我們的MainActivity類

public class MainActivity extends AppCompatActivity {

    private CustomCircleProgress circleProgress;
    private int progress;

    private Handler handler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what){
                case PROGRESS_CIRCLE_STARTING:
                    progress = circleProgress.getProgress();
                    circleProgress.setProgress(++progress);
                    if(progress >= 100){
                        handler.removeMessages(PROGRESS_CIRCLE_STARTING);
                        progress = 0;
                        circleProgress.setProgress(0);
                        circleProgress.setStatus(CustomCircleProgress.Status.End);//修改顯示狀態為完成
                    }else{
                        //延遲100ms後繼續發消息,實現循環,直到progress=100
                        handler.sendEmptyMessageDelayed(PROGRESS_CIRCLE_STARTING, 100);
                    }
                    break;
            }
        }
    };
    public static final int PROGRESS_CIRCLE_STARTING = 0x110;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        circleProgress = (CustomCircleProgress) findViewById(R.id.circleProgress);

        circleProgress.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if(circleProgress.getStatus() == CustomCircleProgress.Status.Starting){//如果是開始狀態
                    //點擊則變成關閉暫停狀態
                    circleProgress.setStatus(CustomCircleProgress.Status.End);
                    //注意,當我們暫停時,同時還要移除消息,不然的話進度不會被停止
                    handler.removeMessages(PROGRESS_CIRCLE_STARTING);
                }else{
                    //點擊則變成開啟狀態
                    circleProgress.setStatus(CustomCircleProgress.Status.Starting);
                    Message message = Message.obtain();
                    message.what = PROGRESS_CIRCLE_STARTING;
                    handler.sendMessage(message);
                }
            }
        });
    }
}

最後,希望對你能有所幫助,有問題歡迎留言,大家一塊探討,寫博客確實挺累的。。。有需要源碼的

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved