Android 打造形形色色的進度條 實現可以如此簡單

jopen 8年前發布 | 9K 次閱讀 Android開發 移動開發

轉載請標明出處:http://blog.csdn.net/lmj623565791/article/details/43371299 ,本文出自:【張鴻洋的博客】

1、概述

最近需要用進度條,秉著不重復造輪子的原則,上github上搜索了一番,看了幾個覺得比較好看的ProgressBar,比如:daimajia的等。簡單看了下代碼,基本都是繼承自View,徹徹底底的自定義了一個進度條。盯著那絢麗滾動條,忽然覺得,為什么要通過View去寫一個滾動條,系統已經提供了ProgressBar以及屬于它的特性,我們沒必要重新去構建一個,但是系統的又比較丑,不同版本變現還不一定一樣。那么得出我們的目標:改變系統ProgressBar的樣子。 

??對沒錯,我們沒有必要去從0打造一個ProgressBar,人家雖然長的不好看,但是特性以及穩定性還是剛剛的,我們只需要為其整下容就ok了。 

說到整容,大家都知道我們的控件是通過onDraw()畫出來的,那么我們只需要去覆蓋它的onDraw()方法,自己寫下就ok 。 

對了,我創建了一個微信公眾號,歡迎關注,左邊欄目上掃一掃即可。

??接下來,我們貼下效果圖:

2、效果圖

1、橫向的進度條


2、圓形的進度條

沒錯,這就是我們的進度條效果,橫向的模仿了daimajia的進度條樣子。不過我們繼承子ProgressBar,簡單的為其整個容,代碼清晰易懂 。為什么說,易懂呢?

橫向那個進度條,大家會drawLine()和drawText()吧,那么通過getWidth()拿到控件的寬度,再通過getProgress()拿到進度,按比例控制繪制線的長短,字的位置還不是分分鐘的事。

github源碼地址Android-ProgressBarWidthNumber歡迎大家star or fork 。

 

3、實現

橫向的滾動條繪制肯定需要一些屬性,比如已/未到達進度的顏色、寬度,文本的顏色、大小等。
本來呢,我是想通過系統ProgressBar的progressDrawable,從里面提取一些屬性完成繪制需要的參數的。但是,最終呢,反而讓代碼變得復雜。所以最終還是改用自定義屬性。 說道自定義屬性,大家應該已經不陌生了。

1、HorizontalProgressBarWithNumber

1、自定義屬性

values/attr_progress_bar.xml:

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <declare-styleable name="HorizontalProgressBarWithNumber">
        <attr name="progress_unreached_color" format="color" />
        <attr name="progress_reached_color" format="color" />
        <attr name="progress_reached_bar_height" format="dimension" />
        <attr name="progress_unreached_bar_height" format="dimension" />
        <attr name="progress_text_size" format="dimension" />
        <attr name="progress_text_color" format="color" />
        <attr name="progress_text_offset" format="dimension" />
        <attr name="progress_text_visibility" format="enum">
            <enum name="visible" value="0" />
            <enum name="invisible" value="1" />
        </attr>
    </declare-styleable>

    <declare-styleable name="RoundProgressBarWidthNumber">
        <attr name="radius" format="dimension" />
    </declare-styleable>

</resources>

2、構造中獲取

public class HorizontalProgressBarWithNumber extends ProgressBar
{

    private static final int DEFAULT_TEXT_SIZE = 10;
    private static final int DEFAULT_TEXT_COLOR = 0XFFFC00D1;
    private static final int DEFAULT_COLOR_UNREACHED_COLOR = 0xFFd3d6da;
    private static final int DEFAULT_HEIGHT_REACHED_PROGRESS_BAR = 2;
    private static final int DEFAULT_HEIGHT_UNREACHED_PROGRESS_BAR = 2;
    private static final int DEFAULT_SIZE_TEXT_OFFSET = 10;

    /**
     * painter of all drawing things
     */
    protected Paint mPaint = new Paint();
    /**
     * color of progress number
     */
    protected int mTextColor = DEFAULT_TEXT_COLOR;
    /**
     * size of text (sp)
     */
    protected int mTextSize = sp2px(DEFAULT_TEXT_SIZE);

    /**
     * offset of draw progress
     */
    protected int mTextOffset = dp2px(DEFAULT_SIZE_TEXT_OFFSET);

    /**
     * height of reached progress bar
     */
    protected int mReachedProgressBarHeight = dp2px(DEFAULT_HEIGHT_REACHED_PROGRESS_BAR);

