Android 环形进度条上的圆边
Android round edges on ring shaped progressbar
我正在尝试在 android 上制作一个圆形进度条,这似乎是一项非常简单的任务,但我正在努力解决进度和次级进度的边缘问题。
有没有办法在不创建自定义视图的情况下做到这一点?使用角半径?或九个可绘制的补丁?
对于这个视图(见附件)我使用了一个简单的 xml 文件
<item android:id="@android:id/progress">
<shape
android:useLevel="true"
android:innerRadius="@dimen/sixty_dp"
android:shape="ring"
android:thickness="@dimen/seven_dp">
<solid android:color="#477C5B"/>
<stroke android:width="1dip"
android:color="#FFFF"/>
</shape>
</item>
只需在您的包中创建名为 MyProgress
的 class .. 并粘贴以下代码..
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.RectF;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.view.View;
public class MyProgress extends View {
private Paint mPrimaryPaint;
private Paint mSecondaryPaint;
private RectF mRectF;
private TextPaint mTextPaint;
private Paint mBackgroundPaint;
private boolean mDrawText = false;
private int mSecondaryProgressColor;
private int mPrimaryProgressColor;
private int mBackgroundColor;
private int mStrokeWidth;
private int mProgress;
private int mSecodaryProgress;
private int mTextColor;
private int mPrimaryCapSize;
private int mSecondaryCapSize;
private boolean mIsPrimaryCapVisible;
private boolean mIsSecondaryCapVisible;
private int x;
private int y;
private int mWidth = 0, mHeight = 0;
public MyProgress(Context context) {
super(context);
init(context, null);
}
public MyProgress(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
public MyProgress(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs);
}
void init(Context context, AttributeSet attrs) {
TypedArray a;
if (attrs != null) {
a = context.getTheme().obtainStyledAttributes(
attrs,
R.styleable.MyProgress,
0, 0);
} else {
throw new IllegalArgumentException("Must have to pass the attributes");
}
try {
mDrawText = a.getBoolean(R.styleable.MyProgress_showProgressText, false);
mBackgroundColor = a.getColor(R.styleable.MyProgress_backgroundColor, android.R.color.darker_gray);
mPrimaryProgressColor = a.getColor(R.styleable.MyProgress_progressColor, android.R.color.darker_gray);
mSecondaryProgressColor = a.getColor(R.styleable.MyProgress_secondaryProgressColor, android.R.color.black);
mProgress = a.getInt(R.styleable.MyProgress_progress, 0);
mSecodaryProgress = a.getInt(R.styleable.MyProgress_secondaryProgress, 0);
mStrokeWidth = a.getDimensionPixelSize(R.styleable.MyProgress_strokeWidth, 20);
mTextColor = a.getColor(R.styleable.MyProgress_textColor, android.R.color.black);
mPrimaryCapSize = a.getInt(R.styleable.MyProgress_primaryCapSize, 20);
mSecondaryCapSize = a.getInt(R.styleable.MyProgress_secodaryCapSize, 20);
mIsPrimaryCapVisible = a.getBoolean(R.styleable.MyProgress_primaryCapVisibility, true);
mIsSecondaryCapVisible = a.getBoolean(R.styleable.MyProgress_secodaryCapVisibility, true);
} finally {
a.recycle();
}
mBackgroundPaint = new Paint();
mBackgroundPaint.setAntiAlias(true);
mBackgroundPaint.setStyle(Paint.Style.STROKE);
mBackgroundPaint.setStrokeWidth(mStrokeWidth);
mBackgroundPaint.setColor(mBackgroundColor);
mPrimaryPaint = new Paint();
mPrimaryPaint.setAntiAlias(true);
mPrimaryPaint.setStyle(Paint.Style.STROKE);
mPrimaryPaint.setStrokeWidth(mStrokeWidth);
mPrimaryPaint.setColor(mPrimaryProgressColor);
mSecondaryPaint = new Paint();
mSecondaryPaint.setAntiAlias(true);
mSecondaryPaint.setStyle(Paint.Style.STROKE);
mSecondaryPaint.setStrokeWidth(mStrokeWidth - 2);
mSecondaryPaint.setColor(mSecondaryProgressColor);
mTextPaint = new TextPaint();
mTextPaint.setColor(mTextColor);
mRectF = new RectF();
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mRectF.set(getPaddingLeft(), getPaddingTop(), w - getPaddingRight(), h - getPaddingBottom());
mTextPaint.setTextSize(w / 5);
x = (w / 2) - ((int) (mTextPaint.measureText(mProgress + "%") / 2));
y = (int) ((h / 2) - ((mTextPaint.descent() + mTextPaint.ascent()) / 2));
mWidth = w;
mHeight = h;
invalidate();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mPrimaryPaint.setStyle(Paint.Style.STROKE);
mSecondaryPaint.setStyle(Paint.Style.STROKE);
// for drawing a full progress .. The background circle
canvas.drawArc(mRectF, 0, 360, false, mBackgroundPaint);
// for drawing a secondary progress circle
int secondarySwipeangle = (mSecodaryProgress * 360) / 100;
canvas.drawArc(mRectF, 270, secondarySwipeangle, false, mSecondaryPaint);
// for drawing a main progress circle
int primarySwipeangle = (mProgress * 360) / 100;
canvas.drawArc(mRectF, 270, primarySwipeangle, false, mPrimaryPaint);
// for cap of secondary progress
int r = (getHeight() - getPaddingLeft() * 2) / 2; // Calculated from canvas width
double trad = (secondarySwipeangle - 90) * (Math.PI / 180d); // = 5.1051
int x = (int) (r * Math.cos(trad));
int y = (int) (r * Math.sin(trad));
mSecondaryPaint.setStyle(Paint.Style.FILL);
if (mIsSecondaryCapVisible)
canvas.drawCircle(x + (mWidth / 2), y + (mHeight / 2), mSecondaryCapSize, mSecondaryPaint);
// for cap of primary progress
trad = (primarySwipeangle - 90) * (Math.PI / 180d); // = 5.1051
x = (int) (r * Math.cos(trad));
y = (int) (r * Math.sin(trad));
mPrimaryPaint.setStyle(Paint.Style.FILL);
if (mIsPrimaryCapVisible)
canvas.drawCircle(x + (mWidth / 2), y + (mHeight / 2), mPrimaryCapSize, mPrimaryPaint);
if (mDrawText)
canvas.drawText(mProgress + "%", x, y, mTextPaint);
}
public void setDrawText(boolean mDrawText) {
this.mDrawText = mDrawText;
invalidate();
}
public void setBackgroundColor(int mBackgroundColor) {
this.mBackgroundColor = mBackgroundColor;
invalidate();
}
public void setSecondaryProgressColor(int mSecondaryProgressColor) {
this.mSecondaryProgressColor = mSecondaryProgressColor;
invalidate();
}
public void setPrimaryProgressColor(int mPrimaryProgressColor) {
this.mPrimaryProgressColor = mPrimaryProgressColor;
invalidate();
}
public void setStrokeWidth(int mStrokeWidth) {
this.mStrokeWidth = mStrokeWidth;
invalidate();
}
public void setProgress(int mProgress) {
this.mProgress = mProgress;
invalidate();
}
public void setSecondaryProgress(int mSecondaryProgress) {
this.mSecodaryProgress = mSecondaryProgress;
invalidate();
}
public void setTextColor(int mTextColor) {
this.mTextColor = mTextColor;
invalidate();
}
public void setPrimaryCapSize(int mPrimaryCapSize) {
this.mPrimaryCapSize = mPrimaryCapSize;
invalidate();
}
public void setSecondaryCapSize(int mSecondaryCapSize) {
this.mSecondaryCapSize = mSecondaryCapSize;
invalidate();
}
public boolean isPrimaryCapVisible() {
return mIsPrimaryCapVisible;
}
public void setIsPrimaryCapVisible(boolean mIsPrimaryCapVisible) {
this.mIsPrimaryCapVisible = mIsPrimaryCapVisible;
}
public boolean isSecondaryCapVisible() {
return mIsSecondaryCapVisible;
}
public void setIsSecondaryCapVisible(boolean mIsSecondaryCapVisible) {
this.mIsSecondaryCapVisible = mIsSecondaryCapVisible;
}
public int getSecondaryProgressColor() {
return mSecondaryProgressColor;
}
public int getPrimaryProgressColor() {
return mPrimaryProgressColor;
}
public int getProgress() {
return mProgress;
}
public int getBackgroundColor() {
return mBackgroundColor;
}
public int getSecodaryProgress() {
return mSecodaryProgress;
}
public int getPrimaryCapSize() {
return mPrimaryCapSize;
}
public int getSecondaryCapSize() {
return mSecondaryCapSize;
}
}
并在 res->values->attr.xml 标签下添加以下行并构建它
<declare-styleable name="MyProgress">
<attr name="showProgressText" format="boolean" />
<attr name="progress" format="integer" />
<attr name="secondaryProgress" format="integer" />
<attr name="progressColor" format="color" />
<attr name="secondaryProgressColor" format="color" />
<attr name="backgroundColor" format="color" />
<attr name="primaryCapSize" format="integer" />
<attr name="secodaryCapSize" format="integer" />
<attr name="primaryCapVisibility" format="boolean" />
<attr name="secodaryCapVisibility" format="boolean" />
<attr name="strokeWidth" format="dimension" />
<attr name="textColor" format="color" />
</declare-styleable>
就是这样....
并在您的布局中使用 ..
<Your_Package_Name.MyProgress
android:padding="20dp"
android:id="@+id/timer1"
app:strokeWidth="10dp"
app:progress="30"
app:secondaryProgress="50"
app:backgroundColor="@android:color/black"
app:progressColor="@android:color/holo_blue_bright"
app:secondaryProgressColor="@android:color/holo_blue_dark"
app:primaryCapSize="30"
app:secodaryCapSize="40"
app:primaryCapVisibility="true"
app:secodaryCapVisibility="true"
android:layout_width="200dp"
android:layout_height="200dp" />
您还可以使用 setMethods() 以编程方式更改所有 属性...
有什么问题随时问..
祝你好运
[更新 23-01-2016]
我终于在 github 上上传了代码。
你可以从这里参考
https://github.com/msquare097/MProgressBar
现在您只需在您的应用 build.gradle 文件中写入以下行即可使用此 ProgressBar。 您不必复制以上代码。
compile 'com.msquare.widget.mprogressbar:mprogressbar:1.0.0'
你可以在环的末端和开始边添加一个circle/oval形状,就像下面的代码
一个图层列表可绘制对象包含两个 circle/oval 形状可绘制对象和一个环形可绘制对象
round_progress_drawable.xml
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:bottom="294px"
android:left="540px"
android:right="48px"
android:top="294px">
<shape
android:innerRadius="6px"
android:shape="oval">
<solid android:color="#FFFFFF"/>
</shape>
</item>
<item>
<shape
android:innerRadius="240px"
android:shape="ring"
android:thickness="12px">
<size
android:width="600px"
android:height="600px"/>
<solid android:color="#FFFFFF"/>
</shape>
</item>
<item>
<rotate>
<layer-list>
<item
android:bottom="294px"
android:left="540px"
android:right="48px"
android:top="294px">
<shape
android:innerRadius="6px"
android:shape="oval">
<solid android:color="#FFFFFF"/>
</shape>
</item>
</layer-list>
</rotate>
</item>
</layer-list>
round_progress_animation_selector.xml
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_window_focused="true">
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="5000"
android:propertyName="ImageLevel"
android:repeatCount="infinite"
android:valueFrom="0"
android:valueTo="10000"
android:valueType="intType">
</objectAnimator>
</item>
<item>
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="0"
android:propertyName="ImageLevel"
android:valueFrom="0"
android:valueTo="0"
android:valueType="intType">
</objectAnimator>
</item>
</selector>
activity_main.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"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorPrimaryDark"
tools:context=".MainActivity">
<ImageView
android:stateListAnimator="@animator/round_progress_animation_selector"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/round_progress_drawable" />
</FrameLayout>
一个简单高效的class扩展View绘制圆形进度条,圆角可选。进度颜色、背景颜色、笔划宽度也可自定义。如 my other answer.
中所示
import android.content.Context
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.RectF
import android.util.AttributeSet
import android.view.View
import androidx.annotation.FloatRange
class CircularProgressView : View {
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
private val progressPaint: Paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
style = Paint.Style.STROKE
}
private val backgroundPaint: Paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
style = Paint.Style.STROKE
}
private val rect = RectF()
private val startAngle = -90f
private val maxAngle = 360f
private val maxProgress = 100
private var diameter = 0f
private var angle = 0f
override fun onDraw(canvas: Canvas) {
drawCircle(maxAngle, canvas, backgroundPaint)
drawCircle(angle, canvas, progressPaint)
}
override fun onSizeChanged(width: Int, height: Int, oldWidth: Int, oldHeight: Int) {
diameter = Math.min(width, height).toFloat()
updateRect()
}
private fun updateRect() {
val strokeWidth = backgroundPaint.strokeWidth
rect.set(strokeWidth, strokeWidth, diameter - strokeWidth, diameter - strokeWidth)
}
private fun drawCircle(angle: Float, canvas: Canvas, paint: Paint) {
canvas.drawArc(rect, startAngle, angle, false, paint)
}
private fun calculateAngle(progress: Float) = maxAngle / maxProgress * progress
fun setProgress(@FloatRange(from = 0.0, to = 100.0) progress: Float) {
angle = calculateAngle(progress)
invalidate()
}
fun setProgressColor(color: Int) {
progressPaint.color = color
invalidate()
}
fun setProgressBackgroundColor(color: Int) {
backgroundPaint.color = color
invalidate()
}
fun setProgressWidth(width: Float) {
progressPaint.strokeWidth = width
backgroundPaint.strokeWidth = width
updateRect()
invalidate()
}
fun setRounded(rounded: Boolean) {
progressPaint.strokeCap = if (rounded) Paint.Cap.ROUND else Paint.Cap.BUTT
invalidate()
}
}
我能够使用图层列表并在线条的每一侧添加一个点来实现此目的。第一个将卡在顶部,而第二个将跟随进度,因为在旋转元素中添加了插图。不过,您必须根据进度条布局的大小对其进行调整。我的是 250dp x 250dp。
progress_drawable.xml
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<rotate android:fromDegrees="270" android:toDegrees="270">
<shape
android:innerRadiusRatio="2.55"
android:shape="ring"
android:thickness="15dp"
android:useLevel="true">
<solid android:color="@color/main_color" />
</shape>
</rotate>
</item>
<item android:bottom="211dp">
<shape
android:innerRadiusRatio="1000"
android:shape="ring"
android:thickness="7dp"
android:useLevel="false">
<solid android:color="@color/main_color" />
</shape>
</item>
<item>
<rotate>
<inset android:insetBottom="211dp">
<shape
android:innerRadiusRatio="1000"
android:shape="ring"
android:thickness="7dp"
android:useLevel="false">
<solid android:color="@color/main_color" />
</shape>
</inset>
</rotate>
</item>
</layer-list>
我这里没有辅助流程,但你应该也可以针对它进行调整。
我尝试针对任意大小的进度条来解决这个问题,如果您可以确保进度条是正方形并且您不使用边距,那么这应该可行,但事实并非如此。 Android 10 有时会正确渲染它,有时却不会。特别是进度值 > 50% 往往会严重中断。这不是一个可行的解决方案,但可能会给某人一个想法。渐变填充仍然存在问题。最终颜色与它不匹配。但如果你使用纯色,它应该可以正常工作。此外,圆角帽使条形图显示更大的值范围。我的想法是用背景颜色为 5dp x 2.5dp 矩形的形状替换环,并剪下半个圆圈。您需要在开始和结束时使用不同的旋转:
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<rotate
android:fromDegrees="270"
android:toDegrees="270">
<shape
android:innerRadiusRatio="2.4"
android:shape="ring"
android:thickness="5dp"
android:useLevel="true"><!-- this line fixes the issue for lollipop api 21 -->
<gradient
android:angle="0"
android:endColor="@color/yellow"
android:startColor="@color/orange"
android:type="sweep"
android:useLevel="false" />
</shape>
</rotate>
</item>
<item
android:gravity="top">
<shape
android:innerRadiusRatio="1000"
android:shape="ring"
android:thickness="2.5dp"
android:useLevel="false">
<solid android:color="@color/orange" />
<size android:height="5dp"/>
</shape>
</item>
<item>
<rotate>
<scale android:scaleGravity="top|center_horizontal"
android:scaleHeight="150%"
android:scaleWidth="150%">
<shape
android:innerRadiusRatio="1000"
android:shape="ring"
android:thickness="2.5dp"
android:useLevel="false">
<solid android:color="@color/yellow" />
</shape>
</scale>
</rotate>
</item>
</layer-list>
我正在尝试在 android 上制作一个圆形进度条,这似乎是一项非常简单的任务,但我正在努力解决进度和次级进度的边缘问题。
有没有办法在不创建自定义视图的情况下做到这一点?使用角半径?或九个可绘制的补丁?
对于这个视图(见附件)我使用了一个简单的 xml 文件
<item android:id="@android:id/progress">
<shape
android:useLevel="true"
android:innerRadius="@dimen/sixty_dp"
android:shape="ring"
android:thickness="@dimen/seven_dp">
<solid android:color="#477C5B"/>
<stroke android:width="1dip"
android:color="#FFFF"/>
</shape>
</item>
只需在您的包中创建名为 MyProgress
的 class .. 并粘贴以下代码..
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.RectF;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.view.View;
public class MyProgress extends View {
private Paint mPrimaryPaint;
private Paint mSecondaryPaint;
private RectF mRectF;
private TextPaint mTextPaint;
private Paint mBackgroundPaint;
private boolean mDrawText = false;
private int mSecondaryProgressColor;
private int mPrimaryProgressColor;
private int mBackgroundColor;
private int mStrokeWidth;
private int mProgress;
private int mSecodaryProgress;
private int mTextColor;
private int mPrimaryCapSize;
private int mSecondaryCapSize;
private boolean mIsPrimaryCapVisible;
private boolean mIsSecondaryCapVisible;
private int x;
private int y;
private int mWidth = 0, mHeight = 0;
public MyProgress(Context context) {
super(context);
init(context, null);
}
public MyProgress(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
public MyProgress(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs);
}
void init(Context context, AttributeSet attrs) {
TypedArray a;
if (attrs != null) {
a = context.getTheme().obtainStyledAttributes(
attrs,
R.styleable.MyProgress,
0, 0);
} else {
throw new IllegalArgumentException("Must have to pass the attributes");
}
try {
mDrawText = a.getBoolean(R.styleable.MyProgress_showProgressText, false);
mBackgroundColor = a.getColor(R.styleable.MyProgress_backgroundColor, android.R.color.darker_gray);
mPrimaryProgressColor = a.getColor(R.styleable.MyProgress_progressColor, android.R.color.darker_gray);
mSecondaryProgressColor = a.getColor(R.styleable.MyProgress_secondaryProgressColor, android.R.color.black);
mProgress = a.getInt(R.styleable.MyProgress_progress, 0);
mSecodaryProgress = a.getInt(R.styleable.MyProgress_secondaryProgress, 0);
mStrokeWidth = a.getDimensionPixelSize(R.styleable.MyProgress_strokeWidth, 20);
mTextColor = a.getColor(R.styleable.MyProgress_textColor, android.R.color.black);
mPrimaryCapSize = a.getInt(R.styleable.MyProgress_primaryCapSize, 20);
mSecondaryCapSize = a.getInt(R.styleable.MyProgress_secodaryCapSize, 20);
mIsPrimaryCapVisible = a.getBoolean(R.styleable.MyProgress_primaryCapVisibility, true);
mIsSecondaryCapVisible = a.getBoolean(R.styleable.MyProgress_secodaryCapVisibility, true);
} finally {
a.recycle();
}
mBackgroundPaint = new Paint();
mBackgroundPaint.setAntiAlias(true);
mBackgroundPaint.setStyle(Paint.Style.STROKE);
mBackgroundPaint.setStrokeWidth(mStrokeWidth);
mBackgroundPaint.setColor(mBackgroundColor);
mPrimaryPaint = new Paint();
mPrimaryPaint.setAntiAlias(true);
mPrimaryPaint.setStyle(Paint.Style.STROKE);
mPrimaryPaint.setStrokeWidth(mStrokeWidth);
mPrimaryPaint.setColor(mPrimaryProgressColor);
mSecondaryPaint = new Paint();
mSecondaryPaint.setAntiAlias(true);
mSecondaryPaint.setStyle(Paint.Style.STROKE);
mSecondaryPaint.setStrokeWidth(mStrokeWidth - 2);
mSecondaryPaint.setColor(mSecondaryProgressColor);
mTextPaint = new TextPaint();
mTextPaint.setColor(mTextColor);
mRectF = new RectF();
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mRectF.set(getPaddingLeft(), getPaddingTop(), w - getPaddingRight(), h - getPaddingBottom());
mTextPaint.setTextSize(w / 5);
x = (w / 2) - ((int) (mTextPaint.measureText(mProgress + "%") / 2));
y = (int) ((h / 2) - ((mTextPaint.descent() + mTextPaint.ascent()) / 2));
mWidth = w;
mHeight = h;
invalidate();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mPrimaryPaint.setStyle(Paint.Style.STROKE);
mSecondaryPaint.setStyle(Paint.Style.STROKE);
// for drawing a full progress .. The background circle
canvas.drawArc(mRectF, 0, 360, false, mBackgroundPaint);
// for drawing a secondary progress circle
int secondarySwipeangle = (mSecodaryProgress * 360) / 100;
canvas.drawArc(mRectF, 270, secondarySwipeangle, false, mSecondaryPaint);
// for drawing a main progress circle
int primarySwipeangle = (mProgress * 360) / 100;
canvas.drawArc(mRectF, 270, primarySwipeangle, false, mPrimaryPaint);
// for cap of secondary progress
int r = (getHeight() - getPaddingLeft() * 2) / 2; // Calculated from canvas width
double trad = (secondarySwipeangle - 90) * (Math.PI / 180d); // = 5.1051
int x = (int) (r * Math.cos(trad));
int y = (int) (r * Math.sin(trad));
mSecondaryPaint.setStyle(Paint.Style.FILL);
if (mIsSecondaryCapVisible)
canvas.drawCircle(x + (mWidth / 2), y + (mHeight / 2), mSecondaryCapSize, mSecondaryPaint);
// for cap of primary progress
trad = (primarySwipeangle - 90) * (Math.PI / 180d); // = 5.1051
x = (int) (r * Math.cos(trad));
y = (int) (r * Math.sin(trad));
mPrimaryPaint.setStyle(Paint.Style.FILL);
if (mIsPrimaryCapVisible)
canvas.drawCircle(x + (mWidth / 2), y + (mHeight / 2), mPrimaryCapSize, mPrimaryPaint);
if (mDrawText)
canvas.drawText(mProgress + "%", x, y, mTextPaint);
}
public void setDrawText(boolean mDrawText) {
this.mDrawText = mDrawText;
invalidate();
}
public void setBackgroundColor(int mBackgroundColor) {
this.mBackgroundColor = mBackgroundColor;
invalidate();
}
public void setSecondaryProgressColor(int mSecondaryProgressColor) {
this.mSecondaryProgressColor = mSecondaryProgressColor;
invalidate();
}
public void setPrimaryProgressColor(int mPrimaryProgressColor) {
this.mPrimaryProgressColor = mPrimaryProgressColor;
invalidate();
}
public void setStrokeWidth(int mStrokeWidth) {
this.mStrokeWidth = mStrokeWidth;
invalidate();
}
public void setProgress(int mProgress) {
this.mProgress = mProgress;
invalidate();
}
public void setSecondaryProgress(int mSecondaryProgress) {
this.mSecodaryProgress = mSecondaryProgress;
invalidate();
}
public void setTextColor(int mTextColor) {
this.mTextColor = mTextColor;
invalidate();
}
public void setPrimaryCapSize(int mPrimaryCapSize) {
this.mPrimaryCapSize = mPrimaryCapSize;
invalidate();
}
public void setSecondaryCapSize(int mSecondaryCapSize) {
this.mSecondaryCapSize = mSecondaryCapSize;
invalidate();
}
public boolean isPrimaryCapVisible() {
return mIsPrimaryCapVisible;
}
public void setIsPrimaryCapVisible(boolean mIsPrimaryCapVisible) {
this.mIsPrimaryCapVisible = mIsPrimaryCapVisible;
}
public boolean isSecondaryCapVisible() {
return mIsSecondaryCapVisible;
}
public void setIsSecondaryCapVisible(boolean mIsSecondaryCapVisible) {
this.mIsSecondaryCapVisible = mIsSecondaryCapVisible;
}
public int getSecondaryProgressColor() {
return mSecondaryProgressColor;
}
public int getPrimaryProgressColor() {
return mPrimaryProgressColor;
}
public int getProgress() {
return mProgress;
}
public int getBackgroundColor() {
return mBackgroundColor;
}
public int getSecodaryProgress() {
return mSecodaryProgress;
}
public int getPrimaryCapSize() {
return mPrimaryCapSize;
}
public int getSecondaryCapSize() {
return mSecondaryCapSize;
}
}
并在 res->values->attr.xml 标签下添加以下行并构建它
<declare-styleable name="MyProgress">
<attr name="showProgressText" format="boolean" />
<attr name="progress" format="integer" />
<attr name="secondaryProgress" format="integer" />
<attr name="progressColor" format="color" />
<attr name="secondaryProgressColor" format="color" />
<attr name="backgroundColor" format="color" />
<attr name="primaryCapSize" format="integer" />
<attr name="secodaryCapSize" format="integer" />
<attr name="primaryCapVisibility" format="boolean" />
<attr name="secodaryCapVisibility" format="boolean" />
<attr name="strokeWidth" format="dimension" />
<attr name="textColor" format="color" />
</declare-styleable>
就是这样.... 并在您的布局中使用 ..
<Your_Package_Name.MyProgress
android:padding="20dp"
android:id="@+id/timer1"
app:strokeWidth="10dp"
app:progress="30"
app:secondaryProgress="50"
app:backgroundColor="@android:color/black"
app:progressColor="@android:color/holo_blue_bright"
app:secondaryProgressColor="@android:color/holo_blue_dark"
app:primaryCapSize="30"
app:secodaryCapSize="40"
app:primaryCapVisibility="true"
app:secodaryCapVisibility="true"
android:layout_width="200dp"
android:layout_height="200dp" />
您还可以使用 setMethods() 以编程方式更改所有 属性...
有什么问题随时问.. 祝你好运
[更新 23-01-2016]
我终于在 github 上上传了代码。 你可以从这里参考 https://github.com/msquare097/MProgressBar
现在您只需在您的应用 build.gradle 文件中写入以下行即可使用此 ProgressBar。 您不必复制以上代码。
compile 'com.msquare.widget.mprogressbar:mprogressbar:1.0.0'
你可以在环的末端和开始边添加一个circle/oval形状,就像下面的代码
一个图层列表可绘制对象包含两个 circle/oval 形状可绘制对象和一个环形可绘制对象
round_progress_drawable.xml
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:bottom="294px"
android:left="540px"
android:right="48px"
android:top="294px">
<shape
android:innerRadius="6px"
android:shape="oval">
<solid android:color="#FFFFFF"/>
</shape>
</item>
<item>
<shape
android:innerRadius="240px"
android:shape="ring"
android:thickness="12px">
<size
android:width="600px"
android:height="600px"/>
<solid android:color="#FFFFFF"/>
</shape>
</item>
<item>
<rotate>
<layer-list>
<item
android:bottom="294px"
android:left="540px"
android:right="48px"
android:top="294px">
<shape
android:innerRadius="6px"
android:shape="oval">
<solid android:color="#FFFFFF"/>
</shape>
</item>
</layer-list>
</rotate>
</item>
</layer-list>
round_progress_animation_selector.xml
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_window_focused="true">
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="5000"
android:propertyName="ImageLevel"
android:repeatCount="infinite"
android:valueFrom="0"
android:valueTo="10000"
android:valueType="intType">
</objectAnimator>
</item>
<item>
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="0"
android:propertyName="ImageLevel"
android:valueFrom="0"
android:valueTo="0"
android:valueType="intType">
</objectAnimator>
</item>
</selector>
activity_main.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"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorPrimaryDark"
tools:context=".MainActivity">
<ImageView
android:stateListAnimator="@animator/round_progress_animation_selector"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/round_progress_drawable" />
</FrameLayout>
一个简单高效的class扩展View绘制圆形进度条,圆角可选。进度颜色、背景颜色、笔划宽度也可自定义。如 my other answer.
中所示import android.content.Context
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.RectF
import android.util.AttributeSet
import android.view.View
import androidx.annotation.FloatRange
class CircularProgressView : View {
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
private val progressPaint: Paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
style = Paint.Style.STROKE
}
private val backgroundPaint: Paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
style = Paint.Style.STROKE
}
private val rect = RectF()
private val startAngle = -90f
private val maxAngle = 360f
private val maxProgress = 100
private var diameter = 0f
private var angle = 0f
override fun onDraw(canvas: Canvas) {
drawCircle(maxAngle, canvas, backgroundPaint)
drawCircle(angle, canvas, progressPaint)
}
override fun onSizeChanged(width: Int, height: Int, oldWidth: Int, oldHeight: Int) {
diameter = Math.min(width, height).toFloat()
updateRect()
}
private fun updateRect() {
val strokeWidth = backgroundPaint.strokeWidth
rect.set(strokeWidth, strokeWidth, diameter - strokeWidth, diameter - strokeWidth)
}
private fun drawCircle(angle: Float, canvas: Canvas, paint: Paint) {
canvas.drawArc(rect, startAngle, angle, false, paint)
}
private fun calculateAngle(progress: Float) = maxAngle / maxProgress * progress
fun setProgress(@FloatRange(from = 0.0, to = 100.0) progress: Float) {
angle = calculateAngle(progress)
invalidate()
}
fun setProgressColor(color: Int) {
progressPaint.color = color
invalidate()
}
fun setProgressBackgroundColor(color: Int) {
backgroundPaint.color = color
invalidate()
}
fun setProgressWidth(width: Float) {
progressPaint.strokeWidth = width
backgroundPaint.strokeWidth = width
updateRect()
invalidate()
}
fun setRounded(rounded: Boolean) {
progressPaint.strokeCap = if (rounded) Paint.Cap.ROUND else Paint.Cap.BUTT
invalidate()
}
}
我能够使用图层列表并在线条的每一侧添加一个点来实现此目的。第一个将卡在顶部,而第二个将跟随进度,因为在旋转元素中添加了插图。不过,您必须根据进度条布局的大小对其进行调整。我的是 250dp x 250dp。
progress_drawable.xml
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<rotate android:fromDegrees="270" android:toDegrees="270">
<shape
android:innerRadiusRatio="2.55"
android:shape="ring"
android:thickness="15dp"
android:useLevel="true">
<solid android:color="@color/main_color" />
</shape>
</rotate>
</item>
<item android:bottom="211dp">
<shape
android:innerRadiusRatio="1000"
android:shape="ring"
android:thickness="7dp"
android:useLevel="false">
<solid android:color="@color/main_color" />
</shape>
</item>
<item>
<rotate>
<inset android:insetBottom="211dp">
<shape
android:innerRadiusRatio="1000"
android:shape="ring"
android:thickness="7dp"
android:useLevel="false">
<solid android:color="@color/main_color" />
</shape>
</inset>
</rotate>
</item>
</layer-list>
我这里没有辅助流程,但你应该也可以针对它进行调整。
我尝试针对任意大小的进度条来解决这个问题,如果您可以确保进度条是正方形并且您不使用边距,那么这应该可行,但事实并非如此。 Android 10 有时会正确渲染它,有时却不会。特别是进度值 > 50% 往往会严重中断。这不是一个可行的解决方案,但可能会给某人一个想法。渐变填充仍然存在问题。最终颜色与它不匹配。但如果你使用纯色,它应该可以正常工作。此外,圆角帽使条形图显示更大的值范围。我的想法是用背景颜色为 5dp x 2.5dp 矩形的形状替换环,并剪下半个圆圈。您需要在开始和结束时使用不同的旋转:
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<rotate
android:fromDegrees="270"
android:toDegrees="270">
<shape
android:innerRadiusRatio="2.4"
android:shape="ring"
android:thickness="5dp"
android:useLevel="true"><!-- this line fixes the issue for lollipop api 21 -->
<gradient
android:angle="0"
android:endColor="@color/yellow"
android:startColor="@color/orange"
android:type="sweep"
android:useLevel="false" />
</shape>
</rotate>
</item>
<item
android:gravity="top">
<shape
android:innerRadiusRatio="1000"
android:shape="ring"
android:thickness="2.5dp"
android:useLevel="false">
<solid android:color="@color/orange" />
<size android:height="5dp"/>
</shape>
</item>
<item>
<rotate>
<scale android:scaleGravity="top|center_horizontal"
android:scaleHeight="150%"
android:scaleWidth="150%">
<shape
android:innerRadiusRatio="1000"
android:shape="ring"
android:thickness="2.5dp"
android:useLevel="false">
<solid android:color="@color/yellow" />
</shape>
</scale>
</rotate>
</item>
</layer-list>