Android Material Design风格自定义View2——球形滚动百分比的圆形进度条

————自定义PercentProgressBar继承自View,五个自定义属性progressWidth(进度条宽度),progressBackColor(进度条背景色),progressFrontColor(进度条前景色),percentTextSize(百分比文字大小),percentTextColor(百分比文字颜色),可在布局文件中直接使用。

如需下载源码,请访问
https://github.com/fengchuanfang/PercentProgressBarDemo2

文章原创,转载请注明出处:
自定义Material Design风格带滚动百分比的圆形进度条

运行效果如下:

percent_progress_bar.gif.gif

引入步骤

访问git地址下载源码,拷贝com.feng.edward.percentprogressbardemo2.view包下的PercentProgressBar类和attrs.xml中的PercentProgressBar的五项属性

或者新建java类PercentProgressBar,直接复制以下代码,再将attrs.xml中的代码放入项目对应attrs文件中。便可以在布局文件中直接使用。

package com.feng.edward.percentprogressbardemo2.view;

import com.feng.edward.percentprogressbardemo2.R;
import com.feng.edward.percentprogressbardemo2.util.DimensionUtils;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Typeface;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.View;

/**
 * 功能描述:带滚动百分比的圆形进度条
 *
 * @author (作者) edward(冯丰枫)
 * @link http://www.jianshu.com/u/f7176d6d53d2
 * 创建时间: 2018/4/16 0016
 */
public class PercentProgressBar extends View {
    private Paint
            progressBackPaint, //进度条背景画笔
            progressFrontPaint,//进度条前景画笔
            percentTextPaint;  //百分比文字画笔

    private RectF
            progressRectF;//进度条圆弧所在的矩形

    private float
            progressPaintWidth, //进度条画笔的宽度
            radiusArc,//圆形进度条半径
            textHeight;//“100%”文字的高度

    private int percentProgress;//百分比进度(0 ~ 100)

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

    public PercentProgressBar(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        TypedArray attributes = context.obtainStyledAttributes(attrs, R.styleable.PercentProgressBar, defStyleAttr, 0);

        //初始化进度条画笔的宽度
        progressPaintWidth = attributes.getDimension(R.styleable.PercentProgressBar_progressWidth, DimensionUtils.dip2px(context, 10));
        //初始化百分比文字的大小
        float percentTextPaintSize = attributes.getDimensionPixelSize(R.styleable.PercentProgressBar_percentTextSize, DimensionUtils.sp2px(context, 10));
        int progressBackColor = attributes.getColor(R.styleable.PercentProgressBar_progressBackColor, 0xffaaaaaa);
        int progressFrontColor = attributes.getColor(R.styleable.PercentProgressBar_progressFrontColor, 0xffFF4081);
        int percentTextColor = attributes.getColor(R.styleable.PercentProgressBar_percentTextColor, 0xffff0077);
        attributes.recycle();
        //初始化进度条背景画笔
        progressBackPaint = new Paint();
        progressBackPaint.setColor(progressBackColor);
        progressBackPaint.setStrokeWidth(progressPaintWidth);
        progressBackPaint.setAntiAlias(true);
        progressBackPaint.setStyle(Paint.Style.STROKE);

        //初始化进度条前景画笔
        progressFrontPaint = new Paint();
        progressFrontPaint.setColor(progressFrontColor);
        progressFrontPaint.setStrokeWidth(progressPaintWidth);
        progressFrontPaint.setAntiAlias(true);
        progressFrontPaint.setStyle(Paint.Style.STROKE);

        //初始化百分比文字画笔
        percentTextPaint = new Paint();
        percentTextPaint.setColor(percentTextColor);
        percentTextPaint.setTextSize(percentTextPaintSize);// 设置文字画笔的尺寸(px)
        percentTextPaint.setAntiAlias(true);
        percentTextPaint.setStyle(Paint.Style.STROKE);
        percentTextPaint.setTypeface(Typeface.DEFAULT_BOLD);
    }


    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int width = getMeasuredWidth();// 获取控件的layout_width
        int height = getMeasuredHeight(); // 获取控件的layout_height
        //获取内切圆圆心坐标
        int centerX = width / 2;
        int centerY = height / 2;
        int radius = Math.min(width, height) / 2;//获取控件内切圆的半径

