1. 程式人生 > >【Android】自定義控制元件實現帶百分比顯示進度條,可自定義顏色

【Android】自定義控制元件實現帶百分比顯示進度條,可自定義顏色

介紹

前天做了一個帶百分比顯示的條形進度條,效果如下:
這裡寫圖片描述

實現

這個自定義進度條, 看起來簡單, 做起來。。。其實也很簡單: 主要通過繼承View類, 並重寫其onDraw方法實現。
思路分為3步:
1. 畫進圖條背景(圖中灰色部分
2. 根據進度畫出進度條(圖中綠色部分
3. 繪製進度百分比(圖中白色文字

前面2個步驟非常簡單, 通過drawRoundRect方法進行繪製即可, 第3步也不難, 重點在於定位好繪製文字的位置。文字的水平位置很容易確認, 因為Paint物件提供了measureText方法, 可以獲得到文字的長度。用綠色進度條的長度和它做一個減法, 就能得出繪製文字的水平座標。
豎直座標, 就有些複雜了。先看下圖(圖片來源:

http://www.xyczero.com/blog/article/20/):
這裡寫圖片描述

在Canvas物件的drawText方法中, y座標引數指的是baseline線的y座標引數。我們所要做的, 就是求出, 當文字垂直居中顯示時, 該y座標的值。
求值, 需要用到Paint的內部類:FontMetrics。

public static class FontMetrics {
        /**
         * The maximum distance above the baseline for the tallest glyph in
         * the font at a given text size.
         */
public float top; /** * The recommended distance above the baseline for singled spaced text. */ public float ascent; /** * The recommended distance below the baseline for singled spaced text. */ public float descent; /** * The maximum distance below the baseline for the lowest glyph in * the font at a given text size. */
public float bottom; /** * The recommended additional space to add between lines of text. */ public float leading; }

由原始碼可以看到該類物件提供的幾個值的含義。其中:
ascent 代表的就是上圖中ascent線的y座標減去baseline線的y座標, 所以該值為負數
descent 代表的就是上圖中的descent線的y座標減去baseline的y座標, 所以該值為正數
由此,可知: 文字的高度為2個距離之和, 即2個數字之差:
height = descent - ascent; (1)
又:設空間高度為Height
baseline y座標 baseY = 1/2 Height + (1/ 2 height - descent); (2)

由(1) (2)式可得:
baseY = 1/2 Height - 1/2 ascent - 1/2 descent;

由此, 需要的資料都被求出來了。
同時, 在values/attrs.xml中新增自定義引數, 使三種顏色可以在佈局檔案中被配置:

attrs.xml

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


    <declare-styleable name="RoundedRectProgressBar">
        <attr name="backColor" format="color" />
        <attr name="barColor" format="color" />
        <attr name="textColor" format="color" />
    </declare-styleable>

</resources>

自定義進度條RoundedRectProgressBar.java:

package com.landemo.rectprogressbar;

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.util.AttributeSet;
import android.view.View;

/**
 * Created by lankton on 16/1/8.
 */
public class RoundedRectProgressBar extends View {

    private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    private int barColor;
    private int backColor;
    private int textColor;
    private float radius;

    int progress = 0;

    public RoundedRectProgressBar(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        /*獲取自定義引數的顏色值*/
        TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.RoundedRectProgressBar, defStyle, 0);
        int n = a.getIndexCount();
        for (int i = 0; i < n; i++)
        {
            int attr = a.getIndex(i);
            switch (attr)
            {
                case R.styleable.RoundedRectProgressBar_backColor:
                    backColor = a.getColor(attr, Color.GRAY);
                    break;
                case R.styleable.RoundedRectProgressBar_barColor:
                    barColor = a.getColor(attr, Color.GREEN);
                    break;
                case R.styleable.RoundedRectProgressBar_textColor:
                                        textColor = a.getColor(attr, Color.WHITE);
                    break;

            }

        }
        a.recycle();
    }

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

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

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        radius = this.getMeasuredHeight() / 5;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //背景
        mPaint.setColor(backColor);
        mPaint.setStyle(Paint.Style.FILL);
        canvas.drawRoundRect(new RectF(0, 0, this.getMeasuredWidth(), this.getMeasuredHeight()), radius, radius, mPaint);
        //進度條
        mPaint.setColor(barColor);
        mPaint.setStyle(Paint.Style.FILL);
        canvas.drawRoundRect(new RectF(0, 0, this.getMeasuredWidth() * progress / 100f, this.getMeasuredHeight()), radius, radius, mPaint);
        //進度
        mPaint.setColor(textColor);
        mPaint.setTextSize(this.getMeasuredHeight() / 1.2f);
        String text = "" + progress + "%";
        float x = this.getMeasuredWidth() * progress / 100 - mPaint.measureText(text) - 10;
        float y = this.getMeasuredHeight() / 2f - mPaint.getFontMetrics().ascent / 2f - mPaint.getFontMetrics().descent / 2f;
        canvas.drawText(text, x, y, mPaint);
    }

    /*設定進度條進度, 外部呼叫*/
    public void setProgress(int progress) {
        if (progress > 100) {
            this.progress = 100;
        } else if (progress < 0) {
            this.progress = 0;
        } else {
            this.progress = progress;
        }
        postInvalidate();
    }
}

然後在MainActivity裡新增方法, 呼叫RoundedRectProgressBar的setProgress方法, 重繪進度條。 這裡用Timer物件模擬進度的不斷變化。
activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:background="@android:color/white"
    tools:context="com.souche.rectprogressbar.MainActivity">

    <com.souche.rectprogressbar.RoundedRectProgressBar
        android:id="@+id/bar"
        android:layout_width="match_parent"
        android:layout_height="24dp"
        android:layout_marginTop="100dp"
        app:backColor="#E6E6E6"
        app:barColor="#33CC99"
        app:textColor="#FFFFFF"/>

    <Button
        android:id="@+id/btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="reset"
        android:layout_centerInParent="true"/>
</RelativeLayout>

MainActivity.java

package com.landemo.rectprogressbar;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;

import java.util.Timer;
import java.util.TimerTask;

public class MainActivity extends Activity {

    private RoundedRectProgressBar bar;
    private Button btn;
    private int progress;
    private Timer timer;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        bar = (RoundedRectProgressBar) findViewById(R.id.bar);
        btn = (Button) findViewById(R.id.btn);
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                reset();
            }
        });

    }

    /**
     * 進度條從頭到尾跑一次
     */
    private void reset() {
        progress = 0;
        timer = new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                bar.setProgress(progress);
                progress ++;
                if (progress > 100) {
                    timer.cancel();
                }
            }
        }, 0, 30);
    }
}