自定义进度条
2020-04-29 本文已影响0人
CoolKin
设计图.png
截屏1.png 截屏2.png 截屏3.png 截屏4.png
项目需要以上设计图中进度条样式,百度了下没有找到相同的,只能自己写一个。先上完成的效果图
截屏1.png 截屏2.png 截屏3.png 截屏4.png
上代码
package com.wsf.myprogressbar;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
import android.text.Layout;
import android.text.SpannableString;
import android.text.Spanned;
import android.text.StaticLayout;
import android.text.TextPaint;
import android.text.style.AbsoluteSizeSpan;
import android.text.style.ForegroundColorSpan;
import android.util.AttributeSet;
import android.view.View;
/**
* 进度条上方文字view
*
* @author wsf
*/
public class TextProgressView extends View {
private Paint paint;
private float percent;
private float total;
/**
* 进度条文字颜色
*/
private int textColor;
/**
* 进度条数字颜色
*/
private int numColor;
/**
* 进度条数字
*/
private int number;
public TextProgressView(Context context) {
this(context, null);
}
public TextProgressView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public TextProgressView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
paint = new Paint();
paint.setAntiAlias(true);
TypedArray mTypedArray = context.obtainStyledAttributes(attrs,
R.styleable.TextProgressView);
//获取自定义属性和默认值
textColor = mTypedArray.getColor(R.styleable.TextProgressView_textColor, getResources().getColor(R.color.thimcolor));
numColor = mTypedArray.getColor(R.styleable.TextProgressView_numColor, getResources().getColor(R.color.color_ffb202));
mTypedArray.recycle();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
paint.setColor(getResources().getColor(R.color.thimcolor));// 设置累计颜色
//用SpannableString设置字体不同颜色大小
SpannableString spannableString = new SpannableString("剩余天数: " + getNumber() + "天");
spannableString.setSpan(new ForegroundColorSpan(numColor), 5, spannableString.length() - 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
spannableString.setSpan(new AbsoluteSizeSpan(sp2px(16)), 5, spannableString.length() - 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
paint.setColor(textColor);// 设置累计颜色
paint.setTextSize(sp2px(16));
//计算文本宽高
Rect rect = new Rect();
String text = "剩余天数: " + getNumber() + "天";
paint.getTextBounds(text, 0, text.length(), rect);
float textWidth = rect.width();
float textHeight = rect.height() + (2 * sp2px(4));
paint.setStrokeWidth(sp2px(1));
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeJoin(Paint.Join.ROUND);
Path path = new Path();
//先画文本框
if (total != 0) {
if (percent == 0) { //如果进度为0,则从0,0开始画
path.moveTo(0, 0);
path.lineTo(0, textHeight);
path.lineTo((getWidth() * percent / total), getHeight());
path.lineTo((getWidth() * percent / total) + sp2px(5), textHeight);
path.lineTo(textWidth, textHeight);
path.lineTo(textWidth, 0);
path.close();
} else if (percent == total) {//如果进度等于总和,则文本框右侧贴着画布右侧
path.moveTo(getWidth() - textWidth, 0);
path.lineTo(getWidth() - textWidth, textHeight);
path.lineTo((getWidth() * percent / total) - sp2px(5), textHeight);
path.lineTo((getWidth() * percent / total), getHeight());
path.lineTo(getWidth(), textHeight);
path.lineTo(getWidth(), 0);
path.close();
} else {//其他情况按正常画
path.moveTo((getWidth() * percent / total) - (textWidth * percent / total), 0);
path.lineTo((getWidth() * percent / total) - (textWidth * percent / total), textHeight);
if ((getWidth() * percent / total) - sp2px(5) < 0) {
path.lineTo(0, textHeight);
} else {
path.lineTo((getWidth() * percent / total) - sp2px(5), textHeight);
}
path.lineTo((getWidth() * percent / total), getHeight());
if ((getWidth() * percent / total) + sp2px(5) > getWidth()) {
path.lineTo(getWidth(), textHeight);
} else {
path.lineTo((getWidth() * percent / total) + sp2px(5), textHeight);
}
path.lineTo((getWidth() * percent / total) + (textWidth * (total - percent) / total), textHeight);
path.lineTo((getWidth() * percent / total) + (textWidth * (total - percent) / total), 0);
path.close();
}
} else {//如果总和为0,直接在最右端画文本框
path.moveTo(getWidth() - textWidth, 0);
path.lineTo(getWidth() - textWidth, textHeight);
path.lineTo((getWidth() * percent / total) - sp2px(5), textHeight);
path.lineTo((getWidth() * percent / total), getHeight());
if ((getWidth() * percent / total) + sp2px(5) > getWidth()) {
path.lineTo(getWidth(), textHeight);
} else {
path.lineTo((getWidth() * percent / total) + sp2px(5), textHeight);
path.lineTo(getWidth(), textHeight);
}
path.lineTo(getWidth(), 0);
path.close();
}
canvas.drawPath(path, paint);
//画文本
TextPaint paint1 = new TextPaint();
paint1.setTextSize(sp2px(12));
paint1.setColor(textColor);
paint1.setAntiAlias(true);
StaticLayout layout = new StaticLayout(spannableString, paint1, getWidth(), Layout.Alignment.ALIGN_NORMAL, 1, 0, false);
canvas.save();
if (total != 0) {
if (percent == 0){
canvas.translate(0 + sp2px(10), 0); //不知道为什么文字的画布位置靠左,所以加10是为了文字可以显示到文字框的中间
} else if (percent == total) {
canvas.translate(getWidth() - textWidth + sp2px(10), 0);
} else {
canvas.translate((getWidth() * percent / total) - (textWidth * percent / total) + sp2px(10), 0);
}
} else {
canvas.translate(getWidth() - textWidth + sp2px(10), 0);
}
layout.draw(canvas);
canvas.restore();
}
public float getPercent() {
return percent;
}
public void setPercent(float percent) {
this.percent = percent;
}
public float getTotal() {
return total;
}
public void setTotal(float total) {
this.total = total;
}
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
private int dp2px(int value) {
float v = getContext().getResources().getDisplayMetrics().density;
return (int) (v * value + 0.5f);
}
private int sp2px(int value) {
float v = getContext().getResources().getDisplayMetrics().scaledDensity;
return (int) (v * value + 0.5f);
}
}
package com.wsf.myprogressbar;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.View;
/**
* 进度条
*
* @author wsf
*/
public class DayProgressView extends View {
/**
* 画笔对象的引用
*/
private Paint paint;
/**
* 进度
*/
private float mRate;
/**
* 总共
*/
private float total;
/**
* 进度条左侧颜色
*/
private int leftColor;
/**
* 进度条右侧颜色
*/
private int rightColor;
/**
* 进度条右侧颜色
*/
private int defaultColor;
/**
* 进度条左右两端弧度
*/
private float mRadius;
private Path mPath;
private Path mPath1;
private Path mPath2;
public DayProgressView(Context context) {
this(context, null);
}
public DayProgressView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public DayProgressView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
paint = new Paint();
paint.setAntiAlias(true);
TypedArray mTypedArray = context.obtainStyledAttributes(attrs,
R.styleable.DayProgressView);
// 获取自定义属性和默认值
mRadius = mTypedArray.getDimension(R.styleable.DayProgressView_rectRadius, dp2px(10));
leftColor = mTypedArray.getColor(R.styleable.DayProgressView_leftColor, getResources().getColor(R.color.thimcolor));
rightColor = mTypedArray.getColor(R.styleable.DayProgressView_rightColor, getResources().getColor(R.color.color_ffb202));
defaultColor = mTypedArray.getColor(R.styleable.DayProgressView_defaultColor, getResources().getColor(R.color.white));
mTypedArray.recycle();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//先画背景色
paint.setColor(defaultColor);// 设置累计颜色
paint.setStyle(Paint.Style.FILL);//设置填满
mPath2 = new Path();
mPath2.moveTo(0, 0);
//画白色背景图
RectF RoundRect = new RectF(0, 0, getHeights(canvas), getHeights(canvas));
//左端画弧形,从270的角度开始逆时针画180度
mPath2.arcTo(RoundRect, 270, -180);
mPath2.lineTo(getWidths(canvas), getHeights(canvas));
RectF RoundRect1 = new RectF(getWidths(canvas) - getHeights(canvas), 0, getWidths(canvas), getHeights(canvas));
//右端画弧形,从90的角度开始逆时针画180度
mPath2.arcTo(RoundRect1, 90, -180);
mPath2.close();
canvas.drawPath(mPath2,paint);
if (mRate == total){//当前进度等于全部时,代表全部完成,左侧颜色填充满
paint.setColor(leftColor);// 蓝色
paint.setStyle(Paint.Style.FILL);
canvas.drawPath(mPath2,paint);
}else if (mRate == 0){//当前进度等于0时,代表位开始,右侧颜色填充满
paint.setColor(rightColor);// 橙色
paint.setStyle(Paint.Style.FILL);
canvas.drawPath(mPath2,paint);
}else {
//画左侧进度条
paint.setColor(leftColor);// 蓝色
paint.setStyle(Paint.Style.FILL);
mPath = new Path();
//从0,0位置开始画
mPath.moveTo(0, 0);
//画左端半圆,直径是画布的高
RectF rectF = new RectF(0, 0, getHeights(canvas), getHeights(canvas));
//左端画弧形,从270的角度开始逆时针画180度
mPath.arcTo(rectF, 270, -180);
//左右两种颜色中间间隙是 getHeights(canvas) / 4
if (getWidths(canvas) * mRate / total < getHeights(canvas)/2){//当进度条的左侧颜色小于左端半圆宽度时,默认是至少显示左侧半圆
mPath.lineTo(getHeights(canvas)/2 + getHeights(canvas) / 4, 0);
}else if (getWidths(canvas) - getHeights(canvas)/2 < getWidths(canvas) * mRate / total){//当进度条的右侧颜色小于右端半圆宽度时,默认是至少显示右侧半圆
mPath.lineTo(getWidths(canvas) - getHeights(canvas) / 2 - getHeights(canvas) / 4 * 2, getHeights(canvas));
mPath.lineTo(getWidths(canvas) - getHeights(canvas) / 2 - getHeights(canvas) / 4, 0);
}else {//其他情况按正常进度画
mPath.lineTo((getWidths(canvas)) * mRate / total - getHeights(canvas) / 4, getHeights(canvas));
mPath.lineTo((getWidths(canvas)) * mRate / total, 0);
}
mPath.close();
canvas.drawPath(mPath, paint);
//画右侧进度条
paint.setColor(rightColor);
paint.setStyle(Paint.Style.FILL);
mPath1 = new Path();
//右侧判断同左侧判断
if (getWidths(canvas) * mRate / total < getHeights(canvas)/2){
mPath1.moveTo(getHeights(canvas)/2 + getHeights(canvas) / 4 + getHeights(canvas) / 4, 0);
mPath1.lineTo(getHeights(canvas)/2 + getHeights(canvas) / 4, getHeights(canvas));
}else if (getWidths(canvas) - getHeights(canvas)/2 < getWidths(canvas) * mRate / total){
mPath1.moveTo(getWidths(canvas) - getHeights(canvas) / 2, 0);
mPath1.lineTo(getWidths(canvas) - getHeights(canvas) / 2 - getHeights(canvas) / 4, getHeights(canvas));
}else {
mPath1.moveTo(getWidths(canvas) * mRate / total + getHeights(canvas) / 4, 0);
mPath1.lineTo(getWidths(canvas) * mRate / total, getHeights(canvas));
}
mPath1.lineTo(getWidths(canvas), getHeights(canvas));
RectF rectF1 = new RectF(getWidths(canvas) - getHeights(canvas), 0, getWidths(canvas), getHeights(canvas));
//右端画弧形,从90的角度开始逆时针画180度
mPath1.arcTo(rectF1, 90, -180);
mPath1.close();
canvas.drawPath(mPath1, paint);
}
}
public float getRate() {
return mRate;
}
public void setRate(float mRate) {
this.mRate = mRate;
}
public int getLeftColor() {
return leftColor;
}
public void setLeftColor(int leftColor) {
this.leftColor = leftColor;
}
public int getRightColor() {
return rightColor;
}
public void setRightColor(int rightColor) {
this.rightColor = rightColor;
}
public float getRadius() {
return mRadius;
}
public void setRadius(float mRadius) {
this.mRadius = mRadius;
}
public int getDefaultColor() {
return defaultColor;
}
public void setDefaultColor(int defaultColor) {
this.defaultColor = defaultColor;
}
public float getTotal() {
return total;
}
public void setTotal(float total) {
this.total = total;
}
private float getWidths(Canvas canvas) {
return (float) canvas.getWidth();
}
private float getHeights(Canvas canvas) {
return (float) canvas.getHeight();
}
private int sp2px(int value) {
float v = getContext().getResources().getDisplayMetrics().scaledDensity;
return (int) (v * value + 0.5f);
}
private int dp2px(int value) {
float v = getContext().getResources().getDisplayMetrics().density;
return (int) (v * value + 0.5f);
}
}
这里是将下方的进度条和上方显示的文字分开写了,因为项目时间紧张,合在一起计算太麻烦,分开写好计算,等以后有时间再合并,因为是分开显示并且左右两端是半圆形中间有斜着的分隔,所以在左右两端文本框箭头指示位置有问题,不过影响不大,除了合并,暂时没想到好的办法,有哪位朋友有方法可以评论告诉我,十分感谢!
package com.wsf.myprogressbar;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
public class MainActivity extends AppCompatActivity {
private EditText et_total;
private EditText et_rate;
private Button btn_refresh;
private DayProgressView dpvClassdetail;
private TextProgressView tpvClassdetail;
private TextView tvClassDetailStarttime;
private TextView tvClassDetailEndtime;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
et_total = findViewById(R.id.et_total);
et_rate = findViewById(R.id.et_rate);
btn_refresh = findViewById(R.id.btn_refresh);
dpvClassdetail = findViewById(R.id.dpv_classdetail);
tpvClassdetail = findViewById(R.id.tpv_classdetail);
tvClassDetailStarttime = findViewById(R.id.tv_class_detail_starttime);
tvClassDetailEndtime = findViewById(R.id.tv_class_detail_endtime);
et_total.setText(10 + "");
et_rate.setText(3 + "");
refresh();
btn_refresh.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
refresh();
}
});
}
private void refresh() {
if (!TextUtils.isEmpty(et_total.getText().toString()) && !TextUtils.isEmpty(et_rate.getText().toString())) {
int total = Integer.parseInt(et_total.getText().toString());
int rate = Integer.parseInt(et_rate.getText().toString());
dpvClassdetail.setTotal(total);
if (total - rate < 0) {
dpvClassdetail.setRate(0);
tpvClassdetail.setPercent(0);
} else {
dpvClassdetail.setRate(rate);
tpvClassdetail.setPercent(rate);
}
tpvClassdetail.setNumber(total - rate);
tpvClassdetail.setTotal(total);
tpvClassdetail.postInvalidate();
dpvClassdetail.postInvalidate();
tvClassDetailStarttime.setText("开始时间 \n" + "2020.4.20");
tvClassDetailEndtime.setText("截止时间 \n" + "2020.5.20");
} else {
Toast.makeText(MainActivity.this, "请填写总和与进度", Toast.LENGTH_LONG).show();
}
}
}
这里是mainActivity中使用
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="vertical">
<EditText
android:id="@+id/et_total"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="number"
android:hint="请填写总和"
android:padding="5dp" />
<EditText
android:id="@+id/et_rate"
android:layout_marginTop="10dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="number"
android:hint="请填写进度"
android:padding="5dp" />
<Button
android:id="@+id/btn_refresh"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:padding="5dp"
android:text="刷新" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="15dp"
android:layout_marginRight="15dp"
android:orientation="horizontal"
android:paddingLeft="5dp"
android:paddingTop="15dp"
android:paddingRight="5dp"
android:paddingBottom="15dp">
<TextView
android:id="@+id/tv_class_detail_starttime"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:gravity="center"
android:text=""
android:textColor="#666666"
android:textSize="12sp" />
<RelativeLayout
android:layout_width="0dp"
android:layout_height="50dp"
android:layout_marginLeft="5dp"
android:layout_marginRight="5dp"
android:layout_marginBottom="10dp"
android:layout_weight="1">
<com.wsf.myprogressbar.DayProgressView
android:id="@+id/dpv_classdetail"
android:layout_width="match_parent"
android:layout_height="11dp"
android:layout_alignParentBottom="true" />
<com.wsf.myprogressbar.TextProgressView
android:id="@+id/tpv_classdetail"
android:layout_width="match_parent"
android:layout_height="33dp" />
</RelativeLayout>
<TextView
android:id="@+id/tv_class_detail_endtime"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:gravity="center"
android:text=""
android:textColor="#666666"
android:textSize="12sp" />
</LinearLayout>
</LinearLayout>
demo中xml文件