        float textWidth = percentTextPaint.measureText("100%");
        //比较进度条的宽度和百分比文字的高度,去两者中较大者,用以计算进度条的半径,保证精度条和百分比文字互为中心
        radiusArc = radius - (progressPaintWidth > textWidth ? progressPaintWidth / 2 : textWidth / 2);
        Rect rect = new Rect();
        percentTextPaint.getTextBounds("100%", 0, "100%".length(), rect);//获取最大百分比文字的高度
        textHeight = rect.height();
        //初始化进度条圆弧所在的矩形
        progressRectF = new RectF();
        progressRectF.left = centerX - radiusArc;
        progressRectF.top = centerY - radiusArc;
        progressRectF.right = centerX + radiusArc;
        progressRectF.bottom = centerY + radiusArc;
    }


    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //绘制进度框的背景
        canvas.drawArc(progressRectF, 0, 360, false, progressBackPaint);
        //绘制进度框的前景(从圆形最高点中间,顺时针绘制)
        progressFrontPaint.setStyle(Paint.Style.STROKE);
        canvas.drawArc(progressRectF, -90, percentProgress / 100.0f * 360, false, progressFrontPaint);
        //百分比文字
        String text = percentProgress + "%";
        double cos = Math.cos(Math.toRadians(percentProgress / 100.0f * 360)) * radiusArc;
        double sin = Math.sin(Math.toRadians(percentProgress / 100.0f * 360)) * radiusArc;
        //获取百分比文字的宽度
        float percentTextWidth = percentTextPaint.measureText(text) / 2;
        //画百分比文字的实心背景圆
        progressFrontPaint.setStyle(Paint.Style.FILL_AND_STROKE);
        canvas.drawCircle(progressRectF.centerX() + (float) sin, progressRectF.centerY() - (float) cos, percentTextWidth, progressFrontPaint);
        //画百分比文字
        canvas.drawText(text, progressRectF.centerX() - percentTextWidth + (float) sin, progressRectF.centerY() + textHeight / 2 - (float) cos, percentTextPaint);
    }

    /**
     * 设置当前百分比进度
     */
    public void setPercentProgress(int percentProgress) {
        this.percentProgress = percentProgress;
        invalidate();
    }
}


attrs.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="PercentProgressBar">
        <attr name="progressWidth" format="dimension"/><!--进度条宽度-->
        <attr name="progressBackColor" format="color"/><!--进度条背景色-->
        <attr name="progressFrontColor" format="color"/><!--进度条前景色-->
        <attr name="percentTextSize" format="dimension"/><!--百分比文字的大小-->
        <attr name="percentTextColor" format="color"/><!--百分比文字的颜色-->
    </declare-styleable>
</resources>

图示:

percent_progress_bar_quality.png

在布局文件dialog_layout.xml中使用如下:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
        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">

    <com.feng.edward.percentprogressbardemo2.view.PercentProgressBar
            android:id="@+id/circle_percent_view"
            android:layout_width="120dp"
            android:layout_height="150dp"
            android:focusable="true"
            android:clickable="true"
            android:layout_gravity="center"
            app:percentTextSize="10sp"
            app:percentTextColor="#ffffff"
            app:progressFrontColor="@color/colorPrimary"
            app:progressWidth="2dp"
    />
    <com.feng.edward.percentprogressbardemo2.view.CircleImageView
            android:id="@+id/circle_image_view"
            android:layout_width="70dp"
            android:layout_height="70dp"
            android:alpha="0"
            android:layout_gravity="center"
            android:src="@mipmap/jianshu_logo"/>
</FrameLayout>