    /**
     * color of reached bar
     */
    protected int mReachedBarColor = DEFAULT_TEXT_COLOR;
    /**
     * color of unreached bar
     */
    protected int mUnReachedBarColor = DEFAULT_COLOR_UNREACHED_COLOR;
    /**
     * height of unreached progress bar
     */
    protected int mUnReachedProgressBarHeight = dp2px(DEFAULT_HEIGHT_UNREACHED_PROGRESS_BAR);
    /**
     * view width except padding
     */
    protected int mRealWidth;

    protected boolean mIfDrawText = true;

    protected static final int VISIBLE = 0;

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

    public HorizontalProgressBarWithNumber(Context context, AttributeSet attrs,
            int defStyle)
    {
        super(context, attrs, defStyle);

        setHorizontalScrollBarEnabled(true);

        obtainStyledAttributes(attrs);

        mPaint.setTextSize(mTextSize);
        mPaint.setColor(mTextColor);

    }

    /**
     * get the styled attributes
     * 
     * @param attrs
     */
    private void obtainStyledAttributes(AttributeSet attrs)
    {
        // init values from custom attributes
        final TypedArray attributes = getContext().obtainStyledAttributes(
                attrs, R.styleable.HorizontalProgressBarWithNumber);

        mTextColor = attributes
                .getColor(
                        R.styleable.HorizontalProgressBarWithNumber_progress_text_color,
                        DEFAULT_TEXT_COLOR);
        mTextSize = (int) attributes.getDimension(
                R.styleable.HorizontalProgressBarWithNumber_progress_text_size,
                mTextSize);

        mReachedBarColor = attributes
                .getColor(
                        R.styleable.HorizontalProgressBarWithNumber_progress_reached_color,
                        mTextColor);
        mUnReachedBarColor = attributes
                .getColor(
                        R.styleable.HorizontalProgressBarWithNumber_progress_unreached_color,
                        DEFAULT_COLOR_UNREACHED_COLOR);
        mReachedProgressBarHeight = (int) attributes
                .getDimension(
                        R.styleable.HorizontalProgressBarWithNumber_progress_reached_bar_height,
                        mReachedProgressBarHeight);
        mUnReachedProgressBarHeight = (int) attributes
                .getDimension(
                        R.styleable.HorizontalProgressBarWithNumber_progress_unreached_bar_height,
                        mUnReachedProgressBarHeight);
        mTextOffset = (int) attributes
                .getDimension(
                        R.styleable.HorizontalProgressBarWithNumber_progress_text_offset,
                        mTextOffset);

        int textVisible = attributes
                .getInt(R.styleable.HorizontalProgressBarWithNumber_progress_text_visibility,
                        VISIBLE);
        if (textVisible != VISIBLE)
        {
            mIfDrawText = false;
        }
        attributes.recycle();
    }

嗯,看起來代碼挺長,其實都是在獲取自定義屬性,沒什么技術含量。

 

3、onMeasure

剛才不是出onDraw里面寫寫就行了么,為什么要改onMeasure呢,主要是因為我們所有的屬性比如進度條寬度讓用戶自定義了,所以我們的測量也得稍微變下。

@Override
    protected synchronized void onMeasure(int widthMeasureSpec,
            int heightMeasureSpec)
    {
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);

        if (heightMode != MeasureSpec.EXACTLY)
        {

            float textHeight = (mPaint.descent() + mPaint.ascent());
            int exceptHeight = (int) (getPaddingTop() + getPaddingBottom() + Math
                    .max(Math.max(mReachedProgressBarHeight,
                            mUnReachedProgressBarHeight), Math.abs(textHeight)));

            heightMeasureSpec = MeasureSpec.makeMeasureSpec(exceptHeight,
                    MeasureSpec.EXACTLY);
        }
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

    }

寬度我們不變,所以的自定義屬性不涉及寬度,高度呢,只考慮不是EXACTLY的情況(用戶明確指定了,我們就不管了),根據padding和進度條寬度算出自己想要的,如果非EXACTLY下,我們進行exceptHeight封裝,傳入給控件進行測量高度。

測量完,就到我們的onDraw了~~~

4、onDraw

