Android 打造形形色色的进度条 实现可以如此简单

文章详细介绍了如何在Android中自定义View实现HorizontalProgressBarWithNumber和RoundProgressBarWidthNumber,重点讲解了onMeasure和onDraw方法的处理,以及如何根据用户自定义属性调整测量和绘制过程。
摘要由CSDN通过智能技术生成

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” />

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() {

总结:

各行各样都会淘汰一些能力差的,不仅仅是IT这个行业,所以,不要被程序猿是吃青春饭等等这类话题所吓倒,也不要觉得,找到一份工作,就享受安逸的生活,你在安逸的同时,别人正在奋力的向前跑,这样与别人的差距也就会越来越遥远,加油,希望,我们每一个人,成为更好的自己。

  • BAT大厂面试题、独家面试工具包,

  • 资料包括 数据结构、Kotlin、计算机网络、Framework源码、数据结构与算法、小程序、NDK、Flutter,


网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

talProgressBarWithNumber mProgressBar;

private static final int MSG_PROGRESS_UPDATE = 0x110;

private Handler mHandler = new Handler() {

总结:

各行各样都会淘汰一些能力差的,不仅仅是IT这个行业,所以,不要被程序猿是吃青春饭等等这类话题所吓倒,也不要觉得,找到一份工作,就享受安逸的生活,你在安逸的同时,别人正在奋力的向前跑,这样与别人的差距也就会越来越遥远,加油,希望,我们每一个人,成为更好的自己。

  • BAT大厂面试题、独家面试工具包,

  • 资料包括 数据结构、Kotlin、计算机网络、Framework源码、数据结构与算法、小程序、NDK、Flutter,

    [外链图片转存中…(img-CZG7I1We-1714321231833)]
    [外链图片转存中…(img-LYVIlZMu-1714321231833)]

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值