如果想了解CircleImageView,请访问上篇文章史上最简洁高效的圆形ImageView

自定义进度条弹出框ProgressDialog,代码如下:

package com.feng.edward.percentprogressbardemo2.dialog;

import com.feng.edward.percentprogressbardemo2.R;
import com.feng.edward.percentprogressbardemo2.view.CircleImageView;
import com.feng.edward.percentprogressbardemo2.view.PercentProgressBar;

import android.app.Dialog;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;

/**
 * 功能描述:
 *
 * @author (作者) edward(冯丰枫)
 * @link http://www.jianshu.com/u/f7176d6d53d2
 * 创建时间: 2018/4/17 0017
 */
public class ProgressDialog {
    private final Dialog mDialog;
    private final PercentProgressBar mPercentProgress;
    private final CircleImageView mCircleImageView;

    public ProgressDialog(Context context) {
        View view = LayoutInflater.from(context).inflate(R.layout.dialog_layout, null);
        mPercentProgress = view.findViewById(R.id.circle_percent_view);
        mCircleImageView = view.findViewById(R.id.circle_image_view);
        mDialog = new Dialog(context, R.style.ProgressDialogTheme);
        mDialog.setContentView(view);
        mDialog.setCanceledOnTouchOutside(true);
    }

    public void show() {
        mDialog.show();
    }

    public void setPercentProgress(int percentProgress) {
        mPercentProgress.setPercentProgress(percentProgress);
        mCircleImageView.setAlpha((float) percentProgress/100);
    }

    public void dismiss() {
        mDialog.dismiss();
    }
}

在MainActivity中使用如下:

package com.feng.edward.percentprogressbardemo2;

import com.feng.edward.percentprogressbardemo2.dialog.ProgressDialog;
import com.feng.edward.percentprogressbardemo2.util.IPublishProgress;
import com.feng.edward.percentprogressbardemo2.util.MyAsyncTask;

import android.os.Build;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity implements MyAsyncTask.IIsViewActive {

    private ProgressDialog mProgressDialog;
    private TextView mainText;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mProgressDialog = new ProgressDialog(this);
        mainText = findViewById(R.id.main_text);
        mainText.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mainText.setText(R.string.downloading);
                downLoad();
            }
        });
    }

    private void downLoad() {
        MyAsyncTask.<Void, Integer, Void>newBuilder()
                .setPreExecute(new MyAsyncTask.IPreExecute() {
                    @Override
                    public void onPreExecute() {
                        mProgressDialog.show();
                    }
                })
                .setDoInBackground(new MyAsyncTask.IDoInBackground<Void, Integer, Void>() {
                    @Override
                    public Void doInBackground(IPublishProgress<Integer> publishProgress, Void... voids) {
                        try {
                            for (int i = 0; i <= 100; i++) {
                                Thread.sleep(100);
                                publishProgress.showProgress(i);
                            }
                        } catch (Exception ignore) {
                        }
                        return null;
                    }
                })
                .setProgressUpdate(new MyAsyncTask.IProgressUpdate<Integer>() {
                    @Override
                    public void onProgressUpdate(Integer... values) {
                        mProgressDialog.setPercentProgress(values[0]);
                    }
                })
                .setViewActive(this)
                .setPostExecute(new MyAsyncTask.IPostExecute<Void>() {
                    @Override
                    public void onPostExecute(Void aVoid) {
                        mProgressDialog.dismiss();
                        mainText.setText(R.string.download_finish);
                    }
                })
                .start();
    }

    @Override
    public boolean isViewActive() {
        return !(isFinishing() || (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 && isDestroyed()));
    }


}

如想了解MyAsyncTask,请访问Android AsyncTask 优化封装

activity_mian.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"
        android:id="@+id/main_layout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">
    <TextView
            android:id="@+id/main_text"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/main_text"
            android:layout_centerInParent="true"/>
</RelativeLayout>
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342

推荐阅读更多精彩内容