@Override
    protected synchronized void onDraw(Canvas canvas)
    {
        canvas.save();
        //畫筆平移到指定paddingLeft, getHeight() / 2位置,注意以后坐標都為以此為0,0
        canvas.translate(getPaddingLeft(), getHeight() / 2);

        boolean noNeedBg = false;
        //當前進度和總值的比例
        float radio = getProgress() * 1.0f / getMax();
        //已到達的寬度
        float progressPosX = (int) (mRealWidth * radio);
        //繪制的文本
        String text = getProgress() + "%";

        //拿到字體的寬度和高度
        float textWidth = mPaint.measureText(text);
        float textHeight = (mPaint.descent() + mPaint.ascent()) / 2;

        //如果到達最后,則未到達的進度條不需要繪制
        if (progressPosX + textWidth > mRealWidth)
        {
            progressPosX = mRealWidth - textWidth;
            noNeedBg = true;
        }

        // 繪制已到達的進度
        float endX = progressPosX - mTextOffset / 2;
        if (endX > 0)
        {
            mPaint.setColor(mReachedBarColor);
            mPaint.setStrokeWidth(mReachedProgressBarHeight);
            canvas.drawLine(0, 0, endX, 0, mPaint);
        }

        // 繪制文本
        if (mIfDrawText)
        {
            mPaint.setColor(mTextColor);
            canvas.drawText(text, progressPosX, -textHeight, mPaint);
        }

        // 繪制未到達的進度條
        if (!noNeedBg)
        {
            float start = progressPosX + mTextOffset / 2 + textWidth;
            mPaint.setColor(mUnReachedBarColor);
            mPaint.setStrokeWidth(mUnReachedProgressBarHeight);
            canvas.drawLine(start, 0, mRealWidth, 0, mPaint);
        }

        canvas.restore();

    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh)
    {
        super.onSizeChanged(w, h, oldw, oldh);
        mRealWidth = w - getPaddingRight() - getPaddingLeft();

    }

其實核心方法就是onDraw了,但是呢,onDraw也很簡單,繪制線、繪制文本、繪制線,結束。

 

還有兩個簡單的輔助方法:

/**
     * dp 2 px
     * 
     * @param dpVal
     */
    protected int dp2px(int dpVal)
    {
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
                dpVal, getResources().getDisplayMetrics());
    }

    /**
     * sp 2 px
     * 
     * @param spVal
     * @return
     */
    protected int sp2px(int spVal)
    {
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,
                spVal, getResources().getDisplayMetrics());

    }

好了,到此我們的橫向進度就結束了,是不是很簡單~~如果你是自定義View,你還得考慮progress的更新,考慮狀態的銷毀與恢復等等復雜的東西。

接下來看我們的RoundProgressBarWidthNumber圓形的進度條。

2、RoundProgressBarWidthNumber 

圓形的進度條和橫向的進度條基本變量都是一致的,于是我就讓RoundProgressBarWidthNumber extends HorizontalProgressBarWithNumber 了。

然后需要改變的就是測量和onDraw了:

完整代碼:

package com.zhy.view;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint.Cap;
import android.graphics.Paint.Style;
import android.graphics.RectF;
import android.util.AttributeSet;

import com.zhy.library.view.R;

public class RoundProgressBarWidthNumber extends
        HorizontalProgressBarWithNumber {
    /**
     * mRadius of view
     */
    private int mRadius = dp2px(30);

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

    public RoundProgressBarWidthNumber(Context context, AttributeSet attrs) {
        super(context, attrs);

        mReachedProgressBarHeight = (int) (mUnReachedProgressBarHeight * 2.5f);
        TypedArray ta = context.obtainStyledAttributes(attrs,
                R.styleable.RoundProgressBarWidthNumber);
        mRadius = (int) ta.getDimension(
                R.styleable.RoundProgressBarWidthNumber_radius, mRadius);
        ta.recycle();

        mTextSize = sp2px(14);

        mPaint.setStyle(Style.STROKE);
        mPaint.setAntiAlias(true);
        mPaint.setDither(true);
        mPaint.setStrokeCap(Cap.ROUND);

    }

    @Override
    protected synchronized void onMeasure(int widthMeasureSpec,
            int heightMeasureSpec) {
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);

        int paintWidth = Math.max(mReachedProgressBarHeight,
                mUnReachedProgressBarHeight);

        if (heightMode != MeasureSpec.EXACTLY) {

            int exceptHeight = (int) (getPaddingTop() + getPaddingBottom()
                    + mRadius * 2 + paintWidth);
            heightMeasureSpec = MeasureSpec.makeMeasureSpec(exceptHeight,
                    MeasureSpec.EXACTLY);
        }
        if (widthMode != MeasureSpec.EXACTLY) {
            int exceptWidth = (int) (getPaddingLeft() + getPaddingRight()
                    + mRadius * 2 + paintWidth);
            widthMeasureSpec = MeasureSpec.makeMeasureSpec(exceptWidth,
                    MeasureSpec.EXACTLY);
        }

        super.onMeasure(heightMeasureSpec, heightMeasureSpec);

    }

    @Override
    protected synchronized void onDraw(Canvas canvas) {

        String text = getProgress() + "%";
        // mPaint.getTextBounds(text, 0, text.length(), mTextBound);
        float textWidth = mPaint.measureText(text);
        float textHeight = (mPaint.descent() + mPaint.ascent()) / 2;

        canvas.save();
        canvas.translate(getPaddingLeft(), getPaddingTop());
        mPaint.setStyle(Style.STROKE);
        // draw unreaded bar
        mPaint.setColor(mUnReachedBarColor);
        mPaint.setStrokeWidth(mUnReachedProgressBarHeight);
        canvas.drawCircle(mRadius, mRadius, mRadius, mPaint);
        // draw reached bar
        mPaint.setColor(mReachedBarColor);
        mPaint.setStrokeWidth(mReachedProgressBarHeight);
        float sweepAngle = getProgress() * 1.0f / getMax() * 360;
        canvas.drawArc(new RectF(0, 0, mRadius * 2, mRadius * 2), 0,
                sweepAngle, false, mPaint);
        // draw text
        mPaint.setStyle(Style.FILL);
        canvas.drawText(text, mRadius - textWidth / 2, mRadius - textHeight,
                mPaint);

        canvas.restore();

    }

}

首先獲取它的專有屬性mRadius,然后根據此屬性去測量,測量完成繪制;

繪制的過程呢?

先繪制一個細一點的圓,然后繪制一個粗一點的弧度,二者疊在一起就行。文本呢,繪制在中間~~~總體,沒什么代碼量。

好了,兩個進度條就到這了,是不是發現簡單很多。總體設計上,存在些問題,如果抽取一個BaseProgressBar用于獲取公共的屬性;然后不同樣子的進度條繼承分別實現自己的測量和樣子,這樣結構可能會清晰些~~~

 

4、使用

布局文件

<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:zhy="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:padding="25dp" >

        <com.zhy.view.HorizontalProgressBarWithNumber
            android:id="@+id/id_progressbar01"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="50dip"
            android:padding="5dp" />



        <com.zhy.view.HorizontalProgressBarWithNumber
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="50dip"
            android:padding="5dp"
            android:progress="50"
            zhy:progress_text_color="#ffF53B03"
            zhy:progress_unreached_color="#ffF7C6B7" />

        <com.zhy.view.RoundProgressBarWidthNumber
            android:id="@+id/id_progress02"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="50dip"
            android:padding="5dp"
            android:progress="30" />

        <com.zhy.view.RoundProgressBarWidthNumber
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="50dip"
            android:padding="5dp"
            android:progress="50"
            zhy:progress_reached_bar_height="20dp"
            zhy:progress_text_color="#ffF53B03"
            zhy:radius="60dp" />

    </LinearLayout>

</ScrollView>

MainActivity

package com.zhy.sample.progressbar;

import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;

import com.zhy.annotation.Log;
import com.zhy.view.HorizontalProgressBarWithNumber;

public class MainActivity extends Activity {

    private HorizontalProgressBarWithNumber mProgressBar;
    private static final int MSG_PROGRESS_UPDATE = 0x110;

    private Handler mHandler = new Handler() {
        @Log
        public void handleMessage(android.os.Message msg) {
            int progress = mProgressBar.getProgress();
            mProgressBar.setProgress(++progress);
            if (progress >= 100) {
                mHandler.removeMessages(MSG_PROGRESS_UPDATE);

            }
            mHandler.sendEmptyMessageDelayed(MSG_PROGRESS_UPDATE, 100);
        };
    };

    @Log
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mProgressBar = (HorizontalProgressBarWithNumber) findViewById(R.id.id_progressbar01);
        mHandler.sendEmptyMessage(MSG_PROGRESS_UPDATE);

    }

}

最后,本篇博客的目的呢?就是為了說下,類似ProgressBar這樣的控件,如果你只是想去改變顯示的樣子,完全沒必要從0去創建,復寫onDraw即可,當然是個人觀點,提出供大家參考。

 

源碼點擊下載  


群號:423372824


博主部分視頻已經上線,如果你不喜歡枯燥的文本,請猛戳(初錄,期待您的支持):

視頻目錄地址:本人錄制的視頻教程


來自: http://blog.csdn.net//lmj623565791/article/details/43371299

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!